Grapic dots

Doctrine Extensions in API Platform

Mike Milano

API Platform provides a way to create your own Doctrine Extensions, allowing you to modify collection and item queries for API resources.

The power here is that you can customize or create conditional query logic on the queries generated by the default controllers/operations API platform provides. There’s no need to write a custom controller if you simply want to tweak the underlying query.

Use Case

Imagine an app with a Product entity which has a boolean active property. You want to restrict results to only active products for everyone except administrators.

  1. <?php
  2. // src/Entity/Product.php
  3.  
  4. namespace App\Entity;
  5.  
  6. use ApiPlatform\Core\Annotation\ApiResource;
  7.  
  8. /**
  9.  * @ApiResource
  10.  */
  11. class Product
  12. {
  13. //...
  14.  
  15. /**
  16.   * @var boolean
  17.   * @ORM\Column(type="boolean", nullable=false)
  18.   */
  19. private $active = true;
  20.  
  21. //...
  22. }

The Solution

  1. Create the extension class
  2. Create a service for the class

Creating the Extension Class

  1. Create the directory: src/Doctrine
  2. Add a new file: ActiveProductExtension.php
  3. Copy the source below into the new file

The extension class implements QueryCollectionExtensionInterface and QueryItemExtensionInterface which forces us to create the applyToCollection() and applyToItem() methods.

If you were only concerned with results returned from a collection, you may only implement QueryCollectionExtensionInterface.

In addition to the 2 methods required by the interface, there is also an addWhere() method. It is called from both apply* methods and is where the real work happens.

  1. <?php
  2. // src/Doctrine/ActiveProductExtension.php
  3. // Adapted from the API Platform CurrentUserExtension example.
  4.  
  5. namespace App\Doctrine;
  6.  
  7. use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
  8. use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface;
  9. use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
  10. use App\Entity\Product;
  11. use App\Entity\User;
  12. use Doctrine\ORM\QueryBuilder;
  13. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  14. use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
  15.  
  16. final class ActiveProductExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
  17. {
  18. private $tokenStorage;
  19. private $authorizationChecker;
  20.  
  21. public function __construct(TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $checker)
  22. {
  23. $this->tokenStorage = $tokenStorage;
  24. $this->authorizationChecker = $checker;
  25. }
  26.  
  27. /**
  28.   * {@inheritdoc}
  29.   */
  30. public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
  31. {
  32. $this->addWhere($queryBuilder, $resourceClass);
  33. }
  34.  
  35. /**
  36.   * {@inheritdoc}
  37.   */
  38. public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = [])
  39. {
  40. $this->addWhere($queryBuilder, $resourceClass);
  41. }
  42.  
  43. /**
  44.   *
  45.   * @param QueryBuilder $queryBuilder
  46.   * @param string $resourceClass
  47.   */
  48. private function addWhere(QueryBuilder $queryBuilder, string $resourceClass)
  49. {
  50. $user = $this->tokenStorage->getToken()->getUser();
  51.  
  52. // Add the where clause if we're operating on a Product resource, and the user is not an admin.
  53. if ($user instanceof User && Product::class === $resourceClass && !$this->authorizationChecker->isGranted('ROLE_ADMIN')) {
  54. $rootAlias = $queryBuilder->getRootAliases()[0];
  55. $queryBuilder->andWhere(sprintf('%s.active = true', $rootAlias));
  56. }
  57. }
  58. }

Creating the Service

With the extension class created, all that is left is to create the service.

Add the following to: config/services.yaml.

This assumes you have autowire enabled by default.

  1. services:
  2.  
  3. 'App\Doctrine\CurrentProductExtension':
  4. tags:
  5. - { name: api_platform.doctrine.orm.query_extension.collection, priority: 9 }
  6. - { name: api_platform.doctrine.orm.query_extension.item }

Result

With the extension and service in place, /api/products and /api/products/{id} will only return records which have active set to true, unless the user has the role ROLE_ADMIN.

For more information on Doctrine Extensions with API Platform, visit the docs.

8 Reasons Why Standardizing on Drupal is the Right Choice for Enterprise.
Read about it in this free white paper.

Download for Free Now!