yii2 php反射,Yii2 完整框架分析(详细)

Yii2 框架Trace

准备

了解composer的autoload psr0 psr4 加载机制

了解spl_autoload_register

了解依赖注入的实现原理反射

了解常用魔术方法__set,__get,__call

热情与专注

入口分析

加载composer 的自动加载器,支持了PSR-0 PSR-4

require(__DIR__ . '/../vendor/autoload.php');

进行常量的定义,并且声明了最基本的方法例如getVersion

require __DIR__ . '/BaseYii.php';

加载Yii自己的autoload加载器,从classmap中寻找,指定的类,如果没有找到,会解析名称到路径。

spl_autoload_register(['Yii', 'autoload'], true, true);

Yii::$classMap = require __DIR__ . '/classes.php';

todo 容器生成

Yii::$container = new yii\di\Container();

开始生成一个应用主体,并且加载了config配置,直接运行

(new yii\web\Application($config))->run();

主体生成

application is the base class for all web application classes.

Yii::$app 是应用主体的实例,一个请求只会生成一个应用主体

在Application类中,定义了初始的defaultRoute,以及coreComponent核心组件的列表,还有一些请求和相应相关的方法

继承链

web/application=>base/application=>base/Model=>id/ServiceLocator=>base/component=>base/object=>base/configurable

初始化主体的配置

public function __construct($config = [])

{

Yii::$app = $this; // 这样在任何地方都可以通过静态方法的方式,来调用应用主体

static::setInstance($this); // 将请求过来的的类实力,进行保存

$this->state = self::STATE_BEGIN;

$this->preInit($config); // 进行config的初始化,给路径起别名,设置时区等,并且最后加载了核心组件

$this->registerErrorHandler($config); // 注册错误句柄,用来捕捉和处理错误的方法

Component::__construct($config); //

}

其中preInit会进行一个注册核心组件,这里web的入口进行了扩展,包含了request等

public function coreComponents()

{

return array_merge(parent::coreComponents(), [

'request' => ['class' => 'yii\web\Request'],

'response' => ['class' => 'yii\web\Response'],

'session' => ['class' => 'yii\web\Session'],

'user' => ['class' => 'yii\web\User'],

'errorHandler' => ['class' => 'yii\web\ErrorHandler'],

]);

}

讲configure格式为对象,存储到应用主体中

public function __construct($config = [])

{

if (!empty($config)) {

Yii::configure($this, $config);

}

$this->init();

}

组件注册

这里我们来看下组件是如何注册到应用主体中的,这个-> 实际上调用的是__Set魔术方法,

那我们再看这个$this是什么,很明显是指yii\web\application

public static function configure($object, $properties)

{

foreach ($properties as $name => $value) {

$object->$name = $value;

}

return $object;

}

我们从webapplication向parent一层一层的找,找到了__set 的定义,在basecomponent

public function __set($name, $value)

{

$setter = 'set' . $name;

if (method_exists($this, $setter)) {

// set property

$this->$setter($value);

return;

} elseif (strncmp($name, 'on ', 3) === 0) {

// on event: attach event handler

$this->on(trim(substr($name, 3)), $value);

return;

} elseif (strncmp($name, 'as ', 3) === 0) {

// as behavior: attach behavior

$name = trim(substr($name, 3));

$this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));

return;

}

// behavior property

$this->ensureBehaviors();

foreach ($this->_behaviors as $behavior) {

if ($behavior->canSetProperty($name)) {

$behavior->$name = $value;

return;

}

}

if (method_exists($this, 'get' . $name)) {

throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);

}

throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);

}

上面的set方法,会遍历config的属性,来交给不同的set方法处理,果然恰好存在了一个setComponents,用来处理组件

public function setComponents($components)

{

foreach ($components as $id => $component) {

$this->set($id, $component);

}

}

最终组件配置就这样被注册到了$this->_definitions里面,后面我们可以通过$this->get($id) 来获取组件配置

public function set($id, $definition)

{

unset($this->_components[$id]);

if ($definition === null) {

unset($this->_definitions[$id]);

return;

}

if (is_object($definition) || is_callable($definition, true)) {

// an object, a class name, or a PHP callable

$this->_definitions[$id] = $definition;

} elseif (is_array($definition)) {

// a configuration array

if (isset($definition['class'])) {

$this->_definitions[$id] = $definition;

} else {

throw new InvalidConfigException("The configuration for the \"$id\" component must contain a \"class\" element.");

}

} else {

throw new InvalidConfigException("Unexpected configuration type for the \"$id\" component: " . gettype($definition));

}

}

