Laravel Octane FrankenPHP: Safe Singletons &amp; Shared State | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com) - [Home](https://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [About](https://www.msaied.com#about)

           [  Contact](https://www.msaied.com#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com)
- [ProjectsCase studies](https://www.msaied.com/projects)
- [ArticlesEngineering notes](https://www.msaied.com/articles)
- [CertificatesCredentials](https://www.msaied.com/certificates)
- [AboutHow I work](https://www.msaied.com#about)
- [ContactGet in touch](https://www.msaied.com#contact)

  [Start a conversation](https://www.msaied.com#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com)
2. /
3. [Articles](https://www.msaied.com/articles)
4. /
5. Laravel Octane + FrankenPHP: Request Lifecycle, Shared State, and Safe Singleton Patterns

 Laravel Octane + FrankenPHP: Request Lifecycle, Shared State, and Safe Singleton Patterns
==========================================================================================

 Running Laravel under FrankenPHP workers changes everything about object lifetime. Learn how to reason about shared state, safely register singletons, and avoid the subtle bugs that only appear under persistent-process runtimes.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com#person) Published 3 Oct 2026 · Updated 3 Oct 2026 · 3 min read

ShareCopy linkCopied

 ![Laravel Octane + FrankenPHP: Request Lifecycle, Shared State, and Safe Singleton Patterns](https://cdn.msaied.com/731/7cabd03b86a18f1db2c9ff7de1510270.png) 

  On this page +1. [Why FrankenPHP Changes the Rules](#why-frankenphp-changes-the-rules)
2. [The Request Lifecycle Under Octane](#the-request-lifecycle-under-octane)
3. [Declaring Flushable Bindings](#declaring-flushable-bindings)
4. [The Safe Singleton Pattern](#the-safe-singleton-pattern)
5. [Detecting Leaks Before Production](#detecting-leaks-before-production)
6. [Static Properties: The Hidden Trap](#static-properties-the-hidden-trap)
7. [Takeaways](#takeaways)

 Why FrankenPHP Changes the Rules
--------------------------------

Traditional PHP-FPM boots the framework on every request and discards everything when the script exits. Laravel Octane with FrankenPHP keeps workers alive across thousands of requests. The application container is bootstrapped once; only a subset of bindings is flushed between requests.

This is the source of the most insidious production bugs: a service that was safe under FPM silently accumulates state across requests under Octane.

The Request Lifecycle Under Octane
----------------------------------

Octane hooks into the framework via `Illuminate\Foundation\Application` and a custom HTTP kernel. Between requests it calls `$sandbox->flush()` on a cloned container — not the master container. The master container (and everything bound as a singleton in it) persists for the worker's lifetime.

```php
Worker boots → master app bootstrapped
  ↓
Request arrives → sandbox clone created
  ↓
Request handled → sandbox flushed
  ↓
Next request → new sandbox, same master

```

Anything registered in a service provider's `register()` method as a singleton lives in the master container unless you explicitly tell Octane to flush it.

Declaring Flushable Bindings
----------------------------

Octane reads a `flush` array from `config/octane.php`. Add your stateful services there:

```php
// config/octane.php
'flush' => [
    \App\Services\CartSession::class,
    \App\Services\TenantContext::class,
],

```

Octane calls `$app->forgetInstance($abstract)` for each entry after every request, forcing the container to rebuild the binding on the next resolve.

The Safe Singleton Pattern
--------------------------

For services that are expensive to construct (database connection wrappers, HTTP clients with connection pools) but must not carry per-request state, use an explicit reset contract:

```php
interface ResettableContract
{
    public function reset(): void;
}

class MetricsCollector implements ResettableContract
{
    private array $spans = [];

    public function record(string $name, float $duration): void
    {
        $this->spans[] = compact('name', 'duration');
    }

    public function flush(): array
    {
        $spans = $this->spans;
        $this->spans = [];
        return $spans;
    }

    public function reset(): void
    {
        $this->spans = [];
    }
}

```

Then register a listener in your service provider:

```php
public function boot(): void
{
    if (app()->bound(\Laravel\Octane\Contracts\OperationTerminated::class)) {
        app('events')->listen(
            \Laravel\Octane\Events\RequestTerminated::class,
            function () {
                app(MetricsCollector::class)->reset();
            }
        );
    }
}

```

This keeps the expensive singleton alive while guaranteeing its mutable state is wiped between requests.

Detecting Leaks Before Production
---------------------------------

Write a simple Pest test that simulates two sequential requests through the Octane sandbox:

```php
use Laravel\Octane\Testing\MakesOctaneRequests;

uses(MakesOctaneRequests::class);

it('does not leak cart state across requests', function () {
    $this->octaneRequest('POST', '/cart/add', ['sku' => 'ABC']);
    $response = $this->octaneRequest('GET', '/cart');

    // A fresh request must not see the previous request's cart
    $response->assertJsonCount(0, 'items');
});

```

The `MakesOctaneRequests` trait reuses the same worker container between calls, exposing exactly the leaks that FPM would hide.

Static Properties: The Hidden Trap
----------------------------------

Static class properties are never touched by the container flush. A common mistake:

```php
// DANGEROUS under Octane
class FeatureFlags
{
    private static array $cache = [];

    public static function get(string $flag): bool
    {
        if (!isset(self::$cache[$flag])) {
            self::$cache[$flag] = DB::table('flags')->where('name', $flag)->value('enabled');
        }
        return (bool) self::$cache[$flag];
    }
}

```

A flag toggled in the database will not be visible until the worker restarts. Replace static caches with instance-level caches on a flushable singleton, or use a TTL-based cache driver.

Takeaways
---------

- The master container persists for the worker's lifetime; only the sandbox clone is flushed between requests.
- Register stateful services in `octane.php flush` or listen to `RequestTerminated` to call a `reset()` method.
- Static properties are invisible to Octane's flush mechanism — audit every `static $cache` in your codebase.
- Use `MakesOctaneRequests` in Pest to catch leaks in CI before they reach production.
- Expensive-to-construct singletons (HTTP clients, metric collectors) should be kept alive but implement a clean reset contract.

- [laravel](https://www.msaied.com/articles?search=laravel)
- [octane](https://www.msaied.com/articles?search=octane)
- [frankenphp](https://www.msaied.com/articles?search=frankenphp)
- [performance](https://www.msaied.com/articles?search=performance)
- [architecture](https://www.msaied.com/articles?search=architecture)

 Frequently asked questions 
---------------------------

  Does adding a class to the Octane `flush` array hurt performance?Slightly, because the container must reconstruct the binding on the next request. For cheap services this is negligible. For expensive ones (e.g. HTTP clients), implement a `reset()` method and listen to `RequestTerminated` instead of flushing the binding entirely.

   Are Eloquent models safe to use as singletons under Octane?No. Eloquent models carry per-request state (loaded relations, dirty attributes). Never bind a model instance as a singleton. Bind repositories or query objects instead, and let them create fresh model instances per query.

   How do I know which of my services are currently registered as singletons?Run `php artisan octane:status` (if available) or dump `app()-&gt;getBindings()` in a local Tinker session after booting. Look for entries where `shared` is `true` — those are your singletons and the first candidates to audit for mutable state.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://www.msaied.com#person)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

[About](https://www.msaied.com#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleEloquent Custom Relations: Polymorphic Pivots, Has-One-Of-Many, and Raw Join Relations](https://www.msaied.com/articles/eloquent-custom-relations-polymorphic-pivots-has-one-of-many-and-raw-join-relations)  

   On this page
-------------

1. [Why FrankenPHP Changes the Rules](#why-frankenphp-changes-the-rules)
2. [The Request Lifecycle Under Octane](#the-request-lifecycle-under-octane)
3. [Declaring Flushable Bindings](#declaring-flushable-bindings)
4. [The Safe Singleton Pattern](#the-safe-singleton-pattern)
5. [Detecting Leaks Before Production](#detecting-leaks-before-production)
6. [Static Properties: The Hidden Trap](#static-properties-the-hidden-trap)
7. [Takeaways](#takeaways)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/728/b96439f5ef5f084b44d736951875f964.png)  · 3 min read### Eloquent Custom Relations: Polymorphic Pivots, Has-One-Of-Many, and Raw Join Relations

2 Oct 2026 ](https://www.msaied.com/articles/eloquent-custom-relations-polymorphic-pivots-has-one-of-many-and-raw-join-relations) [ ![](https://cdn.msaied.com/727/7873d7495db459e7a6e3bfbb8835852d.png)  · 3 min read### Laravel Caching Strategies: Tags, Stampede Prevention, and Cache-Aside at Scale

2 Oct 2026 ](https://www.msaied.com/articles/laravel-caching-strategies-tags-stampede-prevention-and-cache-aside-at-scale-2) [ ![](https://cdn.msaied.com/729/4eab9f8b1fbacb1b12abdbafcf28331f.png) Laravel · 3 min read### WhenMounted and BigInt Props in Inertia.js v3.8

2 Oct 2026 ](https://www.msaied.com/articles/whenmounted-and-bigint-props-in-inertiajs-v38) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/sitemap.xml)
