由于工作中暂时没有用到yii,而自己平时又太多的杂事,似乎都没怎么深入过,着实惭愧。
重新看深入理解yii2.0,似乎更加清晰,本文就是按照自己的理解写一下yii对象加载时,对属性的处理过程。

打开当前应用下的入口文件,可以看到加载了很多配置文件,其实这些配置文件就是一个各种配置项的数组,Yii中就是通过这些数组对对象进行配置的,这一方式贯穿整个Yii。

// comment out the following two lines when deployed to production
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');

require(__DIR__ . '/../vendor/autoload.php');
require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');

$config = require(__DIR__ . '/../config/web.php');

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

那么这个所谓的配置数组是在哪里用到的呢,逐级追可以看到在 yii\base\Object 的构造函数中:

public function __construct($config = [])
{
    if (!empty($config)) {
        Yii::configure($this, $config);
    }
    $this->init();
}

而 Yii::configure() 是何许人也?其实此方法只是执行了一个数组遍历,将配置数组的键作为属性名,将对应数组元素的值为对象的属性赋值。

public static function configure($object, $properties)
{
    foreach ($properties as $name => $value) {
        $object->$name = $value;
    }

    return $object;
}

这样你配置数据就可以通过访问 Yii 的属性进行访问了。

但是,到这里还没有完,Yii很多默认的组件,比如 Yii::$app->request ,是配置文件中 components 的嵌套配置数组里的,但是也是直接访问的,是哪里做的处理呢?

在Yii的规则里,如果一个属性是对象,那么他就要通过数组来进行配置。Yii应用 yii\web\Application 就是依靠定义专门的setter函数,实现自动处理配置项的。比如,我们上面说的配置项 components 的内容是这样的:

'components' => [
    'request' => [
        // !!! insert a secret key in the following (if it is empty) -
        // this is required by cookie validation
        'cookieValidationKey' => 'Ruesin',
    ],
    'user' => [
        'identityClass' => 'common\models\User',
        'enableAutoLogin' => true,
    ],
    'log' => [
        'traceLevel' => YII_DEBUG ? 3 : 0,
        'targets' => [
            [
                'class' => 'yii\log\FileTarget',
                'levels' => ['error', 'warning'],
            ],
        ],
    ],
    'errorHandler' => [
        'errorAction' => 'site/error',
    ],
],

Yii 在服务定位器 yii\di\ServiceLocator 中定义了一个名为 setComponents 的setter函数。

public function setComponents($components)
{
    foreach ($components as $id => $component) {
        $this->set($id, $component);
    }
}
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));
    }
}

这个方法服务定位器用来注册服务的方法。通过这个方法判断配置文件中的 components 配置项是对象还是数组,如果是数组就实例化成对象。并在 setComponents 方法中将对象赋值给 app 的属性。

所有 yii\base\Object 的构建流程是:

1.引入由外部代码或者通过配置文件规范的配置数组项。
2.yii\base\Object 构造函数,设置属性的默认值,加载配置数组。
3.通过构造函数调用 Yii::configure($this, $config) 设置对象属性的默认值,重写上一步的属性值。如果对象属性还是一个对象或数组,定义并调用 setKey 的setter方法,将数组实例化成对象再复制给app对象的属性。比如我们常用的组件对象就是使用 setComponents 将各个组件实例化成对象并赋值给app对象的属性的。
4.如上步骤完成后,调用初始化方法 init() ,通过在 init() 写入代码,可以对配置阶段设置的值进行检查,并规范类的property。

此时,该对象的状态是确定且可靠的,不存在不确定的property。 所有的属性要么是默认值,要么是传入的配置值,如果传入的配置有误或者冲突,那么也经过了检查和规范。 也就是说,你就放心用吧。

如果你的对象继承自 yii\base\Object 。那么你要做到一下几点:
为对象属性提供setter方法,以正确处理配置过程。
如果需要重载构造函数, $config 要作为该构造函数的最后一个参数,并要传递该参数给父构造函数。
重载的构造函数的最后,一定记得调用父构造函数。
如果重载了初始化方法 init(),注意一定要在重载方法的开头调用父类的 init() 。