Date: 2023-08-24
Accepted
In PrestaShop 9.0, a new API is being built powered by ApiPlatform, based on Commands (from CQRS). We need to define a convention for the path format and the ApiPlatform resources classes. We will mostly follow REST conventions but it's useful to write down clearly a few things to avoid confusion later.
We base the naming on the domain from CQRS (which usually matches the ObjectModel entity name as well) in PrestaShop/PrestaShop/Core/Domain
Example: Hook
Domain: PrestaShop/PrestaShop/Core/Domain/Hook
ApiPlatform resource class: PrestaShopBundle\ApiPlatform\Resources\Hook
URI conventions, we use the domain word as a base for the URI, to follow REST conventions we use the plural (even for single entity endpoints).
For the identifier related to one entity we use the domain name suffixed by Id (ex: Hook -> hookId).
List: PrestaShopBundle\ApiPlatform\Metadata\PaginatedList /hooks
One hook endpoint: PrestaShopBundle\ApiPlatform\Metadata\CQRSGet /hooks/{hookId}
For APIs that are sub part of a larger entity (whether it's to display its related entities or small parts of a big entity) we keep the initial domain name as the beginning
of the URI and append it with the definition of the sub parts (separated by a /). If it's the sub part of an identified entity, we complete the initial URI path and append
the sub part after the entity ID. When the sub part is an action name or a compound name we use kebab case convention.
Example:
- Hook status (sub part):
/hooks/{hookId}/status - Search (action) products:
/products/search - Product combinations (sub part):
/products/{productId}/combinations - Assign product to category (action):
/products/{productId}/assign-to-category - Set product carriers (set a sub part):
/products/{productId}/carriers - Attribute group (list):
/attribute-groups - Attribute group associated values (list of sub part):
/attribute-groups/{attributeGroupId}/attributes
Some entities in PrestaShop have multilang values (like product names, category descriptions, ...), this data must be presented in the API endpoints:
- single entities endpoints return ALL the languages in an associative array indexed by locale (ex:
{"names": {"en-US": "english name", "fr-FR": "nom français"}}) - list of entities return only one language so multilang fields are returned as strings (ex:
{"name": "english name"}), the language used by default is the default language configured on the shop, but you can specify alangIdquery parameter to fetch another language values
To allow knowing the association between languages and language IDs a /languages endpoint will be accessible without any needed permission.
Read operations use GET method and should use PrestaShopBundle\ApiPlatform\Metadata\CQRSGet
Creation operations use POST method (without ID specified) and should use PrestaShopBundle\ApiPlatform\Metadata\CQRSCreate
Full update operations use PUT method (ex: PUT /products/{productId}) and should use PrestaShopBundle\ApiPlatform\Metadata\CQRSUpdate
Partial update operations use PATCH method (ex: PATCH /products/{productId}) and should use PrestaShopBundle\ApiPlatform\Metadata\CQRSPartialUpdate
Delete operations use DELETE method (ex: DELETE /products/{productId}) and should use PrestaShopBundle\ApiPlatform\Metadata\CQRSDelete
Duplicate operations use POST method with ID specified (ex: POST /products/{productId}/duplicate) and should use PrestaShopBundle\ApiPlatform\Metadata\CQRSCreate
To simplify the integration of our custom CQRS based implementation some custom operations were developed, they must be used in the core endpoints to remain consistent.
PrestaShopBundle\ApiPlatform\Metadata\CQRSGetfor read operations on a single resourcePrestaShopBundle\ApiPlatform\Metadata\CQRSGetCollectionfor read operations on a list of resources (not paginated)PrestaShopBundle\ApiPlatform\Metadata\CQRSCreatefor creation and duplication operationsPrestaShopBundle\ApiPlatform\Metadata\CQRSUpdatefor full update operationsPrestaShopBundle\ApiPlatform\Metadata\CQRSPartialUpdatefor partial update operationsPrestaShopBundle\ApiPlatform\Metadata\CQRSDeletefor delete operationsPrestaShopBundle\ApiPlatform\Metadata\PaginatedListto paginate elements
Similar convention for list of IDs we use the domain and append Ids at the end, bulk endpoints always use bulk- as a prefix for the action they are doing (even if the HTTP method implies it).
- Bulk delete products: Method DELETE
/products/bulk-deletewithproductIdsparameter in request body (array of product IDs) - Bulk duplicate products: Method POST
/products/bulk-duplicatewithproductIdsparameter in request body (array of product IDs) - Bulk update status: Method PUT
/products/bulk-update-statuswithproductIdsparameter in request body (array of product IDs)
In case no HTTP method finds a consensus for the bulk operation, POST method is used by default.
This convention is only for the Core API, if modules wish to define their own API they are free to use any convention for the scopes. We use the entity domain name in its singular form, and append the action, the whole string is written in snake case. Each scope is supposed to represent a single authorized action.
Basic Example:
- Orders read operations:
order_read - Orders write operations:
order_write
In the future we may define some more detailed sub scope, but they should follow a similar convention:
Example:
- Orders update address:
order_update_address - Orders create invoice:
order_create_invoice
- all the class fields must be strictly typed
- the localized properties do not start by
localized(ex:public array $publicNames;notpublic array $localizedPublicNames;) and should use thePrestaShopBundle\ApiPlatform\Metadata\LocalizedValuePHP attribute to have the automatic ID->locale conversion - the boolean fields don't need to start by
iswhich is redundant with the type (ex:public bool $ready;notpublic bool $isReady;) - the status of the entities is not always expressed the same way (active, enable, enabled, ...) we want to homogenize this field by always using
public bool $enabled; - you should use the internal
CQRSQueryMapping,CQRSCommandMapping,ApiResourceMappinginstead of theSerializedNameattribute (because it is not applied everywhere appropriately for the documentation)