Home > database >  Symfony 6 Doctrine Extension
Symfony 6 Doctrine Extension

Time:02-02

I'm a beginner on Sf6 and i'm stuck on a problem with the doctrine extension. I try to recover some datas from an API and send them to a front-end Angular 13. My personnal project is an application for manage some garden equipments and i look for to recover datas according to the role of the user.

If the current user have ['ROLE_USER'] i want to fetch his owns datas but if the user have ['ROLE_ADMIN'] i want to fetch all the datas for this entity. I'm ever able to do it with my entity Garden but not for the equipments entity. I give you my relationnal logical data model:

1

And the code for CurrentUserExtension.php :

<?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\Garden;
use App\Entity\Watering;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Security;

/**
 * This extension makes sure normal users can only access their own Gardens
 */
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 (Garden::class !== $resourceClass 
            || $this->security->isGranted('ROLE_ADMIN') 
            || null === $user = $this->security->getUser()) {
            return;
        }
        $rootAlias = $queryBuilder->getRootAliases()[0];
        $queryBuilder->andWhere(sprintf('%s.user = :current_user', $rootAlias));
        $queryBuilder->setParameter('current_user', $user->getId());
    }
}

It's my first post on Stackoverflow so feel free to say me if my post isn't formated as well. Some help will be appreciated. Ps: As you could see in the final class CurrentUserExtension.php i'm using Api Platform.

CodePudding user response:

Your problem can be solved with a "conception" change. I would say, do no try to make a single api url with different behaviors based on user.
To make this work i advise you to do something like this :

Be carefull, my answer is on Symfony 6.2, Php 8 and Api Platform 3

#[ApiResource(
    operations: [
        new GetCollection(),
        new GetCollection(
            uriTemplate: "/gardens/my-gardens"
            security: "is_granted('ROLE_USER')"
            controller: MyGardenController.php
        )
    ],
    security: "is_granted('ROLE_ADMIN')"
)]
class Garden {}

And inside

class MyGardenController extends AbstractController
{

    public function __construct(
        private Security $security,
        private GardenRepository $gardenRepository,
    )
    {}

    public function __invoke()
    {
        return $this->gardenRepository->findBy(['user' => $this->security->getUser());
    }
}

So ! What does it do.
By default, Garden entity is only accessible to ADMIN.
So by default /api/gardens cant be accessed by non admin user.
But, /api/gardens/my-gardens as a custom controller which return only garden that are link to the current connected user.
On your front end, just call a different endpoint based on user role.

But if you want to keep one endpoint, you could do this inside the custom controller :

public function __invoke()
{

    if($this->isGranted('ROLE_ADMIN')){
       return $this->gardenRepository->findAll();
    }

    return $this->gardenRepository->findBy(['user' => $this->security->getUser());
}

CodePudding user response:

Thanks a lot for your answer but unfortunately I think you don't have understand what i ask. According to the documentation of Api Platform (https://api-platform.com/docs/core/extensions/) i'm able to fetch gardens depending of the user role, the final class CurrentUserExtension work as expected. I'm looking for doing the same for the equipments entities (Watering, Lawnmower, Pool, Portal and Lightning). For example one user own two gardens, one of these gardens have two waterings and the others have 5 waterings. I want to fetch all the waterings that belongs to a user (and even if themselves belongs to 2 differents gardens). So in my example my endpoint api/waterings have to recover 7 waterings. But if the user have the role admin i want to recover all the waterings existing in the database. Notice the relation between my entities (one-to-many):

  • A User could have many gardens but a Garden could belong to a single User.
  • A Garden could have many waterings but a Watering could belong to a single Garden.

I just saw there is an error on my relationnal logical data model: the entities Lawnmower, Pool, Portal and Lightning doesn't have the property garden_user_id in their classe. But the entity Watering is ok, i have just a single foreign key garden_id.

  • Related