行为和动态类扩展

Introduction

动态类扩展提供了以下好处:

  1. 动态添加新的属性和方法。
  2. 绑定到在模型、小部件和其他位置触发的本地事件,甚至是核心模块或其他插件中的事件。
  3. 向类添加额外的行为(私有特征,见下文)。
  4. 必须延长Winter\Storm\Extension\Extendable 类或实现Winter\Storm\Extension\ExtendableTrait.
  5. 不能覆盖已经定义的方法和属性。
  6. 可扩展类受到保护,不会在首次使用时设置未定义的属性,并且必须使用$object->addDynamicProperty(); 反而。
  7. 动态添加的方法不能覆盖代码中定义的方法。

请参阅下面的示例,了解动态类扩展的可能性:

// 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 特性 除了它们有一些明显的好处:

  1. 行为有自己的构造函数。
  2. 行为可以有私有或受保护的方法。
  3. 方法和属性名称可以安全地发生冲突。
  4. 为跨控制器共享功能提供安全机制,同时仍保持其自身状态。
  5. 类可以动态扩展行为。

行为力量的最好例子是后端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);
    }
}

扩展对象总是作为第一个参数传递给行为的构造函数。

总结一下:

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:

豫ICP备18041297号-2