core: Non anemic doctrine models are not supported

I’d like to write my model this way:

class Mail
{
    private $from;
    private $to;
    private $content;
    public function __construct($from, $to, $content)
    {
        $this->from = $from;
        $this->to = $to;
        $this->content = $content;
    }
}

My model looks like this following DDD guidelines.

But with ApiPlatform having this model with a correct (standard) configuration will result in an error of deserialization because fields are not considered as “writable” by the DoctrineOrmPropertyMetadataFactory class. Which is wrong on a Serializer point of view… and even on a Doctrine point of view as this entity is perfectly valid and its fields are also perfectly writable.

To me, this is an issue but @soyuka seems to disagree after a short discussion on slack.

There are many solutions for this problem inside ApiPlatform:

  • ~patch the DoctrineOrmPropertyMetadataFactory~
  • patch the SerializerPropertyMetadataFactory (after all if I say they are writable, then they are)
  • probably many more as there’s also a “configuration” that may set the property writable by default (adding its documentation would be a great help)

There’s a last thing about adding a custom PropertyMetadataFactoryInterface (that still needs some documentation) but I don’t think it’s relevant as to me, ApiPlatform must support this by default (to be compliant with Doctrine and Symfony)

[edit] this has nothing to do with the DoctrineOrmPropertyMetadataFactory.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 28 (21 by maintainers)

Most upvoted comments

Regarding the general design of API Platform, and how it fits with command buses, and/or event sourcing, here are some thought:

  • API Platform is a “design-first” oriented API framework: first you design the public shape of API endpoints. To do so, you write a plain old PHP object (the class marked with the @ApiResource annotation) representing the input and output of your endpoint. This object doesn’t have to be mapped with Doctrine, or any other persistence system. This object must be simple (usually just a data structure with no or minimal behaviors) and will be automatically to converted to a Swagger and Hydra documentation by API Platform (there is a 1-1 mapping between this object and those docs).
  • Then, it’s up to the developper to feed API Platform with an hydrated version of this API resource object by implementing the DataProviderInterface. Basically, the data provider will query the persistence system (RDBMS, document or graph DB, external API…), and must hydrate and return the POPO that has been designed in the first time.
  • Again, when updating a state (POST, PUT, PATCH, DELETE HTTP methods), it’s up to the developper to persist properly the data provided by API Platform’s resource object hydrated by the serializer to the persistence system. To do so, there is another interface to implement: DataPersisterInterface (this one still need to be documented). Here, it’s the reverse operation, this class will read the API Platform resource object, hydrate a DTO and trigger a command, populate and event store, or persist the data in any other useful way (it’s not the responsibility of API Platform, it’s up to the developer).
  • For RAD, convenience and prototyping, if and only if the class is marked with @ApiResource is also a Doctrine entity, the developper can use the Doctrine ORM’s DataProvider and DataPersister implementations shipped with API Platform. Then, the public (@ApiResource) and internal (Doctrine entity) data model are shared. In this case, API Platform will be able to query, to filter, to paginate and to persist automatically data. This is very convenient in some cases, but probably not a good idea for non-CRUD and/or large systems. It’s up to the developper to use, or to not use those RAD data providers/persisters or to implement CQS/CQRS/whatever ones, depending of the business you’re dealing with.

For event sourcing based systems, a very convenient approach is:

  • to persist data in an event store using a custom data persister implementation (not the RAD one)
  • to create projections in standard Postgres (or MariaDB…) tables or views
  • to map those projections with classes marked as (read-only) Doctrine entities and @ApiResource

Then you can benefit of all built-in Doctrine filters, sorting, pagination, auto-joins and so on provided by API Platform.

I would be very happy to get some feedback you about those thought. If it looks good enough, I would like to add a new doc entry about this.

Text from http://tactician.thephpleague.com/ :

Commands really help capture user intent. They’re also a great stand-in for the models when it comes to forms or serializer libraries that expect getter/setter objects.

I’ve recently experimented with this approach:

  • non standard entities are not registered as ApiResources
  • Command objects are registered as ApiResources
  • in CommandHandler mapping, persisting (or other connected tasks) takes place
  • I’m using http://symfony.com/doc/4.1/components/messenger.html for dispatching Commands
  • to automagically dispatch Commands by api-platform I registered kernel.view listener with priority 40 (after validation) which executes $bus->dispatch($event->getControllerResult());

Since 2.4 we support objects that aren’t resources and we handle DTO’s with inputs/outputs. Closing this as fixed, please don’t hesitate to ping me if you need more informations.

Another possibility if you’re stuck might be to create a DTO to represent the writable fields, then validate it with the Validation component, and if it is valid and only then, attempt to create your entity from that.

Yes, but not in its current version, actually it support private property hydrating / reading, but it miss creating the object without calling the constructor or calling it with correct mapping, however this is something planned.

@greg0ire what do you mean? I use a DTO as a representation of underlying resource. All fields I need to expose would go in the DTO (public), others not.