PHP code example of soylentgreenstudio / laravel-enum-states

1. Go to this page and download the library: Download soylentgreenstudio/laravel-enum-states library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

soylentgreenstudio / laravel-enum-states example snippets


// 1. Define your enum with attributes
enum OrderStatus: string
{
    #[InitialState]
    #[Transition(to: [self::Processing])]
    case Pending = 'pending';

    #[Transition(to: [self::Shipped])]
    case Processing = 'processing';

    #[FinalState]
    case Shipped = 'shipped';

    // Reachable from any non-final case — no need to list it on each source
    #[FinalState]
    #[TransitionFrom(from: '*')]
    case Cancelled = 'cancelled';
}

// 2. Add the trait to your model — that's it
class Order extends Model
{
    use HasStateMachines;

    protected $casts = [
        'status' => OrderStatus::class,
    ];
}

// 3. Transition states
$order->transitionTo(OrderStatus::Processing);
$order->transitionTo(OrderStatus::Shipped, ['tracking' => 'ABC123']);

// 4. Check if transition is allowed (never throws)
$order->canTransitionTo(OrderStatus::Cancelled); // bool

// 4b. Or list every state reachable right now, guards applied
$order->allowedTransitions(); // [OrderStatus::Shipped, OrderStatus::Cancelled]

// 5. Query by state
Order::whereState('status', OrderStatus::Pending)->get();

// 6. View transition history
$order->stateHistory('status');

return [
    'table' => env('ENUM_STATES_TABLE', 'state_transitions'),
];

// config/enum-states.php
'table' => 'audit_state_transitions',

use SoylentGreenStudio\EnumStates\Attributes\InitialState;
use SoylentGreenStudio\EnumStates\Attributes\FinalState;
use SoylentGreenStudio\EnumStates\Attributes\Transition;
use SoylentGreenStudio\EnumStates\Attributes\TransitionFrom;

enum OrderStatus: string
{
    #[InitialState]
    #[Transition(
        to: [self::Processing],
        guard: HasItemsInStock::class,
        after: SendOrderConfirmation::class,
    )]
    case Pending = 'pending';

    #[Transition(
        to: [self::Shipped],
        before: ValidateShippingAddress::class,
    )]
    case Processing = 'processing';

    #[FinalState]
    case Shipped = 'shipped';

    // Reverse-declared: reachable from any non-final case
    #[FinalState]
    #[TransitionFrom(from: '*')]
    case Cancelled = 'cancelled';
}

#[Transition(to: [self::Approved], guard: ManagerApproval::class)]
#[Transition(to: [self::Rejected], guard: CanReject::class)]
case Pending = 'pending';

use SoylentGreenStudio\EnumStates\Traits\HasStateMachines;

class Order extends Model
{
    use HasStateMachines;

    protected $casts = [
        'status'         => OrderStatus::class,
        'payment_status' => PaymentStatus::class,
    ];
}

$order->transitionTo(OrderStatus::Processing);

$order->transitionTo(OrderStatus::Processing, [
    'reason'  => 'Payment confirmed',
    'user_id' => auth()->id(),
]);

$order->transitionTo('payment_status', PaymentStatus::Paid, $metadata);

if ($order->canTransitionTo(OrderStatus::Shipped)) {
    $order->transitionTo(OrderStatus::Shipped);
}

$order->allowedTransitions();
// => [OrderStatus::Processing, OrderStatus::Cancelled]

$order->allowedTransitions(metadata: ['user_id' => auth()->id()]);

$order->allowedTransitions('payment_status');

enum OrderStatus: string
{
    #[InitialState]
    #[Transition(to: [self::Processing])]
    case Pending = 'pending';

    #[Transition(to: [self::Shipped])]
    case Processing = 'processing';

    #[FinalState]
    case Shipped = 'shipped';

    // Cancelled is reachable from Pending and Processing —
    // without touching those cases' own attribute lists.
    #[FinalState]
    #[TransitionFrom(from: '*')]
    case Cancelled = 'cancelled';
}

enum DocumentStatus: string
{
    #[InitialState]
    case Draft = 'draft';

    case Review = 'review';
    case Approved = 'approved';

    // Archived from Draft or Review — but NOT from Approved
    #[FinalState]
    #[TransitionFrom(from: [self::Draft, self::Review])]
    case Archived = 'archived';
}

#[FinalState]
#[TransitionFrom(
    from: '*',
    guard: [IsAdmin::class, HasCloseReason::class],
    after: NotifyClosureWebhook::class,
)]
case Closed = 'closed';

