dive-be/laravel-stateful

该软件包已被废弃且不再维护。没有建议的替代软件包。

Laravel中任何对象的策略模式

0.6 2022-02-12 15:50 UTC

This package is auto-updated.

Last update: 2023-05-03 08:59:59 UTC


README

Latest Version on Packagist

此软件包为Laravel中非Eloquent对象添加了状态支持。

如果您需要Eloquent模型的支持,有出色的替代方案

⚠️ 此软件包的次要版本可能引起破坏性更改,因为它还没有稳定的版本。

此软件包解决什么问题?

通常,在定义对象的“状态”或“状态”时,使用枚举-like值/对象来表示对象的当前状态。

虽然这种方法在实际和简单的用例中足够好,但当需要结合复杂的领域逻辑时,往往会变得混乱。

此软件包通过使用 状态模式状态机 的概念来解决此问题。

安装

您可以通过composer安装此软件包

composer require dive-be/laravel-stateful

使用

最好的例子是实际例子。

上下文

假设有一个CheckoutWizard类,它有多个可能的状态:AddressSelectShippingSelectSummaryComplete。这些状态应映射到前端中Wizard的当前索引,每个步骤都有一个相关的视图,将由Blade解析。

viz.png

抽象状态

因此,我们首先应该创建一个抽象的CheckoutState类,并定义所有可能的转换,以及每个状态的要求(我们希望显示哪个视图,以及该状态的索引是什么?)。

仔细考虑可能的转换很重要:如果CheckoutWizard已经达到Complete状态,回到AddressSelect状态是完全没意义的。然而,只要没有达到Complete状态,用户回到前面的步骤是完全可以的(下面没有显示)。

以下是CheckoutState的抽象版本可能的样子

abstract class CheckoutState extends State
{
    /* You can define which methods need to be implemented for each particular state.
     * For example, you might want each state to have an associated view to resolve. */
    abstract public function view(): string;

    /* Like `view()`, each state should also have an associated index. 
     * We'll enforce that this method needs to be implemented by each class 
     * that inherits from this one. */
    abstract public function step(): int;
    
    /* Set up which state transitions are allowed. 
     * This is required for the states to function as expected. */
    public static function config(): Config
    {
        return parent::config()
            ->allowTransition(AddressSelect::class, ShippingSelect::class)
            ->allowTransition(ShippingSelect::class, Summary::class)
            ->allowTransition(Summary::class, Complete::class);
    }
}

以下是AddressSelect状态可能的样子,它扩展了抽象的CheckoutState

class AddressSelect extends CheckoutState
{
    public function view(): string
    { 
        return 'checkout.address_select'; 
    }
    
    public function step(): int
    {
        return 0; 
    }
}

有状态类

您的有状态类(将使用状态和可能转换的类)应实现Stateful契约并使用InteractsWithState特质。(后者是可选的,但建议。遵守契约就足够了。)

另外,不要忘记在构造函数中定义初始状态。

class CheckoutWizard implements Stateful
{
    use InteractsWithState;
    
    public function __construct() 
    {
        $this->state = AddressSelect::make($this);
    }
    
    // Your code here
}

转换

让我们继续我们的CheckoutWizard示例。要转换到另一个状态,您可以这样做

$checkout = new CheckoutWizard();

$checkout->getState()->transitionTo(Complete::class);

除非允许转换,否则将抛出 TransitionFailedException,以防止不可能的状态转换。

“自定义”转换

您可以定义自己的 Transition 类,这些类将可以访问 guard 以及 after/before 钩子。在配置中将 FQCN 传递给 allowTransition 即可。

class AdvanceToShipping extends Transition
{
    public function from(): string { return AddressSelect::class; }
    
    public function to(): string { return ShippingSelect::class; }
}
public static function config(): Config
{
    return parent::config()->allowTransition(AdvanceToShipping::class);
}

注意:在没有自定义类的情况下注册转换时,系统会自动为您创建一个 DefaultTransition

守卫(验证)

有时,即使在允许特定转换的情况下,也必须满足某些条件才能进行转换。在这种情况下,可以使用守卫来实现此行为。必须使用自定义的 Transition 来使用此功能。

  • 从守卫中返回 true 将允许转换发生。
  • 返回 false 将导致抛出 TransitionFailedException
class AdvanceToShipping extends Transition
{
    // omitted for brevity
    
    public function guard(CheckoutWizard $wizard, MyService $service)
    {
        return $service->isValid($wizard);
    }
}

您可以通过定义一个带有 Stateful 合约或您自己的子类类型提示的方法参数来访问状态对象。任何其他类型提示的依赖项将通过容器使用方法注入。

现在,每次尝试进行转换时,都会首先执行守卫。

副作用/钩子

您可以在 Transition 类上使用 afterbefore 钩子来定义在特定转换之前或之后应该执行的方法。

class AdvanceToShipping extends Transition
{
    // omitted for brevity
    
    public function after(CheckoutWizard $wizard)
    {
        // run immediately after the transition
    }
    
    public function before(CheckoutWizard $wizard)
    {
        // run just before the transition
    }
}

与任何守卫一样,钩子可以通过正确类型提示方法参数来访问状态对象。

测试

composer test

变更日志

有关最近更改的更多信息,请参阅 变更日志

贡献

有关详细信息,请参阅 贡献指南

安全

如果您发现任何与安全相关的问题,请通过电子邮件 oss@dive.be 而不是使用问题跟踪器。

鸣谢

许可证

MIT 许可证(MIT)。有关更多信息,请参阅 许可证文件