API Platform version(s) affected:
/srv/api # composer show | grep api-platform
api-platform/core v2.6.8 Build a fully-featured hypermedia or GraphQL API in minutes!
Description
To define the response of our API endpoints, we have used attributes on the generated Doctrine entity such as:
/**
* @ORM\Table(name = "products")
* @ORM\Entity(repositoryClass=ProductRepository::class)
*/
#[ApiResource(
collectionOperations: [
'get' => [
'path' => '/products',
],
],
itemOperations: [
'get' => [
'path' => '/products/{id}',
],
],
normalizationContext: [
'groups' => [
'product:read',
],
],
output: ProductOutput::class,
)]
class Product {
.... // properties and getters+setters
}
The Product
entity has a 1:n
relation to the Variant
entity which is also a ApiResource
with an different endpoint /variants
. The Variant
entity has several relations to other entities and some values of all entities are translatable with https://github.com/doctrine-extensions/DoctrineExtensions/blob/main/doc/translatable.md.
The performance was as expected => good enough.
Later on, it was required to "enrich" the response of /products
and /variants
with some data, which was not mapped in relations between Product
<> additional-data | Variant
<> additional-data, so we decided to use Outputs DTO with DataTransformers, as documented in the API-Platform docs.
The DataTransformer's method transform
puts the data into the DTO by using therespective getters of the entities, e. g.:
$output = new ProductOutput();
$output->id = $object->getId();
$output->category = null !== $object->getCategory() ?
$this->iriConverter->getIriFromItem($object->getCategory()) :
'';
$output->identifierValue = $object->getIdentifierValue();
$output->manufacturer = $object->getManufacturer();
$output->variants = $object->getVariants();
The $object
is a Product
entity, in this case.
The DTO contains only public properties, such as
/**
* @var Collection<int, Variant>
*/
#[Groups(['product:read'])]
public Collection $variants;
and the Groups
attributes, which are also defined in the normalizationContext
of the ApiResource
attribute in the Product
entity above.
After that, we found the performance had drastically deteriorated: A request to the /products
endpoint which "lists" 30 products
with the related variants
needs around 25 seconds.
After analyzing, we determined the following:
Eager-Fetching
(see https://api-platform.com/docs/core/performance/#force-eager), but it seems so that will be ignored if the getters
of a entity are used in the DTO.In a try to reduce the Doctrine queries, we created a DataProvider to fetch the related data. This actually worked, as using the DataProvider reduced the number of queries to +/-
50, but the serialization process also needed around 25s. So the cause of the performance problem does not seem to be the lazy-loading of doctrine, which is now done.
The question is: Why is using a DTO so much slower how would it be it possible to get performance back to an acceptable level?
It was not possible to improve performance when using a DTO for this data-structure. Instead of a DTO and data transformer we used a Doctrine Event Listener (postLoad
) to set the value which is a unmapped property (Doctrine/Symfony: Entity with non-mapped property) now.