行为和动态类扩展
Introduction
动态类扩展提供了以下好处:
- 动态添加新的属性和方法。
- 绑定到在模型、小部件和其他位置触发的本地事件,甚至是核心模块或其他插件中的事件。
- 向类添加额外的行为(私有特征,见下文)。
- 必须延长
Winter\Storm\Extension\Extendable
类或实现Winter\Storm\Extension\ExtendableTrait
. - 不能覆盖已经定义的方法和属性。
- 可扩展类受到保护,不会在首次使用时设置未定义的属性,并且必须使用
$object->addDynamicProperty();
反而。 - 动态添加的方法不能覆盖代码中定义的方法。
请参阅下面的示例,了解动态类扩展的可能性:
// Dynamically extend a model that belongs to a third party plugin
Post::extend(function($model) {
// Bind to an event that's only fired locally
$model->bindEvent('model.afterSave', function () use ($model) {
if (!$model->isValid()) {
throw new \Exception("Invalid Model!");
}
});
// Add a new property to the class
$model->addDynamicProperty('tagsCache', null);
// Add a new method to the class
$model->addDynamicMethod('getTagsAttribute', function() use ($model) {
if ($model->tagsCache) {
return $model->tagsCache;
} else {
return $model->tagsCache = $model->tags()->lists('name');
}
});
});
动态类扩展还增加了类的能力私人特质,也称为行为。这些类似于原生 PHP 特性 除了它们有一些明显的好处:
- 行为有自己的构造函数。
- 行为可以有私有或受保护的方法。
- 方法和属性名称可以安全地发生冲突。
- 为跨控制器共享功能提供安全机制,同时仍保持其自身状态。
- 类可以动态扩展行为。
行为力量的最好例子是后端form,list, 和relation 在 Winter CMS 中为任何实现它们的控制器实现大部分 CRUD 要求的 ControllerBehaviors。
与性状的比较
你可能会像这样使用 PHP 特性的地方:
class MyClass
{
use \Winter\Storm\UtilityFunctions;
use \Winter\Storm\DeferredBinding;
}
以类似的方式使用行为:
class MyClass extends \Winter\Storm\Extension\Extendable
{
public $implement = [
'Winter.Storm.UtilityFunctions',
'Winter.Storm.DeferredBinding',
];
}
您可以在哪里定义这样的特征:
trait UtilityFunctions
{
public function sayHello()
{
echo "Hello from " . get_class($this);
}
}
行为定义如下:
class UtilityFunctions extends \Winter\Storm\Extension\ExtensionBase
{
protected $parent;
public function __construct($parent)
{
$this->parent = $parent;
}
public function sayHello()
{
echo "Hello from " . get_class($this->parent);
}
}
扩展对象总是作为第一个参数传递给行为的构造函数。
总结一下:
- 扩展 \Winter\Storm\Extension\ExtensionBase 以将您的类声明为 Behavior
- 想要实现行为的类需要扩展 \Winter\Storm\Extension\Extendable
NOTE: 看使用特征而不是基类
扩展构造函数
任何使用Extendable
或者ExtendableTrait
可以用静态扩展它的构造函数extend
方法。参数应该传递一个将作为类构造函数的一部分调用的闭包。
MyNamespace\Controller::extend(function($controller) {
//
});
动态声明属性
可以通过调用在可扩展对象上声明属性addDynamicProperty
并传递属性名称和值。
Post::extend(function($model) {
$model->addDynamicProperty('tagsCache', null);
});
NOTE:试图通过正常方式设置未声明的属性(
$this->foo = 'bar';
) 在一个实现了Winter\Storm\Extension\ExtendableTrait 不管用。它不会抛出异常,但也不会自动声明该属性。addDynamicProperty
必须调用以在可扩展对象上设置以前未声明的属性。
检索动态属性
可以使用继承自的 getDynamicProperties 函数检索动态创建的属性 可扩展性状。
因此,检索所有动态属性将如下所示:
$model->getDynamicProperties();
这将返回一个关联数组 [key => value],键是动态属性名称 值是属性值。
如果我们知道我们想要什么属性,我们可以简单地将键(属性名称)附加到函数:
$model->getDynamicProperties()[$key];
动态创建方法
可以通过调用为可扩展对象创建方法addDynamicMethod
并传递方法名称和可调用对象,例如Closure
.
Post::extend(function($model) {
$model->addDynamicProperty('tagsCache', null);
$model->addDynamicMethod('getTagsAttribute', function() use ($model) {
if ($model->tagsCache) {
return $model->tagsCache;
} else {
return $model->tagsCache = $model->tags()->lists('name');
}
});
});
检查方法是否存在
您可以检查一个方法是否存在Extendable
通过使用类methodExists
方法 - 类似于 PHPmethod_exists()
功能。这将检测标准方法和通过addDynamicMethod
称呼。methodExists
接受一个参数:要检查是否存在的方法名称的字符串。
Post::extend(function($model) {
$model->addDynamicMethod('getTagsAttribute', function () use ($model) {
return $model->tagsCache;
});
});
$post = new Post;
$post->methodExists('getTagsAttribute'); // true
$post->methodExists('missingMethod'); // false
列出所有可用方法
检索一个列表中所有可用方法的列表Extendable
类,你可以使用getClassMethods
方法。此方法的操作类似于 PHPget_class_methods()
函数,因为它返回类中可用方法的数组,但除了类中已定义的方法外,它还会列出扩展或通过扩展提供的任何方法addDynamicMethod
称呼。
Post::extend(function($model) {
$model->addDynamicMethod('getTagsAttribute', function () use ($model) {
return $model->tagsCache;
});
});
$post = new Post;
$methods = $post->getClassMethods();
/**
* $methods = [
* 0 => '__construct',
* 1 => 'extend',
* 2 => 'getTagsAttribute',
* ...
* ];
*/
动态实现行为
这种扩展构造函数的独特能力允许动态实现行为,例如:
/**
* Extend the Winter.Users Users controller to include the RelationController behavior too
*/
Winter\Users\Controllers\Users::extend(function($controller) {
// Implement the list controller behavior dynamically
$controller->implement[] = \Backend\Behaviors\RelationController::class;
// Declare the relationConfig property dynamically for the RelationController behavior to use
$controller->addDynamicProperty('relationConfig', '$/myvendor/myplugin/controllers/users/config_relation.yaml');
});
使用示例
行为/扩展类
<?php namespace MyNamespace\Behaviors;
class FormController extends \Winter\Storm\Extension\ExtensionBase
{
/**
* @var Reference to the extended object.
*/
protected $controller;
/**
* Constructor
*/
public function __construct($controller)
{
$this->controller = $controller;
}
public function someMethod()
{
return "I come from the FormController Behavior!";
}
public function otherMethod()
{
return "You might not see me...";
}
}
扩展类
这Controller
类将实现FormController
行为,然后方法将对类可用(混入)。我们将覆盖otherMethod
方法。
<?php namespace MyNamespace;
class Controller extends \Winter\Storm\Extension\Extendable
{
/**
* Implement the FormController behavior
*/
public $implement = [
'MyNamespace.Behaviors.FormController'
];
public function otherMethod()
{
return "I come from the main Controller!";
}
}
使用扩展
$controller = new MyNamespace\Controller;
// Prints: I come from the FormController Behavior!
echo $controller->someMethod();
// Prints: I come from the main Controller!
echo $controller->otherMethod();
// Prints: You might not see me...
echo $controller->asExtension('FormController')->otherMethod();
检测使用的扩展
要检查对象是否已扩展行为,您可以使用isClassExtendedWith
对象上的方法。
$controller->isClassExtendedWith(\Backend\Behaviors\RelationController::class);
下面是一个动态扩展的例子UsersController
使用此方法的第三方插件,以避免阻止其他插件也扩展上述第三方插件。
UsersController::extend(function($controller) {
// Implement behavior if not already implemented
if (!$controller->isClassExtendedWith(\Backend\Behaviors\RelationController::class)) {
$controller->implement[] = \Backend\Behaviors\RelationController::class;
}
// Define property if not already defined
if (!isset($controller->relationConfig)) {
$controller->addDynamicProperty('relationConfig');
}
// Splice in configuration safely
$myConfigPath = '$/myvendor/myplugin/controllers/users/config_relation.yaml';
$controller->relationConfig = $controller->mergeConfig(
$controller->relationConfig,
$myConfigPath
);
}
软定义
如果一个行为类不存在,比如一个特征,一个找不到类 将抛出错误。在某些情况下,您可能希望抑制此错误,以便在系统中存在行为时有条件地执行。您可以通过放置一个@
类名开头的符号。
class User extends \Winter\Storm\Extension\Extendable
{
public $implement = ['@Winter.Translate.Behaviors.TranslatableModel'];
}
如果类名Winter\Translate\Behaviors\TranslatableModel
不存在,不会抛出错误。这等效于以下代码:
class User extends \Winter\Storm\Extension\Extendable
{
public $implement = [];
public function __construct()
{
if (class_exists('Winter\Translate\Behaviors\TranslatableModel')) {
$this->implement[] = 'Winter.Translate.Behaviors.TranslatableModel';
}
parent::__construct();
}
}
使用 Traits 代替基类
对于您可能不希望延长ExtensionBase
或者Extendable
类,您可以改用特征。您的课程必须按如下方式实施:
首先让我们创建将充当行为的类,即。可以由其他类“实现”。
<?php namespace MyNamespace\Behaviours;
class WaveBehaviour
{
use \Winter\Storm\Extension\ExtensionTrait;
/**
* When using the Extensiontrait, your behaviour also has to implement this method
* @see \Winter\Storm\Extension\ExtensionBase
*/
public static function extend(callable $callback)
{
self::extensionExtendCallback($callback);
}
public function wave()
{
echo "*waves*<br>";
}
}
现在让我们创建能够使用 ExtendableTrait 实现行为的类。
class AI
{
use \Winter\Storm\Extension\ExtendableTrait;
/**
* @var array Extensions implemented by this class.
*/
public $implement;
/**
* Constructor
*/
public function __construct()
{
$this->extendableConstruct();
}
public function __get($name)
{
return $this->extendableGet($name);
}
public function __set($name, $value)
{
$this->extendableSet($name, $value);
}
public function __call($name, $params)
{
return $this->extendableCall($name, $params);
}
public static function __callStatic($name, $params)
{
return self::extendableCallStatic($name, $params);
}
public static function extend(callable $callback)
{
self::extendableExtendCallback($callback);
}
public function youGotBrains()
{
echo "I've got an AI!<br>";
}
}
AI 类现在可以使用行为。让我们扩展它并让这个类实现 WaveBehaviour。
<?php namespace MyNamespace\Classes;
class Robot extends AI
{
public $implement = [
'MyNamespace.Behaviours.WaveBehaviour'
];
public function identify()
{
echo "I'm a Robot<br>";
echo $this->youGotBrains();
echo $this->wave();
}
}
您现在可以按如下方式使用机器人:
$robot = new Robot();
$robot->identify();
这将输出:
I'm a Robot
I've got an AI!
*waves*
Remember:
- 当使用
ExtensionTrait
来自的方法ExtensionBase
应该应用到类中。 - 当使用
ExtendableTrait
来自的方法Extendable
应该应用到类中。