Storefront API Development
Description of the request processing routine
The request processing routine comprises three levels:
- API level;
- Logic level;
- Database level.
getOne
For the first level, a DataProvider \XCart\Bundle\APIPlatformBridgeBundle\API\DataProvider\Item\DataProvider is registered automatically.
In the DataProvider, data is prepared for transition to the logic level, and after calling the logic level, the response is converted to an API DTO.
For example, for API DTO - XCart\API\Entity\Storefront\SomeEntity the following DataProvider will be generated:
xcart.api.storefront.someentity.data_provider.item:
class: XCart\Bundle\APIPlatformBridgeBundle\API\DataProvider\Item\DataProvider
arguments:
$classes: [ XCart\API\Entity\Storefront\SomeEntity ] # list of API DTO for which DataProvider can be employed
$action: '@xcart.logic.storefront.someentity.get_one.action' # logic level
$requestAssembler: '@xcart.api.storefront.someentity.data_provider.item.request.assembler' # request assembler that converts parameter $id into a logic-level parameter
$responseAssembler: '@xcart.api.storefront.someentity.data_provider.item.response.assembler' # response assembler that converts the logic response into an API DTO
$operationNames: [ get ] # names of operations for which DataProvider is employed
In the standard version, XCart\Bundle\DoctrineBridgeBundle\Action\GetOneAction will be registered as the logic level.
The request and response, as in the DataProvider, undergo additional processing.
xcart.logic.storefront.someentity.get_one.action: # This name is generated based on Logic DTO, not API DTO.
class: XCart\Bundle\DoctrineBridgeBundle\Action\GetOneAction
arguments:
$dataSource: '@xcart.data_source.someentity.read' # The name of this service is generated based on the linked Doctrine Entity
$entityIdAssembler: '@xcart.logic.storefront.someentity.get_one.action.entity_id.assembler' # extracts ID from parameter (@todo: argumentsAssembler will be used similarly to UpdateOne)
$responseAssembler: '@xcart.logic.storefront.someentity.get_one.response.assembler' # response assembler that converts Doctrine Entity into a logic response
Also, to this service the following decorators are applied:
\XCart\Logic\Decorator\RequestEnricher\Profile\ProfileOwnerEnrichDecorator- adding information on the current user\XCart\Bundle\LogicBundle\Decorator\Validator\Request\ClassNameValidator- validating the parameter type\XCart\Bundle\LogicBundle\Decorator\Validator\Request\AssertValidator- validating the request via\Symfony\Component\Validator\Validator\RecursiveValidatorActual interaction with the database happens in the DataSource
xcart.data_source.someentity.read:
class: XCart\Bundle\DoctrineBridgeBundle\DataSource\DoctrineReadDataSource
arguments:
$repository: '@xcart.repository.someentity.read'
$collectionClassName: XCart\Entity\TransactionCollection
$queryBuilderFilterEnricher: '@xcart.data_source.someentity.read.query_builder_filter.enricher'
$queryBuilderOrderRuleEnricher: '@xcart.data_source.someentity.read.query_builder_order_rule.enricher'
In the case of getOne, a relatively trivial implementation is used.
public function findOne(int|string $id): ?EntityInterface
{
return $this->repository->find($id);
}
Thus, the general getOne flow is as follows:
- The request is received from the user and is processed initially by the API Platform, while the API DTO is used as the object (type).
- In the DataProvider, the request parameter is transformed into a logic level parameter.
- At the logic level, the request parameter is transformed into a DataSource parameter.
- A Doctrine Entity is returned from the DataSource.
- At the level of logic, the Doctrine Entity is transformed into a Logic DTO.
- In the DataProvider, the Logic DTO is transformed into an API DTO and is returned to the user.
getList
The general principle is the same as in the case of getOne, except for the more complicated preparation of queryBuilder in the DataSource.
Since, from the viewpoint of API Platform, we are not working with doctrine, we cannot use standard filters.
For basic cases, filters from \XCart\Bundle\APIPlatformBridgeBundle\Filter\* are used (more details can be found in the examples and in the description of the generator).
The class \XCart\Bundle\APIPlatformBridgeBundle\API\DataProvider\Collection\DataProvider is used as the DataProvider.
It provides the transmission of filtering data to the logic level.
Also, if necessary, pagination data is added to the response.
xcart.api.storefront.someentity.data_provider.collection:
class: XCart\Bundle\APIPlatformBridgeBundle\API\DataProvider\Collection\DataProvider
arguments:
$classes: [ XCart\API\Entity\Storefront\SomeEntity ] # list of API DTO for which the DataProvider can be employed
$action: '@xcart.logic.storefront.payment.action.get_collection.action' # logic level
$requestAssembler: '@xcart.api.storefront.payment.action.data_provider.collection.request.assembler' # packing the request (filters, sorting and pagination) into logic-level parameter
$responseAssembler: '@xcart.api.storefront.payment.action.data_provider.collection.response.assembler' # unpacking the logic response into API DTO list
$paginationResponseAssembler: '@XCart\Bundle\APIPlatformBridgeBundle\API\DataProvider\Collection\Assembler\PaginationResponseAssembler' # repacking the response with the addition of information on pagination
As the logic level, the class \XCart\Bundle\DoctrineBridgeBundle\Action\GetListAction is used.
The same class supports work with filters and pagination data.
xcart.logic.storefront.someentity.get_collection.action:
class: XCart\Bundle\DoctrineBridgeBundle\Action\GetListAction
arguments:
$readDataSource: '@xcart.data_source.someentity.read' # the name of this service is generated based on the linked Doctrine Entity
$criteriaAssembler: '@xcart.doctrine.storefront.someentity.criteria.assembler' # transfer of request parameters from the logic level to the level of DataSource
$responseAssembler: '@xcart.logic.storefront.someentity.get_collection.response.assembler' # packing the DoctrineEntity list into logic response; also, information on pagination is added, if necessary.
Similarly to getOne, decorators are applied to the logic level in order to add information about the user and to validate the request. At the level of interaction with the database, the same DataSource is used as for getOne, but instead of working with the repository directly, QueryBuilder is used, to which filters, sorting and pagination are applied.
public function findList(FindListCriteria $criteria): EntityCollectionInterface
{
$qb = $this->enrichQueryBuilder($this->createQueryBuilder(), $criteria->getFilter(), $criteria->getOrderRule());
if ($criteria->getOffset() !== null) {
$qb->setFirstResult($criteria->getOffset());
}
if ($criteria->getLength() !== null) {
$qb->setMaxResults($criteria->getLength());
}
return $this->assembleResult($qb);
}
public function countList(CountListCriteria $criteria): int
{
return $this->assembleCount(
$this->enrichQueryBuilder($this->createQueryBuilder(), $criteria->getFilter(), $criteria->getOrderRule())
);
}
The general getList flow is as follows:
- The request is received from the user and is processed initially by the API Platform, while the API DTO is used as the object (type).
- In the DataProvider, the request parameter (filters) is transformed into a logic level parameter.
- At the logic level, the request parameter is transformed into a DataSource parameter.
- A list of Doctrine Entities is returned from the DataSource.
- At the level of logic, the list of Doctrine Entities is transformed into a list of Logic DTO. Also, pagination data is added to the response.
- In the DataProvider, the list of Logic DTO from the logic response is transformed into a list of API DTO. Also, pagination data is used, if present.
createOne
On the API Platform end, an API DTO is created and filled with data from the request; then DataPersister is executed. In our case, in the DataPersister, a conversion into a Logic DTO occurs and an action is executed.
xcart.api.storefront.someentity.data_persister:
class: XCart\Bundle\APIPlatformBridgeBundle\API\DataPersister\DataPersister
arguments:
$classes: [ XCart\API\Entity\Storefront\SomeEntity ]
$persister: '@xcart.api.storefront.someentity.data_persister.persister'
$remover: '@xcart.api.storefront.someentity.data_persister.remover'
xcart.api.storefront.someentity.data_persister.persister:
class: XCart\Bundle\APIPlatformBridgeBundle\API\DataPersister\Persister\Persister
arguments:
$addAction: '@xcart.logic.storefront.someentity.create_one.action'
$updateAction: '@xcart.logic.storefront.someentity.update_one.action'
$addRequestAssembler: '@xcart.api.storefront.someentity.data_persister.persister.request.assembler.create'
$updateRequestAssembler: '@xcart.api.storefront.someentity.data_persister.persister.request.assembler.update'
The logic level in its basic version is implemented in the class \XCart\Bundle\DoctrineBridgeBundle\Action\CreateOneAction, within which, in its turn, a Doctrine Entity is created, filled with data and saved to the database.
xcart.logic.storefront.someentity.create_one.action:
class: XCart\Bundle\DoctrineBridgeBundle\Action\CreateOneAction
arguments:
$writeRepository: '@xcart.repository.someentity.write'
$entityAssembler: '@xcart.logic.storefront.someentity.create_one.entity.assembler'
$responseAssembler: '@xcart.logic.storefront.someentity.create_one.response.assembler'