[Laravel] Metadata cached forever from a missing table makes a resource permanently 500
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 62/100
调研方向
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 内容生成。
描述
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.
- 主要语言
- PHP
- 星标
- 2.6k
- 派生
- 989
- 平均合并
- 1 天 23 小时
- 30 天内合并 PR
- 102
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
api-platform/core 的其他 Issue
-
JSON:API: resource schemas (`*.jsonapi`) have `properties` but no `type: object`可能已有人在做 @ousamabenyounes 于 2 天前认领。 未关闭
难度 2/5 1 小时以内 新手友好度 80/100
api-platform/core#8701 ·
维护者通常 1 天内回复
-
JSON:API: `JsonApiCollectionBaseSchemaNoPagination` declares `data` as `type: array` without `items`未关闭
难度 2/5 1-3 小时 新手友好度 82/100
api-platform/core#8699 ·
维护者通常 1 天内回复
-
api:openapi:export --spec-version=3.0.0 outputs `webhooks`, which is invalid in OpenAPI 3.0可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭
难度 2/5 1-3 小时 新手友好度 72/100
api-platform/core#8690 ·
维护者通常 1 天内回复
-
难度 1/5 1 小时以内 新手友好度 85/100
api-platform/core#8680 ·
维护者通常 1 天内回复
-
难度 3/5 1-2 天 新手友好度 68/100
api-platform/core#8725 ·
维护者通常 1 天内回复
查看 api-platform/core 的全部 Issue
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 65/100
维护者通常 1 天内回复
-
bug
难度 2/5 1-3 小时 新手友好度 84/100
nextcloud/integration_openai#446 ·
-
bug customer-reported
难度 2/5 1-3 小时 新手友好度 76/100
MagnaCapax/PMSS#1101 ·
维护者通常 5 天内回复
-
InvoiceSettled is answered 200 when the invoice cannot be re-fetched, so BTCPay never redelivers it未关闭
难度 2/5 1-3 小时 新手友好度 76/100
btcpayserver/woocommerce-greenfield-plugin#93 ·
维护者通常 1 天内回复
-
kind/bug status/triage
难度 2/5 1-3 小时 新手友好度 68/100