简体   繁体   中英

Symfony4 extends controller with route annotation

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;

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',
        );
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM