PHP code example of spawnflow / spawnflow-laravel
1. Go to this page and download the library: Download spawnflow/spawnflow-laravel 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/ */
spawnflow / spawnflow-laravel example snippets
(new Flow)
->spawn($request)->auth()
->resolve('posts')
->ask('POST', $id)
->fields(PostContext::class)
->validate()
->save($request->all())
->present();
'subjects' => [
'posts' => \App\Models\Post::class,
'comments' => \App\Models\Comment::class,
],
use Spawnflow\Flow;
class PostController extends Controller
{
public function store(Request $request)
{
return (new Flow)
->spawn($request)->auth()
->resolve('posts')
->validate(['title' => ' ->ask('POST', $id)
->validate(['title' => '
Route::middleware('auth:api')->group(function () {
Route::get('/posts', [PostController::class, 'index']);
Route::post('/posts', [PostController::class, 'store']);
Route::post('/posts/{id}', [PostController::class, 'update']);
Route::delete('/posts/{id}', [PostController::class, 'destroy']);
});
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User;
use Spawnflow\Contracts\FieldContext;
enum PostContext: string implements FieldContext
{
case OwnerDraft = 'owner:draft';
case OwnerPublished = 'owner:published';
case Viewer = 'viewer';
public static function resolve(User $user, Model $record): static
{
return match (true) {
$user->id === $record->owner_id && $record->status === 'draft'
=> self::OwnerDraft,
$user->id === $record->owner_id
=> self::OwnerPublished,
default
=> self::Viewer,
};
}
public function editableFields(): array
{
return match ($this) {
self::OwnerDraft => ['title', 'body', 'status'],
self::OwnerPublished => ['title'],
self::Viewer => [],
};
}
public function validation(): array
{
return match ($this) {
self::OwnerDraft => [
'title' => '
// config/spawnflow.php
'contexts' => [
'posts' => \App\Spawnflow\PostContext::class,
],
use Spawnflow\SpawnflowController;
Route::middleware('auth:api')->prefix('v2')->group(function () {
Route::get('/{subject}', [SpawnflowController::class, 'index']);
Route::post('/{subject}', [SpawnflowController::class, 'store']);
Route::post('/{subject}/{id}', [SpawnflowController::class, 'update']);
Route::delete('/{subject}/{id}', [SpawnflowController::class, 'destroy']);
});
use Spawnflow\Schema\Field;
use Spawnflow\Schema\FieldSet;
class PostFields extends FieldSet
{
public static function fields(): array
{
return [
Field::string('title')->rules('lass) // FK: searchable select, exists rule
->display('name')->searchable(),
Field::email('email')->rules('
// config/spawnflow.php
'fields' => [
'posts' => \App\Spawnflow\PostFields::class,
],
Field::string('company_name')
->visibleWhen(['==' => [['var' => 'type'], 'business']]),
Field::string('vat_number')
->enabledWhen(['and' => [
['==' => [['var' => 'type'], 'business']],
['in' => [['var' => 'country'], ['DE', 'FR', 'NL']]],
]]),
Field::string('discount_code')->visibleWhen(...)->serverResolved(), // verdict only, no client re-eval
class BillingFields extends FieldSet
{
public static function groups(): array
{
return [
Group::make('company', ['company_name', 'vat_number'])
->visibleWhen(['==' => [['var' => 'type'], 'business']]),
];
}
}
(new Flow)
->spawn($request)->auth()
->resolve('posts')
->ask('POST', $id)
->fields()
->validate() // rules resolved from PostFields + resolved context
->save($request->all())
->present();
use Spawnflow\Http\SpawnflowFormRequest;
class UpdatePostRequest extends SpawnflowFormRequest
{
protected string $subject = 'posts';
}
// config/spawnflow.php
'schema_routes' => true,
'schema_middleware' => ['auth:api'],
// config/spawnflow.php
'events' => true, // registered with the schema routes, same middleware
public function stats(Request $request, int $id)
{
$flow = (new Flow)
->spawn($request)->auth()
->resolve('campaigns')
->ask('GET', $id);
// Break out — use accessors for custom work
$campaign = $flow->getInstance();
$user = $flow->getUser();
$stats = CampaignStatsService::compute($campaign);
return response()->json($stats);
}
$flow->getUser(); // Authenticated user
$flow->getInstance(); // Loaded record (after ask() or save())
$flow->getSubject(); // Unhydrated model (after resolve())
$flow->getContext(); // Resolved FieldContext enum case
$flow->getRequest(); // Original HTTP request
(new Flow)
->spawn($request)->auth()
->resolve('campaigns')
->ask('POST', $id)
->gate(fn ($f) => $f->getInstance()->status === 'draft'
|| throw new StateException('Cannot edit a published campaign'))
->save($request->all())
->present();
->save($data)
->after(fn ($f) => CampaignCreated::dispatch($f->getInstance()))
->present();
// config/spawnflow.php
return [
// Maps URL segment aliases to Eloquent model classes.
'subjects' => [
// 'posts' => \App\Models\Post::class,
],
// Maps subjects to FieldContext enum classes.
// Subjects without a context allow all $fillable fields for the owner.
'contexts' => [
// 'posts' => \App\Spawnflow\PostContext::class,
],
// Maps subjects to FieldSet classes (type-aware field descriptors).
'fields' => [
// 'posts' => \App\Spawnflow\PostFields::class,
],
// #[SpawnSubject] attribute discovery — FieldSets under the discovery
// path self-register; config entries above override on conflict.
// Deploy-time: spawnflow:cache freezes the scan, spawnflow:clear unfreezes.
'discovery' => true,
'discovery_path' => null, // defaults to app_path('Spawnflow')
// SSE invalidation channel (GET /spawnflow/events) — opt-in.
'events' => false,
'events_poll_interval' => 2, // seconds between version checks
'events_max_polls' => null, // null = stream until client disconnects
'events_cache_store' => null, // null = default cache store
// Database column linking records to their owner.
'ownership_column' => 'ownerId',
// Key on the User model used for ownership checks.
'user_key' => 'id',
// Enable GET /spawnflow/schema/{subject}/{id?} routes.
'schema_routes' => false,
// Middleware applied to schema routes.
'schema_middleware' => ['auth:api'],
// Frontend code generation settings (php artisan spawnflow:generate).
'generator' => [
'output_path' => base_path('../frontend/src/generated'),
'type_format' => 'typescript',
'validation' => 'zod',
'emit_client' => true,
'emit_unions' => true,
],
// MCP server — disabled by default; 'enabled' exposes stdio,
// 'web' additionally exposes streamable HTTP behind web_middleware.
'mcp' => [
'enabled' => false,
'web' => false,
'web_route' => '/mcp/spawnflow',
'web_middleware' => ['auth:api', 'throttle:60,1'],
],
];
bash
php artisan vendor:publish --tag=spawnflow-config
bash
composer spawnflow:install
php artisan spawnflow:resource Post --generate
bash
php artisan make:spawnflow-context PostContext # → app/Spawnflow/PostContext.php
bash
php artisan spawnflow:generate # writes to generator.output_path
php artisan spawnflow:generate --path=resources/js/generated
bash
composer wnflow.php: 'mcp' => ['enabled' => true]
claude mcp add spawnflow -- php artisan mcp:start spawnflow