行为

介绍

行为增加了类具有私有特征(private traits)的能力,也称为行为。 这些类似于原生PHP特征trait ,另外它们有一些明显的好处:

  1. 行为有自己的构造函数。
  2. 行为可以有私有或受保护的方法。
  3. 方法和属性名称可以安全地冲突。
  4. 可以动态地扩展类的类。

与性状比较

PHP trait的用法 如下所示

  1. class MyClass
  2. {
  3. use \October\Rain\UtilityFunctions;
  4. use \October\Rain\DeferredBinding;
  5. }

行为的用法如下所示

  1. class MyClass extends \October\Rain\Extension\Extendable
  2. {
  3. public $implement = [
  4. 'October.Rain.UtilityFunctions',
  5. 'October.Rain.DeferredBinding',
  6. ];
  7. }

trait的定义方法如下所示:

  1. trait UtilityFunctions
  2. {
  3. public function sayHello()
  4. {
  5. echo "Hello from " . get_class($this);
  6. }
  7. }

行为的定义方法如下所示

  1. class UtilityFunctions extends \October\Rain\Extension\ExtensionBase
  2. {
  3. protected $parent;
  4. public function __construct($parent)
  5. {
  6. $this->parent = $parent;
  7. }
  8. public function sayHello()
  9. {
  10. echo "Hello from " . get_class($this->parent);
  11. }
  12. }

扩展对象始终作为第一个参数传递给Behavior的构造函数。

总结一下:

  • 扩展 \October\Rain\Extension\ExtensionBase 以将您的类声明为行为
  • 想要实现的类 - 行为需要扩展\October\Rain\Extension\ExtensionBase

注意: 参见使用traits而不是基础类

扩展构造函数

任何使用ExtendableExtendableTrait的类都可以使用静态extend方法扩展其构造函数。 参数应传递一个闭包,该闭包将作为类构造函数的一部分进行调用。

  1. MyNamespace\Controller::extend(function($controller) {
  2. //
  3. });

动态声明属性

Properties can be declared on an extendable object by calling addDynamicProperty and passing a property name and value.

  1. Post::extend(function($model) {
  2. $model->addDynamicProperty('tagsCache', null);
  3. });

Note: Attempting to set undeclared properties through normal means ($this->foo = 'bar';) on an object that implements the October\Rain\Extension\ExtendableTrait will not work. It won’t throw an exception, but it will not autodeclare the property either. addDynamicProperty must be called in order to set previously undeclared properties on extendable objects.

检索动态属性

可以使用从中继承的getDynamicProperties函数检索动态创建的属性 ExtendableTrait。

因此,检索所有动态属性将如下所示:

  1. $model->getDynamicProperties();

这将返回一个关联数组[key => value],其中键是动态属性名称 而值是属性值。

如果我们知道我们想要什么属性,我们可以简单地将键(属性名称)附加到函数:

  1. $model->getDynamicProperties()[$key];

动态创建方法

可以通过调用“addDynamicMethod”并传递方法名称和可调用对象(如“Closure”)来为可扩展对象创建方法。

  1. Post::extend(function($model) {
  2. $model->addDynamicProperty('tagsCache', null);
  3. $model->addDynamicMethod('getTagsAttribute', function() use ($model) {
  4. if ($this->tagsCache) {
  5. return $this->tagsCache;
  6. } else {
  7. return $this->tagsCache = $model->tags()->lists('name');
  8. }
  9. });
  10. });

动态实现行为

这种扩展构造函数的独特功能允许动态实现行为,例如:

  1. /**
  2. * 扩展RainLab.Users控制器以包含RelationController行为
  3. */
  4. RainLab\Users\Controllers\Users::extend(function($controller) {
  5. // 动态实现列表控制器行为
  6. $controller->implement[] = 'Backend.Behaviors.RelationController';
  7. // 为RelationController行为动态声明relationConfig属性
  8. $controller->addDynamicProperty('relationConfig', '$/myvendor/myplugin/controllers/users/config_relation.yaml');
  9. });

用法示例

行为/扩展类

  1. <?php namespace MyNamespace\Behaviors;
  2. class FormController extends \October\Rain\Extension\ExtensionBase
  3. {
  4. /**
  5. * @var 引用扩展对象。
  6. */
  7. protected $controller;
  8. /**
  9. * Constructor
  10. */
  11. public function __construct($controller)
  12. {
  13. $this->controller = $controller;
  14. }
  15. public function someMethod()
  16. {
  17. return "I come from the FormController Behavior!";
  18. }
  19. public function otherMethod()
  20. {
  21. return "You might not see me...";
  22. }
  23. }

继承类

这个Controller类将实现FormController行为,然后这些方法将可用(混入)到类中。 我们将覆盖otherMethod方法。

  1. <?php namespace MyNamespace;
  2. class Controller extends \October\Rain\Extension\Extendable
  3. {
  4. /**
  5. * Implement the FormController behavior
  6. */
  7. public $implement = [
  8. 'MyNamespace.Behaviors.FormController'
  9. ];
  10. public function otherMethod()
  11. {
  12. return "I come from the main Controller!";
  13. }
  14. }

