根据用户角色将一条路由绑定到不同的控制器

时间:2012-04-17 11:25:29

标签: symfony

在我的Symfony 2应用程序中,我有3个不同的用户角色,可以访问后端管理部分:

role_hierarchy:
    ROLE_STAFF:     ROLE_USER
    ROLE_MODERATOR: ROLE_STAFF
    ROLE_ADMIN:     ROLE_MODERATOR

对于像http://example.org/admin/post/这样的路线,我希望我的应用根据用户角色显示不同的信息,这意味着 3个控制器绑定到唯一的路径

处理此问题的最佳方法是什么?

我正在考虑一些解决方案,但似乎没有一个对我好:

  1. 一个控制器,在每个操作中我只测试用户角色:

    <?php
    
    /**
     * @Route("/admin/post")
     */
    class PostController extends Controller
    {
        /**
         * Lists all post entities.
         *
         * @Route("/", name="post_index")
         * @Template()
         * @Secure(roles="ROLE_STAFF")
         */
        public function indexAction()
        {
            $user = $this->get('security.context')->getToken()->getUser();
    
            if ($this->get('security.context')->isGranted('ROLE_STAFF')) {
                // Do ROLE_STAFF related stuff
            } else if ($this->get('security.context')->isGranted('ROLE_MODERATOR')) {
                // Do ROLE_MODERATOR related stuff
            } else if ($this->get('security.context')->isGranted('ROLE_ADMIN')) {
                // Do ROLE_ADMIN related stuff
            }
    
            return array('posts' => $posts);
        }
    }
    

    即使这样做,IMO显然也不是一个好的设计。

  2. 一个BackendController调度到3个不同的控制器:

    <?php
    
    /**
     * @Route("/admin/post")
     */
    class PostBackendController extends Controller
    {
        /**
         * Lists all post entities.
         *
         * @Route("", name="admin_post_index")
         * @Template("AcmeBlogBundle:PostAdmin:index.html.twig")
         * @Secure(roles="ROLE_STAFF")
         */
        public function indexAction()
        {
            if ($this->get('security.context')->isGranted('ROLE_STAFF')) {
                $response = $this->forward('AcmeBlogBundle:PostStaff:index');
            } else if ($this->get('security.context')->isGranted('ROLE_MODERATOR')) {
                $response = $this->forward('AcmeBlogBundle:PostModerator:index');
            } else if ($this->get('security.context')->isGranted('ROLE_ADMIN')) {
                $response = $this->forward('AcmeBlogBundle:PostAdmin:index');
            }
    
            return $response;
        }
    }
    

    与第一名相同。

  3. 我试图让控制器相互扩展:

    <?php
    
    /**
     * @Route("/admin/post")
     */
    class PostStaffController extends Controller
    {
        /**
         * Lists all post entities.
         *
         * @Route("/", name="post_index")
         * @Template()
         * @Secure(roles="ROLE_STAFF")
         */
        public function indexAction()
        {
            $user = $this->get('security.context')->getToken()->getUser();
    
            // Do ROLE_STAFF related stuff
    
            return array('posts' => $posts);
        }
    }
    
    <?php
    
    /**
     * @Route("/admin/post")
     */
    class PostModeratorController extends PostStaffController
    {
        /**
         * Lists all post entities.
         *
         * @Route("/", name="post_index")
         * @Template()
         * @Secure(roles="ROLE_MODERATOR")
         */
        public function indexAction()
        {
            $user = $this->get('security.context')->getToken()->getUser();
    
            // As PostModeratorController extends PostStaffController,
            // I can either use parent action or redefine it here
    
            return array('posts' => $posts);
        }
    }
    
    <?php
    
    /**
     * @Route("/admin/post")
     */
    class PostAdminController extends PostModeratorController
    {
        /**
         * Lists all post entities.
         *
         * @Route("/", name="post_index")
         * @Template()
         * @Secure(roles="ROLE_ADMIN")
         */
        public function indexAction()
        {
            $user = $this->get('security.context')->getToken()->getUser();
    
            // Same applies here
    
            return array('posts' => $posts);
        }
    }
    

    IMO这是一个更好的设计,但我无法让它成功。路由系统在匹配的第一个控制器上停止。我想让它自动成为级联风格的王者(即如果用户是工作人员则转到PostStaffController,否则如果用户是主持人,请转到PostModeratorController,否则转到PostAdminController)。

  4. 在我的BlogBu​​ndle中为kernel.controller添加一个监听器,它将完成与2号相同的工作?

  5. 我正在寻找最好的设计和更灵活的解决方案,我们有可能在未来添加更多角色。

4 个答案:

答案 0 :(得分:1)

恕我直言,你不会根据角色为相同的路线发射不同的控制器。这只是不同的责任。路由用于选择控制器,角色用于特权。一年后你将不记得这个伎俩,即。当你尝试添加新角色时。

当然,不同角色的不同内容问题经常出现,所以我最喜欢的解决方案是:

  1. 当不同角色的控制器差异很大时,我会在需要时使用不同的路由并重定向。
  2. 当控制器相似但内容不同时,即。不同的数据库查询条件,我使用类似于你的解决方案2.但转而使用来自同一控制器的私有/受保护方法来制作作业。有一个黑客 - 你必须从上到下检查角色,即。首先检查ROLE_ADMIN,下一个ROLE_OPERATOR和最后一个ROLE_STAFF,因为当你的ROLE_ADMIN继承自ROLE_STAFF,然后阻止用户捕获它。
  3. 如果区别在于应该为不同角色显示/隐藏的某些信息块,我会留在一个控制器并检查模板中的角色以确定是否渲染了块。

答案 1 :(得分:0)

第二个解决方案的自动化版本怎么样?像:

    // Roles ordered from most to least significant (ROLE_ADMIN -> ROLE_MODERATOR -> etc)
    $roles = $myUserProvider->getRoles();
    foreach ($roles as $role) {
        // add a check to test, if the function you're calling really exists
        $roleName = ucfirst(strtolower(mb_substr($role, 0, 5)));
        $response = $this->forward(sprintf('AcmeBlogBundle:Post%s:index', $roleName))

        break;
    }

    // Check that $response is not null and do something with it ...

由于我没有您的设置,我没有测试上面的代码。 顺便问一下:发布不同方法的区别是什么?

答案 2 :(得分:0)

请参阅http://symfony.com/doc/current/book/internals.html#kernel-controller-event

应该做的,并确保注入security.context服务

答案 3 :(得分:0)

vendor/symfony/symfony/src/Symfony/Component/Routing/Router.php

中的

可以选择替换matcher_class中可能存在的config.yml

如果你继承UrlMatcher和overRide matchRequest,它将优先于Path匹配(仅限url)。

matchRequest接受参数$ request(请求对象)

Request对象应包含安全提供程序侦听器在路由器侦听器之前运行的用户信息,并允许您通过组合URL和用户角色来选择路由。路由存储在按名称索引的数组中,因此名称必须不同。

您可以使用post_index[USER] post_index[STAFF] post_index[MODERATOR]

等名称

为了生成{{ path('post_index', {...}) }}的网址,您还需要替换URLGenerator的子类,并使用generator_class选项将其注入路由器。