1. 程式人生 > >yii2源碼分析之組件實例化流程

yii2源碼分析之組件實例化流程

yii2

讀本篇文章,建議先看看我之前的文章php依賴註入


到此,現在我們正式開始分析yii2框架組件構造流程


我們先從yii\di\ServiceLocator(服務定位器)入手吧!!讓我們先看個實例:

use yii\di\ServiceLocator;
use yii\caching\FileCache;

$locator = new ServiceLocator;

// 通過一個可用於創建該組件的類名,註冊 "cache" (緩存)組件。
$locator->set('cache', 'yii\caching\ApcCache');

// 通過一個可用於創建該組件的配置數組,註冊 "db" (數據庫)組件。
$locator->set('db', [
    'class' => 'yii\db\Connection',
    'dsn' => 'mysql:host=localhost;dbname=demo',
    'username' => 'root',
    'password' => '',
]);


我們直接打開ServiceLocator的set方法:

//這個函數很簡單,就是定義一個id對應$definition,並保存到$this->_definitions數組中
public function set($id, $definition)
{
    if ($definition === null) {
        unset($this->_components[$id], $this->_definitions[$id]);
        return;
    }

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

    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));
    }
}


現在我們有set方法,如果我們需要指定類名的對象呢,這時候需要看看ServiceLocator的get方法:

public function get($id, $throwException = true)
{
    if (isset($this->_components[$id])) {
        return $this->_components[$id];
    }

    if (isset($this->_definitions[$id])) {
        $definition = $this->_definitions[$id];
        //如果是一個對象,則直接返回
        if (is_object($definition) && !$definition instanceof Closure) {
            return $this->_components[$id] = $definition;
        } else {
            //Yii::createObject創建對象實例(重點方法)
            return $this->_components[$id] = Yii::createObject($definition);
        }
    } elseif ($throwException) {
        throw new InvalidConfigException("Unknown component ID: $id");
    } else {
        return null;
    }
}


跟蹤Yii::createObject方法

public static function createObject($type, array $params = [])
{
    if (is_string($type)) {
        //static::$container就是yii\di\Container對象(依賴註入容器)
        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);
    } elseif (is_callable($type, true)) {
        return call_user_func($type, $params);
    } elseif (is_array($type)) {
        throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');
    } else {
        throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type));
    }
}


我們來看看yii2的依賴註入容器怎麽實現的,打開yii\di\Container的get方法:

public function get($class, $params = [], $config = [])
{
    //$this->_singletons保存單例對象
    if (isset($this->_singletons[$class])) {
        // singleton
        return $this->_singletons[$class];
    } elseif (!isset($this->_definitions[$class])) {
        //通過php反射機制實現$class的實例化
        return $this->build($class, $params, $config);
    }
    //代表已經使用過set方法定義過$class
    $definition = $this->_definitions[$class];

    if (is_callable($definition, true)) {
        $params = $this->resolveDependencies($this->mergeParams($class, $params));
        $object = call_user_func($definition, $this, $params, $config);
    } elseif (is_array($definition)) {
        $concrete = $definition['class'];
        unset($definition['class']);

        $config = array_merge($definition, $config);
        $params = $this->mergeParams($class, $params);

        if ($concrete === $class) {
            $object = $this->build($class, $params, $config);
        } else {
            $object = $this->get($concrete, $params, $config);
        }
    } elseif (is_object($definition)) {
        return $this->_singletons[$class] = $definition;
    } else {
        throw new InvalidConfigException('Unexpected object definition type: ' . gettype($definition));
    }

    if (array_key_exists($class, $this->_singletons)) {
        // 單例對象
        $this->_singletons[$class] = $object;
    }

    return $object;
}


我們打開這個$this->build方法:

