Skip to main content
page last edited on 10 October 2022

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\RecursiveValidator Actual 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'

updateOne

On the API Platform end, happens in three stages:

  • The DataProvider is run to get the initial state of the object being updated.
  • The object is updated with the data from the request.
  • The DataPersister is run to save the object in the database. Since in our case, the API Platform does not work with Doctrine Entity, we cannot save the data to the database directly. The DataProvider is the same one that is used for the GetOne operation, which means that updateOne cannot be implemented without getOne. The transfer of data from the request to an API DTO is carried out by the standard mechanism of the API Platform. The wrapper class \XCart\Bundle\APIPlatformBridgeBundle\API\DataPersister\DataPersister is used as DataPersister. The actual implementation is in the class \XCart\Bundle\APIPlatformBridgeBundle\API\DataPersister\Persister\Persister. Here, the same way as in the case of getOne, the API DTO is converted to a logic level parameter, and then the response is converted back to an API DTO.
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\UpdateOneAction. From the database, a Doctrine Entity is obtained in its initial state; a repository is used for that. Data is transferred from the logic-level parameter to the Doctrine Entity and is stored in the database. For a response, the Doctrine Entity is transformed into a Logic DTO.

xcart.logic.storefront.someentity.update_one.action:
class: XCart\Bundle\DoctrineBridgeBundle\Action\UpdateOneAction
arguments:
$writeRepository: '@xcart.repository.someentity.write'
$argumentsAssembler: '@XCart\Bundle\DoctrineBridgeBundle\Assembler\UpdateOne\Arguments\IdBasedArgumentsAssembler'
$entityEnricher: '@xcart.logic.storefront.someentity.update_one.entity.enricher' # transferring data from Logic DTO to Doctrine Entity
$responseAssembler: '@xcart.logic.storefront.someentity.update_one.response.assembler'

The general update flow is as follows:

  • The DataProvider is run - the same one as for the getOne operation; as a result, we get an API DTO.
  • The API DTO is updated using the data from the request; it is important that the API DTO contains an identifier of the object.
  • In the DataPersister, the API DTO is transformed to a logic parameter.
  • At the logic level, a Doctrine Entity in its initial state is obtained from the database.
  • The data is transferred from the logic-level parameter to the Doctrine Entity.
  • The Doctrine Entity is saved to the database.
  • The Doctrine Entity is transformed into a Logic DTO.
  • In the DataPersister, the Logic DTO is transformed into an API DTO and is returned to the user.

delete

The delete operation is organized the same way as updateOne, but in DataPersister, $remover is used instead of $persister.

xcart.api.storefront.someentity.data_persister.remover:
class: XCart\Bundle\APIPlatformBridgeBundle\API\DataPersister\Remover\Remover
arguments:
$action: '@xcart.logic.storefront.someentity.delete_one.action'
$requestAssembler: '@xcart.api.storefront.someentity.data_persister.remover.request.assembler'
xcart.logic.storefront.someentity.delete_one.action:
class: XCart\Bundle\DoctrineBridgeBundle\Action\DeleteOneAction
arguments:
$writeRepository: '@xcart.repository.someentity.write'
$argumentsAssembler: '@XCart\Bundle\DoctrineBridgeBundle\Assembler\DeleteOne\Arguments\IdBasedArgumentsAssembler'
$responseAssembler: '@xcart.logic.storefront.someentity.delete_one.response.assembler'

Entity generator description

A special generator is used to create DTOs and to register the services required for the Storefront API to work. The yaml syntax is used for the description. In modules, you can both declare new entities and supplement the existing ones. Descriptions are placed by the key dto_generator; depending on the type they are divided into several branches.

doctrine entities

@todo: is not implemented yet

dto