enum Status: string
{
    #[InitialState]
    #[Transition(to: [self::Done], guard: FastPathGuard::class)]
    case Pending = 'pending';

    #[TransitionFrom(from: [self::Pending], guard: FallbackGuard::class)]
    case Done = 'done';
}

use SoylentGreenStudio\EnumStates\Contracts\TransitionGuard;

class HasItemsInStock implements TransitionGuard
{
    public function allow(Model $model, array $metadata): bool
    {
        return $model->items()->where('in_stock', true)->exists();
    }
}

class HasSufficientBalance implements TransitionGuard
{
    public function __construct(
        private PaymentGateway $gateway,
    ) {}

    public function allow(Model $model, array $metadata): bool
    {
        return $this->gateway->getBalance($model->user_id) >= $model->total;
    }
}

#[Transition(
    to: [self::Approved],
    guard: [IsAdmin::class, HasApprovalPermission::class, BudgetAvailable::class],
)]
case Pending = 'pending';

#[Transition(to: [self::Approved], guard: IsAdmin::class)]
#[Transition(to: [self::Approved], guard: [IsManager::class, BudgetAvailable::class])]
case Pending = 'pending';

use SoylentGreenStudio\EnumStates\Contracts\TransitionHook;

class SendOrderConfirmation implements TransitionHook
{
    public function handle(Model $model, mixed $from, mixed $to, array $metadata): void
    {
        Mail::to($model->user)->send(new OrderConfirmed($model));
    }
}

class StampShippedAt implements TransitionHook
{
    public function handle(Model $model, mixed $from, mixed $to, array $metadata): void
    {
        $model->shipped_at = now(); // persisted with the transition
    }
}

class NotifySlack implements TransitionHook
{
    public function __construct(
        private SlackClient $slack,
    ) {}

    public function handle(Model $model, mixed $from, mixed $to, array $metadata): void
    {
        $this->slack->send("Order #{$model->id} changed from {$from->name} to {$to->name}");
    }
}

// History for a specific field
$order->stateHistory('status');
// => Collection of StateTransition models

// History for all state machine fields
$order->stateHistory();

$orders = Order::with('stateTransitions')->get();

foreach ($orders as $order) {
    $order->stateHistory('status'); // no extra queries — filtered in memory
}

$transition->from;              // 'pending'
$transition->to;                // 'processing'
$transition->field;             // 'status'
$transition->metadata;          // ['reason' => 'Payment confirmed'] or null
$transition->transitioned_at;   // Carbon instance
$transition->created_at;        // Carbon instance

// Exact match
Order::whereState('status', OrderStatus::Pending)->get();

// Exclude a state
Order::whereNotState('status', OrderStatus::Cancelled)->get();

// Match multiple states
Order::whereStateIn('status', [
    OrderStatus::Pending,
    OrderStatus::Processing,
])->get();

// In EventServiceProvider or via Event::listen()
use SoylentGreenStudio\EnumStates\Events\TransitionCompleted;

Event::listen(TransitionCompleted::class, function (TransitionCompleted $event) {
    Log::info("Order #{$event->model->id}: {$event->field} changed", [
        'from' => $event->from->value,
        'to'   => $event->to->value,
        'meta' => $event->metadata,
    ]);
});

use SoylentGreenStudio\EnumStates\Contracts\AsyncTransitionHook;

class SendShipmentNotification implements AsyncTransitionHook
{
    public function handle(Model $model, mixed $from, mixed $to, array $metadata): void
    {
        Mail::to($model->user)->send(new OrderShipped($model));
    }

    public function queue(): ?string
    {
        return 'notifications'; // or null for default queue
    }
}

#[Transition(
    to: [self::Shipped],
    before: ValidateShippingAddress::class,    // sync — runs inside transaction
    after: SendShipmentNotification::class,     // async — dispatched to queue
)]
case Processing = 'processing';

interface AsyncTransitionHook
{
    public function handle(Model $model, mixed $from, mixed $to, array $metadata): void;
    public function queue(): ?string; // queue name or null for default
}
bash
composer vendor:publish --tag=enum-states-migrations
php artisan migrate
bash
php artisan vendor:publish --tag=enum-states-migrations
php artisan migrate
bash
php artisan vendor:publish --tag=enum-states-config
bash
php artisan enum-states:graph "App\Enums\OrderStatus"
bash
php artisan enum-states:graph "App\Enums\OrderStatus" --format=mermaid
bash
php artisan make:transition-guard HasItemsInStock