sofa / state-machine
易于使用的有限状态机实现
资助包维护!
jarektkaczyk
softonsofa.com
Requires
- php: >=7.1.0
Requires (Dev)
- phpunit/phpunit: >=9.1
This package is auto-updated.
Last update: 2024-08-29 04:54:27 UTC
README
有限状态机 实现
安装
将包添加到您的项目中
path/to/your/app$ composer require sofa/state-machine
用法
状态机帮助您消除代码中的 switch
和/或 if/else
语句,以确定给定状态下的可用操作。
让我们用一个来自 Laravel Blade 视图模板和底层 Eloquent Order
模型的简单示例来说明
@foreach($orders as $order) {{ $order->reference }} status: {{ $order->status }} @if($order->status === 'new') <button>start processing</button> @elseif($order->status === 'awaiting_payment') <button>record payment</button> @elseif($order->status === 'awaiting_shipment') <button>save tracking number</button> @elseif($order->status === 'in_delivery') <button>record delivery</button> <button>open claim</button> @elseif($order->status === 'complete') <button>open claim</button> @elseif($order->status === 'processing_claim') <button>refund</button> <button>close claim</button> @endif @endforeach
这很快就会变得难以控制,尤其是在引入新的状态或处理顺序发生变化时。
为了使其流程化,我们可以为订单实体实现状态机
-
在
Order
模型上实现接口class Order extends Model implements \Sofa\StateMachine\StateMachineInterface { //... public function getCurrentState() : string { return $this->status; } public function setState(string $state) : void { $this->status = $state; $this->save(); } }
-
定义可用转换并准备模板数据
$transitions = [ Transition::make(/*from_state*/ 'new', /*action*/ 'start processing', /*to_state*/ 'awaiting_payment'), Transition::make('awaiting_payment', 'record payment', 'awaiting_shipment'), Transition::make('awaiting_shipment', 'save tracking number', 'in_delivery'), Transition::make('in_delivery', 'record delivery', 'complete'), Transition::make('in_delivery', 'open claim', 'processing_claim'), Transition::make('complete', 'open claim', 'processing_claim'), Transition::make('processing_claim', 'close claim', 'complete'), Transition::make('processing_claim', 'refund', 'refunded'), ]; foreach ($orders as $order) { $order_state = new \Sofa\StateMachine\Fsm($order, $transitions); $order->available_actions = $order_state->getAvailableActions(); }
-
这样我们就得到了控制器和模板代码与处理逻辑及订单的解耦
@foreach($orders as $order) {{ $order->reference }} status: {{ $order->status }} @foreach($order->available_actions as $action) <button>{{ $action }}</button> @endforeach @endforeach
-
最后让我们处理操作
// controller handling the action public function handleAction($order_id, Request $request) { $order_state = new \Sofa\StateMachine\Fsm(Order::find($order_id), $transitions); $this->validate($request, [ 'action' => Rule::in($order_state->getAvailableActions()), // ... ]); $order_state->process($request->get('action')); return Redirect::to('some/place'); }
有了这个设置,我们就不再需要在业务需求改变时更改我们的控制器或视图,而是向状态机定义中添加一个新的转换。
我需要在转换过程中有更多的控制 - 如何做?
上面的示例假设了一个非常简单的转换过程,即 $order->status = $new_status
。这有时可能足够,但通常我们需要在转换过程中有更多的灵活性。为了满足这一需求,您可以自定义您的 Transition
定义,使它们从简单的 POPO 转变为在状态机处理适当的 action 时将被调用的 callable
。
class Refund extends \Sofa\StateMachine\Transition { public function __invoke(StateMachineInterface $order, $payload) { // $payload is any object you pass to the process method: // $order_state->process('refund', $anything_you_need_here); $order->refunded_at = $payload['time']; $order->refunded_by = $payload['user_id']; $order->setState($this->to_state); } } // Then our transitions definition would like something like: $transitions = [ // ... Transition::make('processing_claim', 'close claim', 'complete'), Refund::make('processing_claim', 'refund', 'refunded'), ];
编码愉快!
贡献
欢迎所有贡献。确保您的 PR 符合 PSR-2 规范并经过测试。