【问题标题】:Symfony4 extends controller with route annotationSymfony4 使用路由注释扩展控制器
【发布时间】:2018-10-17 15:08:37
【问题描述】:

我正在使用 Symfony 构建一个 web 应用程序,从现在起我不得不为我构建的每个新控制器重复一个特定的模式。

例如我有这个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 时继续复制/粘贴这些注释?

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

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

【问题讨论】:

  • 哪个版本的 Symfony?
  • 对不起,Symfony4
  • 请出示Company模型属性声明。
  • 您想要公司的全部财产吗?这有什么关系?
  • 您可以通过目录将具有相同合约的控制器分开,然后在您的 routes.yml 中添加您的需求。

标签: php symfony annotations symfony4


【解决方案1】:

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

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


路由

这是最简单的。您可以在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',
        );
    }
}

【讨论】:

  • 对于路由,是否可以只对少数 Controller 进行?痛点正是我只需要某些控制器的所有这些。
  • 您可以将这些控制器移动到一个目录中,并在您的annotations.yaml 文件中创建第二个配置条目。我编辑了我的答案以提供一个例子。
  • 当然,我的错。但是对于ParamConverter 部分。 Symfony 告诉我Unable to guess how to get a doctrine...
  • 我觉得还是得用@ParamConverter注解吧?
猜你喜欢
  • 2023-03-18
  • 1970-01-01
  • 1970-01-01
  • 2012-04-12
  • 1970-01-01
  • 2016-03-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多