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

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

Menu
----

Close 

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

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

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

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

 Running Laravel under FrankenPHP workers means your singletons live across requests. Learn exactly which bindings leak state, how to scope them correctly, and patterns that keep your app safe at scale.

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

ShareCopy linkCopied

 ![Laravel Octane + FrankenPHP: Shared State, Request Isolation, and Safe Singleton Patterns](https://cdn.msaied.com/349/e8f1b783afac1995d090c041f705d8c5.png) 

  On this page +1. [The Problem With Long-Lived Workers](#the-problem-with-long-lived-workers)
2. [Scoped Bindings: The Right Tool](#scoped-bindings-the-right-tool)
3. [When scoped() Is Not Enough](#when-codescopedcode-is-not-enough)
4. [Flush Hooks for Third-Party Singletons](#flush-hooks-for-third-party-singletons)
5. [Detecting Leaks Before Production](#detecting-leaks-before-production)
6. [Safe Patterns at a Glance](#safe-patterns-at-a-glance)
7. [Takeaways](#takeaways)

 The Problem With Long-Lived Workers
-----------------------------------

Traditional PHP-FPM boots the framework on every request and discards everything afterward. Octane — whether backed by Swoole, RoadRunner, or FrankenPHP — keeps a single worker process alive across thousands of requests. That is the source of its speed advantage, and the source of its most subtle bugs.

When you bind a class as a singleton in a service provider, that instance persists for the lifetime of the worker. Any mutable state it holds bleeds from one request into the next.

```php
// AppServiceProvider.php — dangerous under Octane
$this->app->singleton(CartService::class, function () {
    return new CartService(); // holds $this->items = []
});

```

Request A adds items to the cart. Request B — from a completely different user — resolves the same `CartService` instance and sees Request A's items. This is a classic Octane state-leak.

Scoped Bindings: The Right Tool
-------------------------------

Laravel ships a first-class solution: `scoped()`. A scoped binding behaves like a singleton within a single request lifecycle, then is flushed automatically by Octane between requests.

```php
// Correct: scoped to one request
$this->app->scoped(CartService::class, function () {
    return new CartService();
});

```

Octane calls `$app->forgetScopedInstances()` at the end of every request, so the next request gets a fresh instance. No manual cleanup required.

### When `scoped()` Is Not Enough

Some objects are legitimately shared across requests — database connection pools, compiled route collections, config repositories. The rule of thumb:

- **Stateless or immutable** → `singleton()` is fine.
- **Mutable, request-specific** → use `scoped()`.
- **Mutable, shared intentionally** → use `singleton()` but make mutation thread-safe (Swoole) or accept that FrankenPHP workers are isolated processes.

FrankenPHP's worker mode uses separate PHP fibers per request within the same process, so true shared memory between concurrent requests is not a concern the way it is with Swoole coroutines. However, sequential requests in the same worker still share the same singleton instances.

Flush Hooks for Third-Party Singletons
--------------------------------------

You do not always control whether a package registers a singleton or a scoped binding. Octane provides flush hooks to reset specific instances between requests.

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

```

For more granular control, listen to the `RequestReceived` and `RequestTerminated` Octane events:

```php
use Laravel\Octane\Events\RequestReceived;
use Laravel\Octane\Events\RequestTerminated;

Event::listen(RequestReceived::class, function ($event) {
    $event->sandbox->forgetInstance(ReportBuilder::class);
});

```

`$event->sandbox` is the per-request application clone Octane creates — mutating it does not affect the master container.

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

Octane ships with a `--watch` flag for development, but it will not surface state leaks automatically. A practical detection pattern is to inject a unique request ID into any suspicious singleton and assert it changes between requests in a test:

```php
it('creates a fresh CartService per request', function () {
    $first = $this->get('/cart')->json('session_id');
    $second = $this->get('/cart')->json('session_id');

    expect($first)->not->toBe($second);
});

```

For deeper inspection, Telescope's request watcher combined with a custom `Octane:RequestTerminated` listener that dumps resolved instances is invaluable during staging.

Safe Patterns at a Glance
-------------------------

```php
// 1. Immutable value object — singleton is fine
$this->app->singleton(CurrencyFormatter::class);

// 2. Request-scoped accumulator — use scoped
$this->app->scoped(AuditLog::class);

// 3. External client with connection pool — singleton, but reset on error
$this->app->singleton(RedisClient::class, fn() => new RedisClient(config('redis')));

```

Takeaways
---------

- Use `scoped()` for any service that accumulates request-specific state; Octane flushes it automatically.
- `singleton()` is safe only for stateless or intentionally shared objects.
- FrankenPHP workers are isolated processes, not coroutines — concurrent request bleed is sequential, not parallel.
- Octane's `flush` config key and `RequestReceived` event are your escape hatches for third-party singletons.
- Write a simple request-ID test to catch leaks before they reach production.

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

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

  What is the difference between singleton() and scoped() in Laravel Octane?A singleton() persists for the entire worker lifetime across all requests. A scoped() binding behaves like a singleton within a single request and is automatically flushed by Octane between requests, preventing state from leaking to the next user.

   Does FrankenPHP have the same concurrency risks as Swoole coroutines?No. FrankenPHP uses separate PHP fibers per request within a worker process, so concurrent requests do not share memory the way Swoole coroutines can. However, sequential requests in the same worker still share singleton instances, so scoped bindings are still essential.

   How do I reset a third-party package's singleton between Octane requests?Add the class to the flush array in config/octane.php, or listen to the Octane RequestReceived event and call $event-&gt;sandbox-&gt;forgetInstance(YourClass::class) to drop the instance before the next request resolves it.

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

[Mohamed Said](https://www.msaied.com/public#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/public#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 articleCommune: A Private Community for Laravel Founders and Builders](https://www.msaied.com/public/articles/commune-a-private-community-for-laravel-founders-and-builders) [Next articleMySQL Index Strategies for Laravel: Composite, Prefix, and Invisible Indexes](https://www.msaied.com/public/articles/mysql-index-strategies-for-laravel-composite-prefix-and-invisible-indexes)  

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

1. [The Problem With Long-Lived Workers](#the-problem-with-long-lived-workers)
2. [Scoped Bindings: The Right Tool](#scoped-bindings-the-right-tool)
3. [When scoped() Is Not Enough](#when-codescopedcode-is-not-enough)
4. [Flush Hooks for Third-Party Singletons](#flush-hooks-for-third-party-singletons)
5. [Detecting Leaks Before Production](#detecting-leaks-before-production)
6. [Safe Patterns at a Glance](#safe-patterns-at-a-glance)
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/public#contact) 

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

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

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

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

 [Discuss your project ↗](https://www.msaied.com/public#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/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/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/public/sitemap.xml)
