[RFC] Resources defined outside of PHP code (runtime / external metadata sources)

Aperta
#8,791 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 2 giorni

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
30/100
Tipo di issue
Funzionalità
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
php, symfony

Direzione di ricerca

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.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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", properties question, 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.

What breaks

Once a resource is identified by a name that is not a class (DynamicFaq):

Where Code Effect
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:

  1. Generate one PHP class per resource at runtime: code generation in production, opcache/container
    rebuilds, multi-server synchronisation.
  2. Map all resources onto one shared class: getObjectClass() always returns that class, so the resource
    of a nested object cannot be determined.
  3. Decorate the normalizers, IriConverter and ResourceClassResolver: 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

  1. Is a resource identity that is not a class name acceptable for the project?
  2. ResourceAwareInterface only, or also a generic DynamicResource shipped by the core?
  3. Resource name vs. shortName: reuse shortName as identity, or introduce a new key?
  4. 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.

Lingua principale
PHP
Stelle
2.6k
Fork
988
Merge medio
4g 10h
PR unite (30g)
92

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.