dive-be / laravel-stateful
Laravel中任何对象的策略模式
Requires
- php: ^8.1
- illuminate/support: ^9.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.6
- nunomaduro/larastan: ^2.0
- orchestra/testbench: ^7.0
- pestphp/pest: ^1.21
- pestphp/pest-plugin-laravel: ^1.2
- phpunit/phpunit: ^9.5
README
此软件包为Laravel中非Eloquent对象添加了状态支持。
如果您需要Eloquent模型的支持,有出色的替代方案
⚠️ 此软件包的次要版本可能引起破坏性更改,因为它还没有稳定的版本。
此软件包解决什么问题?
通常,在定义对象的“状态”或“状态”时,使用枚举-like值/对象来表示对象的当前状态。
虽然这种方法在实际和简单的用例中足够好,但当需要结合复杂的领域逻辑时,往往会变得混乱。
此软件包通过使用 状态模式 和 状态机 的概念来解决此问题。
安装
您可以通过composer安装此软件包
composer require dive-be/laravel-stateful
使用
最好的例子是实际例子。
上下文
假设有一个CheckoutWizard类,它有多个可能的状态:AddressSelect、ShippingSelect、Summary、Complete。这些状态应映射到前端中Wizard的当前索引,每个步骤都有一个相关的视图,将由Blade解析。
抽象状态
因此,我们首先应该创建一个抽象的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 类上使用 after 或 before 钩子来定义在特定转换之前或之后应该执行的方法。
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 而不是使用问题跟踪器。
鸣谢
- Muhammed Sari
- 所有贡献者
- Spatie 的出色 Model States 包,该包对本次包产生了巨大影响
许可证
MIT 许可证(MIT)。有关更多信息,请参阅 许可证文件。