Generation of simple classes (DTO) Configuration section dto_generator -> entities -> dto Available options (the key is used as FCQN class):

  • type: string (defult: null) - DTO type, possible values logic_request_get_list_filter, logic_request_get_list_order_rule, doctrine_entity_filter, doctrine_entity_order_rule
  • trait bool (defult: false)
  • trais: string[] (default: []) - list of traits that need to be imported into the class
  • uses: string[] (default: []) - list of classes that need to be imported into a file; is used for specifying short names in traits, interfaces, extends, annotations or type of fields
  • interfaces: string[] (default: []) - list of interfaces that are implemented by the class
  • extends: string (default: null) - parent for the class
  • annotations: array (default: []) - list of annotations that need to be added for the class
  • fields: array (default: []) - list of class fields Available options for fields (the key is used as class name):
  • type: string (required) - field type; it is possible to specify both the scalar type and the object type (there is magic for some cases)
  • nullable: bool (default: false) - flag that allows a field to have the value 'null'.
  • annotations: array (default: []) - list of annotations that need to be added for the field
  • groups: string[] (default ['all', 'read', 'insert', 'update']) - serialization groups; are required for different sets of fields for different operations Example:
dto_generator:
entities:
dto:
XCart\API\Entity\Storefront\SomeEntity:
uses:
- XCart\Bundle\APIPlatformBridgeBundle\API\Entity\EntityInterface
interfaces:
- EntityInterface
fields:
name:
type: string
nullable: true
value:
type: string
serviceCode:
type: int
annotations:
Assert\Positive: [] # use Symfony\Component\Validator\Constraints as Assert will be added automatically;

Result:

<?php declare(strict_types=1);
namespace XCart\API\Entity\Storefront;
use JMS\Serializer\Annotation as JMS;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use XCart\Bundle\APIPlatformBridgeBundle\API\Entity\EntityInterface;
use XCart\Bundle\DTOGeneratorBundle\DTO\GeneratedDTOInterface;
final class SomeEntity implements EntityInterface, GeneratedDTOInterface
{
#[Groups(groups: ["all", "read", "insert", "update"])]
#[JMS\Type("string")]
private ?string $name = null;
#[Groups(groups: ["all", "read", "insert", "update"])]
#[JMS\Type("string")]
private string $value;
#[Assert\Positive()]
#[Groups(groups: ["all", "read", "insert", "update"])]
#[JMS\Type("int")]
private int $serviceCode;
public function getName(): ?string
{
return $this->name;
}
public function getValue(): string
{
return $this->value;
}
public function getServiceCode(): int
{
return $this->serviceCode;
}
public function setName(?string $name): static
{
$this->name = $name;
return $this;
}
public function setValue(string $value): static
{
$this->value = $value;
return $this;
}
public function setServiceCode(int $serviceCode): static
{
$this->serviceCode = $serviceCode;
return $this;
}
}

In addition, a class will be generated for the collection:

<?php declare(strict_types=1);
namespace XCart\API\Entity\Storefront;
use Closure;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use XCart\API\Entity\Storefront\SomeEntity as Entity;
use XCart\Bundle\DTOGeneratorBundle\DTO\GeneratedCollectionInterface;
/**
* @method __construct(Entity[] $elements = [])
* @method Entity[] toArray()
* @method false|Entity first()
* @method static createFrom(Entity[] $elements = [])
* @method false|Entity last()
* @method false|Entity next()
* @method false|Entity current()
* @method ?Entity remove(string $key)
* @method bool removeElement(Entity $element)
* @method bool offsetExists(string $offset)
* @method ?Entity offsetGet(string $offset)
* @method void offsetSet(string $offset, Entity $value)
* @method void offsetUnset(string $offset)
* @method bool containsKey(string $key)
* @method bool contains(Entity $element)
* @method bool|string indexOf(Entity $element)
* @method ?Entity get(string $key)
* @method string[] getKeys()
* @method Entity[] getValues()
* @method void set(string $key, Entity $value)
* @method bool add(Entity $element)
* @method static map(Closure $func)
* @method static filter(Closure $p)
* @method static partition(Closure $p)
* @method Entity[] slice(int $offset, ?int $length = null)
* @method static matching(Criteria $criteria)
*/
final class SomeEntityCollection extends ArrayCollection implements GeneratedCollectionInterface
{
}

As well as a factory:

<?php declare(strict_types=1);
namespace XCart\Factory\API\Entity\Storefront;
use XCart\API\Entity\Storefront\SomeEntity as DTO;
class SomeEntity implements SomeEntityInterface
{
public function __invoke(): DTO
{
return $this->create();
}
public function create(): DTO
{
return new DTO()
}
}