注意这个init,并不是当前类下面的init方法,而是被webapplication 覆盖

public function init()

{

$this->state = self::STATE_INIT;

$this->bootstrap();

}

同样bootstrap方法也被覆盖

protected function bootstrap()

{

$request = $this->getRequest();

Yii::setAlias('@webroot', dirname($request->getScriptFile()));

Yii::setAlias('@web', $request->getBaseUrl());

parent::bootstrap();

}

看下getRequest是怎么回事,跟踪代码,它通过get方法,来从_definitions中获取request对应的配置

然后根据配置中的yiiwebrequest 来进行创建对象,其中$type 就是配置

public static function createObject($type, array $params = [])

{

if (is_string($type)) {

return static::$container->get($type, $params);

} elseif (is_array($type) && isset($type['class'])) {

$class = $type['class'];

unset($type['class']);

return static::$container->get($class, $params, $type); // request 走的是这里

} elseif (is_callable($type, true)) {

return static::$container->invoke($type, $params);

} elseif (is_array($type)) {

throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');

}

throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type));

}

进行完web层的引导之后,继续进行base层的引导,包括对配置中bootstrap的引导注册,会直接通过容器得到实例。(具体不详)

小结

在上面的new的过程中,完成了组件的注册,配置的初始化,并且学到了get是createObject通过依赖注入的方法,获取组件对象

请求的处理

前面所有的配置和准备都初始化完毕之后,要进行请求处理了。

这一句的run方法,就是处理请求的整个启动入口

$application->run();

使用handleRequest方法来处理请求,并且参数是request实例,其中handleRequest方法要看webapplication层的封装

$response = $this->handleRequest($this->getRequest());

调用resolve进行解析

list($route, $params) = $request->resolve();

我们再看Urlmanager的时候,发现里面定义的routeParam是r也就是默认接受参数的值(重要)

实际上下面这段代码完成的就是解析了$_GET的值,从里面寻找routeParam 定义的值(r)所代表的内容

$result = Yii::$app->getUrlManager()->parseRequest($this);

这里要调用action方法了

$this->requestedRoute = $route;

$result = $this->runAction($route, $params);

创建控制器+调用action

实际上runAction中的主要内容就是createController的实现:

// parts 分为两部分,0 是controller的实例,1 是实例里面的方法名称

$parts = $this->createController($route);

...

...

/* @var $controller Controller */ // 这是一个跟踪优化,不然controller->runaction 就定位不了了

list($controller, $actionID) = $parts;

$oldController = Yii::$app->controller;

Yii::$app->controller = $controller;

$result = $controller->runAction($actionID, $params);

如果遇到了控制器,那么直接返回控制器对象和方法route的名称,这里route类似于action方法名称,代码略微繁琐,但是很清晰,具体实现就是

路由解析规则

例如r=site

寻找controller,找到site控制器,直接实例化

例如r=site/index

构造控制器,并且id为site、route为index

例如r=site/index/test

发现没有找到控制器,那么从模块中获取,这里是重新构造id为site/index route为test,然后调用createControllerById 方法来获取控制器(具体不详,不过应该是通过namespace定位了)

而且createController直接返回了controller的实例

然后我们在createAction中找到解析action方法名称的代码

例如r=site/index-test 那么下面对应的methodName就是actionIndexTest

$methodName = 'action' . str_replace(' ', '', ucwords(implode(' ', explode('-', $id))));

内容输出

将调用action方法的值,进行返回,然后直接交给yii\web\Response作为data属性的一部分。最后调用send方法,进行输出。

public function send()

{

if ($this->isSent) {

return;

}

$this->trigger(self::EVENT_BEFORE_SEND);

$this->prepare();

$this->trigger(self::EVENT_AFTER_PREPARE);

$this->sendHeaders();

$this->sendContent();

$this->trigger(self::EVENT_AFTER_SEND);

$this->isSent = true;

}

行为是如何注册到组件的呢?

通过attacheBehavior注册行为之后,实际上是添加到了$this的_behaviors属性中

那么行为中的属性,就添加到了,_behaviors中

进行直接调用行为里面的方法的时候,实际上触发了yii\base\Component里面的__call魔术方法

继承链图解

bVYXLh?w=1201&h=333

END

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值