php高级编程
反射
反射是指在PHP运行状态中,扩展分析PHP程序,导出或提出关于类、方法、属性、参数等的详细信息,包括注释。这种动态获取信息以及动态调用对象方法的功能称为反射API。通过反射可以根据一串拿到的类信息如"App\Models\User"来定位到具体的类User,并实例化类、获取类函数信息等操作。
类定义:
<?php
namespace App\Models;
class User
{
private $level;
public function __construct(){
$this->level = 'L';
}
public function run(){
return $this->level;
}
public function test(){
return "hahahhahahah";
}
}
?>
反射类实例化:
<?php
$class = new ReflectionClass('App\Models\User');
// 获取MyClass对象的所有属性,包含私有属性
var_dump($class->getProperties());
var_dump($class->getMethods());
if ($class->hasMethod('test')) {
$instance = $class->newInstance();
$res = $instance->test();
var_dump($res);
}
?>
通过反射不仅可以获取到类信息,还能获取注释。
<?php
namespace App\Models;
/**
* class 注解
*/
class User
{
private $level;
public function __construct(){
$this->level = 'L';
}
/**
* function 注解在这儿
*/
public function run(){
return $this->level;
}
/*
hhhhhh
*/
public function test(){
return "hahahhahahah";
}
}
?>
获取注解信息:
var_dump($class->getDocComment());
var_dump($class->getMethod('run')->getDocComment());
var_dump($class->getMethod('test')->getDocComment());
运行结果:
string(23) “/** * class 注解 /" string(37) "/* * function 注解在这儿 */” bool(false)
getDocComment()获取注解只对以/**开头的注释信息有效,其余注释将被忽略。
通过注解可以处理路由,如symfony的注解路由:
/**
* This route has a greedy pattern and is defined first.
*
* @Route("/blog/{slug}", name="blog_show")
*/
public function show(string $slug)
{
// ...
}
注解路由背后的原理其实很简单,首先通过反射将所有控制器中的action方法通过反射全部注册路由信息如下:
array(1) {
["GET"]=>
array(4) {
[0]=>
array(2) {
["routePath"]=>
string(10) "test/index"
["handle"]=>
string(39) "App\Api\Controller\TestController@index"
}
[1]=>
array(2) {
["routePath"]=>
string(9) "test/test"
["handle"]=>
string(38) "App\Api\Controller\TestController@test"
}
[2]=>
array(2) {
["routePath"]=>
string(10) "test/index"
["handle"]=>
string(39) "App\Api\Controller\TestController@index"
}
[3]=>
array(2) {
["routePath"]=>
string(9) "test/test"
["handle"]=>
string(38) "App\Api\Controller\TestController@test"
}
}
}
,然后在服务器接收到Http请求时提取http请求的path_info和request_uri信息如下:
["server"]=>
array(10) {
["request_method"]=>
string(3) "GET"
["request_uri"]=>
string(11) "/index/test"
["path_info"]=>
string(11) "/index/test"
["request_time"]=>
int(1606789304)
["request_time_float"]=>
float(1606789304.2045)
["server_protocol"]=>
string(8) "HTTP/1.1"
["server_port"]=>
int(9501)
["remote_port"]=>
int(53450)
["remote_addr"]=>
string(9) "127.0.0.1"
["master_time"]=>
int(1606789304)
}
将http请求与服务器路由列表进行匹配,调用相应控制器的对应方法,然后将结果返回给前端。
自动实例化
自动实例化是指将需要实例化的对象在程序运行时动态创建对象,上述反射示例中实现了不同的类对象通过统一的封装函数进行实例化,所有类对象通过传入不同的className变量在newReflection()时进行初始化。如果我们有一批对象要进行实例化,此时需要手动的在这些节点依次调用反射函数进行初始化。
自动实例化的核心在于将需要自动加载的类遵循统一的格式统一放在一个目录下,按照相同的层级规律和类文件命名规律统一创建,然后依照规律读取所有要实例化的目录下的类文件中定义的类,实例化操作需要统一获取每个类文件头部的命名空间及定义的类名和方法名如App\Api\Controller\TestController@index,通过new App\Api\Controller\TestController()->index()的方式调用相应方法。
composer.json文件定义psr4自动加载:
{
"name": "im",
"description": "im framework",
"keywords": [
"php",
"swoole",
"im"
],
"autoload": {
"psr-4": {
"App\\": "application/",
"Im\\": "framework/Im/"
}
},
"require": {
"php": ">=7.0",
"ext-swoole": ">=4.0"
}
}
全局启动脚本文件im.php,控制台执行php im.php启动服务。
<?php
require dirname(__DIR__). '/vendor/autoload.php';
(new \Im\App())->run();
?>
定义路由注解:
<?php
namespace Im;
use Swoole\Http\Server;
use App\Api\Controller\TestController;
use App\Api\Controller\IndexController;
use Im\Core\Route\Route;
class App
{
public function run(){
$this->init(); //初始化
$this->loadAnnotations(); //载入注解
// var_dump($this->tree(APP_PATH, 'Controller'));
$http = new Server("0.0.0.0", 9501);
$http->on('request', function ($request, $response){
$path_info = $request->server['path_info'];
$method = $request->server['request_method'];
// var_dump($method, $path_info);
$res = Route::dispatch($method, $path_info);
$response->end($res);
});
$http->start(); //启动服务
}
public function init(){
define("ROOT_PATH", dirname(dirname(__DIR__))); //根目录:当前目录上一层的上一层
define("APP_PATH", ROOT_PATH.'/application');
}
public function loadAnnotations(){
// $dirs=glob(APP_PATH.'/Api/Controller/*');
//自动实例化,自动加载类不需要手动写死每个类反射加载
$dirs = $this->tree(APP_PATH, "Controller");
if (!empty($dirs)){
foreach ($dirs as $file) {
//面向对象自动实例化:文件名和类名必须统一遵循相同的规则
$fileName = explode("/", $file);
$className = explode(".", end($fileName))[0];//拿到类名TestController
//接下来提取命名空间
$file = file_get_contents($file, false, null, 0, 500);
preg_match('/namespace\s(.*)/i', $file, $nameSpace); //正则匹配拿到文件头部的namespace申明
// var_dump($className);
// var_dump($nameSpace);
//类实例化:new 命名空间+类名
if (isset($nameSpace)) {
$nameSpace = str_replace([" ", ";", "\""], "", $nameSpace[1]);
$obj = trim($nameSpace. '\\'. $className);
// var_dump($obj); //App\Admin\Controller\IndexController
$reflect = new \ReflectionClass($obj);
$classDocComment = $reflect->getDocComment(); //获取类注解
// var_dump($classDocComment);
preg_match('/@Controller\((.*)\)/i', $classDocComment, $prefix);
if (isset($prefix)) {
$prefix = str_replace("\"", "", explode("=", $prefix[1]))[1];//提取Controller类注解前缀
// var_dump($prefix); //专门有个解析类
}
//匹配前缀
foreach ($reflect->getMethods() as $method) {
// var_dump($method->getDocComment());
$methodDocComment = $method->getDocComment();
preg_match('/@RequestMapping\((.*)\)/i', $methodDocComment, $suffix);//提取Controller类action方法注解前缀
$suffix = str_replace("\"", "", explode("=", $suffix[1]))[1];
// var_dump($suffix);
$routeInfo = [
'routePath' => '/'. $prefix. '/'. $suffix, //test/index
'handle' => $reflect->getName()."@".$method->getName() //类名和方法名拼接:App\Api\Controller\TestController@index
];
// var_dump($routeInfo);
Route::addRoute('GET', $routeInfo); //注册路由
}
}
//面向过程手写注解路由方法
// $obj = new TestController();
// $reflect = new \ReflectionClass($obj);
// $classDocComment = $reflect->getDocComment(); //获取类注解
// // var_dump($classDocComment);
// preg_match('/@Controller\((.*)\)/i', $classDocComment, $prefix);
// if (isset($prefix)) {
// $prefix = str_replace("\"", "", explode("=", $prefix[1]))[1];//提取Controller类注解前缀
// // var_dump($prefix); //专门有个解析类
// }
// //匹配前缀
// foreach ($reflect->getMethods() as $method) {
// // var_dump($method->getDocComment());
// $methodDocComment = $method->getDocComment();
// preg_match('/@RequestMapping\((.*)\)/i', $methodDocComment, $suffix);//提取Controller类action方法注解前缀
// $suffix = str_replace("\"", "", explode("=", $suffix[1]))[1];
// // var_dump($suffix);
// $routeInfo = [
// 'routePath' => '/'. $prefix. '/'. $suffix, //test/index
// 'handle' => $reflect->getName()."@".$method->getName() //类名和方法名拼接:App\Api\Controller\TestController@index
// ];
// // var_dump($routeInfo);
// Route::addRoute('GET', $routeInfo); //注册路由
// }
}
}
}
/**
* 遍历目录
* @param $dir
*/
public function tree($dir, $filter){
$dirs = glob($dir. '/*');
$dirFiles = [];
foreach ($dirs as $dir) {
if (is_dir($dir)) {
$res = $this->tree($dir, $filter);
if (is_array($res)) {
foreach ($res as $v) {
$dirFiles[] = $v;
}
}
} else {
//判断是否是控制器
if (stristr($dir, $filter)) {
$dirFiles[] = $dir;
}
}
}
return $dirFiles;
}
}
?>
路由核心类:
<?php
namespace Im\Core\Route;
class Route
{
/**
* Example
* GET=>[
*
* [
* routePath=>'/index/test',
* handle => App\Api\IndexController@indeex
* ]
* ]
*/
private static $route;
/**
* 添加一个路由
*/
public static function addRoute($method, $routeInfo){
self::$route[$method][] = $routeInfo;
}
/**
* 路由分发
*/
public static function dispatch($method, $pathInfo){
// var_dump(self::$route);
switch ($method){
case 'GET':
foreach (self::$route[$method] as $v) {
//判断路径是否在注册的路由上
// var_dump($pathInfo, $v['routePath']);
if ($pathInfo == $v['routePath']) {
// var_dump($v['handle']);
$handle = explode("@", $v['handle']);
$class = $handle[0];
$method = $handle[1];
return (new $class)->$method();
}
}
break;
case 'POST':
break;
}
}
}
?>
控制器类:
<?php
namespace App\Admin\Controller;
/**
* Class IndexController
* @Controller(prefix="admin/index")
*/
class IndexController
{
/**
* @RequestMapping(route="index")
*/
public function index(){
return "admin控制器index方法";
}
}
?>
控制台启动服务后的效果:
在浏览器中访问地址效果:autoload
自动实例化和自动加载思想类似,都是进行类实例化,自动实例化是在项目执行入口处将需要的类全部实例化,而自动加载是在程序执行过程中使用类时在当前文件中没有引入类文件,导致程序无法执行时自动调用自动加载函数。
默认自动加载函数为__autoload(),当程序中有定义spl_autoload_register()函数时,程序会默认执行spl_autoload_register()函数中定义的自定义加载函数,如项目中类和类文件遵循规则与__autoload()中默认定义的规则不一致时可自定义函数明确加载文件的规则,然后在spl_autoload_register()中加入自定义函数名称,如spl_autoload_register(‘myRule’)
IOC容器
容器好比是一个巨大的工厂,用于存放和管理对象的生命周期,并且能够解决程序的依赖关系,实现解耦。
依赖注入:在A类中使用了B类的实例时,B对象的构造不是在A类某个方法中初始化的,而是在A类外部初始化以后以B类的对象传入进来。这个过程就是依赖注入。所需要的类通过参数的形式传入的就是依赖注入。
控制反转IoC:控制反转是说创建对象的控制权进行转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权利转移到第三方,比如转移给IoC容器,它就是一个专门用来创建对象的工厂。有了IoC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IoC容器来建立它们之间的关系,控制反转的意思是说将依赖类的控制权交出去,由主动变为被动。比方在业务逻辑中要用到数据库查询时,先要初始化数据库操作类,建立连接、查询等一系列操作都是在业务逻辑节点进行编码,引入依赖注入后数据库类的初始化和数据库连接建立操作都由外部程序执行,在业务逻辑节点不再参与类实例化过程,即将对象创建和管理的权利转交给容器。
如在A类中要使用B类,IoC容器执行类的实例化,并将类实例化的结果注入到要调用类实例的A业务代码中。
一、创建单例类:
<?php
namespace Lib;
class RequestResponseHandle
{
public static $handle;
private function __construct() {
return True;
}
public static function getInstance() {
if (is_null(self::$handle)) {
self::$handle = new self();
}
return self::$handle;
}
public function getList() {
return ["aaa", "bbb", "ccc"];
}
}
?>
二、创建服务容器
<?php
return [
'RequestResponseHandle' => function() {
return \Lib\RequestResponseHandle::getInstance();
},
'Config' => function() {
return \Im\Core\Config::getInstance();
},
];
?>
三、创建服务容器工厂类
<?php
namespace Im\Core\Bean;
class BeanFactory
{
private static $container = [];
public static function set(string $name, callable $func)
{
self::$container[$name] = $func;
}
public static function get(string $name)
{
if (isset(self::$container[$name])) {
return (self::$container[$name])();//执行对象方法
}
return null;
}
}
?>
四、调用容器中的服务
use Im\Core\Bean\BeanFactory;
class App
{
protected $beanFile = 'Bean.php';
public function run()
{
BeanFactory::get('RequestResponseHandle')->getList()
}
}
将数据库、redis缓存、server配置等定义成配置文件,然后在应用程序中统一使用容器来获得配置便于后续开发过程中便于修改配置信息。
如config/db.php
<?php
return [
'mysql' => [
'host' => '127.0.0.1',
'port' => 3306
]
]
?>
定义config类载入配置项:
<?php
namespace Im\Core;
class Config
{
private static $instance;
private static $configMap = [];
private function __construct()
{
}
public static function getInstance()
{
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
public function load()
{
//载入
$files = glob(CONFIG_PATH. "/*.php");
if (!empty($files)) {
foreach ($files as $dir => $fileName) {
self::$configMap += include "{$fileName}";
}
}
}
public function get($key)
{
if (isset(self::$configMap[$key])) {
return self::$configMap[$key];
}
return false;
}
}
?>
使用容器加载配置项:
//载入容器
BeanFactory::get('Config')->load();
//获取服务
$config = BeanFactory::get('Config')->get('http');
//载入路由的注解
$http = new Server($config['host'], $config['port']);
$http->set($config['setting']);