Descriptions of DTOs can use other generated DTOs as field types.

api

First level description (API) Configuration section dto_generator -> entities -> api Available options (the key is used as FCQN of API DTO class):

  • idType: string (default: int) - identifier type; possible values: int, uuid, manual; if using the value manual, it is required to change the type and verification in itemOperations
  • logicEntity: string (required) - Name of logic DTO with which this API DTO will be linked
  • path: string (required) - API endpoint
  • name: string (calculated by class name) - API name
  • trais: string[] (default: []) - list of traits that need to be imported into the API DTO class
  • interfaces: string[] (default: []) - list of interfaces that are implemented by the API DTO class
  • annotations: array (default: []) - list of annotations that need to be added for the API DTO class
  • factory - ??
  • normalizationContext: array - ??
  • denormalizationContext: array - ??
  • itemOperations: array (default: []) - description of operations over an element; is converted into an APIResource attribute
  • collectionOperations: array (default: []) - description of operations over a collection; is converted into an APIResource attribute
  • filters: array (default: []) - description of filters; is converted into an APIResource attribute
  • fields: array (default: []) - list of API DTO class fields (matches the abilities of the DTO)
  • register_get_one: bool (default: false) - usage of the typical description of the operation of getting an element (GET with specifying an ID)
  • register_get_collection: bool (default: false) - usage of the typical description of the operation of getting a collection (GET)
  • register_create_one: bool (default: false) - usage of the typical description of the operation of creating an element (POST)
  • register_full_update_one: bool (default: false) - usage of the typical description of the operation of completely updating an element (PUT)
  • register_update_one: bool (default: false) - usage of the typical description of the operation of partially updating an element (PATCH)
  • register_delete_one: bool (default: false) - usage of the typical description of the operation of deleting an element (DELETE)

normalizationContext

  • groups: string[] (default: ['read'])

denormalizationContext

  • groups: string[] (default ['insert', 'update'])

filters

List of filters available the list: For each filter the following fields are available:

  • className - name of filter class. With this class, data is converted into a logic-level filtration parameter
  • properties - list of fields for which the filter is available
  • operations - list of names of operations of collections for which the filter is available
  • arguments - additional arguments className - one of the classes:
XCart\Bundle\APIPlatformBridgeBundle\Filter\IdIntegerListFilter
XCart\Bundle\APIPlatformBridgeBundle\Filter\BooleanFilter
XCart\Bundle\APIPlatformBridgeBundle\Filter\IntegerEnumerationFilter
XCart\Bundle\APIPlatformBridgeBundle\Filter\FloatListFilter
XCart\Bundle\APIPlatformBridgeBundle\Filter\StringListFilter
XCart\Bundle\APIPlatformBridgeBundle\Filter\DateTimeFilter
XCart\Bundle\APIPlatformBridgeBundle\Filter\FloatFilter
XCart\Bundle\APIPlatformBridgeBundle\Filter\IntegerRangeFilter
XCart\Bundle\APIPlatformBridgeBundle\Filter\IdIntegerFilter
XCart\Bundle\APIPlatformBridgeBundle\Filter\IdIntegerEnumerationFilter
XCart\Bundle\APIPlatformBridgeBundle\Filter\DateTimeRangeFilter
XCart\Bundle\APIPlatformBridgeBundle\Filter\IntegerFilter
XCart\Bundle\APIPlatformBridgeBundle\Filter\FloatRangeFilter
XCart\Bundle\APIPlatformBridgeBundle\Filter\StringFilter
XCart\Bundle\APIPlatformBridgeBundle\Filter\DateTimeListFilter
XCart\Bundle\APIPlatformBridgeBundle\Filter\IntegerListFilter

With this class, a logic-level filtration parameter is created, where each filter is converted into one of the classes:

