Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

State machines for Laravel with Enums

License

NotificationsYou must be signed in to change notification settings

norotaro/enumata

Repository files navigation

Latest VersionTestsPackagist PHP VersionPackagist Laravel Version

State Machines for Eloquent models using Enums.

Table of Contents

Description

This package helps to implement State Machines to Eloquent models in an easy way using Enum files to represent all possible states and also to configure transitions.

Live demo

You can check thenorotaro/enumata-demo repository or go to the live version of the demo in thisPHP Sandbox.

Installation

composer require norotaro/enumata

Basic usage

Having a model with astatus field and 4 possible states:

$order->status;// 'pending', 'approved', 'declined' or 'processed'

We need to create anenum file with the State Definitions which we will callOrderStatus.We can do this with themake:model-state command:

php artisan make:model-state OrderStatus

The State Definition file - enum file

The above command will create a default file that we can adapt to meet our needs:

namespaceApp\Models;useNorotaro\Enumata\Contracts\Nullable;useNorotaro\Enumata\Contracts\DefineStates;enum OrderStatusimplements DefineStates{case Pending;case Approved;case Declined;case Processed;publicfunctiontransitions():array    {returnmatch ($this) {// when the order is Pending we can approve() or decline() itself::Pending => ['approve' =>self::Approved,'decline' =>self::Delined,            ],// when the order is Approved we can apply the processOrder() transitionself::Approved => ['processOrder' =>self::Processed,            ],        };    }publicstaticfunctiondefault():self    {returnself::Pending;    }}

Thetransitions() method must return an array withkey=>value where the key is the name of the transition and the value is the state to apply in that transition.

Note that, by default, methods will be created in the model for each transition. In the case of the example, theapprove(),decline(), andprocessOrder() methods will be created.

Configuring the model

In the model we have to implement the contractHasStateMachine and register theEloquentHasStateMachines trait and then theenum file in the$casts property:

useNorotaro\Enumata\Contracts\HasStateMachine;useNorotaro\Enumata\Traits\EloquentHasStateMachines;class Orderextends Modelimplements HasStateMachine{use EloquentHasStateMachines;protected$casts = ['status' => OrderStatus::class,    ];}

That's it! Now we can transition between the states.

Access the current state

If you access the attributes, Eloquent will return theenum object with the current state:

$model =newOrder;$model->save();$model->status;// App\Model\OrderStatus{name: "Pending"}$model->fulfillment;// null

Transitioning

By default this package will create methods in the model for each transition returned bytransitions() so, for this example, we will have these methods available:

$model->approve();// Change status to OrderStatus::Approved$model->decline();// Change status to OrderStatus::Declined$model->processOrder();// Change status to OrderStatus::Processed

Disable default transition methods

You can disable the creation of transition methods by making the$defaultTransitionMethods attribute of the modelfalse.

Internally these methods use thetransitionTo($state) method available in theStateMachine class, so you can implement your custom transition methods with it.

useNorotaro\Enumata\Contracts\HasStateMachine;useNorotaro\Enumata\Traits\EloquentHasStateMachines;class Orderextends Modelimplements HasStateMachine{use EloquentHasStateMachines;// disable the creation of transition methodspublicbool$defaultTransitionMethods =false;protected$casts = ['status' => OrderStatus::class,    ];// custom transition methodpublicfunctionmyApproveTransition():void {$this->status()->transitionTo(OrderStatus::Approved);//...    }}

Transition not allowed exception

If a transition is applied and the current state does not allow it, theTransitionNotAllowedException will be thrown.

$model->status;// App\Model\OrderStatus{name: "Pending"}$model->processOrder();// throws Norotaro\Enumata\Exceptions\TransitionNotAllowedException

Force transitions

All the methods of transitions created by the trait and also thetransitionTo() method have theforce parameter which, when true, the transition is applied without checking the defined rules.

$model->status;// App\Model\OrderStatus{name: "Pending"}$model->processOrder(force:true);// this will apply the transition and will not throw the exception$model->status;// App\Model\OrderStatus{name: "Processed"}$model->status()->transitionTo(OrderStatus::Pending, force:true);// will apply the transition without errors

Nullable States

If the model has nullable states we only have to implement theNorotaro\Enumata\Contracts\Nullable contract in the State Definition file.

As an example, we will add thefulfillment attribute to the Order model:

$order->fulfillment;// null, 'pending', 'completed'

Create a State Definition file

We can create the enum file with themake:model-state command and the--nullable option:

php artisan make:model-state OrderFulfillment --nullable

After editing the generated file we can have something like this:

namespaceApp\Models;useNorotaro\Enumata\Contracts\Nullable;useNorotaro\Enumata\Contracts\DefineStates;enum OrderFulfillmentimplements DefineStates, Nullable{case Pending;case Completed;publicfunctiontransitions():array    {returnmatch ($this) {self::Pending => ['completeFulfillment' =>self::Completed,            ],        };    }publicstaticfunctiondefault(): ?self    {returnnull;    }publicstaticfunctioninitialTransitions():array    {return ['initFulfillment' =>self::Pending,        ];    }}

TheinitialTransitions() method must return the list of available transitions when the field is null.

As withtransitions(), by default methods will be created with the name of the keys returned byinitialTransitions().

Register the State Definition file

As we previously did with thestatus definition, we need to register the file in the$casts property:

useNorotaro\Enumata\Contracts\HasStateMachine;useNorotaro\Enumata\Traits\EloquentHasStateMachines;class Orderextends Modelimplements HasStateMachine{use EloquentHasStateMachines;protected$casts = ['status'      => OrderStatus::class,'fulfillment' => OrderFulfillment::class,    ];}

The State Machine

To access the State Machine we only need to add parentheses to the attribute name:

$model->status();// Norotaro\Enumata\StateMachine

If the attribute uses underscore such asmy_attribute, you can access the state machine using the camel case name of the attribute,myAttribute() in this case.

Using the State Machine

Transitioning

We can transition between states with thetransitionTo($state) method:

$model->status()->transitionTo(OrderStatus::Approved);

Checking available transitions

$model->status;// App\Model\OrderStatus{name: "Pending"}$model->status()->canBe(OrderStatus::Approved);// true$model->status()->canBe(OrderStatus::Processed);// false

Events

This package adds two new events to those dispatched by Eloquent by default and can be used in the same way.

More information about Eloquent Events can be found in theofficial documentation.

  • transitioning:{attribute}: This event is dispatched before saving the transition to a new state.
  • transitioned:{attribute}: This event is dispatched after saving the transition to a new state.

In thetransitioning event you can access the original and the new state in this way:

$from =$order->getOriginal('fulfillment');// App\Model\OrderFulfillment{name: "Pending"}$to   =$order->fulfillment;// App\Model\OrderFulfillment{name: "Complete"}

Listening to events using$dispatchesEvents

useApp\Events\TransitionedOrderFulfillment;useApp\Events\TransitioningOrderStatus;useNorotaro\Enumata\Traits\HasStateMachines;class Orderextends Model{use HasStateMachines;protected$casts = ['status'      => OrderStatus::class,'fulfillment' => OrderFulfillment::class,    ];protected$dispatchesEvents = ['transitioning:status'     => TransitioningOrderStatus::class,'transitioned:fulfillment' => TransitionedOrderFulfillment::class,    ];}

Listening to events using Closures

Thetransitioning($field, $callback) andtransitioned($field, $callback) methods help to register closures.

Note that the first parameter must be the name of the field we want to listen to.

useNorotaro\Enumata\Contracts\HasStateMachine;useNorotaro\Enumata\Traits\EloquentHasStateMachines;class Orderextends Modelimplements HasStateMachine{use EloquentHasStateMachines;protected$casts = ['status'      => OrderStatus::class,'fulfillment' => OrderFulfillment::class,    ];protectedstaticfunctionbooted():void    {static::transitioning('fulfillment',function (Order$order) {$from =$order->getOriginal('fulfillment');$to   =$order->fulfillment;            \Log::debug('Transitioning fulfillment field', ['from' =>$from->name,'to' =>$to->name,            ]);        });static::transitioned('status',function (Order$order) {            \Log::debug('Order status transitioned to' .$order->status->name);        });    }}

Testing

To run the test suite:

composer run test

Inspiration

This package was inspired byasantibanez/laravel-eloquent-state-machines.

LICENSE

The MIT License (MIT). Please seeLicense File for more information.

Contributors2

  •  
  •  

Languages


[8]ページ先頭

©2009-2025 Movatter.jp