skip to content

In Laravel Octane's config/octane.php, what do the warm and flush arrays do, and when would you add your own binding to either?

level: middleimportance: nice to knowfreq 20%

answer

  1. warm: resolved at worker boot
  2. defaultServicesToWarm list
  3. flush: forgotten after each request
  4. FlushTemporaryContainerInstances listener
  5. warmed means shared across requests

basics

~20 s

warm lists bindings Octane resolves once at worker boot, shared by later requests; flush lists bindings Octane forgets after every request, so they are rebuilt next time. Warm only stateless services; flush ones holding per-request state.

solid answer

~40 s

`'warm' => [...Octane::defaultServicesToWarm()]` makes Octane's application factory call `make()` on each listed binding at worker boot, so the first request does not pay to build them. The defaults are framework services such as `auth`, `cache`, `config`, `db`, `encrypter`, `router`, `session`, `translator`, `url` and `view`. Warming puts the instance into the base application, which every per-request clone shares, so only add services that hold no per-request state. `'flush' => []` lists bindings that Octane's `FlushTemporaryContainerInstances` listener calls `forgetInstance()` on after each request, task or tick, forcing a fresh resolve next time. The same listener also forgets scoped instances. Use `flush` for a third-party singleton that keeps request data and that you cannot rebind as scoped; warming such a service would turn a latent leak into a certain one.

code

php · 19 lines
php
<?php

use App\Support\CurrencyTable;
use Laravel\Octane\Octane;
use Vendor\Tracking\RequestTracker;

// config/octane.php (excerpt)
return [
    // Built once per worker, then shared: stateless only
    'warm' => [
        ...Octane::defaultServicesToWarm(),
        CurrencyTable::class,
    ],

    // Forgotten after every request, task and tick
    'flush' => [
        RequestTracker::class,
    ],
];

go deeper

for a junior

Recall that warm resolves services once when a worker boots and flush forgets bindings after each request.

for a middle

Explain the base-app versus per-request clone model behind the two lists, the default warm services, and the FlushTemporaryContainerInstances listener.

for a senior

Show you warm only stateless services, flush package singletons that hold request data, and verify with object IDs on a single worker.

for a principal

Weigh warm-up gains against leak risk and decide which team owns the Octane config for third-party packages.

## Two knobs over the base application **Laravel Octane** keeps one booted base application per worker and handles each request in a clone of it. Objects resolved in the base application are shared by every clone; objects first resolved during a request are discarded with the clone. The `warm` and `flush` arrays in `config/octane.php` let you move bindings deliberately between those two worlds. ## `warm`: resolve at boot, share afterwards ```php 'warm' => [ ...Octane::defaultServicesToWarm(), ], ``` When a worker boots, Octane's `ApplicationFactory::warm()` walks this list and calls `$app->make($service)` for every string entry that is bound. The instance ends up in the base application's instance list. `Octane::defaultServicesToWarm()` returns framework services: `auth`, `cache`, `cache.store`, `config`, `cookie`, `db`, `db.factory`, `db.transactions`, `encrypter`, `files`, `hash`, `log`, `router`, `routes`, `session`, `session.store`, `translator`, `url` and `view`. Octane knows how to reset their per-request state through its listeners (it flushes authentication guards, gives new application instances to managers, clones config, and so on). Why warm your own service: - its construction is expensive (parsing large files, building lookup tables); - it is **stateless** per request, or read-only after construction. Why not: - a warmed service is **shared by every request on the worker**; if it keeps any per-request data, warming turns a possible leak into a guaranteed one; - warming a service that holds `$app['request']` or config captures the boot-time versions. ## `flush`: forget after every operation ```php 'flush' => [ \Vendor\Package\RequestContext::class, ], ``` Octane registers `FlushTemporaryContainerInstances` for the `OperationTerminated` event, which fires after every request, task and tick. That listener: 1. calls `forgetScopedInstances()` on the application, dropping instances of bindings registered with `scoped()`; 2. for each entry in `octane.flush`, calls `forgetInstance($binding)` on the base application. The next time something resolves that binding, the container builds a new instance. That is exactly what you want for a singleton that stores per-request state and that you cannot change - typically a package service. ## Choosing between them | Situation | Use | |---|---| | Expensive, stateless service used on most requests | `warm` | | Your own service that stores per-request data | redesign it, or bind it with `scoped()` | | A package singleton that stores per-request data | `flush` | | A framework service already in the default warm list | leave it; Octane resets it | `scoped()` and `flush` reach the same outcome for a binding - a fresh instance per operation - but `scoped()` is declared at the binding, next to the code, and also works for queue workers, while `flush` is Octane configuration for bindings you do not own. (The binding methods themselves are a container topic.) ## An example of each - **Warm**: an exchange-rate lookup table loaded from a large bundled file; read-only after construction, used on most requests. Warming moves the load from the first request on each worker to worker start-up. - **Flush**: a package's request-tracking singleton that stores the current route and user in properties and offers no reset method. Listing it in `flush` makes each request build a new tracker. - **Neither**: your own service that stores per-request state. Change the service, or declare its binding with `scoped()`, so the lifetime is visible in code rather than hidden in Octane configuration. ## Pitfalls - **Assuming `flush` resets statics.** It only removes a container instance; static properties on the class are untouched. - **Flushing something that was captured elsewhere.** If another shared object received the instance in its constructor, forgetting it in the container does not change that object's reference. - **Warming to "fix" slow first requests** without checking the service for state. - **Entries that are not bound.** `warm()` silently skips strings the container does not know, so a typo gives no error. ## How to verify Log `spl_object_id(app(Service::class))` in two consecutive requests on a single worker (`--workers=1`): the same ID means shared, different IDs mean per-request. Run it before and after adding a binding to either array.

  • Does adding a class to octane.flush reset its static properties?
    No. The flush list only makes Octane call `forgetInstance()` for that binding, so the container builds a new object next time. Static properties belong to the class, not the instance, and survive; reset those in a `RequestReceived` listener or remove them.
  • What happens to an octane.warm entry that is not bound in the container?
    Octane's `warm()` checks `$app->bound($service)` and skips anything unbound without an error. A misspelled class name or a binding registered in a provider that does not load is silently ignored, so verify by checking the object ID across requests.

saying these in an interview costs you the question

  • Warming a service makes it safe because it is rebuilt per request
  • octane.flush clears a class's static properties after each request
  • Octane throws when a warm entry is not bound in the container
  • flush entries are forgotten only when a worker restarts
  • Every expensive service should be added to warm for speed