XCart\Bundle\LogicBundle\DTO\Request\Filter\Value\BooleanValue
XCart\Bundle\LogicBundle\DTO\Request\Filter\Value\DateTimeListValue
XCart\Bundle\LogicBundle\DTO\Request\Filter\Value\DateTimeRangeValue
XCart\Bundle\LogicBundle\DTO\Request\Filter\Value\DateTimeValue
XCart\Bundle\LogicBundle\DTO\Request\Filter\Value\FloatListValue
XCart\Bundle\LogicBundle\DTO\Request\Filter\Value\FloatRangeValue
XCart\Bundle\LogicBundle\DTO\Request\Filter\Value\FloatValue
XCart\Bundle\LogicBundle\DTO\Request\Filter\Value\IntegerListValue
XCart\Bundle\LogicBundle\DTO\Request\Filter\Value\IntegerRangeValue
XCart\Bundle\LogicBundle\DTO\Request\Filter\Value\IntegerValue
XCart\Bundle\LogicBundle\DTO\Request\Filter\Value\StringListValue
XCart\Bundle\LogicBundle\DTO\Request\Filter\Value\StringValue

To the logic level, filtration parameters are passed over via a DTO which needs to be described in advance in the section dto_generator -> entities -> dto. The type should be specified as logic_request_get_list_filter, the fields correspond to the ones specified in properties (for each of them - separately), the types correspond to XCart\Bundle\LogicBundle\DTO\Request\Filter\Value\* At the level of data, the same scheme is used: filtration parameters are passed over via a DTO which needs to be described in advance in the section dto_generator -> entities -> dto The type should be specified as doctrine_entity_filter, the fields correspond to the ones specified in properties (for each of them - separately), the types correspond to XCart\Bundle\DoctrineBridgeBundle\DTO\Filter\Value\*

XCart\Bundle\DoctrineBridgeBundle\DTO\Filter\Value\BooleanValue
XCart\Bundle\DoctrineBridgeBundle\DTO\Filter\Value\DateTimeListValue
XCart\Bundle\DoctrineBridgeBundle\DTO\Filter\Value\DateTimeRangeValue
XCart\Bundle\DoctrineBridgeBundle\DTO\Filter\Value\DateTimeValue
XCart\Bundle\DoctrineBridgeBundle\DTO\Filter\Value\FloatListValue
XCart\Bundle\DoctrineBridgeBundle\DTO\Filter\Value\FloatRangeValue
XCart\Bundle\DoctrineBridgeBundle\DTO\Filter\Value\FloatValue
XCart\Bundle\DoctrineBridgeBundle\DTO\Filter\Value\IntegerListValue
XCart\Bundle\DoctrineBridgeBundle\DTO\Filter\Value\IntegerRangeValue
XCart\Bundle\DoctrineBridgeBundle\DTO\Filter\Value\IntegerValue
XCart\Bundle\DoctrineBridgeBundle\DTO\Filter\Value\StringListValue
XCart\Bundle\DoctrineBridgeBundle\DTO\Filter\Value\StringValue

In the long run, for each filtration type, a corresponding Applier is selected in which QueryBuilder modification logic is implemented:

XCart\Bundle\DoctrineBridgeBundle\Enricher\GetList\Filter\Applier\BooleanApplier
XCart\Bundle\DoctrineBridgeBundle\Enricher\GetList\Filter\Applier\DateTimeAsIntegerApplier
XCart\Bundle\DoctrineBridgeBundle\Enricher\GetList\Filter\Applier\ListApplier
XCart\Bundle\DoctrineBridgeBundle\Enricher\GetList\Filter\Applier\NumberApplier
XCart\Bundle\DoctrineBridgeBundle\Enricher\GetList\Filter\Applier\RangeApplier
XCart\Bundle\DoctrineBridgeBundle\Enricher\GetList\Filter\Applier\StringApplier

In non-trivial situations, it is recommended to use existing filters and types, and for logic implementation it is recommended to create a new Applier and to use this Applier for the required case through the decoration of the respective service. (See examples)

fields

The same fields as for DTO plus the following:

  • id: bool (defualt: false) - if true, then the field is an identifier

doctrine

logic

