/ yii2

yii2-key

yii2 关键概念:

  • Configurable(可配置)
  • Property(属性)
  • Components
  • Events(事件)
  • Behaviors(行为)

关键概念:Configurable(可配置)

https://www.yiiframework.com/doc/api/2.0/yii-base-configurable

凡是 implements 了 yii\base\Configurable 的类(yii\base\BaseObject 及其子类),其构造方法的最后一个参数都必须为 $config = []。例如:

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

The configuration must have a class key.

Yii::createObject() 方法接受一个配置数组并根据数组中指定的类名创建对象。对象实例化后,剩余的参数被用来初始化对象的属性事件行为

请注意,如果配置一个已存在的对象,那么配置数组中不应该包含指定类名的 class 元素。例如应用本身:

$config = [
    'id' => 'basic',
    'basePath' => dirname(__DIR__),
    'extensions' => require __DIR__ . '/../vendor/yiisoft/extensions.php',
    'components' => [
        'cache' => [],
        'mailer' => [],
        'log' => [],
        'db' => [],
    ],
];
(new yii\web\Application($config))->run();

关键概念:Property(属性)

yii\base\BaseObject 实现了 Property

BaseObject is the base class that implements the property feature.

https://www.yiiframework.com/doc/api/2.0/yii-base-baseobject

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;`.
     */
    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;`.
     */
    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.
     */
    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.
     */
    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.
     */
    public function __call($name, $params)
    {
        throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
    }
    
    /**
     * Returns a value indicating whether a property is defined.
     */
    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:
     *
     */
    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:
     *
     */
    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.
     */
    public function hasMethod($name)
    {
        return method_exists($this, $name);
    }
}

Components

  • yii\base\Component 继承了 yii\base\BaseObject,所以它也拥有 Property 特性;
  • yii\base\Component 实现了 EventsBehaviors
class Component extends BaseObject
{
    /**
     * @var array the attached event handlers (event name => handlers)
     */
    private $_events = [];
    
    /**
     * @var array the event handlers attached for wildcard patterns (event name wildcard => handlers)
     * @since 2.0.14
     */
    private $_eventWildcards = [];
    
