Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

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

クローズ
#8,585 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
62/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
laravel, php
領域
api, backend, databases

調査の方向性

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.

索引モデルが issue の本文から書いたものです。

説明

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.
主要言語
PHP
スター
2.6k
フォーク
987
平均マージ
1日 8時間
マージ済み PR(30日)
80

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

api-platform/core のほかの issue

api-platform/core の issue をすべて見る

似ている issue

PHP の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。