Description for logic level (Logic) Configuration section dto_generator -> entities -> logic Available options (the key is used as FCQN class):

  • idType:string (default: int) - identifier type; possible values: int, uuid, manual
  • doctrineEntity: string (default: null) - name of the Model with which this Logic DTO will be linked
  • trais: string[] (default: []) - list of traits that need to be imported into the API DTO class
  • interfaces: string[] (default: []) - list of interfaces that are implemented by the class API DTO
  • annotations: string[] (default: []) - list of annotations that need to be added for the class API DTO
  • generate_new_entity: bool (default: true) - creation of additional DTO for create_one operations
  • generate_get_one: bool (default: false) - generation of Request and Response DTO for the logic level, for the get_one operation
  • generate_get_collection: bool (default: false) - generation of Request and Response DTO for the logic level, for the get_collection operation
  • generate_create_one: bool (default: false) - generation of Request and Response DTO for the logic level, for the create_one operation
  • generate_update_one: bool (default: false) - generation of Request and Response DTO for the logic level, for the update_one operation
  • generate_delete_one: bool (default: false) - generation of Request and Response DTO for the logic level, for the delete_one operation
  • use_doctrine_get_one: bool (default: false) - Use class \XCart\Bundle\DoctrineBridgeBundle\Action\GetOneAction as action
  • use_doctrine_get_collection: bool (default: false) - Use class \XCart\Bundle\DoctrineBridgeBundle\Action\GetListAction as action
  • use_doctrine_create_one: bool (default: false) - Use class \XCart\Bundle\DoctrineBridgeBundle\Action\CreateOneAction as action
  • use_doctrine_update_one: bool (default: false) - Use class \XCart\Bundle\DoctrineBridgeBundle\Action\UpdateOneAction as action
  • use_doctrine_delete_one: bool (default: false) - Use class \XCart\Bundle\DoctrineBridgeBundle\Action\DeleteOneAction as action
  • enricher - service for transferring data from LogicDTO to model (for the update_one operation)
  • fields: array (default: []) - list of API DTO class fields (matches the abilities of the DTO)

fields

The same fields as for DTO plus the following:

  • id - if true, then the field is an identifier

logic_request

