PHP code example of juststeveking / scenario

1. Go to this page and download the library: Download juststeveking/scenario 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/ */

    

juststeveking / scenario example snippets


readonly class RegisterUserData
{
    public function __construct(public string $name, public string $email) {}
}

use JustSteveKing\Scenario\Contracts\Action;
use JustSteveKing\Scenario\Context\Context;
use JustSteveKing\Scenario\Support\Result;

class CreateUser implements Action
{
    public function handle(RegisterUserData $data): Result
    {
        $user = new User($data->name, $data->email);
        $user->save();

        return Result::success($user); // $user is recorded in Context automatically
    }

    public function compensate(mixed $input, Context $context): void
    {
        $context->get(User::class)?->delete();
    }
}

use JustSteveKing\Scenario\Contracts\Scenario;
use JustSteveKing\Scenario\Engine\Blueprint;

class RegistrationScenario implements Scenario
{
    public function build(Blueprint $plan): void
    {
        $plan->add(CreateUser::class)
             ->add(SendWelcomeEmail::class);
    }
}

use JustSteveKing\Scenario\Scenario;

Scenario::for(RegistrationScenario::class)
    ->run(new RegisterUserData('John Doe', '[email protected]'))
    ->onSuccess(function (Context $context): void {
        $user = $context->get(User::class);
        // $user is the object returned by CreateUser
    })
    ->onFailure(function (string $error, Context $context): void {
        // $error is the message from Result::failure(...)
        // $context holds any objects recorded before the failure
    });

$pending = Scenario::for(RegistrationScenario::class)
    ->through([LoggingMiddleware::class, DatabaseTransactionMiddleware::class]) // optional, outermost first
    ->onStep(function (string $action, Result $result, Context $context): void {
        // fires after every action, success or failure
    })
    ->run(new RegisterUserData('John Doe', '[email protected]'))
    ->onSuccess(function (Context $context): void {
        // fires once if the whole scenario succeeded
        $user = $context->get(User::class);
    })
    ->onFailure(function (string $error, Context $context): void {
        // fires once if any action returned Result::failure(...)
    });

// Escape hatches — useful in tests or when you need the result outside a callback
$result  = $pending->result();  // ?Result
$context = $pending->context(); // Context

class SendWelcomeEmail implements Action
{
    public function __construct(private Mailer $mailer) {}

    // User comes from Context (returned by the previous CreateUser action).
    // RegisterUserData comes from the initial run() input.
    // Mailer is resolved from the container.
    public function handle(User $user, RegisterUserData $data): Result
    {
        $this->mailer->send($user->email, $data->name);
        return Result::success();
    }

    public function compensate(mixed $input, Context $context): void {}
}

class CreateOrder implements Action
{
    public function handle(CheckoutData $data): Result
    {
        $order = Order::create($data);
        return Result::success($order);
    }

    public function compensate(mixed $input, Context $context): void
    {
        // Called if a later step fails — undo the order creation
        $context->get(Order::class)?->delete();
    }
}

use JustSteveKing\Scenario\Contracts\Middleware;

class MyMiddleware implements Middleware
{
    public function handle(mixed $input, Context $context, Closure $next): Result
    {
        // Before execution
        $result = $next($input, $context);
        // After execution
        return $result;
    }
}

Scenario::for(CheckoutScenario::class)
    ->through([MyMiddleware::class])
    ->run($input);

use JustSteveKing\Scenario\Middleware\LoggingMiddleware;

Scenario::for(CheckoutScenario::class)
    ->through([LoggingMiddleware::class])
    ->run($input);

use JustSteveKing\Scenario\Middleware\DatabaseTransactionMiddleware;

Scenario::for(CheckoutScenario::class)
    ->through([DatabaseTransactionMiddleware::class])
    ->run($input);

->through([LoggingMiddleware::class, DatabaseTransactionMiddleware::class])

Scenario::for(CheckoutScenario::class)
    ->onStep(function (string $action, Result $result, Context $context): void {
        // $action  — fully-qualified class name of the action that just ran
        // $result  — its Result; check isSuccess() / isFailure()
        // $context — context at this point; on success the action's return
        //            value is already recorded here
    })
    ->run($input);

Scenario::for(CheckoutScenario::class)
    ->onStep(fn($action, $result, $ctx) => Log::info('step', ['action' => $action]))
    ->onStep(fn($action, $result, $ctx) => Metrics::increment('scenario.step'))
    ->run($input);

class SendNotification implements Action
{
    // 'channel' and 'priority' are matched by parameter name from the payload
    public function handle(string $channel, string $priority): Result
    {
        // send via $channel at $priority...
        return Result::success();
    }

    public function compensate(mixed $input, Context $context): void {}
}

class OrderScenario implements Scenario
{
    public function build(Blueprint $plan): void
    {
        $plan->add(CreateOrder::class)
             ->add(SendNotification::class, ['channel' => 'email', 'priority' => 'high'])
             ->add(SendNotification::class, ['channel' => 'slack', 'priority' => 'low']);
    }
}

class ValidationScenario implements Scenario
{
    public function build(Blueprint $plan): void
    {
        $plan->add(ValidateAddress::class)
             ->add(ValidatePaymentMethod::class);
    }
}

class CheckoutScenario implements Scenario
{
    public function build(Blueprint $plan): void
    {
        $plan->add(ValidationScenario::class) // entire sub-scenario runs as one step
             ->add(ReserveInventory::class)
             ->add(ChargePayment::class)
             ->add(SendConfirmation::class);
    }
}

$pending = Scenario::for(RegistrationScenario::class)
    ->run(new RegisterUserData('John Doe', '[email protected]'));

if ($pending->result()->isSuccess()) {
    $user = $pending->context()->get(User::class);
}

use JustSteveKing\Scenario\Scenario;

public function test_it_dispatches_registration_scenario()
{
    Scenario::fake();

    $this->post('/register', [
        'name' => 'John',
        'email' => '[email protected]',
    ]);

    // Assert it ran at all
    Scenario::assertRan(RegistrationScenario::class);

    // Or assert it ran with specific input
    Scenario::assertRan(RegistrationScenario::class, function ($input) {
        return $input->email === '[email protected]';
    });
    
    Scenario::assertNotRan(DeleteUserScenario::class);
}

public function test_registration_scenario_succeeds()
{
    Scenario::for(RegistrationScenario::class)
        ->run(new RegisterUserData('John', '[email protected]'))
        ->assertPassed()
        ->assertContextHas(User::class)
        ->assertContextHas(User::class, fn(User $user) => $user->email === '[email protected]');
}

public function test_registration_scenario_fails_on_duplicate_email()
{
    Scenario::for(RegistrationScenario::class)
        ->run(new RegisterUserData('John', '[email protected]'))
        ->assertFailed();
}

public function test_checkout_handles_payment_failure()
{
    Scenario::for(CheckoutScenario::class)
        ->mock(ChargeCreditCard::class, Result::failure("Card declined."))
        ->run(new CheckoutData())
        ->assertFailed();
}