Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

[Laravel] Metadata cached forever from a missing table makes a resource permanently 500

Chiusa
#8,585 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

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
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
laravel, php
Ambito
api, backend, databases

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

Laravel

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:37
  • src/Laravel/Metadata/CachePropertyMetadataFactory.php:37
  • src/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, with Unable to generate an IRI for the item of type "..." raised from src/Laravel/Routing/IriConverter.php:190.
  • GET /api/<resources>/{id} returns 404.

How to reproduce

  1. Run a Laravel application with app.debug=false, so the file store is selected.
  2. Drop the table of an Eloquent-backed API resource.
  3. Boot a worker, for example by sending any request. Route registration resolves and caches the metadata.
  4. Recreate the table.
  5. 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.php and 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 until route:clear runs.
Lingua principale
PHP
Stelle
2.6k
Fork
984
Merge medio
1g 17h
PR unite (30g)
84

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di api-platform/core

Tutte le issue di api-platform/core

Issue simili

Altre issue su PHP

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.