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