正文
属性的概念
属性用于表征类的状态,从访问的形式上看,属性与成员变量没有区别。
你能一眼看出 $object->foo
中的 foo 是成员变量还是属性么?显然不行。
但是,成员变量是就类的结构构成而言的概念,而属性是就类的功能逻辑而言的概念,两者紧密联系又 相互区别。
比如,我们说People类有一个成员变量 int $age
,表示年龄。那么这里年龄就是属性 , $age
就是成员变量。
看一个例子,与非门:
class NotAndGate extends Object{
private $_key1;
private $_key2;
public function setKey1($value){
$this->_key1 = $value;
}
public function setKey2($value){
$this->_key2 = $value;
}
public function getOutput(){
if (!$this->_key1 || !$this->_key2)
return true;
else if ($this->_key1 && $this->_key2)
return false;
}
}
与非门有两个输入,当两个输入都为真时,与非门的输出为假,否则,输出为真。
上面的代码中,与非门类有两个成员变量, $_key1
和 $_key2
。
但是有3个属性,表示2个输入的 key1 和 key2 ,以及表示输出的 output 。
成员变量和属性的区别与联系在于:
1、成员变量是一个“内”概念,反映的是类的结构构成。属性是一个“外”概念,反映的是类的逻辑意义。
2、成员变量没有读写权限控制,而属性可以指定为只读或只写,或可读可写。
3、成员变量不对读出作任何后处理,不对写入作任何预处理,而属性则可以。
4、public成员变量可以视为一个可读可写、没有任何预处理或后处理的属性。 而private成员变量由于外部不可见, 与属性“外”的特性不相符,所以不能视为属性。
5、虽然大多数情况下,属性会由某个或某些成员变量来表示,但属性与成员变量没有必然的对应关系,
比如与非门的 output 属性,就没有一个所谓的 $output
成员变量与之对应。
Yii2中属性支持
在Yii中,由 yii\base\Object
提供了对属性的支持,因此,如果要使你的类支持属性, 必须继承自 yii\base\Object
。
Yii中属性是通过PHP的魔法函数 __get()
、__set()
来产生作用的。
下面的代码是 yii\base\Object
类对于 __get()
和 __set()
的定义:
<?php
namespace yii\base;
use Yii;
class Object implements Configurable
{
public static function className()
{
return get_called_class();
}
public function __construct($config = [])
{
if (!empty($config)) {
Yii::configure($this, $config);
}
$this->init();
}
public function init()
{
}
public function __get($name) // 这里$name是属性名
{
$getter = 'get' . $name; // getter函数的函数名
if (method_exists($this, $getter)) {
return $this->$getter(); // 调用了getter函数
} elseif (method_exists($this, 'set' . $name)) {
throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
} else {
throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
}
}
public function __set($name, $value) // $name是属性名,$value是拟写入的属性值
{
$setter = 'set' . $name; // setter函数的函数名
if (method_exists($this, $setter)) {
$this->$setter($value); // 调用setter函数
} elseif (method_exists($this, 'get' . $name)) {
throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
} else {
throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
}
}
public function __isset($name)
{
$getter = 'get' . $name;
if (method_exists($this, $getter)) {
return $this->$getter() !== null;
} else {
return false;
}
}
public function __unset($name)
{
$setter = 'set' . $name;
if (method_exists($this, $setter)) {
$this->$setter(null);
} elseif (method_exists($this, 'get' . $name)) {
throw new InvalidCallException('Unsetting read-only property: ' . get_class($this) . '::' . $name);
}
}
public function __call($name, $params)
{
throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
}
public function hasProperty($name, $checkVars = true)
{
return $this->canGetProperty($name, $checkVars) || $this->canSetProperty($name, false);
}
public function canGetProperty($name, $checkVars = true)
{
return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name);
}
public function canSetProperty($name, $checkVars = true)
{
return method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name);
}
public function hasMethod($name)
{
return method_exists($this, $name);
}
}
Yii2中实现属性的步骤
在PHP中,读取和写入对象的一个不存在的成员变量时, __get()
__set()
会被自动调用。
Yii正是利用这点,提供对属性的支持的。
如访问一个对象的某个属性, Yii会调用名为 get属性名() 的函数。如, SomeObject->Foo
, 会自动调用 SomeObject->getFoo()
。
如果修改某一属性,会调用相应的setter函数。 如, SomeObject->Foo = $someValue
,会自动调用 SomeObject->setFoo($someValue)
。
要实现属性,通常有三个步骤:
1、继承自 yii\base\Object
。
2、声明一个用于保存该属性的私有成员变量。
3、提供getter或setter函数,或两者都提供,用于访问、修改上面提到的私有成员变量。 如果只提供了getter,那么该属性为只读属性,只提供了setter,则为只写。
如下的Post类,实现了可读可写的属性title:
class Post extends yii\base\Object // 第一步:继承自 yii\base\Object
{
private $_title; // 第二步:声明一个私有成员变量
public function getTitle() // 第三步:提供getter和setter
{
return $this->_title;
}
public function setTitle($value)
{
$this->_title = trim($value);
}
}
从理论上来讲,将 private $_title
写成 public $title
,也是可以实现对 $post->title
的读写的。
但这不是好的习惯,理由如下:
1、失去了类的封装性。 一般而言,成员变量对外不可见是比较好的编程习惯。
从这里你也许没看出来,但是假如有一天,你不想让用户修改标题了,你怎么改?
怎么确保代码中没有直接修改标题? 如果提供了setter,只要把setter删掉,那么一旦有没清理干净的对标题的写入,就会抛出异常。
而使用 public $title
的方法的话,你改成 private $title
可以排查写入的异常,但是读取的也被禁止了。
2、对于标题的写入,你想去掉空格。 使用setter的方法,只需要像上面的代码段一样在这个地方调用 trim()
就可以了。
但如果使用 public $title
的方法,那么毫无疑问,每个写入语句都要调用 trim()
。 你能保证没有一处遗漏?
因此,使用 public $title
只是一时之快,看起来简单,但今后的修改是个麻烦事。
软件工程的意义就是:通过一定的方法,使代码易于维护、便于修改。
但是,世事无绝对。由于 __get()
和 __set()
是在遍历所有成员变量,找不到匹配的成员变量时才被调用。
因此,其效率天生地低于使用成员变量的形式。在一些表示数据结构、数据集合等简单情况下,且不需读写控制等,
可以考虑使用成员变量作为属性,这样可以提高一点效率。
另外一个提高效率的小技巧就是:使用 $pro = $object->getPro()
来代替 $pro = $object->pro
,
用 $objcect->setPro($value)
来代替 $object->pro = $value
。
这在功能上是完全一样的效果,但是避免了使用 __get()
和 __set()
,相当于绕过了遍历的过程。
这里需要注意的是:
1、由于自动调用 __get()
__set()
的时机仅仅发生在访问不存在的成员变量时。
因此,如果定义了成员变量 public $title
那么,就算定义了 getTitle()
setTitle()
, 他们也不会被调用。
因为 $post->title
时,会直接指向该 pulic $title
, __get()
__set()
是不会被调用的。从根上就被切断了。
2、由于PHP对于类方法不区分大小写,即大小写不敏感, $post->getTitle()
和 $post->gettitle()
是调用相同的函数。
因此, $post->title
和 $post->Title
是同一个属性。即属性名也是不区分大小写的。
3、由于 __get()
__set()
都是public的, 无论将 getTitle()
setTitle()
声明为 public
, private
, protected
,都没有意义,
外部同样都是可以访问。所以,所有的属性都是public
的。
4、由于 __get()
__set()
都不是static
的,因此,没有办法使用static
的属性。
Object的其他与属性相关的方法
除了 __get()
__set()
之外, yii\base\Object
还提供了以下方法便于使用属性:
1、__isset()
用于测试属性值是否不为 null
,在 isset($object->property)
时被自动调用。 注意该属性要有相应的getter。
2、__unset()
用于将属性值设为 null
,在 unset($object->property)
时被自动调用。 注意该属性要有相应的setter。
3、hasProperty()
用于测试是否有某个属性。即,定义了getter或setter。 如果 hasProperty()
的参数 $checkVars = true
(默认为true
),
那么只要具有同名的成员变量也认为具有该属性,如前面提到的 public $title
。
4、canGetProperty()
测试一个属性是否可读,参数 $checkVars
的意义同上。只要定义了getter,属性即可读。
同时,如果 $checkVars
为 true
。那么只要类定义了成员变量,不管是public
, private
还是 protected
,都认为是可读。
5、canSetProperty()
测试一个属性是否可写,参数 $checkVars
的意义同上。只要定义了setter,属性即可写。
同时,在 $checkVars
为 ture
。那么只要类定义了成员变量,不管是public
, private
还是 protected
,都认为是可写。
Object和Component
yii\base\Component
继承自 yii\base\Object
,因此,他也具有属性等基本功能。
但是,由于Componet还引入了事件、行为,因此,它并非简单继承了Object的属性实现方式,而是基于同样的机制,
重载了 __get()
__set()
等函数。但从实现机制上来讲,是一样的。这个不影响理解。
官方将Yii定位于一个基于组件的框架。可见组件这一概念是Yii的基础。
Yii几乎所有的核心类都派生于(继承自) yii\base\Component
。
在Yii1.1时,就已经有了component了,那时是 CComponent。
Yii2将Yii1.1中的CComponent拆分成两个类: yii\base\Object
和 yii\base\Component
。
其中,Object比较轻量级些,通过getter和setter定义了类的属性(property)。 Component派生自Object,并支持事件(event)和行为(behavior)。 因此,Component类具有三个重要的特性:
1、属性(property)
2、事件(event)
3、行为(behavior)
这三个特性是丰富和拓展类功能、改变类行为的重要切入点。因此,Component在Yii中的地位极高。
在提供更多功能、更多便利的同时,Component由于增加了event和behavior这两个特性, 在方便开发的同时,也牺牲了一定的效率。 如果开发中不需要使用event和behavior这两个特性,比如表示一些数据的类。 那么,可以不从Component继承,而从Object继承。 典型的应用场景就是如果表示用户输入的一组数据,那么,使用Object。 而如果需要对对象的行为和能响应处理的事件进行处理,毫无疑问应当采用Component。 从效率来讲,Object更接近原生的PHP类,因此,在可能的情况下,应当优先使用Object。
Object的配置方法
Yii提供了一个统一的配置对象的方式。这一方式贯穿整个Yii。Application对象的配置就是这种配置方式的体现:
$config = yii\helpers\ArrayHelper::merge(
require(__DIR__ . '/../../common/config/main.php'),
require(__DIR__ . '/../../common/config/main-local.php'),
require(__DIR__ . '/../config/main.php'),
require(__DIR__ . '/../config/main-local.php')
);
$application = new yii\web\Application($config);
$config
看着复杂,但本质上就是一个各种配置项的数组。Yii中就是统一使用数组的方式对对象进行配置,
而实现这一切的关键就在 yii\base\Object
定义的构造函数中:
/**
* Constructor.
* The default implementation does two things:
*
* - Initializes the object with the given configuration `$config`.
* - Call [[init()]].
*
* If this method is overridden in a child class, it is recommended that
*
* - the last parameter of the constructor is a configuration array, like `$config` here.
* - call the parent implementation at the end of the constructor.
*
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($config = [])
{
if (!empty($config)) {
Yii::configure($this, $config);
}
$this->init();
}
所有 yii\base\Object
的构建流程是:
1、构建函数以 $config
数组为参数被自动调用。
2、构建函数调用 Yii::configure()
对对象进行配置。
3、在最后,构造函数调用对象的 init()
方法进行初始化。
看一下Yii::configure()
:
namespace yii;
use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
use yii\base\UnknownClassException;
use yii\log\Logger;
use yii\di\Container;
/**
* Gets the application start timestamp.
*/
defined('YII_BEGIN_TIME') or define('YII_BEGIN_TIME', microtime(true));
/**
* This constant defines the framework installation directory.
*/
defined('YII2_PATH') or define('YII2_PATH', __DIR__);
/**
* This constant defines whether the application should be in debug mode or not. Defaults to false.
*/
defined('YII_DEBUG') or define('YII_DEBUG', false);
/**
* This constant defines in which environment the application is running. Defaults to 'prod', meaning production environment.
* You may define this constant in the bootstrap script. The value could be 'prod' (production), 'dev' (development), 'test', 'staging', etc.
*/
defined('YII_ENV') or define('YII_ENV', 'prod');
/**
* Whether the the application is running in production environment
*/
defined('YII_ENV_PROD') or define('YII_ENV_PROD', YII_ENV === 'prod');
/**
* Whether the the application is running in development environment
*/
defined('YII_ENV_DEV') or define('YII_ENV_DEV', YII_ENV === 'dev');
/**
* Whether the the application is running in testing environment
*/
defined('YII_ENV_TEST') or define('YII_ENV_TEST', YII_ENV === 'test');
/**
* This constant defines whether error handling should be enabled. Defaults to true.
*/
defined('YII_ENABLE_ERROR_HANDLER') or define('YII_ENABLE_ERROR_HANDLER', true);
class BaseYii
{
...
/**
* Configures an object with the initial property values.
* @param object $object the object to be configured
* @param array $properties the property initial values given in terms of name-value pairs.
* @return object the object itself
*/
public static function configure($object, $properties)
{
foreach ($properties as $name => $value) {
$object->$name = $value;
}
return $object;
}
...
}
配置的过程就是遍历 $config
配置数组,将数组的键作为属性名,以对应的数组元素的值对对象的属性赋值。
因此,实现Yii这一统一的配置方式的要点有:
1、继承自 yii\base\Object
。
2、为对象属性提供setter方法,以正确处理配置过程。
3、如果需要重载构造函数,请将 $config
作为该构造函数的最后一个参数,并将该参数传递给父构造函数。
4、重载的构造函数的最后,一定记得调用父构造函数。
5、如果重载了 yii\base\Object::init()
函数,注意一定要在重载函数的开头调用父类的 init()
。
只要实现了以上要点,就可以使得你编写的类可以按照Yii约定俗成的方式进行配置。这在编写代码的过程中,带来许多便利。
如果配置数组的某个配置项,也是一个数组,这怎么办? 如果某个对象的属性,也是一个对象,而非一个简单的数值或字符串时,又怎么办?
这两个问题,其实是同质的。如果一个对象的属性,是另一个对象,就像Application里会引入诸多的Component一样,
这是很常见的。如后面会看到的 $app->request
中的 request 属性就是一个对象。 那么,在配置 $app 时,必然要配置到这个 reqeust 对象。
既然 request 也是一个对象,那么他的配置要是按照Yii的规矩来,也就是用一个数组来配置它。 因此,上面提到的这两个问题,其实是同质的。
那么,怎么实现呢?秘密在于setter函数。由于 $app
在进行配置时,最终会调用 Yii::configure()
函数。
该函数又不区分配置项是简单的数值还是数组,就直接使用 $object->$name = $value
完成属性的赋值。
那么,对于对象属性,其配置值 $value
是一个数组,为了使其正确配置。 你需要在其setter函数上做出正确的处理方式。
Yii应用 yii\web\Application
就是依靠定义专门的setter函数,实现自动处理配置项的。 比如,我们在Yii的配置文件中,
可以看到一个配置项 components ,一般情况下,他的内容是这样的:
'components' => [
'request' => [
// !!! insert a secret key in the following (if it is empty) -
// this is required by cookie validation
'cookieValidationKey' => 'v7mBbyetv4ls7t8UIqQ2IBO60jY_wf_U',
],
'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一定是定义了一个名为 setComponents
的setter函数。
Yii并未将该函数放在 yii\web\Application
里,
而是一路 > yii\base\Application
> yii\base\Module
> 放在父类 yii\di\ServiceLocator
里面:
<?php
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));
}
}
public function setComponents($components)
{
foreach ($components as $id => $component) {
$this->set($id, $component);
}
}
ServiceLocator 在 服务定位器(Service Locator) 部分会讲到,
setComponents中的$this->set()
,他是服务定位器用来注册服务的方法。
从 yii\base\Object::__construct()
来看,对于所有Object,包括Component的属性,都经历这么4个阶段:
1、预初始化阶段。这是最开始的阶段,就是在构造函数 __construct()
的开头可以设置property的默认值。
2、对象配置阶段。也就是前面提到构造函数调用 Yii::configure($this, $config)
阶段。
这一阶段可以覆盖前一阶段设置的property的默认值,并补充没有默认值的参数,也就是必备参数。
$config
通常由外部代码传入或者通过配置文件传入。
3、后初始化阶段。也就是构造函数调用 init()
成员函数。 通过在 init()
写入代码,
可以对配置阶段设置的值进行检查,并规范类的property。
4、类方法调用阶段。前面三个阶段是不可分的,由类的构造函数一口气调用的。 也就是说一个类一但实例化,那么就至少经历了前三个阶段。 此时,该对象的状态是确定且可靠的,不存在不确定的property。 所有的属性要么是默认值,要么是传入的配置值,如果传入的配置有误或者冲突, 那么也经过了检查和规范。
源码
yii\base\Configurable
类
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
/**
* Configurable is the interface that should be implemented by classes who support configuring
* its properties through the last parameter to its constructor.
*
* The interface does not declare any method. Classes implementing this interface must declare their constructors
* like the following:
*
*
* public function __constructor($param1, $param2, ..., $config = [])
*
*
* That is, the last parameter of the constructor must accept a configuration array.
*
* This interface is mainly used by [[\yii\di\Container]] so that it can pass object configuration as the
* last parameter to the implementing class' constructor.
*
* For more details and usage information on Configurable, see the [guide article on configurations](guide:concept-configurations).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0.3
*/
interface Configurable
{
}
yii\base\BaseObject
类
因为在 php7.2中 新增了类 Object
,所以 Yii2 把 yii\base\Object
改名为yii\base\BaseObject
,看一下源码:
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
use Yii;
/**
* BaseObject is the base class that implements the *property* feature.
*
* A property is defined by a getter method (e.g. `getLabel`), and/or a setter method (e.g. `setLabel`). For example,
* the following getter and setter methods define a property named `label`:
*
*
* private $_label;
*
* public function getLabel()
* {
* return $this->_label;
* }
*
* public function setLabel($value)
* {
* $this->_label = $value;
* }
*
*
* Property names are *case-insensitive*.
*
* A property can be accessed like a member variable of an object. Reading or writing a property will cause the invocation
* of the corresponding getter or setter method. For example,
*
*
* // equivalent to $label = $object->getLabel();
* $label = $object->label;
* // equivalent to $object->setLabel('abc');
* $object->label = 'abc';
*
*
* If a property has only a getter method and has no setter method, it is considered as *read-only*. In this case, trying
* to modify the property value will cause an exception.
*
* One can call [[hasProperty()]], [[canGetProperty()]] and/or [[canSetProperty()]] to check the existence of a property.
*
* Besides the property feature, BaseObject also introduces an important object initialization life cycle. In particular,
* creating an new instance of BaseObject or its derived class will involve the following life cycles sequentially:
*
* 1. the class constructor is invoked;
* 2. object properties are initialized according to the given configuration;
* 3. the `init()` method is invoked.
*
* In the above, both Step 2 and 3 occur at the end of the class constructor. It is recommended that
* you perform object initialization in the `init()` method because at that stage, the object configuration
* is already applied.
*
* In order to ensure the above life cycles, if a child class of BaseObject needs to override the constructor,
* it should be done like the following:
*
*
* public function __construct($param1, $param2, ..., $config = [])
* {
* ...
* parent::__construct($config);
* }
*
*
* That is, a `$config` parameter (defaults to `[]`) should be declared as the last parameter
* of the constructor, and the parent implementation should be called at the end of the constructor.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0.13
*/
class BaseObject implements Configurable
{
/**
* Returns the fully qualified name of this class.
* @return string the fully qualified name of this class.
* @deprecated since 2.0.14. On PHP >=5.5, use `::class` instead.
*/
public static function className()
{
return get_called_class();
}
/**
* Constructor.
*
* The default implementation does two things:
*
* - Initializes the object with the given configuration `$config`.
* - Call [[init()]].
*
* If this method is overridden in a child class, it is recommended that
*
* - the last parameter of the constructor is a configuration array, like `$config` here.
* - call the parent implementation at the end of the constructor.
*
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($config = [])
{
if (!empty($config)) {
Yii::configure($this, $config);
}
$this->init();
}
/**
* Initializes the object.
* This method is invoked at the end of the constructor after the object is initialized with the
* given configuration.
*/
public function init()
{
}
/**
* Returns the value of an object property.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `$value = $object->property;`.
* @param string $name the property name
* @return mixed the property value
* @throws UnknownPropertyException if the property is not defined
* @throws InvalidCallException if the property is write-only
* @see __set()
*/
public function __get($name)
{
$getter = 'get' . $name;
if (method_exists($this, $getter)) {
return $this->$getter();
} elseif (method_exists($this, 'set' . $name)) {
throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
}
throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
}
/**
* Sets value of an object property.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `$object->property = $value;`.
* @param string $name the property name or the event name
* @param mixed $value the property value
* @throws UnknownPropertyException if the property is not defined
* @throws InvalidCallException if the property is read-only
* @see __get()
*/
public function __set($name, $value)
{
$setter = 'set' . $name;
if (method_exists($this, $setter)) {
$this->$setter($value);
} elseif (method_exists($this, 'get' . $name)) {
throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
} else {
throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
}
}
/**
* Checks if a property is set, i.e. defined and not null.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `isset($object->property)`.
*
* Note that if the property is not defined, false will be returned.
* @param string $name the property name or the event name
* @return bool whether the named property is set (not null).
* @see https://www.php.net/manual/en/function.isset.php
*/
public function __isset($name)
{
$getter = 'get' . $name;
if (method_exists($this, $getter)) {
return $this->$getter() !== null;
}
return false;
}
/**
* Sets an object property to null.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `unset($object->property)`.
*
* Note that if the property is not defined, this method will do nothing.
* If the property is read-only, it will throw an exception.
* @param string $name the property name
* @throws InvalidCallException if the property is read only.
* @see https://www.php.net/manual/en/function.unset.php
*/
public function __unset($name)
{
$setter = 'set' . $name;
if (method_exists($this, $setter)) {
$this->$setter(null);
} elseif (method_exists($this, 'get' . $name)) {
throw new InvalidCallException('Unsetting read-only property: ' . get_class($this) . '::' . $name);
}
}
/**
* Calls the named method which is not a class method.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when an unknown method is being invoked.
* @param string $name the method name
* @param array $params method parameters
* @throws UnknownMethodException when calling unknown method
* @return mixed the method return value
*/
public function __call($name, $params)
{
throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
}
/**
* Returns a value indicating whether a property is defined.
*
* A property is defined if:
*
* - the class has a getter or setter method associated with the specified name
* (in this case, property name is case-insensitive);
* - the class has a member variable with the specified name (when `$checkVars` is true);
*
* @param string $name the property name
* @param bool $checkVars whether to treat member variables as properties
* @return bool whether the property is defined
* @see canGetProperty()
* @see canSetProperty()
*/
public function hasProperty($name, $checkVars = true)
{
return $this->canGetProperty($name, $checkVars) || $this->canSetProperty($name, false);
}
/**
* Returns a value indicating whether a property can be read.
*
* A property is readable if:
*
* - the class has a getter method associated with the specified name
* (in this case, property name is case-insensitive);
* - the class has a member variable with the specified name (when `$checkVars` is true);
*
* @param string $name the property name
* @param bool $checkVars whether to treat member variables as properties
* @return bool whether the property can be read
* @see canSetProperty()
*/
public function canGetProperty($name, $checkVars = true)
{
return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name);
}
/**
* Returns a value indicating whether a property can be set.
*
* A property is writable if:
*
* - the class has a setter method associated with the specified name
* (in this case, property name is case-insensitive);
* - the class has a member variable with the specified name (when `$checkVars` is true);
*
* @param string $name the property name
* @param bool $checkVars whether to treat member variables as properties
* @return bool whether the property can be written
* @see canGetProperty()
*/
public function canSetProperty($name, $checkVars = true)
{
return method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name);
}
/**
* Returns a value indicating whether a method is defined.
*
* The default implementation is a call to php function `method_exists()`.
* You may override this method when you implemented the php magic method `__call()`.
* @param string $name the method name
* @return bool whether the method is defined
*/
public function hasMethod($name)
{
return method_exists($this, $name);
}
}
yii\base\Component
类
看一下Component源码:
<?php
namespace yii\base;
use Yii;
/**
* Component is the base class that implements the *property*, *event* and *behavior* features.
*
* Component provides the *event* and *behavior* features, in addition to the *property* feature which is implemented in
* its parent class [[\yii\base\Object|Object]].
*
* Event is a way to "inject" custom code into existing code at certain places. For example, a comment object can trigger
* an "add" event when the user adds a comment. We can write custom code and attach it to this event so that when the event
* is triggered (i.e. comment will be added), our custom code will be executed.
*
* An event is identified by a name that should be unique within the class it is defined at. Event names are *case-sensitive*.
*
* One or multiple PHP callbacks, called *event handlers*, can be attached to an event. You can call [[trigger()]] to
* raise an event. When an event is raised, the event handlers will be invoked automatically in the order they were
* attached.
*
* To attach an event handler to an event, call [[on()]]:
*
*
* $post->on('update', function ($event) {
* // send email notification
* });
*
*
* In the above, an anonymous function is attached to the "update" event of the post. You may attach
* the following types of event handlers:
*
* - anonymous function: `function ($event) { ... }`
* - object method: `[$object, 'handleAdd']`
* - static class method: `['Page', 'handleAdd']`
* - global function: `'handleAdd'`
*
* The signature of an event handler should be like the following:
*
*
* function foo($event)
*
*
* where `$event` is an [[Event]] object which includes parameters associated with the event.
*
* You can also attach a handler to an event when configuring a component with a configuration array.
* The syntax is like the following:
*
*
* [
* 'on add' => function ($event) { ... }
* ]
*
*
* where `on add` stands for attaching an event to the `add` event.
*
* Sometimes, you may want to associate extra data with an event handler when you attach it to an event
* and then access it when the handler is invoked. You may do so by
*
*
* $post->on('update', function ($event) {
* // the data can be accessed via $event->data
* }, $data);
*
*
* A behavior is an instance of [[Behavior]] or its child class. A component can be attached with one or multiple
* behaviors. When a behavior is attached to a component, its public properties and methods can be accessed via the
* component directly, as if the component owns those properties and methods.
*
* To attach a behavior to a component, declare it in [[behaviors()]], or explicitly call [[attachBehavior]]. Behaviors
* declared in [[behaviors()]] are automatically attached to the corresponding component.
*
* One can also attach a behavior to a component when configuring it with a configuration array. The syntax is like the
* following:
*
*
* [
* 'as tree' => [
* 'class' => 'Tree',
* ],
* ]
*
*
* where `as tree` stands for attaching a behavior named `tree`, and the array will be passed to [[\Yii::createObject()]]
* to create the behavior object.
*
* For more details and usage information on Component, see the [guide article on components](guide:concept-components).
*
* @property Behavior[] $behaviors List of behaviors attached to this component. This property is read-only.
*
*/
class Component extends Object
{
/**
* @var array the attached event handlers (event name => handlers)
*/
private $_events = [];
/**
* @var Behavior[]|null the attached behaviors (behavior name => behavior). This is `null` when not initialized.
*/
private $_behaviors;
/**
* Returns the value of a component property.
* This method will check in the following order and act accordingly:
*
* - a property defined by a getter: return the getter result
* - a property of a behavior: return the behavior property value
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `$value = $component->property;`.
* @param string $name the property name
* @return mixed the property value or the value of a behavior's property
* @throws UnknownPropertyException if the property is not defined
* @throws InvalidCallException if the property is write-only.
* @see __set()
*/
public function __get($name)
{
$getter = 'get' . $name;
if (method_exists($this, $getter)) {
// read property, e.g. getName()
return $this->$getter();
}
// behavior property
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canGetProperty($name)) {
return $behavior->$name;
}
}
if (method_exists($this, 'set' . $name)) {
throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
}
throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
}
/**
* Sets the value of a component property.
* This method will check in the following order and act accordingly:
*
* - a property defined by a setter: set the property value
* - an event in the format of "on xyz": attach the handler to the event "xyz"
* - a behavior in the format of "as xyz": attach the behavior named as "xyz"
* - a property of a behavior: set the behavior property value
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `$component->property = $value;`.
* @param string $name the property name or the event name
* @param mixed $value the property value
* @throws UnknownPropertyException if the property is not defined
* @throws InvalidCallException if the property is read-only.
* @see __get()
*/
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);
}
/**
* Checks if a property is set, i.e. defined and not null.
* This method will check in the following order and act accordingly:
*
* - a property defined by a setter: return whether the property is set
* - a property of a behavior: return whether the property is set
* - return `false` for non existing properties
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `isset($component->property)`.
* @param string $name the property name or the event name
* @return bool whether the named property is set
* @see http://php.net/manual/en/function.isset.php
*/
public function __isset($name)
{
$getter = 'get' . $name;
if (method_exists($this, $getter)) {
return $this->$getter() !== null;
}
// behavior property
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canGetProperty($name)) {
return $behavior->$name !== null;
}
}
return false;
}
/**
* Sets a component property to be null.
* This method will check in the following order and act accordingly:
*
* - a property defined by a setter: set the property value to be null
* - a property of a behavior: set the property value to be null
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `unset($component->property)`.
* @param string $name the property name
* @throws InvalidCallException if the property is read only.
* @see http://php.net/manual/en/function.unset.php
*/
public function __unset($name)
{
$setter = 'set' . $name;
if (method_exists($this, $setter)) {
$this->$setter(null);
return;
}
// behavior property
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canSetProperty($name)) {
$behavior->$name = null;
return;
}
}
throw new InvalidCallException('Unsetting an unknown or read-only property: ' . get_class($this) . '::' . $name);
}
/**
* Calls the named method which is not a class method.
*
* This method will check if any attached behavior has
* the named method and will execute it if available.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when an unknown method is being invoked.
* @param string $name the method name
* @param array $params method parameters
* @return mixed the method return value
* @throws UnknownMethodException when calling unknown method
*/
public function __call($name, $params)
{
$this->ensureBehaviors();
foreach ($this->_behaviors as $object) {
if ($object->hasMethod($name)) {
return call_user_func_array([$object, $name], $params);
}
}
throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
}
/**
* This method is called after the object is created by cloning an existing one.
* It removes all behaviors because they are attached to the old object.
*/
public function __clone()
{
$this->_events = [];
$this->_behaviors = null;
}
/**
* Returns a value indicating whether a property is defined for this component.
* A property is defined if:
*
* - the class has a getter or setter method associated with the specified name
* (in this case, property name is case-insensitive);
* - the class has a member variable with the specified name (when `$checkVars` is true);
* - an attached behavior has a property of the given name (when `$checkBehaviors` is true).
*
* @param string $name the property name
* @param bool $checkVars whether to treat member variables as properties
* @param bool $checkBehaviors whether to treat behaviors' properties as properties of this component
* @return bool whether the property is defined
* @see canGetProperty()
* @see canSetProperty()
*/
public function hasProperty($name, $checkVars = true, $checkBehaviors = true)
{
return $this->canGetProperty($name, $checkVars, $checkBehaviors) || $this->canSetProperty($name, false, $checkBehaviors);
}
/**
* Returns a value indicating whether a property can be read.
* A property can be read if:
*
* - the class has a getter method associated with the specified name
* (in this case, property name is case-insensitive);
* - the class has a member variable with the specified name (when `$checkVars` is true);
* - an attached behavior has a readable property of the given name (when `$checkBehaviors` is true).
*
* @param string $name the property name
* @param bool $checkVars whether to treat member variables as properties
* @param bool $checkBehaviors whether to treat behaviors' properties as properties of this component
* @return bool whether the property can be read
* @see canSetProperty()
*/
public function canGetProperty($name, $checkVars = true, $checkBehaviors = true)
{
if (method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name)) {
return true;
} elseif ($checkBehaviors) {
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canGetProperty($name, $checkVars)) {
return true;
}
}
}
return false;
}
/**
* Returns a value indicating whether a property can be set.
* A property can be written if:
*
* - the class has a setter method associated with the specified name
* (in this case, property name is case-insensitive);
* - the class has a member variable with the specified name (when `$checkVars` is true);
* - an attached behavior has a writable property of the given name (when `$checkBehaviors` is true).
*
* @param string $name the property name
* @param bool $checkVars whether to treat member variables as properties
* @param bool $checkBehaviors whether to treat behaviors' properties as properties of this component
* @return bool whether the property can be written
* @see canGetProperty()
*/
public function canSetProperty($name, $checkVars = true, $checkBehaviors = true)
{
if (method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name)) {
return true;
} elseif ($checkBehaviors) {
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canSetProperty($name, $checkVars)) {
return true;
}
}
}
return false;
}
/**
* Returns a value indicating whether a method is defined.
* A method is defined if:
*
* - the class has a method with the specified name
* - an attached behavior has a method with the given name (when `$checkBehaviors` is true).
*
* @param string $name the property name
* @param bool $checkBehaviors whether to treat behaviors' methods as methods of this component
* @return bool whether the method is defined
*/
public function hasMethod($name, $checkBehaviors = true)
{
if (method_exists($this, $name)) {
return true;
} elseif ($checkBehaviors) {
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->hasMethod($name)) {
return true;
}
}
}
return false;
}
/**
* Returns a list of behaviors that this component should behave as.
*
* Child classes may override this method to specify the behaviors they want to behave as.
*
* The return value of this method should be an array of behavior objects or configurations
* indexed by behavior names. A behavior configuration can be either a string specifying
* the behavior class or an array of the following structure:
*
* ```php
* 'behaviorName' => [
* 'class' => 'BehaviorClass',
* 'property1' => 'value1',
* 'property2' => 'value2',
* ]
* ```
*
* Note that a behavior class must extend from [[Behavior]]. Behaviors can be attached using a name or anonymously.
* When a name is used as the array key, using this name, the behavior can later be retrieved using [[getBehavior()]]
* or be detached using [[detachBehavior()]]. Anonymous behaviors can not be retrieved or detached.
*
* Behaviors declared in this method will be attached to the component automatically (on demand).
*
* @return array the behavior configurations.
*/
public function behaviors()
{
return [];
}
/**
* Returns a value indicating whether there is any handler attached to the named event.
* @param string $name the event name
* @return bool whether there is any handler attached to the event.
*/
public function hasEventHandlers($name)
{
$this->ensureBehaviors();
return !empty($this->_events[$name]) || Event::hasHandlers($this, $name);
}
/**
* Attaches an event handler to an event.
*
* The event handler must be a valid PHP callback. The following are
* some examples:
*
* ```
* function ($event) { ... } // anonymous function
* [$object, 'handleClick'] // $object->handleClick()
* ['Page', 'handleClick'] // Page::handleClick()
* 'handleClick' // global function handleClick()
* ```
*
* The event handler must be defined with the following signature,
*
* ```
* function ($event)
* ```
*
* where `$event` is an [[Event]] object which includes parameters associated with the event.
*
* @param string $name the event name
* @param callable $handler the event handler
* @param mixed $data the data to be passed to the event handler when the event is triggered.
* When the event handler is invoked, this data can be accessed via [[Event::data]].
* @param bool $append whether to append new event handler to the end of the existing
* handler list. If false, the new handler will be inserted at the beginning of the existing
* handler list.
* @see off()
*/
public function on($name, $handler, $data = null, $append = true)
{
$this->ensureBehaviors();
if ($append || empty($this->_events[$name])) {
$this->_events[$name][] = [$handler, $data];
} else {
array_unshift($this->_events[$name], [$handler, $data]);
}
}
/**
* Detaches an existing event handler from this component.
* This method is the opposite of [[on()]].
* @param string $name event name
* @param callable $handler the event handler to be removed.
* If it is null, all handlers attached to the named event will be removed.
* @return bool if a handler is found and detached
* @see on()
*/
public function off($name, $handler = null)
{
$this->ensureBehaviors();
if (empty($this->_events[$name])) {
return false;
}
if ($handler === null) {
unset($this->_events[$name]);
return true;
}
$removed = false;
foreach ($this->_events[$name] as $i => $event) {
if ($event[0] === $handler) {
unset($this->_events[$name][$i]);
$removed = true;
}
}
if ($removed) {
$this->_events[$name] = array_values($this->_events[$name]);
}
return $removed;
}
/**
* Triggers an event.
* This method represents the happening of an event. It invokes
* all attached handlers for the event including class-level handlers.
* @param string $name the event name
* @param Event $event the event parameter. If not set, a default [[Event]] object will be created.
*/
public function trigger($name, Event $event = null)
{
$this->ensureBehaviors();
if (!empty($this->_events[$name])) {
if ($event === null) {
$event = new Event;
}
if ($event->sender === null) {
$event->sender = $this;
}
$event->handled = false;
$event->name = $name;
foreach ($this->_events[$name] as $handler) {
$event->data = $handler[1];
call_user_func($handler[0], $event);
// stop further handling if the event is handled
if ($event->handled) {
return;
}
}
}
// invoke class-level attached handlers
Event::trigger($this, $name, $event);
}
/**
* Returns the named behavior object.
* @param string $name the behavior name
* @return null|Behavior the behavior object, or null if the behavior does not exist
*/
public function getBehavior($name)
{
$this->ensureBehaviors();
return isset($this->_behaviors[$name]) ? $this->_behaviors[$name] : null;
}
/**
* Returns all behaviors attached to this component.
* @return Behavior[] list of behaviors attached to this component
*/
public function getBehaviors()
{
$this->ensureBehaviors();
return $this->_behaviors;
}
/**
* Attaches a behavior to this component.
* This method will create the behavior object based on the given
* configuration. After that, the behavior object will be attached to
* this component by calling the [[Behavior::attach()]] method.
* @param string $name the name of the behavior.
* @param string|array|Behavior $behavior the behavior configuration. This can be one of the following:
*
* - a [[Behavior]] object
* - a string specifying the behavior class
* - an object configuration array that will be passed to [[Yii::createObject()]] to create the behavior object.
*
* @return Behavior the behavior object
* @see detachBehavior()
*/
public function attachBehavior($name, $behavior)
{
$this->ensureBehaviors();
return $this->attachBehaviorInternal($name, $behavior);
}
/**
* Attaches a list of behaviors to the component.
* Each behavior is indexed by its name and should be a [[Behavior]] object,
* a string specifying the behavior class, or an configuration array for creating the behavior.
* @param array $behaviors list of behaviors to be attached to the component
* @see attachBehavior()
*/
public function attachBehaviors($behaviors)
{
$this->ensureBehaviors();
foreach ($behaviors as $name => $behavior) {
$this->attachBehaviorInternal($name, $behavior);
}
}
/**
* Detaches a behavior from the component.
* The behavior's [[Behavior::detach()]] method will be invoked.
* @param string $name the behavior's name.
* @return null|Behavior the detached behavior. Null if the behavior does not exist.
*/
public function detachBehavior($name)
{
$this->ensureBehaviors();
if (isset($this->_behaviors[$name])) {
$behavior = $this->_behaviors[$name];
unset($this->_behaviors[$name]);
$behavior->detach();
return $behavior;
}
return null;
}
/**
* Detaches all behaviors from the component.
*/
public function detachBehaviors()
{
$this->ensureBehaviors();
foreach ($this->_behaviors as $name => $behavior) {
$this->detachBehavior($name);
}
}
/**
* Makes sure that the behaviors declared in [[behaviors()]] are attached to this component.
*/
public function ensureBehaviors()
{
if ($this->_behaviors === null) {
$this->_behaviors = [];
foreach ($this->behaviors() as $name => $behavior) {
$this->attachBehaviorInternal($name, $behavior);
}
}
}
/**
* Attaches a behavior to this component.
* @param string|int $name the name of the behavior. If this is an integer, it means the behavior
* is an anonymous one. Otherwise, the behavior is a named one and any existing behavior with the same name
* will be detached first.
* @param string|array|Behavior $behavior the behavior to be attached
* @return Behavior the attached behavior.
*/
private function attachBehaviorInternal($name, $behavior)
{
if (!($behavior instanceof Behavior)) {
$behavior = Yii::createObject($behavior);
}
if (is_int($name)) {
$behavior->attach($this);
$this->_behaviors[] = $behavior;
} else {
if (isset($this->_behaviors[$name])) {
$this->_behaviors[$name]->detach();
}
$behavior->attach($this);
$this->_behaviors[$name] = $behavior;
}
return $behavior;
}
}
yii\di\ServiceLocator
类
<?php
namespace yii\di;
use Yii;
use Closure;
use yii\base\Component;
use yii\base\InvalidConfigException;
/**
* To use ServiceLocator, you first need to register component IDs with the corresponding component
* definitions with the locator by calling [[set()]] or [[setComponents()]].
* You can then call [[get()]] to retrieve a component with the specified ID. The locator will automatically
* instantiate and configure the component according to the definition.
*
* For example,
*
*
* $locator = new \yii\di\ServiceLocator;
* $locator->setComponents([
* 'db' => [
* 'class' => 'yii\db\Connection',
* 'dsn' => 'sqlite:path/to/file.db',
* ],
* 'cache' => [
* 'class' => 'yii\caching\DbCache',
* 'db' => 'db',
* ],
* ]);
*
* $db = $locator->get('db'); // or $locator->db
* $cache = $locator->get('cache'); // or $locator->cache
*
*
* Because [[\yii\base\Module]] extends from ServiceLocator, modules and the application are all service locators.
*
* For more details and usage information on ServiceLocator, see the [guide article on service locators](guide:concept-service-locator).
*
* @property array $components The list of the component definitions or the loaded component instances (ID => definition or instance).
*/
class ServiceLocator extends Component
{
/**
* @var array shared component instances indexed by their IDs
*/
private $_components = [];
/**
* @var array component definitions indexed by their IDs
*/
private $_definitions = [];
/**
* Getter magic method.
* This method is overridden to support accessing components like reading properties.
* @param string $name component or property name
* @return mixed the named property value
*/
public function __get($name)
{
if ($this->has($name)) {
return $this->get($name);
} else {
return parent::__get($name);
}
}
/**
* Checks if a property value is null.
* This method overrides the parent implementation by checking if the named component is loaded.
* @param string $name the property name or the event name
* @return bool whether the property value is null
*/
public function __isset($name)
{
if ($this->has($name)) {
return true;
} else {
return parent::__isset($name);
}
}
/**
* Returns a value indicating whether the locator has the specified component definition or has instantiated the component.
* This method may return different results depending on the value of `$checkInstance`.
*
* - If `$checkInstance` is false (default), the method will return a value indicating whether the locator has the specified
* component definition.
* - If `$checkInstance` is true, the method will return a value indicating whether the locator has
* instantiated the specified component.
*
* @param string $id component ID (e.g. `db`).
* @param bool $checkInstance whether the method should check if the component is shared and instantiated.
* @return bool whether the locator has the specified component definition or has instantiated the component.
* @see set()
*/
public function has($id, $checkInstance = false)
{
return $checkInstance ? isset($this->_components[$id]) : isset($this->_definitions[$id]);
}
/**
* Returns the component instance with the specified ID.
*
* @param string $id component ID (e.g. `db`).
* @param bool $throwException whether to throw an exception if `$id` is not registered with the locator before.
* @return object|null the component of the specified ID. If `$throwException` is false and `$id`
* is not registered before, null will be returned.
* @throws InvalidConfigException if `$id` refers to a nonexistent component ID
* @see has()
* @see set()
*/
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 {
return $this->_components[$id] = Yii::createObject($definition);
}
} elseif ($throwException) {
throw new InvalidConfigException("Unknown component ID: $id");
} else {
return null;
}
}
/**
* Registers a component definition with this locator.
*
* For example,
*
* ```php
* // a class name
* $locator->set('cache', 'yii\caching\FileCache');
*
* // a configuration array
* $locator->set('db', [
* 'class' => 'yii\db\Connection',
* 'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
* 'username' => 'root',
* 'password' => '',
* 'charset' => 'utf8',
* ]);
*
* // an anonymous function
* $locator->set('cache', function ($params) {
* return new \yii\caching\FileCache;
* });
*
* // an instance
* $locator->set('cache', new \yii\caching\FileCache);
* ```
*
* If a component definition with the same ID already exists, it will be overwritten.
*
* @param string $id component ID (e.g. `db`).
* @param mixed $definition the component definition to be registered with this locator.
* It can be one of the following:
*
* - a class name
* - a configuration array: the array contains name-value pairs that will be used to
* initialize the property values of the newly created object when [[get()]] is called.
* The `class` element is required and stands for the the class of the object to be created.
* - a PHP callable: either an anonymous function or an array representing a class method (e.g. `['Foo', 'bar']`).
* The callable will be called by [[get()]] to return an object associated with the specified component ID.
* - an object: When [[get()]] is called, this object will be returned.
*
* @throws InvalidConfigException if the definition is an invalid configuration array
*/
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));
}
}
/**
* Removes the component from the locator.
* @param string $id the component ID
*/
public function clear($id)
{
unset($this->_definitions[$id], $this->_components[$id]);
}
/**
* Returns the list of the component definitions or the loaded component instances.
* @param bool $returnDefinitions whether to return component definitions instead of the loaded component instances.
* @return array the list of the component definitions or the loaded component instances (ID => definition or instance).
*/
public function getComponents($returnDefinitions = true)
{
return $returnDefinitions ? $this->_definitions : $this->_components;
}
/**
* Registers a set of component definitions in this locator.
*
* This is the bulk version of [[set()]]. The parameter should be an array
* whose keys are component IDs and values the corresponding component definitions.
*
* For more details on how to specify component IDs and definitions, please refer to [[set()]].
*
* If a component definition with the same ID already exists, it will be overwritten.
*
* The following is an example for registering two component definitions:
*
* ```php
* [
* 'db' => [
* 'class' => 'yii\db\Connection',
* 'dsn' => 'sqlite:path/to/file.db',
* ],
* 'cache' => [
* 'class' => 'yii\caching\DbCache',
* 'db' => 'db',
* ],
* ]
* ```
*
* @param array $components component definitions or instances
*/
public function setComponents($components)
{
foreach ($components as $id => $component) {
$this->set($id, $component);
}
}
}
参考资料
深入理解Yii2.0 » Yii 基础 » 属性(Property) http://www.digpage.com/property.html
Yii 2.0 权威指南 关键概念(Key Concepts): 属性(Properties) https://www.yiichina.com/doc/guide/2.0/concept-properties
PHP 7.2禁止类名为Object的巨坑 https://blog.csdn.net/hu_zhenghui/article/details/79222497