Skip to content

Instantly share code, notes, and snippets.

@luxixing
Last active August 29, 2015 14:01
Show Gist options
  • Save luxixing/1cdc4e4efd0d3074686f to your computer and use it in GitHub Desktop.
Save luxixing/1cdc4e4efd0d3074686f to your computer and use it in GitHub Desktop.
YII2 框架关键概念

属性

在PHP里,类的成员变量也被称之为属性,这些变量是类定义的一部分,被用来表示类实例的状态(区分不同类实例)。在具体的实践中,你可能要经常处理可读或者可写属性的特殊方式,举个例子, 你可能想要给属性$label赋值一个字符串,但是这个字符串必须使用trim函数过滤开头结尾的空格,为了完成这个特殊处理你可能需要使用如下代码

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

上述代码的缺点是,凡是在给label属性赋值的时候,你都必须使用trim函数去处理。假设在以后label属性的首字母必须大写,那么凡是给label属性赋值的代码,就必须被修改。你应该尽可能的避免这种代码重复。

为了解决这个问题,yii引入了一个基础类[yii\base\Object],此类可以基于类的getter和setter方法来定义类属性。如果一个类需要以这种方式定义属性,继承[yii\base\Object]或者[yii\base\Object]的子类

提示:几乎所有yii2框架的核心类都继承了[yii\base\Object]类或者它的子类,这意味着核心类的getter或者setter方法你都可以以属性的方式使用

getter方法一get为前缀;setter方法以set为前缀,get或者set后面跟随属性的名称。举个例子,getLabel()和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方法创建了一个label属性,在上述代码中实际上关联的是私有属性$_label)

有getter和setter方法定义的属性,可以被当作类的属性(成员变量)直接使用。不同之处在于,当属性被读时,关联的getter方法被调用,当属性被修改时,关联的setter方法被调用。例如:

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

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

一个由getter方法定义的属性,如果没有setter方法,那么此属性只读,尝试给该属性赋值的行为都会抛出一个[[yii\base\InvalidCallException|InvalidCallException]]异常。同样,假设定义了setter方法的属性没有getter方法,此属性只写,尝试读取只写属性的值也会抛出一个异常,只写属性不常见。

通过getter,setter方法定义的属性有几个特殊的规则和限制

  1. 此类属性大小写不敏感。比如:$object->label 和 $object->Label 是相同的,
  2. 使用getter,setter定义的属性如果在类中同时拥有同名的成员变量,同名的成员变量将会被优先使用。比如: Foo类有一个成员变量label,$foo->label = "test" 将会直接给label赋值,而不会调用setLabel()方法
  3. 此类属性不支持可见度。即无论属性的getter或者setter方法定义为public,protected,private,都没有区别
  4. 此类属性只能有非静态的getter,setter方法来定义,静态方法不会以相同的方式来处理

回到开头的问题,假设需要给label属性赋值,且要对值处理,替代在所有赋值代码处处理,而是在setter方法里处理所要赋予属性的值,当有任何新的需求或者规则施加在属性上时,只需要修改getter或者setter方法。修改一处,所以地方都生效。

组件

组件是YII框架application(应用)的主要组成部分,它是 [yii\base\Component]的实例或者子类。组件有三个重要特征(功能):

  • 属性(properties)
  • 事件(events)
  • 行为(behaviors)

单独或者组合使用这些特征,可以让yii框架的类变得更加易用和可定制。举个例子,引入一个用户交互组件[yii\jui\DataPicker\data picker wigets],可以很轻松的在视图中生成一个日期选择控件(jquery ui date picker).

use yii\jui\DatePicker;

echo DatePicker::widget([
    'language' => 'ru',
    'name'  => 'country',
    'clientOptions' => [
        'dateFormat' => 'yy-mm-dd',
    ],
]);

DatePicker挂件的属性很容易被修改,原因就在于他继承了类[yii\base\Componet]

尽管组件很强大,但是其开销也大于普通的类对象,原因在于组件需要额外的内存和CPU时间去处理事件(events)和行为(behaviors).如果你的组件不需要事件和行为这两个特征(功能),可以直接继承[yii\base\Object],这样你的组件在性能(效率)上和普通类对象一致,且支持属性(properties).

编写一个继承[yii\base\Component]或者[yii\base\Object]的类,需要遵循以下约定:

  • 假设你重写了构造函数,必须定义一个参数$config,作为构造函数的最后一个参数,此参数被传递给父类的构造函数使用
  • 在重写的构造函数的结尾必须调用父类的构造函数
  • 假设你重写了[yii\base\Object::init()|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 = [])
    {
        // ... initialization before configuration is applied

        parent::__construct($config);
    }

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

        // ... initialization after configuration is applied
    }
}

遵循如下示例,可使你的组件在创建时属性即可被配置(初始化):

$component = new MyClass(1, 2, ['prop1' => 3, 'prop2' => 4]);
// alternatively
$component = \Yii::createObject([
    'class' => MyClass::className(),
    'prop1' => 3,
    'prop2' => 4,
], [1, 2]);

提示: 使用[Yii::createOject()]的方式看起来更加复杂,但是因为[Yii::createObject]是基于依赖注入的实现,因此更加强大

[yii\base\Object]类强制实现如下生命周期:

  1. 在构造函数里实现预初始化,可以在这个时候设定默认的属性值
  2. 通过$config配置对象,在构造函数里通过对象配置可以覆盖默认值
  3. 在[yii\base\Object::init()|init()]方法里,进行初始化后的配置。可以在init方法里实现例行检查和属性正常化检查
  4. 对象方法调用

开始的三个阶段都在构造函数里实现,这意味着当你得到一个对象的实例时,它已经被初始化为适当的状态,可以被放心的使用。

@qiansen1386
Copy link

好评!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment