0

I'm building a webapp with Symfony and since now I had to repeat a specific pattern for each new controller I built.

For example I have this AdminController :

/**
 * @Route("/pro/{uniqid}")
 * @ParamConverter("company", options={"mapping":{"uniqid" = "uniqid"}})
 * @Security("is_granted(constant('App\\Security\\Voter\\CompanyVoter::VIEW'), company)")
 * @package App\Controller
 */
 class AdminController extends Controller
 {
    /**
     * @Route("/admin/users/", name="users")
     * @return \Symfony\Component\HttpFoundation\Response
     */
     public function users(Company $company){}
 }

So, each controller must redefine @Route, @ParamConverter and @Security that is extremely redundant.

I tried to create a LoggedController that define every annotation, then make Controller extends that LoggedController, but that does not work.

Is there a solution or should I continue to copy/paste these Annotation each time I create a new Controller that needs to implement it ?

EDIT : I add the declaration of Company entity :

/**
 * @ORM\Entity(repositoryClass="App\Repository\CompanyRepository")
 */
 class Company
 {
   /**
    * @ORM\Id()
    * @ORM\GeneratedValue()
    * @ORM\Column(type="integer")
    */
    private $id;
8
  • Which version of Symfony? Commented May 7, 2018 at 19:07
  • Sorry, Symfony4 Commented May 7, 2018 at 19:18
  • Please show the Company model properties declarations. Commented May 7, 2018 at 19:20
  • You want the whole properties of Company ? How is that relevant ? Commented May 7, 2018 at 19:29
  • 1
    You can separate controllers that have the same contracts by directories, and then in your routes.yml add your requirements. Commented May 8, 2018 at 12:32

1 Answer 1

5

Long story short, you can but it will be a lot easier to duplicate your annotations in every controller.

But if you wan't to do this anyway, here are some solutions.


Routing

This is the easy one. You can define a global prefix in the config/routes/annotations.yaml file.

If you're using the default config, you can try something like this:

# Default controllers
controllers:
    resource: ../../src/Controller/
    type: annotation

# Company controllers
company_controllers:
    resource: ../../src/Controller/Company/
    type: annotation
    prefix: /pro/{uniqid}

All your routes will now start with /pro/{uniqid} and you can remove the @Route annotation from your controller.


ParamConverter

You can create your own ParamConverter. Everytime you'll use a Company type in an action method, it'll be converted to the matching entity using the uniqid attribute.

Something like this:

// src/ParamConverter/CompanyConverter.php
<?php

namespace App\ParamConverter;

use App\Entity\Company;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface;
use Symfony\Component\HttpFoundation\Request;

class CompanyConverter implements ParamConverterInterface
{
    const CONVERTER_ATTRIBUTE = 'uniqid';

    /**
     * @var EntityManagerInterface
     */
    private $entityManager;

    /**
     * CompanyConverter constructor.
     *
     * @param EntityManagerInterface $entityManager
     */
    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    /**
     * @inheritdoc
     */
    public function apply(Request $request, ParamConverter $configuration)
    {
        $uniqid = $request->attributes->get(self::CONVERTER_ATTRIBUTE);

        $company = $this->entityManager->getRepository(Company::class)->findOneBy(['uniqid' => $uniqid]);

        $request->attributes->set($configuration->getName(), $company);
    }

    /**
     * @inheritdoc
     */
    function supports(ParamConverter $configuration)
    {
        return $configuration->getClass() === Company::class;
    }
}

With this, you can remove the @ParamConverter annotation from your controller.

Security

You can't use the access_control section of the security.yaml file since custom functions are not yet supported.

Otherwise, something like this could have been nice:

security:
    ...

    access_control:
        -
            path: ^/pro
            allow_if: "is_granted(constant('App\\Security\\Voter\\CompanyVoter::VIEW'), company)"

(Note: It was fixed in Symfony 4.1 but i don't know yet how it will work).

Instead, you can use a subscriber listening on the kernel.request kernel event:

<?php

namespace App\Subscriber;

use App\Entity\Company;
use App\Security\CompanyVoter;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;

class SecurityListener implements EventSubscriberInterface
{
    /**
     * @var AuthorizationCheckerInterface
     */
    private $authorizationChecker;

    /**
     * @var EntityManagerInterface
     */
    private $entityManager;

    /**
     * @param AuthorizationCheckerInterface $authorizationChecker
     * @param EntityManagerInterface $entityManagerInterface
     */
    public function __construct(AuthorizationCheckerInterface $authorizationChecker, EntityManagerInterface $entityManager)
    {
        $this->authorizationChecker = $authorizationChecker;
        $this->entityManager = $entityManager;
    }

    /**
     * @param GetResponseEvent $event
     */
    public function onKernelRequest(GetResponseEvent $event)
    {
        $request = $event->getRequest();

        if (!$uniqid = $request->attributes->get('uniqid')) {
            return;
        }

        $company = $this->entityManager->getRepository(Company::class)->findOneBy(['titre' => $uniqid]);

        if (!$this->authorizationChecker->isGranted(CompanyVoter::VIEW, $company)) {
            throw new AccessDeniedHttpException();
        }
    }

    /**
     * @return array
     */
    public static function getSubscribedEvents()
    {
        return array(
            KernelEvents::REQUEST => 'onKernelRequest',
        );
    }
}
Sign up to request clarification or add additional context in comments.

9 Comments

For routing, is it possible to do it only for a few Controller ? The pain point is precisely that I need all of that only for certain Controllers.
You can move these controllers inside a directory and create a second configuration entry in your annotations.yaml file. I edited my answer to provide an example.
Of course, my bad. But for the ParamConverter part. Symfony tells me Unable to guess how to get a doctrine...
I think I still have to use @ParamConverter annotation, right ?
I was thinking of something like stackoverflow.com/questions/32398724/…
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.