    /**
     * Attaches an event handler to an event.
     *
     * 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.
     *
     * Since 2.0.14 you can specify event name as a wildcard pattern:
     *
     * ```php
     * $component->on('event.group.*', function ($event) {
     *     Yii::trace($event->name . ' is triggered.');
     * });
     * ```
     *
     * @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 (strpos($name, '*') !== false) {
            if ($append || empty($this->_eventWildcards[$name])) {
                $this->_eventWildcards[$name][] = [$handler, $data];
            } else {
                array_unshift($this->_eventWildcards[$name], [$handler, $data]);
            }
            return;
        }
        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()]].
     *
     * Note: in case wildcard pattern is passed for event name, only the handlers registered with this
     * wildcard will be removed, while handlers registered with plain names matching this wildcard will remain.
     *
     * @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]) && empty($this->_eventWildcards[$name])) {
            return false;
        }
        if ($handler === null) {
            unset($this->_events[$name], $this->_eventWildcards[$name]);
            return true;
        }
        $removed = false;
        // plain event names
        if (isset($this->_events[$name])) {
            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;
            }
        }
        // wildcard event names
        if (isset($this->_eventWildcards[$name])) {
            foreach ($this->_eventWildcards[$name] as $i => $event) {
                if ($event[0] === $handler) {
                    unset($this->_eventWildcards[$name][$i]);
                    $removed = true;
                }
            }
            if ($removed) {
                $this->_eventWildcards[$name] = array_values($this->_eventWildcards[$name]);
                // remove empty wildcards to save future redundant regex checks:
                if (empty($this->_eventWildcards[$name])) {
                    unset($this->_eventWildcards[$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();
        $eventHandlers = [];
        foreach ($this->_eventWildcards as $wildcard => $handlers) {
            if (StringHelper::matchWildcard($wildcard, $name)) {
                $eventHandlers = array_merge($eventHandlers, $handlers);
            }
        }
        if (!empty($this->_events[$name])) {
            $eventHandlers = array_merge($eventHandlers, $this->_events[$name]);
        }
        if (!empty($eventHandlers)) {
            if ($event === null) {
                $event = new Event();
            }
            if ($event->sender === null) {
                $event->sender = $this;
            }
            $event->handled = false;
            $event->name = $name;
            foreach ($eventHandlers 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);
    }
}

EventsBehaviors 需要耗费额外的内存和 CPU 时间,如果你不需要这两项功能,可以继承 yii\base\BaseObject

当你写的类继承自 yii\base\Componentyii\base\BaseObject 时,推荐你使用如下的编码风格:

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

绑定处理程序到事件

您可以通过调用yii\base\Component::on()方法将处理程序附加到事件。例如:

$foo = new Foo();

// this handler is a global function
$foo->on(Foo::EVENT_HELLO, 'function_name');

// this handler is an object method
$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']);

// this handler is a static class method
$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);

// this handler is an anonymous function
$foo->on(Foo::EVENT_HELLO, function ($event) {
    // event handling logic
});

当附加一个事件处理函数时,你可以提供额外的数据作为yii\base\Component::on()的第三个参数。当事件被触发并且处理程序被调用时,数据将被提供给处理程序。例如:

// The following code will display "abc" when the event is triggered
// because $event->data contains the data passed as the 3rd argument to "on"
$foo->on(Foo::EVENT_HELLO, 'function_name', 'abc');

function function_name($event) {
    echo $event->data;
}

有时在触发事件时,您可能希望将其他信息传递给事件处理程序。例如,邮件程序可能希望将消息信息传递给messageSent事件处理程序,以便处理程序可以知道已发送消息的详细信息。为此,您可以提供一个事件对象作为yii\base\Component::trigger()方法的第二个参数。事件对象必须是yii\base\Event类或子类的实例。例如:

namespace app\components;

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

class MessageEvent extends Event
{
    public $message;
}

class Mailer extends Component
{
    const EVENT_MESSAGE_SENT = 'messageSent';

    public function send($message)
    {
        // ...sending $message...

        $event = new MessageEvent;
        $event->message = $message;
        $this->trigger(self::EVENT_MESSAGE_SENT, $event);
    }
}

总结一下向事假处理器传入数据的两种方式:

  • 绑定时向事件处理器传入数据。提供额外的数据作为yii\base\Component::on()的第三个参数。
  • 触发时向事件处理器传入数据。提供一个事件对象作为yii\base\Component::trigger()方法的第二个参数。事件对象必须是yii\base\Event类或子类的实例。

分离事件处理程序

要从事件中分离处理程序,请调用yii\base\Component::off()方法。例如:

// the handler is a global function
$foo->off(Foo::EVENT_HELLO, 'function_name');

// the handler is an object method
$foo->off(Foo::EVENT_HELLO, [$object, 'methodName']);

// the handler is a static class method
$foo->off(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);

// the handler is an anonymous function
$foo->off(Foo::EVENT_HELLO, $anonymousFunction);

请注意,一般情况下你不应该不应尝试分离匿名函数,除非将其存储在一个变量中。在上面的例子中,假定匿名函数被存储为一个变量$anonymousFunction。

要从事件中分离所有处理程序,只需调用yii\base\Component::off()而不使用第二个参数:

$foo->off(Foo::EVENT_HELLO);

Events(事件)

https://www.yiiframework.com/doc/api/2.0/yii-base-event

事件的应用场景是?

参见:yii\web\User 文件中的事件:

  • EVENT_BEFORE_LOGIN
  • EVENT_AFTER_LOGIN
  • EVENT_BEFORE_LOGOUT
  • EVENT_AFTER_LOGOUT

yii\web\UserEvent

可以一个类会在内部预定义好几个事件,并在特定的方法里触发事件,然后我就就可以将回调绑定到特定事件,从而实现将自定义代码**“注入”**到现有代码中的预定义的执行点。

Events(事件)就是观察者模式的应用。

yii\base\Application 为例,他定义了两个事件:

  • EVENT_BEFORE_REQUEST
  • EVENT_AFTER_REQUEST
abstract class Application extends Module
{
    // 定义了两个事件
    const EVENT_BEFORE_REQUEST = 'beforeRequest';
    const EVENT_AFTER_REQUEST = 'afterRequest';

    public function run()
    {
        try {

            $this->state = self::STATE_BEFORE_REQUEST;

            // 先触发EVENT_BEFORE_REQUEST
            $this->trigger(self::EVENT_BEFORE_REQUEST);

            $this->state = self::STATE_HANDLING_REQUEST;

            // 处理Request
            $response = $this->handleRequest($this->getRequest());

            $this->state = self::STATE_AFTER_REQUEST;

            // 处理完毕后触发EVENT_AFTER_REQUEST
            $this->trigger(self::EVENT_AFTER_REQUEST);

            $this->state = self::STATE_SENDING_RESPONSE;
            $response->send();

            $this->state = self::STATE_END;

            return $response->exitStatus;

        } catch (ExitException $e) {

            $this->end($e->statusCode, isset($response) ? $response : null);
            return $e->statusCode;

        }
    }
}

Behaviors(行为)

https://www.yiiframework.com/doc/guide/2.0/en/concept-behaviors

行为是 yii\base\Behavior 或其子类的实例。

行为(也称为mixin)允许您增强现有组件类的功能,而无需更改类的继承。

当行为附加到组件后,它将 “注入” 它的方法属性到组件,然后可以像访问组件内定义的方法和属性一样访问它们。此外,行为通过组件能响应被触发的事件,从而自定义或调整组件正常执行的代码。

namespace app\models\User;

use yii\db\ActiveRecord;
use yii\behaviors\TimestampBehavior;

class User extends ActiveRecord
{
    // ...

    public function behaviors()
    {
        return [
            [
                'class' => TimestampBehavior::className(),
                'attributes' => [
                    ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
                    ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
                ],
                // if you're using datetime instead of UNIX timestamp:
                // 'value' => new Expression('NOW()'),
            ],
        ];
    }
}

其他可用的 behaviors :