Yii2 完整框架分析(詳細)

Yii2 框架Trace

準備

  1. 瞭解composer的autoload psr0 psr4 加載機制php

  2. 瞭解spl_autoload_registerweb

  3. 瞭解依賴注入的實現原理反射bootstrap

  4. 瞭解經常使用魔術方法__set,__get,__callsession

  5. 熱情與專一app

入口分析

加載composer 的自動加載器,支持了PSR-0 PSR-4composer

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

進行常量的定義,而且聲明瞭最基本的方法例如getVersion框架

require __DIR__ . '/BaseYii.php';

加載Yii本身的autoload加載器,從classmap中尋找,指定的類,若是沒有找到,會解析名稱到路徑。yii

spl_autoload_register(['Yii', 'autoload'], true, true);
Yii::$classMap = require __DIR__ . '/classes.php';

todo 容器生成webapp

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

相關文章
相關標籤/搜索