[Laravel] Metadata cached forever from a missing table makes a resource permanently 500
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 62/100
Direzione di ricerca
Start with the three cache decorators in src/Laravel/Metadata/ and compare their behavior with the guard in src/Laravel/Eloquent/Metadata/ModelMetadata.php. Reproduce the missing-table and table-recreated sequence in a non-debug Laravel setup, including a fresh worker. Done means degraded metadata is not persisted and a later worker resolves the resource correctly after the table returns.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
API Platform version(s) affected: 4.4 (verified at 808c67198; the same code is on main)
Laravel, app.debug=false (production)
Description
If a Laravel worker boots while a resource's table is missing, API Platform caches the degraded metadata with rememberForever in a file-backed store. Every later request to that resource then returns 500 on the collection and 404 on the item. The state does not heal when the table comes back, it does not heal when the workers restart, and php artisan cache:clear very likely does not clear it.
The realistic trigger is a routine php artisan migrate during a deploy, or a container that starts before its migration job finishes.
Why it happens
ModelMetadata::getAttributes() already guards against this. It refuses to cache an empty result when the table is missing (src/Laravel/Eloquent/Metadata/ModelMetadata.php:106-110):
// Don't cache an empty result for a missing table: the table may be created later
if ([] === $result && !$schema->hasTable($table)) {
return $result;
}
The three decorators above it have no such guard. They cache whatever the inner factory returned, including the degraded value that ModelMetadata deliberately refused to keep:
src/Laravel/Metadata/CachePropertyNameCollectionMetadataFactory.php:37src/Laravel/Metadata/CachePropertyMetadataFactory.php:37src/Laravel/Metadata/CacheResourceCollectionMetadataFactory.php:35
return $this->localCache[$key] ??= Cache::store($this->cacheStore)->rememberForever($key, function () use ($resourceClass, $options) {
return $this->decorated->create($resourceClass, $options);
});
A missing table is silent: Schema::getColumns() on a dropped table returns [] and throws nothing, so nothing signals that the result is degraded.
Route registration reads this metadata at boot, not per request (src/Laravel/routes/api.php:36-38), so the poisoning happens on the first boot after the table goes missing, with no request needed.
Why it does not heal
The store is chosen by name and is file-backed in production (src/Laravel/ApiPlatformProvider.php:376 and :399):
true === $config->get('app.debug') ? 'array' : $config->get('api-platform.cache', 'file')
rememberForever on the file store writes to disk, so the value outlives the process. A new worker reads the same poisoned entry.
php artisan cache:clear takes an optional store argument. Without it, it flushes config('cache.default') only. API Platform does not use the default store, it uses api-platform.cache. If an application sets cache.default to redis or database and leaves api-platform.cache at file, the standard remediation does not touch the poisoned entry. Nothing aligns or documents those two settings.
Symptom
Once poisoned, against a resource whose table was missing at boot:
GET /api/<resources>returns 500, withUnable to generate an IRI for the item of type "..."raised fromsrc/Laravel/Routing/IriConverter.php:190.GET /api/<resources>/{id}returns 404.
How to reproduce
- Run a Laravel application with
app.debug=false, so the file store is selected. - Drop the table of an Eloquent-backed API resource.
- Boot a worker, for example by sending any request. Route registration resolves and caches the metadata.
- Recreate the table.
- Request that resource again, in a new process if you like. It still fails.
A direct call to ModelMetadata::getAttributes() recovers the correct attributes at step 5, which shows the inner guard works and the outer cache overrides it.
Clearing the decorators one at a time inside a running process does not recover the endpoint, because the routes were already built from the degraded metadata and Laravel's router is not rebuilt mid-process. Only clearing the correct store and starting a new process recovers it.
Suggested fix
Give the three decorators the same guard ModelMetadata already has: detect a degraded result and return it without caching, instead of protecting only the innermost layer.
This does not make the failure disappear. The boot that happens while the table is missing still registers wrong routes. It does make the failure transient: the next worker start after the migration finishes resolves the metadata correctly. That is the difference between a deploy hiccup and an outage that persists until somebody finds the right store to clear.
A finite TTL instead of rememberForever would also bound the damage, and is smaller to implement, but it self-heals only after the interval and changes cache-hit behavior.
Notes
- This is Laravel-specific in practice. The Symfony bridge caches without a TTL too (
src/Metadata/Property/Factory/CachedPropertyNameCollectionFactory.phpand siblings), but Symfony property discovery comes from Doctrine mapping, attributes and reflection, which do not race against migrations the way Eloquent's runtime schema introspection does. - Not verified: whether
php artisan route:cache, common in production, makes recovery harder still by keeping the bad routes across restarts untilroute:clearruns.
- Lingua principale
- PHP
- Stelle
- 2.6k
- Fork
- 984
- Merge medio
- 1g 17h
- PR unite (30g)
- 84
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di api-platform/core
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
api-platform/core#8475 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 38/100
api-platform/core#8591 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 48/100
api-platform/core#8535 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 58/100
api-platform/core#8499 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 48/100
api-platform/core#8494 ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di api-platform/core
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 2 giorni
-
UX
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
ProfessionalWiki/NeoWiki#1573 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
endoflife-date/endoflife.date#11194 ·
I maintainer di solito rispondono entro 1 giorno