sajad torkamani

In a nutshell

API Platform lets you implement extensions to conditionally extend queries on items and collections. For example, suppose you have a User and Offer entity, and you want to restrict queries to Offer to return only those that belong to the currently authenticated user. You can use extensions to do this.

Example entities

<?php
// api/src/Entity/User.php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;

#[ApiResource]
class User
{
    // ...
}
<?php
// api/src/Entity/Offer.php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;

#[ApiResource]
class Offer
{
    #[ORM\ManyToOne] 
    public User $user;

    //...
}

Example extension

When querying for offers, you want to limit the results to offers that belong to the currently authenticated user (i.e., Offer->user === current user). You can do this by implementing an extension like so:

<?php
// api/src/Doctrine/CurrentUserExtension.php

namespace App\Doctrine;

use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use App\Entity\Offer;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Security;

final class CurrentUserExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
{
    private $security;

    public function __construct(Security $security)
    {
        $this->security = $security;
    }

    public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null): void
    {
        $this->addWhere($queryBuilder, $resourceClass);
    }

    public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = []): void
    {
        $this->addWhere($queryBuilder, $resourceClass);
    }

    private function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
    {
        if ($resourceClass !== Offer::class|| $this->security->isGranted('ROLE_ADMIN') || $user = $this->security->getUser() === null) {
            return;
        }

        $rootAlias = $queryBuilder->getRootAliases()[0];
        $queryBuilder->andWhere(sprintf('%s.user = :current_user', $rootAlias));
        $queryBuilder->setParameter('current_user', $user->getId());
    }
}

Now, when there’s a request for the Offer collection (e.g., GET /v1/offers), the CurrentUserExtension#applyToCollection method will be applied and the results will be limited to offers where offer.user = <current-user-id>

Other notes

In the example above, the WHERE clause is only applied when an authenticated user without ROLE_ADMIN tries to access a resource. You’ll typically want to ensure that an access control rule is in place that restricts access to unauthenticated users. Something like:

# app/config/package/security.yaml
security:
    # ...
    access_control:
        # ...
        - { path: ^/offers, roles: IS_AUTHENTICATED_FULLY }
        - { path: ^/users, roles: IS_AUTHENTICATED_FULLY }

Sources

Leave a comment

Your email address will not be published. Required fields are marked *