Octane + FrankenPHP: Safe Singletons in Laravel | 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: Persistent Services, Shared State, and Safe Singletons

 Laravel Octane + FrankenPHP: Persistent Services, Shared State, and Safe Singletons
====================================================================================

 Running Laravel under FrankenPHP workers means your service container lives across requests. Learn which singletons are safe, how to reset dirty state, and patterns for writing Octane-aware code without surprises.

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

ShareCopy linkCopied

 ![Laravel Octane + FrankenPHP: Persistent Services, Shared State, and Safe Singletons](https://cdn.msaied.com/612/2bd17daafd0c48a0aa824a6d744fb403.png) 

  On this page +1. [The Problem Nobody Talks About Until Production](#the-problem-nobody-talks-about-until-production)
2. [Why Singletons Become Dangerous](#why-singletons-become-dangerous)
3. [The Octane Flush Hook](#the-octane-flush-hook)
4. [Designing Octane-Safe Services From the Start](#designing-octane-safe-services-from-the-start)
5. [Prefer Immutable Value Objects](#prefer-immutable-value-objects)
6. [Inject Request-Scoped Data via Method Arguments](#inject-request-scoped-data-via-method-arguments)
7. [Use scoped() for Request-Lifetime Bindings](#use-codescopedcode-for-request-lifetime-bindings)
8. [Auditing Existing Code](#auditing-existing-code)
9. [Takeaways](#takeaways)

 The Problem Nobody Talks About Until Production
-----------------------------------------------

When you move a Laravel application from PHP-FPM to Octane (whether backed by Swoole, RoadRunner, or FrankenPHP), the container is **bootstrapped once** and then reused across every request handled by that worker. That single fact invalidates a large class of assumptions most Laravel code silently makes.

This article focuses on the practical patterns you need to write services that behave correctly under persistent workers — and how to audit existing code before you flip the switch.

---

Why Singletons Become Dangerous
-------------------------------

Under PHP-FPM every request gets a fresh process. A singleton bound in a service provider is instantiated once per request, so stale state is impossible — the process dies.

Under Octane a worker process handles thousands of requests. A singleton is instantiated **once for the lifetime of the worker**. Any mutable state it accumulates is visible to every subsequent request.

```php
// Dangerous under Octane
class CartService
{
    private array $items = [];

    public function add(int $productId): void
    {
        $this->items[] = $productId; // leaks across requests!
    }
}

```

Request 1 adds item 42. Request 2 from a completely different user now sees item 42 in `$this->items`.

---

The Octane Flush Hook
---------------------

Octane ships with a `RequestHandled` event and a dedicated `OctaneServiceProvider` hook for resetting state between requests.

```php
use Laravel\Octane\Facades\Octane;

class CartServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Octane::tick('flush-cart', function () {
            $this->app->forgetInstance(CartService::class);
        })->everySeconds(0); // runs between every request
    }
}

```

A cleaner approach is to listen to the `RequestHandled` event directly:

```php
use Laravel\Octane\Events\RequestHandled;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->app['events']->listen(RequestHandled::class, function () {
            $this->app->forgetInstance(CartService::class);
        });
    }
}

```

Now `CartService` is re-instantiated fresh on the next request, eliminating the leak.

---

Designing Octane-Safe Services From the Start
---------------------------------------------

### Prefer Immutable Value Objects

If a service only holds configuration (injected at construction) and never accumulates mutable state, it is safe as a singleton.

```php
final class CurrencyFormatter
{
    public function __construct(
        private readonly string $locale,
        private readonly string $currency,
    ) {}

    public function format(int $cents): string
    {
        return \NumberFormatter::create($this->locale, \NumberFormatter::CURRENCY)
            ->formatCurrency($cents / 100, $this->currency);
    }
}

```

No mutable fields — safe to keep alive for the worker's entire lifetime.

### Inject Request-Scoped Data via Method Arguments

Avoid storing per-request data on the service. Pass it as method arguments instead.

```php
// Bad: stores request context on the singleton
class AuditLogger
{
    private ?User $actor = null;

    public function setActor(User $user): void { $this->actor = $user; }
    public function log(string $event): void { /* uses $this->actor */ }
}

// Good: actor is a method parameter
class AuditLogger
{
    public function log(User $actor, string $event): void { /* stateless */ }
}

```

### Use `scoped()` for Request-Lifetime Bindings

Laravel's `scoped()` binding (introduced for Octane compatibility) registers a singleton that Octane automatically flushes between requests:

```php
$this->app->scoped(CartService::class, fn () => new CartService());

```

This is the idiomatic solution — prefer it over manual `forgetInstance` calls.

---

Auditing Existing Code
----------------------

Before enabling Octane on an existing app, grep for these patterns:

- `$this->app->singleton(...)` — check if the resolved class holds mutable instance state.
- Static properties on any class (`private static array $cache = []`) — these persist across requests **and** across workers sharing the same process.
- Facades that resolve to singletons with state (e.g., a custom `Auth` driver that caches the resolved user).

```bash
grep -rn 'static \$' app/
grep -rn 'singleton(' app/Providers/

```

For each hit, ask: *does this class accumulate state that must be per-request?* If yes, switch to `scoped()` or add a flush listener.

---

Takeaways
---------

- Under Octane/FrankenPHP, `singleton()` bindings live for the worker's lifetime — mutable state leaks across requests.
- Use `scoped()` for any service that holds per-request state; Octane flushes scoped bindings automatically.
- Immutable services (pure config, no mutable fields) are safe as true singletons and benefit from zero re-instantiation cost.
- Audit static properties carefully — they survive even `forgetInstance` calls.
- Listen to `RequestHandled` for custom teardown logic that `scoped()` cannot cover.

- [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?`singleton()` creates one instance for the entire worker process lifetime. `scoped()` also creates one instance per worker, but Octane automatically flushes and re-instantiates it between requests, making it safe for services that hold per-request state.

   Do static class properties get reset between Octane requests?No. Static properties persist for the entire worker process lifetime and are not affected by `forgetInstance` or `scoped()` bindings. You must reset them manually in a `RequestHandled` listener or redesign the code to avoid static mutable state.

   Is FrankenPHP's worker mode identical to Swoole for these purposes?The container lifecycle is the same — one bootstrap per worker, persistent across requests. The flush hooks and `scoped()` binding behavior work identically. The main differences are in the underlying HTTP server implementation, not in how Laravel manages service lifetimes.

   ![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 articleFilament v3 Custom Table Columns: Rendering Complex UI Without Hacks](https://www.msaied.com/public/articles/filament-v3-custom-table-columns-rendering-complex-ui-without-hacks) [Next articleLaravel 12: New Features, Helpers, and Practical Upgrade Notes](https://www.msaied.com/public/articles/laravel-12-new-features-helpers-and-practical-upgrade-notes-2)  

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

1. [The Problem Nobody Talks About Until Production](#the-problem-nobody-talks-about-until-production)
2. [Why Singletons Become Dangerous](#why-singletons-become-dangerous)
3. [The Octane Flush Hook](#the-octane-flush-hook)
4. [Designing Octane-Safe Services From the Start](#designing-octane-safe-services-from-the-start)
5. [Prefer Immutable Value Objects](#prefer-immutable-value-objects)
6. [Inject Request-Scoped Data via Method Arguments](#inject-request-scoped-data-via-method-arguments)
7. [Use scoped() for Request-Lifetime Bindings](#use-codescopedcode-for-request-lifetime-bindings)
8. [Auditing Existing Code](#auditing-existing-code)
9. [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)
