Download the PHP package juststeveking/scenario without Composer
On this page you can find all versions of the php package juststeveking/scenario. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download juststeveking/scenario
More information about juststeveking/scenario
Files in juststeveking/scenario
Package scenario
Short Description A strictly typed, railway-oriented business logic orchestration engine for PHP and Laravel.
License MIT
Informations about the package scenario
Scenario: Type-Safe Business Orchestration
Scenario is a logic orchestration engine for PHP 8.5+ designed to replace "Fat Services" and messy "Action" patterns with a strictly typed, railway-oriented flow. It's built to bring structure to complex business processes using a type-safe context, saga rollbacks, and a powerful dependency resolution system.
Why Use Scenario?
- No Magic: Everything is discovered via Reflection. No unpredictable
__callor__get. - Type-Safe Context: Share data between steps via class-type injection.
- Saga Pattern: Automatic rollbacks (
compensate) if any step fails. - Railway Oriented: Every step returns a
Resultobject. Success moves forward; failure stops the line. - Middleware: Wrap scenarios in transactions, telemetry, or custom logging.
- Step Hooks: Observe every action as it completes via
onStep. - Recursive: Compose complex workflows by nesting scenarios within each other.
Installation
Scaffolding (Laravel Only)
Quickly generate boilerplate for your workflows using the built-in Artisan commands:
The commands will automatically resolve the correct namespaces and create the directories if they don't exist.
Getting Started
1. Define your Input Data
Create a readonly DTO to represent the starting payload of your scenario.
2. Create an Action
Implement the Action contract. Business logic goes in handle(), rollback logic goes in compensate().
3. Define the Scenario
Implement the Scenario contract and define the steps in order.
4. Run it
Execute the scenario from a controller or service.
Full API at a Glance
Key Concepts
Dependency Resolution
The engine resolves dependencies for handle() methods automatically, checking in this order:
- The Scenario Context — objects returned by previous steps via
Result::success($obj). - The Initial Input passed to
->run(). - The Action Payload — values defined alongside the action in the blueprint.
- The Laravel Service Container — for any remaining type-hinted services.
See Context Sharing Example.
Saga Rollbacks (Compensation)
If an action returns Result::failure($message), the engine stops and triggers compensate() for every previously completed step in reverse order (LIFO). The failing step itself is not compensated.
See Saga Compensation Example.
Middleware
Wrap your scenario in one or more middleware for cross-cutting concerns. Implement the Middleware contract and register classes via ->through(). They wrap execution in the order given — outermost first.
Built-in Middleware
Two middleware classes are included out of the box.
LoggingMiddleware — logs scenario start, completion, and failure via Psr\Log\LoggerInterface. In a Laravel application the logger is resolved from the container automatically.
Emits info on start and success (with duration_ms), and warning on failure (with error and duration_ms).
DatabaseTransactionMiddleware — wraps execution in a database transaction using Illuminate\Database\ConnectionInterface. Commits on success, rolls back on failure.
Both can be combined. Place LoggingMiddleware outermost so it captures the full duration including transaction overhead:
See LoggingMiddleware Example.
Step Hooks (onStep)
onStep fires a callback after every individual action completes — whether it succeeded or failed. Register it before ->run() and use it for per-step observability: audit trails, metrics, debugging.
Key behaviours:
- Fires for every action, including actions inside sub-scenarios.
- On success: fires after the action's return value is recorded in context.
- On failure: fires before saga compensation begins.
- Multiple
onStepregistrations are all called per step, in order — useful for separating concerns:
See onStep Example.
Action Payloads
Pass static configuration to an action directly in the blueprint. This is useful for reusing the same action class with different settings.
See Action Payload Example.
Sub-Scenarios
Add a scenario class as a step inside another scenario's blueprint. This lets you build complex workflows from smaller, independently testable blocks. Saga compensation works globally across the full tree — if a parent step fails, sub-scenario steps are compensated too.
Accessing the Result and Context Directly
The fluent onSuccess / onFailure callbacks cover most use cases, but ->result() and ->context() are available as escape hatches — particularly useful in tests.
Note the callback signatures:
onSuccess(fn(Context $context): void)— context holds all objects recorded during the run.onFailure(fn(string $error, Context $context): void)— error is the message fromResult::failure(...), context holds objects recorded before the failure.
Testing Your Scenarios
The library includes built-in test helpers to make asserting against your workflows fluent and clean.
1. Faking Scenarios
If you're writing a controller test and want to assert that a scenario was triggered without actually executing all of its actions, you can use Scenario::fake().
2. Fluent Assertions
When you are unit testing a specific scenario, you can use the fluent assertions to verify the outcome and the final state of the Context.
3. Action Mocking (Partial Fakes)
Sometimes you want to test the full orchestration of a scenario, but mock out a single step that talks to an external API (like a Payment Gateway). You can use the mock() method to force a specific Result for an action.
Contributing
Please see CONTRIBUTING.md for details.
License
The MIT License (MIT). Please see License File for more information.