DTO for logic level parameter. Configuration section dto_generator -> entities -> logic_request Available options (the key is used as FCQN class):

  • type: string (default: null) - possible values: get_one, get_collection, create_one, insert_collection, update_one, update_collection, delete_one, delete_collection
  • entity: string (default: null)
  • traits: string[] (default: [])
  • interfaces: string[] (default: [])
  • annotations: array (default: [])
  • factory: array (default: [])
  • fields: array (default: [] - same as for DTO

factory

  • interfaces: string[] (default: [])

logic_response

DTO for logic level result. Configuration section dto_generator -> entities -> logic_response Available options (the key is used as FCQN class):

  • type: string (default: null) - possible values: get_one, get_collection, create_one, insert_collection, update_one, update_collection, delete_one, delete_collection
  • entity: string (default: null)
  • traits: string[] (default: [])
  • interfaces: string[] (default: [])
  • annotations: array (default: [])
  • factory: array (default: [])
  • fields: array (default: [] - same as for DTO

factory

  • interfaces: string[] (default: [])

transformer

Description for the generation of transformers

  • from: string (required)
  • to: string (required)
  • type: string (default: null) - possible values: logic_response_item_payload, logic_response_collection_payload, logic_request_item_payload
  • pattern: string (default: serialization) - possible values: predefined, serialization, direct, direct_to, direct_from

enumerations

hashMap

Examples of solving typical problems

Simple CRUD for existing Doctrine entity

Doctrine Entity

\XLite\Model\SomeEntity

<?php
namespace XLite\Model;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table (name="some_entity")
*/
class SomeEntity extends AEntity
{
/**
* @ORM\Id
* @ORM\GeneratedValue (strategy="AUTO")
* @ORM\Column (type="integer", options={ "unsigned": true })
*/
protected int $id;
/**
* @ORM\Column (type="string", length=64)
*/
protected string $name;
/**
* @ORM\Column (type="integer")
*/
protected int $position;
/**
* @ORM\Column (type="text", nullable=true)
*/
protected string $data;
}

\XLite\Model\Repo\SomeEntity

<?php
namespace XLite\Model\Repo;
class SomeEntity extends ARepo
{
}

Configuration

dto_generator:
entities:
api:
XCart\API\Entity\Storefront\SomeEntity:
name: Some Entity
logicEntity: XCart\Logic\Entity\Storefront\SomeEntity
path: /storefront/some_entity
register_get_one: true
register_get_collection: true
register_create_one: true
register_update_one: true
register_delete_one: true
fields:
name:
type: string
position:
type: integer
data:
type: string
logic:
XCart\Logic\Entity\Storefront\SomeEntity:
doctrineEntity: XLite\Model\SomeEntity
generate_get_one: true
generate_get_collection: true
generate_create_one: true
generate_update_one: true
generate_delete_one: true
use_doctrine_get_one: true
use_doctrine_get_collection: true
use_doctrine_create_one: true
use_doctrine_update_one: true
use_doctrine_delete_one: true
fields:
name:
type: string
position:
type: integer
data:
type: string
transformers:
- from: XLite\Model\SomeEntity
to: XCart\Logic\Entity\Storefront\SomeEntity
- from: XCart\Logic\Entity\Storefront\SomeEntity
to: XLite\Model\SomeEntity
- from: XLite\Model\SomeEntity
to: XCart\Logic\Entity\Storefront\NewSomeEntity
- from: XCart\Logic\Entity\Storefront\NewSomeEntity
to: XLite\Model\SomeEntity

Additional services

xcart.query_builder.factory.someentity.read:
class: XCart\Bundle\DoctrineBridgeBundle\QueryBuilder\Factory\QueryBuilderFactory
arguments:
$entityManager: '@doctrine.orm.entity_manager'
$className: XCart\Bundle\DoctrineBridgeBundle\QueryBuilder\CommonQueryBuilder
xcart.query_builder.factory.someentity.write:
class: XCart\Bundle\DoctrineBridgeBundle\QueryBuilder\Factory\QueryBuilderFactory
arguments:
$entityManager: '@doctrine.orm.entity_manager'
$className: XCart\Bundle\DoctrineBridgeBundle\QueryBuilder\CommonQueryBuilder
xcart.repository.someentity.read:
class: XLite\Model\Repo\SomeEntity
factory: ['@doctrine.orm.default_entity_manager', getRepository]
calls:
- [setQueryBuilderFactory, ['@xcart.query_builder.factory.someentity.read']]
arguments:
- XLite\Model\SomeEntity
xcart.repository.someentity.write:
class: XLite\Model\Repo\SomeEntity
factory: ['@doctrine.orm.default_entity_manager', getRepository]
calls:
- [setQueryBuilderFactory, ['@xcart.query_builder.factory.someentity.write']]
arguments:
- XLite\Model\SomeEntity
xcart.data_source.someentity.read:
class: XCart\Bundle\DoctrineBridgeBundle\DataSource\DoctrineReadDataSource
arguments:
$repository: '@xcart.repository.someentity.read'
$collectionClassName: 'XCart\Entity\SomeEntityCollection'
xcart.data_source.someentity.write:
class: XCart\Bundle\DoctrineBridgeBundle\DataSource\DoctrineReadDataSource
arguments:
$repository: '@xcart.repository.someentity.write'
$collectionClassName: 'XCart\Entity\SomeEntityCollection'

Additional code

\XCart\Entity\SomeEntityCollection

<?php
namespace XCart\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use XCart\Bundle\DoctrineBridgeBundle\Collection\EntityCollectionInterface;
use XLite\Model\SomeEntity as Entity;
/**
* @method __construct(Entity[] $elements = [])
* @method Entity[] toArray()
* @method false|Entity first()
* @method static createFrom(Entity[] $elements = [])
* @method false|Entity last()
* @method false|Entity next()
* @method false|Entity current()
* @method ?Entity remove(string $key)
* @method bool removeElement(Entity $element)
* @method bool offsetExists(string $offset)
* @method ?Entity offsetGet(string $offset)
* @method void offsetSet(string $offset, Entity $value)
* @method void offsetUnset(string $offset)
* @method bool containsKey(string $key)
* @method bool contains(Entity $element)
* @method bool|string indexOf(Entity $element)
* @method ?Entity get(string $key)
* @method string[] getKeys()
* @method Entity[] getValues()
* @method void set(string $key, Entity $value)
* @method bool add(Entity $element)
* @method static map(Closure $func)
* @method static filter(Closure $p)
* @method static partition(Closure $p)
* @method Entity[] slice(int $offset, ?int $length = null)
* @method static matching(Criteria $criteria)
*/
final class SomeEntityCollection extends ArrayCollection implements EntityCollectionInterface
{
}

Trivial filter application

This example demonstrates the implementation of filtering a list by a partial match of value in one field

Configuration

Changes to dto_generator -> entities -> api and dto_generator -> entities -> logic

dto_generator:
entities:
api:
XCart\API\Entity\Storefront\SomeEntity:
name: Some Entity
logicEntity: XCart\Logic\Entity\Storefront\SomeEntity
path: /storefront/some_entity
register_get_one: true
register_get_collection: true
register_create_one: true
register_update_one: true
register_delete_one: true
filters:
- className: XCart\Bundle\APIPlatformBridgeBundle\Filter\StringFilter
properties: [ name ] # name of field in the request, matches the filtering field name
operations: [ 'get' ]
arguments:
strategies: [ partial ] # filtering by string occurrence, not by full match
fields:
name:
type: string
position:
type: integer
data:
type: string
logic:
XCart\Logic\Entity\Storefront\SomeEntity:
doctrineEntity: XLite\Model\SomeEntity
generate_get_one: true
generate_get_collection: true
generate_create_one: true
generate_update_one: true
generate_delete_one: true
use_doctrine_get_one: true
use_doctrine_get_collection: true
use_doctrine_create_one: true
use_doctrine_update_one: true
use_doctrine_delete_one: true
fields:
name:
type: string
position:
type: integer
data:
type: string
dto:
# Class containing the list of filters at the Logic level
XCart\Logic\Action\Storefront\SomeEntity\GetList\DTO\Request\Filter:
type: logic_request_get_list_filter
fields:
name:
type: StringValue
nullable: true
# Class containing the list of filters at the Data level
XCart\DTO\Doctrine\Storefront\SomeEntity\Filter:
type: doctrine_entity_filter
fields:
name:
type: StringValue
nullable: true

Creation of a non-trivial filter

This example demonstrates the implementation of filtering a list by a partial match of value in multiple fields

Configuration

Changes to dto_generator -> entities -> api и dto_generator -> entities -> logic

dto_generator:
entities:
api:
XCart\API\Entity\Storefront\SomeEntity:
name: Some Entity
logicEntity: XCart\Logic\Entity\Storefront\SomeEntity
path: /storefront/some_entity
register_get_one: true
register_get_collection: true
register_create_one: true
register_update_one: true
register_delete_one: true
filters:
- className: XCart\Bundle\APIPlatformBridgeBundle\Filter\StringFilter
properties: [ substring ] # field name that will be used in the request; the same name will be used to select an Applier
operations: [ 'get' ]
arguments:
strategies: [ partial ] # filtering by string occurrence, not by full match
fields:
name:
type: string
position:
type: integer
data:
type: string
logic:
XCart\Logic\Entity\Storefront\SomeEntity:
doctrineEntity: XLite\Model\SomeEntity
generate_get_one: true
generate_get_collection: true
generate_create_one: true
generate_update_one: true
generate_delete_one: true
use_doctrine_get_one: true
use_doctrine_get_collection: true
use_doctrine_create_one: true
use_doctrine_update_one: true
use_doctrine_delete_one: true
fields:
name:
type: string
position:
type: integer
data:
type: string
dto:
# Class containing the list of filters at the Logic level
XCart\Logic\Action\Storefront\SomeEntity\GetList\DTO\Request\Filter:
type: logic_request_get_list_filter
fields:
substring:
type: StringValue
nullable: true
# Class containing the list of filters at the Data level
XCart\DTO\Doctrine\Storefront\SomeEntity\Filter:
type: doctrine_entity_filter
fields:
substring:
type: StringValue
nullable: true

Changes to services

xcart.data_source.someentity.read:
class: XCart\Bundle\DoctrineBridgeBundle\DataSource\DoctrineReadDataSource
arguments:
$repository: '@xcart.repository.someentity.read'
$collectionClassName: 'XCart\Entity\SomeEntityCollection'
# in the basic case, this service is generated via autowire, but in this case we need a separate service so that the changes
# will be applied to this request only
$queryBuilderFilterEnricher: '@xcart.data_source.someentity.read.query_builder_filter.enricher'
# Service declarations match the standard
xcart.data_source.someentity.read.query_builder_filter.enricher:
class: XCart\Bundle\DoctrineBridgeBundle\Enricher\GetList\Filter\CommonFilterEnricher
arguments:
$filterListAssembler: '@xcart.data_source.someentity.read.query_builder_filter.enricher.filter_list.assembler'
xcart.data_source.someentity.read.query_builder_filter.enricher.filter_list.assembler:
class: XCart\Bundle\DoctrineBridgeBundle\Enricher\GetList\Filter\Assembler\FilterListAssembler
arguments:
$applierResolver: '@xcart.data_source.someentity.read.query_builder_filter.enricher.filter_applier.resolver'
$fieldNameAssembler: '@xcart.data_source.someentity.read.query_builder.enricher.field_name.assembler'
xcart.data_source.someentity.read.query_builder_filter.enricher.filter_applier.resolver:
class: XCart\Bundle\DoctrineBridgeBundle\Enricher\GetList\Filter\Resolver\FilterApplierResolver
arguments:
$appliers: '@xcart.bundle.doctrine_bridge_bundle.action.get_list.enricher.appliers'
xcart.data_source.someentity.read.query_builder.enricher.field_name.assembler:
class: XCart\Bundle\DoctrineBridgeBundle\Enricher\GetList\Assembler\FieldNameAssembler
# Decorator that is used to select a custom Applier
xcart.data_source.someentity.read.query_builder_filter.enricher.filter_applier.resolver.decorator.substring:
class: XCart\Logic\Action\Storefront\SomeEntity\GetList\Resolver\SubstringFilterApplierResolverDecorator
decorates: xcart.data_source.someentity.read.query_builder_filter.enricher.filter_applier.resolver
arguments:
$inner: '@.inner'

Required code:

<?php
namespace XCart\Logic\Action\Storefront\SomeEntity\GetList\Resolver;
use XCart\Bundle\DoctrineBridgeBundle\DTO\Filter\Value\FilterValueInterface;
use XCart\Bundle\DoctrineBridgeBundle\Enricher\GetList\Filter\Applier\ApplierInterface;
use XCart\Bundle\DoctrineBridgeBundle\Enricher\GetList\Filter\Resolver\FilterApplierResolverInterface;
use XCart\DataSource\SomeEntity\Applier\SubstringApplier;
class SubstringFilterApplierResolverDecorator implements FilterApplierResolverInterface
{
public function __construct(
private FilterApplierResolverInterface $inner,
) {
}
public function resolve(string $property, FilterValueInterface $value): ?ApplierInterface
{
if ($property === 'substring') {
return new SubstringApplier();
}
return $this->inner->resolve($property, $value);
}
}
<?php
namespace XCart\DataSource\SomeEntity\Applier;
use XCart\Bundle\DoctrineBridgeBundle\DTO\Filter\Value\StringValue;
use XCart\Bundle\DoctrineBridgeBundle\DTO\Filter\Value\FilterValueInterface;
use XCart\Bundle\DoctrineBridgeBundle\Enricher\GetList\Filter\Applier\ApplierInterface;
use XCart\Bundle\DoctrineBridgeBundle\QueryBuilder\QueryBuilderInterface;
class SubstringApplier implements ApplierInterface
{
/**
* @param StringValue $filterValue
*/
public function apply(string $name, FilterValueInterface $filterValue, QueryBuilderInterface $queryBuilder): QueryBuilderInterface
{
$orCnd = new \Doctrine\ORM\Query\Expr\Orx();
foreach (['entity.name', 'entity.data'] as $field) {
$orCnd->add($field . ' LIKE :substring');
}
return $queryBuilder->andWhere($orCnd)
->setParameter('substring', "%{$filterValue->getValue()}%");
}
}