Api-platform : filtrer les résultats uniquement sur l'utilisateur connecté
Tags: security, api, webdev
Published: 2024-06-23 09:52:46

Imaginez avoir dans votre projet plusieurs entités avec la relation `author` comme par exemple ces deux entités suivantes : 

```php
<?php

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use App\Repository\CourierFavoriteRepository;
use Doctrine\ORM\Mapping as ORM; 

#[ORM\Entity(repositoryClass: CourierFavoriteRepository::class)]
#[ApiResource()]
class Book
{ 
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\ManyToOne]
    #[ORM\JoinColumn(nullable: false)] 
    private ?User $author = null; 

    #[ORM\Column(length: 255)] 
    private ?string $title = null;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getAuthor(): ?User
    {
        return $this->author;
    }

    public function setAuthor(?User $author): static
    {
        $this->author = $author;

        return $this;
    } 

    public function getTitle(): ?string
    {
        return $this->title;
    }

    public function setTitle(string $title): self
    {
        $this->title = $title;

        return $this;
    }
}
```

```php
<?php

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use App\Repository\TodoRepository; 
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: TodoRepository::class)]
#[ApiResource()]
class Todo
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\ManyToOne]
    #[ORM\JoinColumn(nullable: false)]
    private ?User $author = null;

    #[ORM\Column(type: Types::TEXT)]
    private ?string $content = null;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getAuthor(): ?User
    {
        return $this->author;
    }

    public function setAuthor(?User $author): static
    {
        $this->author = $author;

        return $this;
    }

    public function getContent(): ?string
    {
        return $this->content;
    }

    public function setContent(string $content): static
    {
        $this->content = $content;

        return $this;
    }
}
```

Nous voulons verrouiller les résultats des GET item et collection uniquement sur l'user connecté.

Nous allons créer une interface que nous implémenterons sur nos entités 

```php
<?php

namespace App\Entity;

interface CurrentUserIsAuthorInterface
{
    public function setAuthor(?User $author): static;
}
```

Maitenant nous allons faire une `DoctrineExtension` qui va ajouter la contrainte `where` dans le `QueryBuilder` sur l'utilisateur connecté. 

```php
<?php

namespace App\DoctrineExtension;

use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use App\Entity\CurrentUserIsAuthorInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bundle\SecurityBundle\Security;

class CurrentUserIsAuthorExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
{
    public function __construct(
        private Security $security,
    ) {
    }

    public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
    {
        $this->currentUserIsAuthor($resourceClass, $queryBuilder);
    }

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

    /**
     * @param string $resourceClass
     * @param QueryBuilder $queryBuilder
     * @return void
     * @throws ReflectionException
     */
    public function currentUserIsAuthor(string $resourceClass, QueryBuilder $queryBuilder): void
    {
        $reflectionClass = new \ReflectionClass($resourceClass);
        if ($reflectionClass->implementsInterface(CurrentUserIsAuthorInterface::class)) {
            $alias = $queryBuilder->getRootAliases()[0];
            $queryBuilder->andWhere("$alias.author = :current_author")
                ->setParameter('current_author', $this->security->getUser()->getId());
        }
    }
}
```

Maintenant il nous reste à implémenter notre interface sur nos entités :

```php
<?php

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use App\Repository\CourierFavoriteRepository;
use Doctrine\ORM\Mapping as ORM; 

#[ORM\Entity(repositoryClass: CourierFavoriteRepository::class)]
#[ApiResource()]
class Book implements CurrentUserIsAuthorInterface
{ 
    // ...
```

```php
<?php

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use App\Repository\TodoRepository; 
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: TodoRepository::class)]
#[ApiResource()]
class Todo implements CurrentUserIsAuthorInterface
{
    // ...
```

Et c'est tout. Pour chaque GET item ou collection vous aurez uniquement les entrées avec l'utilisateurs qui est connecté 🚀 

Lisez aussi "comment enregistrer automatiquement l'utilisateur connecté" : https://dev.to/aratinau/automatisons-lenregistrement-du-user-sur-nimporte-quelle-entite-4f68