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?
answer
- warm: resolved at worker boot
- defaultServicesToWarm list
- flush: forgotten after each request
- FlushTemporaryContainerInstances listener
- warmed means shared across requests
basics
~20 swarm 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
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
Recall that warm resolves services once when a worker boots and flush forgets bindings after each request.
Explain the base-app versus per-request clone model behind the two lists, the default warm services, and the FlushTemporaryContainerInstances listener.
Show you warm only stateless services, flush package singletons that hold request data, and verify with object IDs on a single worker.
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