phasync
Frameworks / Laravel

Frameworks

Laravel

Run a Laravel application on Swerve: many requests at once, each in a pooled application of its own.

Concurrency: concurrent. A worker serves many requests at once, each in a Laravel application of its own, taken from a Pool of booted applications. Database and Redis connections live inside that application, so two requests in flight at once never share a connection or a transaction. The one place concurrency still bites is Laravel's view engine — see Concurrency below.

Install and run#

composer require phasync/swerve-laravel
vendor/bin/swerve --http=0.0.0.0:8080 --public=public

That starts the application with no swerve.php — the package's composer.json tells Swerve how to find it. laravel/octane comes along as a dependency, but only for the listeners that reset an application between requests; Octane's own servers and octane:install play no part. public/index.php is untouched, so the same application keeps running under PHP-FPM too.

Load ext-phasync with --ext: without it, sleep(), PDO and mysqli queries, curl and flock() hold the worker while they wait, the way they would under PHP-FPM; with it, they yield to other requests in the same worker.

To serve the application in a subfolder behind a proxy, or to change how many applications the pool keeps, add a swerve.php:

<?php // swerve.php, next to composer.json

return new Swerve\RequestHandler((new Swerve\Laravel\Handler(__DIR__, basePath: '/app'))->handle(...));

and run it with --adapter=swerve.

What persists between requests#

Each request takes an idle application from the pool, or boots a fresh one. Octane's listeners reset it first (session, auth, cookies, locale — your own config/octane.php listeners run too), and once the request ends the container goes back to what it held right after boot. Concretely, none of this leaks from one request to the next:

What does persist, exactly as it would under Octane:

Concurrency#

Each application resolves its own database and Redis connections through its own container — a query one request makes while another is mid-transaction never lands inside that transaction (the adapter's own test suite checks this with overlapping requests, one of which rolls back). Give your database enough connections for the pool, not just for --workers: with ext-phasync loaded, a worker can have as many applications open at once as it has requests waiting, up to maxApplications (128 by default — the fourth constructor argument of Handler).

Two things worth knowing before they surprise you in production:

SQLite#

Once requests overlap, Laravel's default SQLite session and cache writes start timing out under load unless you turn on WAL mode and a busy timeout:

// config/database.php
'sqlite' => [
    'driver' => 'sqlite',
    'database' => database_path('database.sqlite'),
    'journal_mode' => 'wal',
    'busy_timeout' => 5000,
],

Gotchas#

Your application#

Requests overlap in a worker, each in an application of its own from the pool, so container bindings are per request. What the pool does not separate is the process: static properties, globals and anything your code caches outside the container are shared by every application in the worker. The framework running correctly on Swerve does not make your own code safe: see Making your application safe for concurrent requests for the checklist and a fix for each item.

Debugging#

Compatibility#

Laravel PHP ext-phasync
12 8.2 – 8.5 optional; tested with and without
13 8.3 – 8.5 optional; tested with and without

See also#

  • PoolA pool of interchangeable resources, of which only so many may exist at once.
  • Swerve::logThe log of this process: swerve's own PSR-3 logger, in its format (the time, this worker's slot).