-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Hello Team,
I had to build some REST Endpoints for pimcore, and I've decided to use API platform just for fun. It turned out that it wasn't that fun.
Then I checked out this repo, but unfortunately I was not able to use it because of some conceptional issues. So I built my own Bridge and how should I say: That was more than inefficient, but I'm now in possession of a full working REST Api for PIMCORE.
Before I'm going to spend more time on this, I wanted to ask you guys about the future and purpose of this repo. The main question I have is: Should it fully support REST principals?
If the answer is yes, I would love to contribute my package, so we can improve this bundle.
Below, my walkthrough, thoughts, and principals I've applied within my package:
Configuration
- Do not predefine resources, add them to docs and leave it to the integrator, which resources he wants to enable (Even the asset resource, although they probably never change in their way of configuration). Besides, the swagger documentation will be flooded with endpoints I don't need
- Since we cannot use annotation or attributes in our data objects, I define everything via configuration files:
- Resource: config/api_platform/product.yaml
- Serialization: config/serialization/product.yaml
- Validation: config/validator/product.yaml
Define groups for given operations can be really really hard when it comes to relations, collection:post, item:post, so I made good experiences to keep them in configuration files.
Identifier
Probably the biggest issue here. In my case, the id
field is not part of the api but the customer wants to push his data with his own identifier. Since it's super easy, to set the identifier via resource configuration, that was not a big thing. But internaly, this bundles claims to all entities to use the id
field:
https://github.com/w-vision/PimcoreApiPlatform/search?q=withidentifier
This is not required and should be part of the configuration layer. By removing all the if($property === 'id')
you will benefit from less code and more flexibility.
Data Provider
This also assumes, that we're only dealing with the id
field only. Instead, I used the $id
property in getItem
which allows getting the elements by given identifiers.
Data Persister
The data persister in this bundle will call the global pimcore
configuration node to set required fields like key
and parent
. It also comes with some expression language helper. That's all nice but in fact unusable when it comes to complex scenarios (the requirement to store it differently because of conditions like variants for example)
Therefor, I introduced a new service called ElementDataPersistor
and a new tag called app.api.pimcore.element_processor
. It will be determinate the right element processor via suppors
but is also able to validate
the data before persisting (see quirks/cascading)
Quirks
Now it's going to be "hacky". There are some issues I've struggled with, and I'm still unsure if my approaches are the right way to do.
Unpublished Objects
Loading an object with an unpublished relation will end up as null
in the serialization process. I added a DataObject::setHideUnpublished
in the data provider, which is not the right place to do it, I guess.
Cascading
Super exciting! Not! This was probably the biggest challenge for me.
Imaging this:
Example 1:
Easy. Pass the IRI to an existing supplier will automatically map the supplier entity to the product.
Example 2:
Also, easy. This is the valid json+ld definition. Load supplier and adopt the changes to the supplier.
Example 3:
Here comes the fun part. This POST will create a product and also a supplier. So far, so easy.
Cascade Persisting
But as soon we're entering the persister layer, pimcore cries like a baby because supplier needs to persisted FIRST. Ok, but there is one more problem. If I'm going to persist my supplier first and I secondly save my product (which also can rise some exceptions) I already have a persisted supplier which is not what I want (And wouldn't be an issue at all if we would deal with real doctrine entities...).
Therefor, my element processors have to implement a validate
method, which allows validating their data (I'm doing this recursive of an object incl. relational fields).
Relational Updates
Looking again at example 3: In most cases, the user wants to update the relation entity the same way. But that's not possible in a nice way. I need to do this in my data persister (check if identifier is available and search it in database, THEN map all the properties - EVIL). The solution for me was to decorate the api_platform.serializer.normalizer.item
:
There, I'll fetch the entity and pass it via OBJECT_TO_POPULATE
.
However, I am not sure if this should be an API platform thing. In some ways I think this should be supported by API platform since they already introduced the api_allow_update
property to allow updates for relation fields. But they only allow the "@id" and it will throw an exception, if the relation is not existing (which does not exist at the first time of my POST operation.
Filter
I introduced a simple Filter class which allows simple conditions by defining service aliases (similar to the doctrine property filter).
DataObjectPropertyNameCollectionFactory
As you can see here, you're going to override all the existing collection items of previous decorators. In my case, my data object extends a custom model which contains some additional (virtual) properties. Those of course will never show up. That's why I've added some additional property search:
Auth
I've implemented the JWT authorization. Documentation should be enough, i guess.
Type Factories
Love them. Used them. Didn't change anything. Great stuff!
Phew. Let's call this a whitepaper with some burn marks! :) Hopefully you're able to follow my trip to hell and back. Like I said before, it would be great to provide this to the community, but it really needs a lot of discussion. Maybe we should arrange some hackaton videocalls. :)