After a volunteer-scheduling Laravel app's first deploy ran php artisan optimize, why do hot-fixed routes, config edits and new listeners have no effect?
answer
- cached files win while they exist
- route files are not executed
- config/*.php and .env are skipped
- listener discovery replaced by a manifest
- rebuild with optimize on every release
basics
~20 sThe config, route and event caches from the earlier optimize are still in bootstrap/cache, and Laravel uses them instead of reading config files, route files or listener directories. Hot-fixed changes stay invisible until php artisan optimize runs again.
solid answer
~40 sOnce `php artisan optimize` has run, Laravel boots from snapshots: `bootstrap/cache/config.php` replaces every `config/*.php` file and the `.env` load, `bootstrap/cache/routes-v7.php` replaces executing `routes/web.php`, and `bootstrap/cache/events.php` replaces scanning `app/Listeners`. None of them checks whether the source changed. So a route added on the server, a tweaked `config/services.php` or a new `SendShiftReminder` listener class exist on disk but are never read. The fix is procedural: every change reaches production through a deploy that re-runs `php artisan optimize` (each `:cache` step clears its old file first), and nobody edits code or `.env` on the server by hand. Views are the exception, because Blade recompiles a template newer than its compiled copy.
go deeper
Recall that after optimize, new routes, config edits and new listeners are invisible until the caches are rebuilt with php artisan optimize.
Explain which cache hides which change, that the three caches have no freshness check, and why views recompile on their own.
Diagnose stale caches from symptoms, fix them without flushing the cache store, and remove the process gap that let an untracked change reach the server.
Treat caches as release artifacts and push the team toward immutable deploys, where any code or environment change produces a rebuilt, consistent set of caches.
## The scenario A volunteer-scheduling app goes live. The deploy ran `php artisan optimize`. The next day the team makes three quick changes directly on the production server: 1. adds `Route::get('/shifts/open', ...)` to `routes/web.php`; 2. changes the reminder lead time in `config/scheduling.php`, and a mail setting in `.env`; 3. adds `app/Listeners/SendShiftReminder.php`, whose `handle(ShiftAssigned $event)` should email the volunteer. The route returns 404, the old lead time and mail setting are still used, and no reminder is sent. Nothing is "broken" in the code. The application is doing exactly what the caches tell it to. ## Why each change is invisible All three caches follow the same rule: **if the cache file exists, the framework loads it and does not look at the source**. | Change | Cache that hides it | What the framework does instead | |---|---|---| | New route | `bootstrap/cache/routes-v7.php` | loads the compiled route collection; the route files are never executed | | Config or `.env` edit | `bootstrap/cache/config.php` | loads the array snapshot; skips every `config/*.php` file and skips loading `.env` | | New listener | `bootstrap/cache/events.php` | reads the cached event-to-listener map; skips discovery in `app/Listeners` | The mechanics are visible in the source: - `LoadConfiguration` requires the cached config file when it exists and only falls back to reading the `config/` directory when it does not. `LoadEnvironmentVariables` returns early when `configurationIsCached()` is true, so `.env` is not even parsed. - The routing provider calls `loadCachedRoutes()` when `routesAreCached()` is true and `loadRoutes()` only otherwise. - `EventServiceProvider::getEvents()` returns the cached map when `eventsAreCached()` is true; discovery and the provider's `$listen` array are consulted only without a cache. Listeners registered in code with `Event::listen()` inside a provider's `boot()` still register, because that code runs on every request. There is **no freshness check** on any of these three. Blade views are different: the compiler compares modification times and recompiles an edited template, so views are not part of this failure. ## Why there is no freshness check It is tempting to ask why Laravel does not simply notice that `routes/web.php` is newer than the cache. Checking freshness would mean looking at the modification time of every config file, every route file and every listener directory on every request, which is the very filesystem work the caches exist to avoid. So the design is blunt on purpose: **the existence of the cache file is the switch**. Blade can afford a check because views compile lazily, one template at a time, and a single timestamp comparison per rendered view is cheap. Config, routes and events are loaded in bulk at boot, where a per-file check would cost as much as not caching at all. ## The fix The immediate fix is to rebuild the caches: ```bash php artisan optimize ``` Each `:cache` command calls its own `:clear` first, so there is no need to clear anything beforehand. Avoid reaching for `php artisan optimize:clear` on production as a reflex: it also runs `cache:clear`, which empties the default cache store. If the web server runs PHP-FPM with OPcache timestamp validation turned off, the new cache files may also need an OPcache reset to be picked up; that is a PHP-level concern rather than a Laravel cache. ## The real fix is the process The caches are **build artifacts of a release**. They are only correct for the exact code and environment they were built from. That leads to a few rules a team can adopt: - **No hand edits on the server.** Every change, including `.env`, goes out through the deploy, and the deploy always ends with `php artisan optimize`. - **Rebuild after any environment change.** Changing an environment variable in a hosting panel does nothing to a cached config until the caches are rebuilt, so an environment change should trigger a redeploy. - **Know the symptoms.** A 404 for a route that `routes/web.php` clearly contains, a config value that "will not change", or a listener that never runs are the three classic signs of a stale cache. - **Keep development uncached.** Running `optimize` locally reproduces the same confusion on a laptop; if someone did, `php artisan optimize:clear` restores normal behaviour. ## Diagnosing quickly - `ls bootstrap/cache` shows which cache files exist and their timestamps. - Compare the file's modification time with the last deploy or the last edit. - Rebuild, retest, and then fix whatever process allowed an untracked change onto the server.
- Why does a listener registered with Event::listen() in AppServiceProvider::boot() still work when the event cache is stale?The event cache only replaces discovery and the provider's `$listen` array: `getEvents()` returns the cached map instead of scanning directories. Code in a provider's `boot()` method runs on every request whether or not caches exist, so an `Event::listen()` call there registers normally. Only discovered listeners depend on a fresh `event:cache`.
- Should the deploy run php artisan optimize:clear before php artisan optimize to avoid stale caches?No. `config:cache`, `route:cache`, `event:cache` and `view:cache` each call their own `:clear` before writing, so `optimize` alone replaces every stale file. Adding `optimize:clear` also runs `cache:clear`, flushing the default cache store on every deploy for no benefit.
- The team changes an environment variable in the hosting panel without deploying; when does the app see it?Not while `bootstrap/cache/config.php` exists, because cached config is a snapshot of values taken when `config:cache` ran and `.env` is not loaded. The value appears only after the caches are rebuilt, so an environment change should go out as a redeploy that re-runs `php artisan optimize`.
A printed staff rota pinned to the volunteer centre's wall: editing the spreadsheet at home changes nothing until someone prints and pins a fresh copy, and everyone keeps following the old one.
saying these in an interview costs you the question
- Laravel notices changed route files and rebuilds the route cache itself
- Cached config still re-reads .env on every request
- A stale view cache is the usual cause of a missing route
- Always run optimize:clear before optimize in a deploy
- Editing files on the production server is fine if you clear caches later