stateforge/scenario-core
A Laravel/PHP package offering core building blocks for scenario-driven features. It provides lightweight primitives and abstractions for defining, organizing, and running scenarios, helping keep application logic clean and reusable across projects.
Install
composer require stateforge/scenario-core
There is no documented install script, so inspect the package surface before writing code:
composer show stateforge/scenario-core
ls vendor/stateforge/scenario-core
ls vendor/stateforge/scenario-core/src
Look first at src/ for the public contracts (interfaces), config/ if present, and tests/ — the test suite is usually the most reliable documentation in a low-adoption package.
Register the provider (Laravel 10+ auto-discovery)
If discovery doesn't kick in (check composer.json inside the package for extra.laravel.providers), add it manually in bootstrap/providers.php / config/app.php.
Publish config if offered
php artisan vendor:publish --provider="Stateforge\ScenarioCore\ScenarioCoreServiceProvider"
First use case The package is the core of a scenario framework — expect it to provide the primitives (scenario definition, transitions, guards, actions, results) and not the application-facing sugar. A good first exercise:
Do this in a test before wiring it into a controller — the core layer is designed to be framework-agnostic and is easiest to learn in isolation.
Scenario-as-class, not config. The "core" naming implies the package expects you to subclass or implement its contracts per domain concept. Keep one class per scenario; don't stuff a switch statement into a single generic scenario.
Registry pattern. Whatever mechanism lists available scenarios (a facade, a manager, or the container tagged bindings) is your extension point. Bind your scenario classes to the container and tag them if the package supports it, so downstream "runner" packages in the ecosystem can discover them.
Three-layer split. stateforge/* packages typically separate definitions (core, this package) from runtime/handlers and framework adapters. Keep your domain scenarios in app/Scenarios/ and let core own the execution mechanics. If you find yourself editing vendor behavior, you are probably meant to use a sibling package instead.
Driving from HTTP
Route::post('/scenario/{scenario}', function (string $scenario) {
$result = app(ScenarioRunner::class)->run($scenario, request()->all());
return response()->json($result);
})->where('scenario', '.*');
Always validate/allow-list the incoming scenario name — resolving arbitrary class names from user input is a classic security footgun with registry-style packages.
Testing pattern
Wrap each scenario in a unit test that pins the expected outcome. Because the package is small and lightly used, your tests become the real contract that catches breaking changes on composer update.
Integration tip: queue long scenarios. If a scenario touches IO or multiple domain services, dispatch it as a job and return a correlation id — the core layer gives you no backpressure guarantees on its own.
Treat this as pre-1.0-grade despite the dates. With 2 stars, zero dependents, and a last release in the future-ish date (2026-04-19), the API surface is unstable. Pin tightly:
"stateforge/scenario-core": "^0.1.0"
and commit your composer.lock. Run composer outdated stateforge/scenario-core deliberately, not blindly.
It's the core, not the whole thing. Expect to need stateforge/scenario-laravel / scenario-runner (or you writing the glue). Read the package's composer.json for require-dev and suggest — that tells you which siblings it expects.
Config caching. If the package ships a config file, remember php artisan config:cache during deploys; runtime config() mutations will silently not take effect. If scenarios are collected at boot from the container, ensure the collection happens in a register() method, not at file scope.
Debugging. No docs means use the container events. If the package dispatches events (check src/Events), enable a listener logging all of them:
Event::listen('*', fn ($name, $payload) => Log::debug($name, $payload));
Then narrow down. Also php artisan tinker + app()->make(...) on the scenario manager to introspect registered scenarios.
Auto-discovery quirks. If php artisan optimize:clear doesn't make a facade resolve, it's almost always a missing provider registration in the package's composer.json. Workaround: register manually — it's safe and idiomatic.
Extension points to prefer. Use interfaces/contracts the package exposes (guards, actions, results) rather than extending concrete classes. With a low-version core, interfaces are far cheaper to migrate than class hierarchies.
No escape hatch = no adoption. If the package makes it hard to swap out after a few months, wrap it behind your own ScenarioRunner interface in app/Contracts/. That one thin abstraction will save days if the project ever goes stale.
How can I help you explore Laravel packages today?