11

Yii2 框架Trace

准备

  1. 了解composer的autoload psr0 psr4 加载机制

  2. 了解spl_autoload_register

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

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

  5. 热情与专注

入口分析

加载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

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

例如r=site/index

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

例如r=site/index/test

  1. 发现没有找到控制器,那么从模块中获取,这里是重新构造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魔术方法

继承链图解

图片描述

END


jaysun
507 声望25 粉丝

Code is law