Symfony4 使用路由注解扩展控制器

Symfony4 extends controller with route annotation

我正在用 Symfony 构建一个网络应用程序,从现在开始我必须为我构建的每个新控制器重复一个特定的模式。

例如我有这个 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){}
 }

所以,每个控制器都必须重新定义@Route@ParamConverter@Security,这是非常多余的。

我试图创建一个定义每个注释的 LoggedController,然后使 Controller 扩展 LoggedController,但这不起作用。

是否有解决方案,或者我是否应该在每次创建需要实施的新 Controller 时继续 copy/paste 这些注释?

编辑: 我添加了 Company 实体的声明:

/**
 * @ORM\Entity(repositoryClass="App\Repository\CompanyRepository")
 */
 class Company
 {
   /**
    * @ORM\Id()
    * @ORM\GeneratedValue()
    * @ORM\Column(type="integer")
    */
    private $id;

长话短说,你可以,但在每个控制器中复制注释会容易得多。

但如果您不想这样做,这里有一些解决方案。


路由

这很简单。您可以在 config/routes/annotations.yaml 文件中定义全局前缀。

如果您使用的是默认配置,您可以尝试这样的操作:

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

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

您的所有路线现在都将以 /pro/{uniqid} 开头,您可以从控制器中删除 @Route 注释。


参数转换器

您可以创建自己的 ParamConverter。每次您在操作方法中使用 Company 类型时,都会使用 uniqid 属性将其转换为匹配的实体。

像这样:

// 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;
    }
}

有了这个,您可以从控制器中删除 @ParamConverter 注释。

安全

您不能使用 security.yaml 文件的 access_control 部分,因为尚不支持自定义函数。

否则,这样的事情本来不错:

security:
    ...

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

(注意:已在 Symfony 4.1 中修复,但我还不知道它将如何工作。

相反,您可以使用订阅者监听 kernel.request 内核事件:

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