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',
);
}
}
我正在用 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',
);
}
}