protected function build($class, $params, $config)
{
    /* 
        $reflection為反射類
        $denpendencies為$class類構造函數參數,如果構造函數參數裏面有依賴別的類,舉例:
        [new Instance(id屬性=該類名),new Instance(id屬性=該類名)]
    */
    list ($reflection, $dependencies) = $this->getDependencies($class);

    //將$params的key和值加到$class構造函數參數數組列表中
    foreach ($params as $index => $param) {
        $dependencies[$index] = $param;
    }
        //將構造函數參數中有yii\di\Instance的id屬性不為空,則將繼續遞歸調用$this->get將所依賴的類實例化
    $dependencies = $this->resolveDependencies($dependencies, $reflection);
    if (empty($config)) {
        return $reflection->newInstanceArgs($dependencies);
    }

    if (!empty($dependencies) && $reflection->implementsInterface('yii\base\Configurable')) {
        // set $config as the last parameter (existing one will be overwritten)
        $dependencies[count($dependencies) - 1] = $config;
        return $reflection->newInstanceArgs($dependencies);
    } else {
        $object = $reflection->newInstanceArgs($dependencies);
        //將$config數組賦值到實例化對象的屬性上
        foreach ($config as $name => $value) {
            $object->$name = $value;
        }
        return $object;
    }
}


繼續追$this->getDependencies方法:

//得到[$reflection, $dependencies];
protected function getDependencies($class)
{
    if (isset($this->_reflections[$class])) {
        return [$this->_reflections[$class], $this->_dependencies[$class]];
    }

    $dependencies = [];
    $reflection = new ReflectionClass($class);

    $constructor = $reflection->getConstructor();
    if ($constructor !== null) {
        foreach ($constructor->getParameters() as $param) {
            if ($param->isDefaultValueAvailable()) {
                $dependencies[] = $param->getDefaultValue();
            } else {
                $c = $param->getClass();
                $dependencies[] = Instance::of($c === null ? null : $c->getName());
            }
        }
    }
    //緩存$reflection反射類,以及$class類構造函數參數數組
    $this->_reflections[$class] = $reflection;
    $this->_dependencies[$class] = $dependencies;

    return [$reflection, $dependencies];
}


我們打開$this->resolveDependencies方法:

protected function resolveDependencies($dependencies, $reflection = null)
{
    foreach ($dependencies as $index => $dependency) {
        if ($dependency instanceof Instance) {
            if ($dependency->id !== null) {
                $dependencies[$index] = $this->get($dependency->id);
            } elseif ($reflection !== null) {
                $name = $reflection->getConstructor()->getParameters()[$index]->getName();
                $class = $reflection->getName();
                throw new InvalidConfigException("Missing required parameter \"$name\" when instantiating \"$class\".");
            }
        }
    }
    return $dependencies;
}


最後來個yii\di\Instance類的具體內容(跟我之前那個類代碼基本一樣,多了個ensure方法):

namespace yii\di;

use Yii;
use yii\base\InvalidConfigException;

class Instance
{
    public $id;


    protected function __construct($id)
    {
        $this->id = $id;
    }

    public static function of($id)
    {
        return new static($id);
    }

    /**
     *
     * ```php
     * use yii\db\Connection;
     *
     * // returns Yii::$app->db
     * $db = Instance::ensure('db', Connection::className());
     * // returns an instance of Connection using the given configuration
     * $db = Instance::ensure(['dsn' => 'sqlite:path/to/my.db'], Connection::className());
     * ```
     */
    public static function ensure($reference, $type = null, $container = null)
    {
        if (is_array($reference)) {
            $class = isset($reference['class']) ? $reference['class'] : $type;
            if (!$container instanceof Container) {
                $container = Yii::$container;
            }
            unset($reference['class']);
            return $container->get($class, [], $reference);
        } elseif (empty($reference)) {
            throw new InvalidConfigException('The required component is not specified.');
        }

        if (is_string($reference)) {
            $reference = new static($reference);
        } elseif ($type === null || $reference instanceof $type) {
            return $reference;
        }

        if ($reference instanceof self) {
            $component = $reference->get($container);
            if ($type === null || $component instanceof $type) {
                return $component;
            } else {
                throw new InvalidConfigException('"' . $reference->id . '" refers to a ' . get_class($component) . " component. $type is expected.");
            }
        }

        $valueType = is_object($reference) ? get_class($reference) : gettype($reference);
        throw new InvalidConfigException("Invalid data type: $valueType. $type is expected.");
    }

    public function get($container = null)
    {
        if ($container) {
            return $container->get($this->id);
        }
        if (Yii::$app && Yii::$app->has($this->id)) {
            return Yii::$app->get($this->id);
        } else {
            return Yii::$container->get($this->id);
        }
    }
}


yii2源碼分析之組件實例化流程