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:
DoctrineOrmPropertyMetadataFactory~ SerializerPropertyMetadataFactory (after all if I say they are writable, then they are)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.
For the record, I'm to support this kind of objects. But it will be a tedious work.
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.
For the record, I'm to support this kind of objects. But it will be a tedious work.
You mean by using the constructor to set the properties?
To me, this is an issue but @soyuka seems to disagree after a short discussion on slack.
Just saying that by default a property in ApiPlatform is considered writable if:
ApiProperty(writable=true) attributeYour use case is just not supported yet, therefore it's a missing feature (maybe, though can be hotfixed with some metadata manipulation) but not an bug.
Yes, some improvements have been done in the Symfony serializer to support this use case.
Yes, some improvements have been done in the Symfony serializer to support this use case.
Though, even with this implemented, these properties will still be considered as readonly if they don't have any setters.
Until we patch API Platform :)
@greg0ire
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.
That's what I did (https://github.com/DonCallisto/InterNations/commit/167dd4cf18b1cd555fdff2a5ae6d23d4a0ce3e37#diff-8e21998203ae4e337d28fab5806f49f4R33) but I notice some "issues": first of all, in documentation you'll see a resource that is the DTO whereas the (i.e.:) POST action should refers to real resource.
Second, when I get the response back, I need to implement a listener to change every link that present DTO name into real resource name (https://github.com/DonCallisto/InterNations/commit/167dd4cf18b1cd555fdff2a5ae6d23d4a0ce3e37#diff-8e21998203ae4e337d28fab5806f49f4R43)
If this is the only method here I don't feel that's a good one.
Have you alternative solutions?
If you want to take a look at my solution, here it is https://github.com/DonCallisto/InterNations/tree/api
/cc @dunglas @soyuka
I'm not sure we should equate resource and entity, there might be some fields you would not want to publicly expose in there
@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.
You could see the resource as a representation of the underlying entity is what I'm saying
@greg0ire Hence representations are not resources, and should not be identified as such.
@teohhanhui I had a private chat with @greg0ire. We came up with this idea, that's not the best possible as having multiple representations of the same resource should be possible, but could work.
The idea is to implement a unique DTO for every resource that need it and handle write/read fields differently. So in my example I will have User that's the original resource, RegisterUserCommand that's a DTO I'll use outside API (maybe implementing an interface in order to reuse the command handler?), and a User (under a different namespace) that represent User as "API DTO". This way I can refer this resource in every response (jsonld, uri, and so on) as user (name of the resource) and all operations could be done onto this DTO.
Of course there's some work to do to handle the persistance (for sure it works with api platform events, maybe even with DataPersister?) and the denormalization (data provider?) but apart from that, it seems to be the less cumbersome way to handle this situation.
WDYT?
I think if we're going to support DTOs properly (without introducing a lot of unnecessary plumbing), it might need to be done at the Symfony Serializer level? But then again the validation, doc normalizer etc. still need to be aware of them. So I'm not sure...
I'm not sure that only serializer level should be involved as DTOs could (and in this case probably should) have validation and a way to transfer data from DTO to the other resource. While DTO validation and serialization could be standardized, for data passage I'm not so confident.
BTW, as in this version we don't have DTOs support, could solution proposed above (I'm gonna try it in the next hours so I can comeback with some working code) a good compromise?
Symfony Serializer has now (4.1) everything needed for DTO support. PropertyInfo is being enhanced too (it will be for 4.2): https://github.com/symfony/symfony/pull/26997
Proper support in API Platform is almost finished too thanks to @Nek- and @komik966:
Regarding the general design of API Platform, and how it fits with command buses, and/or event sourcing, here are some thought:
@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).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.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).@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:
@ApiResourceThen 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.
@dunglas I like this, moreover I've implemented (in a summary way) first three of your points that helps me a lot with my issue (https://github.com/DonCallisto/InterNations/commit/cb4ef80c47640b9f612df07d349672dd9cb8edcb)
Talking about
Symfony Serializer has now (4.1) everything needed for DTO support. PropertyInfo is being enhanced too (it will be for 4.2): symfony/symfony#26997
Proper support in API Platform is almost finished too thanks to @Nek- and @komik966:
1843
1749
I'll take a look when I've finished my implementation with all use case (that are very basic but I would like to see if all of them are implementable)
/cc @greg0ire your idea combined with @dunglas instructions seems to work!
@dunglas But value objects are not the only thing needed for using DTOs... There needs to be a way to denormalize to the resource class based on a DTO?
@teohhanhui IMO it's out of the API Platform's scope, it's the dev responsibility
Basically to break the round-trip normalize/denormalize assumption that Symfony Serializer makes? I think it should be in Symfony Serializer.
@joelwurtz, do you think your data mapper can help here?
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.
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:
$bus->dispatch($event->getControllerResult());@komik966 if standard entities are not registered as ApiResources, how are you handling read operations?
@rafix — not so sure if understood right but API Platform has a possibility to create own data sources and DTOs handling. You can use no Doctrine entity in API and still get the job done.
@rafix in my description above i used term "command" - it is intended to change system state.
For pure read operations on non entities i prefer using data providers (especially with ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface). But if your need is only to use constructor, you will not have to do such extra work - api-platform has support for it on master branch, but this has not been released yet.
@komik966 @er1z But, I want to use the doctrine entities as read models, and Commands to write operations. The problem is the required item get definition in Commands make no sense to me... cc @dunglas ???
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.
Most helpful comment
Regarding the general design of API Platform, and how it fits with command buses, and/or event sourcing, here are some thought:
@ApiResourceannotation) 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).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.POST,PUT,PATCH,DELETEHTTP 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).@ApiResourceis 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:
@ApiResourceThen 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.