使用扩展

  1. $controller = new MyNamespace\Controller;
  2. // 输出: I come from the FormController Behavior!
  3. echo $controller->someMethod();
  4. // 输出: I come from the main Controller!
  5. echo $controller->otherMethod();
  6. // 输出: You might not see me...
  7. echo $controller->asExtension('FormController')->otherMethod();

检测已使用的扩展

要检查对象是否已使用行为进行扩展,可以在对象上使用isClassExtendedWith方法。

  1. $controller->isClassExtendedWith('Backend.Behaviors.RelationController');

下面是使用此方法动态扩展第三方插件的UsersController以避免其他插件也扩展上述第三方插件的示例。

  1. UsersController::extend(function($controller) {
  2. // 如果尚未实现,则实现行为
  3. if (!$controller->isClassExtendedWith('Backend.Behaviors.RelationController')) {
  4. $controller->implement[] = 'Backend.Behaviors.RelationController';
  5. }
  6. // 定义属性(如果尚未定义)
  7. if (!isset($controller->relationConfig)) {
  8. $controller->addDynamicProperty('relationConfig');
  9. }
  10. // 安全的拼接配置
  11. $myConfigPath = '$/myvendor/myplugin/controllers/users/config_relation.yaml';
  12. $controller->relationConfig = $controller->mergeConfig(
  13. $controller->relationConfig,
  14. $myConfigPath
  15. );
  16. }

软定义

如果行为类不存在(如特征trait),则会抛出Class not found错误。 在某些情况下,如果系统中存在某种行为,您可能希望禁止此错误,以用于条件实现。 您可以通过在类名的开头放置一个@符号来完成此操作。

  1. class User extends \October\Rain\Extension\Extendable
  2. {
  3. public $implement = ['@RainLab.Translate.Behaviors.TranslatableModel'];
  4. }

如果类名称RainLab\Translate\Behaviors\TranslatableModel不存在,则不会引发任何错误。 这相当于以下代码:

  1. class User extends \October\Rain\Extension\Extendable
  2. {
  3. public $implement = [];
  4. public function __construct()
  5. {
  6. if (class_exists('RainLab\Translate\Behaviors\TranslatableModel')) {
  7. $this->implement[] = 'RainLab.Translate.Behaviors.TranslatableModel';
  8. }
  9. parent::__construct();
  10. }
  11. }

使用Traits替代基础类

对于那些您可能不希望扩展ExtensionBaseExtendable类的情况,您可以使用这些特性。 您的类必须按如下方式实现:

首先让我们创建一个充当行为的类,即。 可以由其他类实现。

  1. <?php namespace MyNamespace\Behaviours;
  2. class WaveBehaviour
  3. {
  4. use \October\Rain\Extension\ExtensionTrait;
  5. /**
  6. * 使用Extensiontrait时,您的行为也必须实现此方法
  7. * @see \October\Rain\Extension\ExtensionBase
  8. */
  9. public static function extend(callable $callback)
  10. {
  11. self::extensionExtendCallback($callback);
  12. }
  13. public function wave()
  14. {
  15. echo "*waves*<br>";
  16. }
  17. }

现在让我们创建一个能够使用ExtendableTrait实现行为的类。

  1. class AI
  2. {
  3. use \October\Rain\Extension\ExtendableTrait;
  4. /**
  5. * @var array Extensions implemented by this class.
  6. */
  7. public $implement;
  8. /**
  9. * Constructor
  10. */
  11. public function __construct()
  12. {
  13. $this->extendableConstruct();
  14. }
  15. public function __get($name)
  16. {
  17. return $this->extendableGet($name);
  18. }
  19. public function __set($name, $value)
  20. {
  21. $this->extendableSet($name, $value);
  22. }
  23. public function __call($name, $params)
  24. {
  25. return $this->extendableCall($name, $params);
  26. }
  27. public static function __callStatic($name, $params)
  28. {
  29. return self::extendableCallStatic($name, $params);
  30. }
  31. public static function extend(callable $callback)
  32. {
  33. self::extendableExtendCallback($callback);
  34. }
  35. public function youGotBrains()
  36. {
  37. echo "I've got an AI!<br>";
  38. }
  39. }

AI类现在能够使用行为。 让我们扩展它并让这个类实现WaveBehaviour。

  1. <?php namespace MyNamespace\Classes;
  2. class Robot extends AI
  3. {
  4. public $implement = [
  5. 'MyNamespace.Behaviours.WaveBehaviour'
  6. ];
  7. public function identify()
  8. {
  9. echo "I'm a Robot<br>";
  10. echo $this->youGotBrains();
  11. echo $this->wave();
  12. }
  13. }

您现在可以如下使用Robot:

  1. $robot = new Robot();
  2. $robot->identify();

会输出:

  1. I'm a Robot
  2. I've got an AI!
  3. *waves*

记得:

  • 当使用ExtensionTrait时,应该将来自ExtensionBase的方法应用于该类。

  • 当使用ExtendableTrait时,Extendable中的方法应该应用于该类。