[RFC] Resources defined outside of PHP code (runtime / external metadata sources)
维护者通常 2 天内回复
评估
- 难度
- 5/5
- 预计耗时
- 一周以上
- 新手友好度
- 30/100
- Issue 类型
- 功能
- 描述清晰度
- 基本清楚
- 活跃度
- 活跃
调研方向
Start by reading AbstractItemNormalizer, IriConverter, ResourceClassResolver, and the cached metadata factory implementations named in the issue. The proposal has unresolved design questions about resource identity, instantiation, and cache invalidation, so first agree on the direction with maintainers. Done means implementing the agreed runtime resource support and covering resource discovery, serialization, instantiation, and IRI behavior with tests.
由索引模型根据 Issue 内容生成。
描述
Summary
Allow API resources declared from a source other than PHP code (a database, another API, a configuration
service) to work without a PHP class per resource. Discovery and metadata can already be provided by
decorating the metadata factories; what is missing are a few runtime hooks, because the serializer, the IRI
converter and the resource class resolver derive the resource from $object::class.
Use case
Administrators define content models at runtime (fields, types, relations) without deployment or migration.
We want each model to be a real API resource:
GET /dynamic/faq/{id}→"@type": "DynamicFaq", propertiesquestion,answer,theme…- per-model JSON Schema / OpenAPI / Hydra documentation
- per-model validation derived from field definitions
- IRIs generated and resolved like any other resource (
getIriFromResource,getResourceFromIri) - a new or modified model available without a deployment
How the data is persisted is up to the application (in our case a generic Doctrine entity with a JSON
column), exactly as with DTOs and stateOptions today.
Similar needs: headless CMS builders, form builders, multi-tenant schemas, schemaless document stores,
resources proxied from a remote API whose schema is discovered at runtime.
What already works
Decorating ResourceNameCollectionFactoryInterface, ResourceMetadataCollectionFactoryInterface (below the
cache) and the property factories lets an application declare resources, operations and properties from any
source. The Laravel Eloquent integration does it for properties. Types come from ApiProperty::nativeType /
schema, so no reflection is needed. Operations, routes and documentation can be generated this way,
provided a few metadata factories stop assuming the resource is a class (see below): with the resource
metadata declared at the innermost level (so that the core factories still apply their defaults), route
loading currently fails with ReflectionException: Class "DynamicFaq" does not exist.
What breaks
Once a resource is identified by a name that is not a class (DynamicFaq):
| Where | Code | Effect |
|---|---|---|
ParameterResourceMetadataCollectionFactory::createParametersFromAttributes() |
new \ReflectionClass($resourceClass) |
ReflectionException while loading routes |
FiltersResourceMetadataCollectionFactory |
new \ReflectionClass($resourceClass) |
ResourceClassNotFoundException |
| Doctrine ORM/ODM metadata factories (resource collection, parameters, links, properties, discriminator) | ManagerRegistry::getManagerForClass() |
ReflectionException from Doctrine on an unknown class |
AbstractItemNormalizer::instantiateObject() |
new \ReflectionClass($class) |
ReflectionException on every write operation |
AbstractItemNormalizer::normalize() |
$context['force_resource_class'] ?? $this->getObjectClass($data) |
resource derived from the PHP class of the object |
IriConverter::getIriFromResource() |
same | wrong or missing IRI for nested relations, cache purge, Mercure |
ResourceClassResolver::getResourceClass() / isResourceClass() |
getObjectClass() + is_a() |
an object cannot belong to a resource that is not a class |
CachedResourceNameCollectionFactory, Cached*MetadataFactory |
no invalidation | a new resource is only visible after a cache clear |
Workarounds without core changes:
- Generate one PHP class per resource at runtime: code generation in production, opcache/container
rebuilds, multi-server synchronisation. - Map all resources onto one shared class:
getObjectClass()always returns that class, so the resource
of a nested object cannot be determined. - Decorate the normalizers,
IriConverterandResourceClassResolver: re-implementing part of the core
outside the project, fragile across upgrades.
Proposal
1. Objects carrying their resource
interface ResourceAwareInterface
{
public function getResource(): string;
}
When an object implements it, AbstractItemNormalizer, IriConverter and ResourceClassResolver use
getResource() instead of $object::class. Nested relations and getIriFromResource() outside the
operation context then work as for any resource.
Optionally, the core could ship a generic implementation for applications that do not want their own:
final class DynamicResource implements ResourceAwareInterface, \ArrayAccess, \IteratorAggregate
{
public function __construct(private readonly string $resource, private array $data = []) {}
public function getResource(): string { return $this->resource; }
}
2. Instantiation hook for name-identified resources
When the resource is not a class, the serializer delegates instantiation (and property read/write) instead
of calling new \ReflectionClass(): by default to DynamicResource, or to a class declared in the
resource metadata. The processor receives that object and maps it to the domain model, as DTOs with
stateOptions do today.
3. Metadata cache invalidation (optional)
Cache tags on the metadata pools + MetadataCacheInvalidatorInterface::invalidate(string $resource), so a
resource added or modified at runtime is picked up without a cache clear. Routing can rely on a catch-all
route or a router cache fingerprint, left to the application at first. GraphQL is out of scope.
Backward compatibility
No BC break: everything is opt-in and introduced through new interfaces and classes. Existing interfaces are
not modified, and objects that do not implement ResourceAwareInterface behave exactly as today. It can ship
in a 5.x minor, flagged experimental.
Known limitation: many extension points document string $resourceClass as class-string. Third-party code
assuming so (new \ReflectionClass($resourceClass), class_exists(), getRepository()) keeps working for
class-based resources but will not support name-identified resources. This should be documented for
extension authors.
Open questions
- Is a resource identity that is not a class name acceptable for the project?
ResourceAwareInterfaceonly, or also a genericDynamicResourceshipped by the core?- Resource name vs.
shortName: reuseshortNameas identity, or introduce a new key? - Is metadata cache invalidation in scope, or left to applications?
Contribution
We can implement the ResourceAwareInterface support first, then the instantiation hook, after agreement on
the direction. We can also contribute documentation on providing resources by decorating the metadata
factories.
- 主要语言
- PHP
- 星标
- 2.6k
- 派生
- 988
- 平均合并
- 4 天 10 小时
- 30 天内合并 PR
- 114
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
api-platform/core 的其他 Issue
-
API Platform ignores array shapes in the JSON Schema可能已有人在做 @afraca 于 11 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 78/100
api-platform/core#8766 ·
维护者通常 2 天内回复
-
难度 2/5 1-3 小时 新手友好度 72/100
api-platform/core#8744 ·
维护者通常 2 天内回复
-
JSON:API: resource schemas (`*.jsonapi`) have `properties` but no `type: object`可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭
难度 2/5 1 小时以内 新手友好度 80/100
api-platform/core#8701 ·
维护者通常 2 天内回复
-
难度 4/5 3-5 天 新手友好度 48/100
api-platform/core#8819 ·
维护者通常 2 天内回复
-
难度 4/5 3-5 天 新手友好度 55/100
api-platform/core#8816 ·
维护者通常 2 天内回复
查看 api-platform/core 的全部 Issue
相似的 Issue
-
Administration: required plugin configuration field stays invalid after restoring its saved value未关闭needs-triage Platform(Default)
难度 2/5 1-3 小时 新手友好度 74/100
维护者通常 1 天内回复
-
good first issue priority:low type:docs
难度 1/5 1-3 小时 新手友好度 95/100
crazy-goat/rabbit-stream#969 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 68/100
opensourcepos/opensourcepos#4801 ·
维护者通常 1 天内回复
-
backend docs
难度 2/5 1-3 小时 新手友好度 67/100
Open-Locker/Open-Locker#368 ·
维护者通常 1 天内回复
-
enhancement priority Sales
难度 2/5 1-3 小时 新手友好度 78/100
novalo-dev/Prestashop-MCP-Server#190 ·
维护者通常 1 天内回复