What would be great is if this module could provide deserialization of the incoming request content for POST/PATCH requests and then invoke the controller.
To deserialize, we need a resource type, and the deserialization class, and optional relationship key. If the relationship key is missing, our deserialization class must be JsonApiDocumentTopLevel. Otherwise it is ResourceIdentifier.
A route can provide defaults to define the resource type to be used. This ends up matching Routes::RESOURCE_TYPE_KEY. This isn't quite perfect.
In the Cart API we use the following to support a resource type against the main entity type and no bundle field considerations.
$resource_type = new ResourceType('commerce_order', 'virtual', EntityInterface::class);
This is a custom resource type to handle PATCHING a relationship without knowing the bundle.
Comment | File | Size | Author |
---|---|---|---|
#6 | 3088477-6.patch | 33.88 KB | gabesullice |
#6 | interdiff.txt | 7.14 KB | gabesullice |
#3 | 3088477-3.patch | 30.91 KB | gabesullice |
#3 | interdiff.txt | 17.8 KB | gabesullice |
#2 | 3088477-2.patch | 35.41 KB | gabesullice |
Comments
Comment #2
gabesulliceI still have to do a review of my own work... and probably clean up a ton (I've been writing code for two days and haven't done any cleanup), but I finally got
POST
working in a way that I'm really quite happy with. Implementing resources which create a custom resource are as simple as:In the above,
JsonApiDocumentTopLevel $document
is deserialized by an argument resolver, which means any resource processor can add it, just like a $request, to have it automatically passed in as an argument. I chose not to resolve directly to an entity because the processor might want to access themeta
member for example. Additionally, this means that the initial deserialization is not entity-dependent.$this->processEntityCreation($request, $document)
takes care of extracting the primary data from the deserialized document and creating an entity. It takes care of extra boilerplate from the spec like setting theLocation
header on201 Created
responses.The most WTF'y thing is probably the
Unstable
namespace. It's pretty much what it sounds like and I'd like to move more things into it. The idea is to make it very obvious what is an internal implementation detail and what is not, guiding implementers of custom resources to use the methods on the resource base classes that we provide instead of trying to do complicated things we're not yet sure about.Comment #3
gabesulliceHere's a self-review. Attached patch does most of this.
exceeds line length limits
This needs error handling.
Needs a comment describing how this condition works.
Instead of doing this in the controller, we could decorate the class resolver.
These method name overrides are unused/outdated.
This can be removed. This property is part of the included trait.
Needs a description.
Needs a class comment.
I think that this isn't necessary.
Needs proper docblock.
Explain that is mostly a replication of JSON:API's ContentEntityDenormalizer.
I think we should actually omit config entity support.
This is silly. This method can just call $this->serializer->denormalizer() to reuse JSON:API's denormalizer. It doesn't need to copy its code.
Needs a docblock.
Needs a better comment.
We can eliminate this if we set our resource enhancer to a lower priority. I'll open a separate issue for that.
Comment #4
mglamanTHIS LOOKS SO COOL.
😬I'll have to lend a hand here, as Cart API is solely this.
I think it's fine as a follow up once we land POST/PATCH/DELETE for individuals
ooo gettin' fancy with
yield
Does yield benefit us over return? this isn't an iterator (I'm curious)
Is this validated? Is it prior in createEntityFromResourceObject?
Either way we should after the resource modified it
👍
Comment #5
gabesullice#4:
1. Yeah... I would love your help thinking that feature through. We need some way to indicate that a route is a relationship route. I couldn't think of an elegant way to do that yet.
2. This is just me copying code 🤗 I don't know if
yield
is important or not.3. ohhh, excellent catch. It is not validated. 😬
4. :)
Comment #6
gabesulliceThis addresses #4.3 and sorta starts 4.1. Also addresses #3.3.
Comment #7
gabesulliceDescoping this issue because:
1. I would like to handle relationships in a separate issue.
2. I started to add support for entity
PATCH
requests but then I realized, that would be a pointless feature. Why? Because when you create anode--reminder
resource byPOST
ing to/jsonapi/user/{user}/reminders
, you get back a resource with aLocation: /jsonapi/node/reminder/{id}
header and aself
link on the resource object with the same path. IOW, even if you have custom collections and custom endpoint to create entities, those entities already have a unique URL for updating and deleting them! It would be silly to invest a ton of effort into making custom routes for updating them.Therefore, I think this is ready for a final review by @mglaman.
Comment #8
gabesulliceComment #9
mglaman#7.1 - agreee
#7.2 double agree
will review tomorrow
Comment #10
mglamanI think this looks great. I had one main piece of feedback, for
extractResourceObjectFromRequest
. I ran Cart API tests against the patch and had no breakage. Once this lands I may try to experiment with resolving a JSON API document from PATCH.One reason it would be beneficial to have a custom PATCH route: permissions. You may not want to grant a user permissions to edit X entity type and bundles and then hope there is EntityOwnerInterface and Entity API access control. We have this for Order Items - we don't want you to just edit any order item. Only the one that belongs to the order it is attached to, and that you own the order. Right now it's easier as a custom resource until work is spent moving down into the entity access system. (also, just better DX control). But I went off the rails a bit here.
I know we're not supporting > 1 cardinality to start, it would be probably a good idea to have this called
extractResourceObjectsFromRequest. That way it returns an array and we can know the request cardinality.
This is just a future enhancement for when we support relationships.
Should EntityResourceBase implement this by default? Or pick and choose per implementation, and documented.
I guess it does no harm since you must explicitly invoke
I was trying to track down implementations of ResourceObjectToEntityMapperAwareInterface and had no idea where the implementation existed.
Just fields weird having a trait use another trait to implement an interface. But it makes sense since this requires that property.
Comment #11
gabesulliceWhoa, I totally missed this review @mglaman! Thanks.
#10:
1. The reason that this is not plural is because the spec does not permit request documents with an array of resource objects.
2. That was my conclusion too :)
3. Honestly, I think we could get rid of
ResourceObjectToEntityMapperAwareTrait
entirely by merging it up intoEntityCreationTrait
. The lower level trait was created in anticipation of supporting entityPATCH
requests, however, since #7.2, that feature has been dropped.That said, your comment in #10 suggests that maybe we should not drop that feature. So, because we don't yet know what we want, I think it makes sense to keep the trait in this patch. It lives in the
Unstable
namespace, so we can always drop it later.Comment #12
gabesulliceComment #14
gabesullice