【问题标题】:Symfony2: Get controller as a service classname from service name or Route objectSymfony2:从服务名称或路由对象获取控制器作为服务类名
【发布时间】:2017-02-13 11:32:51
【问题描述】:

我有一些控制器定义为服务,我需要从路由名称中获取控制器的类名。

对于非服务控制器,我可以通过路由器服务获取路由集合:

$route = $this->router->getRouteCollection()->get($routeName);
//Retrieve an object like that:

Route {
  -path: "/admin/dashboard"
  -host: ""
  -schemes: []
  -methods: []
  -defaults: array:1 [
    "_controller" => "AppBundle\Controller\Admin\AdminController::dashboardAction"
  ]
  -requirements: []
  -options: array:1 []
  -compiled: null
  -condition: ""
}

我可以使用$route["defaults"]["_controller"] 访问控制器类名,所以这很好。

问题在于我的控制器作为服务,_controller 属性是服务的名称,而不是控制器类(如app.controller.admin.user:listAction)我有服务的名称,但我需要有类名(AppBundle\Controller\Admin\UserController )

我想出的唯一解决方案是从容器中获取服务并在服务上使用get_class(),但这只会对检索控制器/服务的类产生巨大的性能影响。

还有其他解决办法吗?

【问题讨论】:

  • 我相信没有任何其他替代方案会更高效。你需要对类名做什么?
  • 我正在尝试复制本教程:trisoft.ro/blog/6-symfony2-advanced-menus,我需要 className 才能读取元数据:$this->metadataReader->loadMetadataForClass(new \ReflectionClass($class));跨度>
  • 我想你可以在你的路由中添加一个 _controller_classname 参数。但是需要控制器类名来生成菜单似乎不是理想的设计。
  • 我只想在一个地方设置授权。如果我将我的管理网站的报告部分限制为一个角色,我不想复制此配置来为无权访问的人隐藏菜单。上面的解决方案允许我使用 @Security 注释并使用此信息来隐藏我的菜单的特定部分。
  • 我认为调用服务并调用 get_class() 是可行的方法,但您可以在此之上添加一个缓存层。

标签: symfony service controller containers


【解决方案1】:

按照https://github.com/FriendsOfSymfony/FOSUserBundle/issues/2751 中的建议,我实现了一个缓存映射,以将路由名称解析为控制器类和方法。

<?php
// src/Cache/RouteClassMapWarmer.php
namespace App\Cache;

use Symfony\Component\Cache\Simple\PhpFilesCache;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
use Symfony\Component\Routing\RouterInterface;

class RouteClassMapWarmer implements CacheWarmerInterface
{
    /** @var ContainerInterface */
    protected $container;
    /** @var RouterInterface */
    protected $router;

    public function __construct(ContainerInterface $container, RouterInterface $router)
    {
        $this->container = $container;
        $this->router = $router;
    }

    public function warmUp($cacheDirectory)
    {
        $cache = new PhpFilesCache('route_class_map', 0, $cacheDirectory);
        $controllers = [];
        foreach ($this->router->getRouteCollection() as $routeName => $route) {
            $controller = $route->getDefault('_controller');
            if (false === strpos($controller, '::')) {
                list($controllerClass, $controllerMethod) = explode(':', $controller, 2);
                // service_id gets resolved here
                $controllerClass = get_class($this->container->get($controllerClass));
            }
            else {
                list($controllerClass, $controllerMethod) = explode('::', $controller, 2);
            }
            $controllers[$routeName] = ['class' => $controllerClass, 'method' => $controllerMethod];
        }
        unset($controller);
        unset($route);
        $cache->set('route_class_map', $controllers);
    }

    public function isOptional()
    {
        return false;
    }
}

在我的 RouteHelper 中,读取这个的实现看起来像这样

    $cache = new PhpFilesCache('route_class_map', 0, $this->cacheDirectory);
    $controllers = $cache->get('route_class_map');
    if (!isset($controllers[$routeName])) {
        throw new CacheException('No entry for route ' . $routeName . ' forund in RouteClassMap cache, please warmup first.');
    }

    if (null !== $securityAnnotation = $this->annotationReader->getMethodAnnotation((new \ReflectionClass($controllers[$routeName]['class']))->getMethod($controllers[$routeName]['method']), Security::class))
    {
        return $this->securityExpressionHelper->evaluate($securityAnnotation->getExpression(), ['myParameter' => $myParameter]);
    }

这应该比在每次请求时获取 routeCollection 并针对容器解析 service_id:method 标记的 _controller-properties 快得多。

【讨论】:

    猜你喜欢
    • 2013-05-19
    • 2016-05-06
    • 1970-01-01
    • 1970-01-01
    • 2014-03-21
    • 2020-05-06
    • 1970-01-01
    • 1970-01-01
    • 2019-02-12
    相关资源
    最近更新 更多