/ yii2

Yii2 关键概念

组件(Component)

组件功能的强大,他们比常规的对象(Object)稍微重量级一点, 因为他们要使用额外的内存和 CPU 时间来处理 事件行为 。 如果你不需要这两项功能,可以继承 yii\base\Object 而不是 yii\base\Component。 这样组件可以像普通 PHP 对象一样高效, 同时还支持属性(Property)功能。

  • 对象:属性
  • 组件:属性,事件,行为

当继承 yii\base\Component 或 yii\base\Object 时, 推荐你使用如下的编码风格:

若你需要重写构造方法(Constructor),传入 $config 作为构造器方法最后一个参数, 然后把它传递给父类的构造方法。
永远在你重写的构造方法结尾处调用一下父类的构造方法。
如果你重写了 yii\base\Object::init() 方法,请确保你在 init 方法的开头处调用了父类的 init 方法。
例子如下:

namespace yii\components\MyClass;

use yii\base\Object;

class MyClass extends Object
{
    public $prop1;
    public $prop2;

    public function __construct($param1, $param2, $config = [])
    {
        // ... 配置生效前的初始化过程

        parent::__construct($config);
    }

    public function init()
    {
        parent::init();

        // ... 配置生效后的初始化过程
    }
}

属性(Property)

在 PHP 中,类的成员变量也被称为 属性(properties)。 它们是类定义的一部分,用来表现一个实例的状态(也就是区分类的不同实例)。

在具体实践中,常常会想用一个稍微特殊些的方法实现属性的读写。 例如,如果有需求每次都要对 label 属性执行 trim 操作, 就可以用以下代码实现:

$object->label = trim($label);

上述代码的缺点是只要修改 label 属性就必须再次调用 trim() 函数。 若将来需要用其它方式处理 label 属性,比如首字母大写, 就不得不修改所有给 label 属性赋值的代码。 这种代码的重复会导致 bug,这种实践显然需要尽可能避免。

为解决该问题,Yii 引入了一个名为 yii\base\Object 的基类,它支持基于类内的 gettersetter(读取器和设定器)方法来定义属性。 如果某类需要支持这个特性,只需要继承 yii\base\Object 或其子类即可。

补充: 几乎每个 Yii 框架的核心类都继承自 yii\base\Object 或其子类。 这意味着只要在核心类中见到 gettersetter 方法,就可以像调用属性一样调用它。

getter 方法是名称以 get 开头的方法,而 setter 方法名以 set 开头。 方法名中 getset 后面的部分就定义了该属性的名字。 如下面代码所示,getter 方法 getLabel()setter 方法 setLabel() 操作的是 label 属性,:

namespace app\components;

use yii\base\Object;

class Foo extend Object
{
    private $_label;

    public function getLabel()
    {
        return $this->_label;
    }

    public function setLabel($value)
    {
        $this->_label = trim($value);
    }
}

getter/setter 定义的属性用法与类成员变量一样。 两者主要的区别是:当这种属性被读取时,对应的 getter 方法将被调用; 而当属性被赋值时,对应的 setter 方法就调用。如:

// 等效于 $label = $object->getLabel();
$label = $object->label;

// 等效于 $object->setLabel('abc');
$object->label = 'abc';

只定义了 getter 没有 setter 的属性是只读属性。 尝试赋值给这样的属性将导致 InvalidCallException (无效调用)异常。 类似的,只有 setter 方法而没有 getter 方法定义的属性是只写属性,尝试读取这种属性也会触发异常。 使用只写属性的情况几乎没有。

事件(Events)

事件可以将自定义代码“注入”到现有代码中的特定执行点。附加自定义代码到某个事件,当这个事件被触发时,这些代码就会自动执行。

例如,邮件程序对象成功发出消息时可触发 messageSent 事件。如想追踪成功发送的消息,可以附加相应追踪代码到 messageSent 事件。

Yii 引入了名为 yii\base\Component 的基类以支持事件。如果一个类需要触发事件就应该继承 yii\base\Component 或其子类。

事件处理器(Event Handlers)

事件处理器是一个PHP 回调函数,当它所附加到的事件被触发时它就会执行。可以使用以下回调函数之一:

  • 字符串形式指定的 PHP 全局函数,如 'trim'
  • 对象名和方法名数组形式指定的对象方法,如 [$object, $method]
  • 类名和方法名数组形式指定的静态类方法,如 [$class, $method]
  • 匿名函数,如 function ($event) { ... }

事件处理器的格式是:

function ($event) {
    // $event 是 yii\base\Event 或其子类的对象
}

通过 $event 参数,事件处理器就获得了以下有关事件的信息:

  • event name:事件名
  • event sender:调用 trigger() 方法的对象
  • custom data:附加事件处理器时传入的数据,默认为空,后文详述

为事件添加处理器

$foo = new Foo;

// 添加处理器是全局函数
$foo->on(Foo::EVENT_HELLO, 'function_name');

// 添加处理器是对象方法
$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']);

// 添加处理器是静态类方法
$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);

// 添加处理器是匿名函数
$foo->on(Foo::EVENT_HELLO, function ($event) {
    //事件处理逻辑
});

传递参数:

// 当事件被触发时以下代码显示 "abc"
// 因为 $event->data 包括被传递到 "on" 方法的数据
$foo->on(Foo::EVENT_HELLO, function ($event) {
    echo $event->data;
}, 'abc');

事件处理器的执行顺序

我们可以为一个事件添加多个处理器。当事件被触发时,已附加的处理器将按附加次序依次调用。如果某个处理器需要停止其后的处理器调用,可以设置 $event 参数的 yii\base\Event::$handled 属性为真,如下:

$foo->on(Foo::EVENT_HELLO, function ($event) {
    $event->handled = true;
});

如果需要将事件处理器添加到队列最前面,可以传递第四个参数 $append 为假并调用 yii\base\Component::on() 方法实现:

$foo->on(Foo::EVENT_HELLO, function ($event) {
    // 这个处理器将被插入到处理器队列的第一位...
}, $data, false);

触发事件

事件通过调用 yii\base\Component::trigger() 方法触发,此方法须传递事件名,还可以传递一个事件对象,用来传递参数到事件处理器。如:

namespace app\components;

use yii\base\Component;
use yii\base\Event;

class Foo extends Component
{
    const EVENT_HELLO = 'hello';

    public function bar()
    {
        $this->trigger(self::EVENT_HELLO);
    }
}

当调用 bar() ,它将触发名为 hello 的事件。