Here’s just a view thoughts about implementing “kind of jsonapi.org”.
To me there are couple different things on the agenda.
Feels like a first prototype of this can be implemented as a package without any changes to the core.
Dispatcher request looping over both, the requested data as well as all included relations.
Let’s talk about it as a root request ending up in a controller performing sub requests.
I’m not completely sure if using a controller for this is the best idea or getting the Http Component involved is better. But for the sake of not getting too complex in the first shot, I’m talking of the root request as ending up in a controller.
RootRequest gets resolved to an JsonrgApiController where an empty array is created.
The actual “thing to do” of the
RootRequest is transformed to a
WorkerRequest gets executed separately, “while (count($workerRequests))”.
WorkerRequest holds relations to other objects, a new
WorkerRequest is created and pushed to the $workerRequests array.
As soon as all relations are handled as subrequests, every subrequest can be cached individually.
Even the actual implementation of how those subrequests are handled can be adjusted per project, depending on the available software environment.
The very basic idea is just using Flow subrequests, but this could be enhanced to e.g. doing real HTTP web requests targeting a varnish proxy, or whatever.
The thing is: All relations are handled not nested but iterative and independent, providing a distinct spot where caching is supposed to go later.
DTOs wrap domain objects that provide configuration by PHP code
Not targeting jsonapi.org but to be used with the $resource mechanism of AnuglarJS, I did such a thing for Extbase and ported it to Flow some time later.
This shure needs some improvement in general and some adjustment in order to match the jsonapi.org requirement, but I’d like to think of it as an idea that has proven to be not too bad.
Think about something like this:
All property names meant to be available externally are named separately and provided by a getter of the Dto.
This can be enhanced by privilege mechanism, context checks and so forth.
I’d leave it to a plain getter method in the AbstractDto, so it can be easily implemented per distinct Dto class. This leaves room for complexity when its necessary but no complex configuration required.
They are both at the same time, input and output converters. Just as they provide every information that should be exposed to the public in getter methods, they can cover all the code needed to transform the external data to internal data in setter methods.
The easiest way, of course, is to just pass getters and setters right through the payload object. And again: Room for complex mapping if necessary, but no mapping at all required by default.
The object can be used to alias internal attribute names to external attribute names, simply by providing getter methods and setter methods respectively. They can even be used to travers through nested objects. Think about “person.identifier” which could be a getter method calling “return $this->getPayload()->getAccount()->getIdentifier()”.
In contrast to my implementation available on Github, those objects should not be used to expose nested structures.
Lots of magic of my Github package is related to providing a deeply nested array that can be passed to json_encode. This, of course, is obsolet when it comes to jsonapi.org and should be replaced by “relationships”.
How to map a DTO class
I suggest introducing
TypeConverters, they should be have a priority and asked for “canConvert” by providing a distinct model object.
Mapping Model objects to Dto objects
That’s the easy part. In general, one can simply create a
new Dto($payload). Of course that’s the obligation of the
DtoConverter, but it’s no big deal.
The more complex task of
DtoConverts should be to provide enough information to create absolute resource URIs pointing to the API endpoint for an individual Domain object or Dto.
Those are meant to be used for both, creating the exposed public URI for “links.self” as well as filling the HTTP sub request properly that acts as the
WorkerRequest for related objects.
Creating a view
JsonView dedicated to Dtos can easily iterate through all properties named by
Every scalar value goes into an associative array.
Every object goes back to the list of
Every other object throws an exception.
There’s that nice feature of a
Sparse Fieldset in the API documentation.
I’d like to handle that as a “post processing of a complete result”. Instead of passing all of this information to the view and let the view limit the “properties to be api exposed”, I’d rather let the view create a complete response and have the
RootRequest apply those restrictions, in favor of advanced caching on one hand and a single point of implementing it on the other.
The jsonapi.org requires the “MM relation data” to be exposed.
This means there need to be “relationship” routes that expose the inverse side of the association as well as some information about what owning side it belongs to.
I don’t really know how to tackle this. Maybe we could treat that “MUST” as a “MAY” and postpone this to an improved version.
… well, not talking about the API response. If some of you have doubts or ideals, I’d really love to hear back. I might start creating a little prototype as soon as I have some minutes to spare :).