从MenuBuilder通过RequestVoter访问Container或securityContext或EntityManager

时间:2014-10-02 04:46:21

标签: php symfony dependency-injection knpmenubundle knpmenu

我发现这段代码在Gist中共享(某处我丢失了链接),我需要这样的东西,所以我开始在我的应用程序中使用,但我还没有完全理解,因此我遇到了一些问题。

我正在尝试使用KnpMenuBundle和动态方法创建动态菜单,在某些时候我必须通过数据库验证访问权限,如果我能从控制器读取路由这将是理想的,但这是另一项任务,也许创建一个注释我能做到这一点但是我会在那个时候开启另一个话题。

现在我需要访问SecurityContext以检查用户是否已登录但不知道如何。

我通过RequestVoter(我认为)渲染菜单,这是代码:

namespace PlantillaBundle\Menu;

use Knp\Menu\ItemInterface;
use Knp\Menu\Matcher\Voter\VoterInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\SecurityContextInterface; 

class RequestVoter implements VoterInterface {

    private $container;

    private $securityContext; 

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

    public function matchItem(ItemInterface $item)
    {
        if ($item->getUri() === $this->container->get('request')->getRequestUri())
        {
            // URL's completely match
            return true;
        }
        else if ($item->getUri() !== $this->container->get('request')->getBaseUrl() . '/' && (substr($this->container->get('request')->getRequestUri(), 0, strlen($item->getUri())) === $item->getUri()))
        {
            // URL isn't just "/" and the first part of the URL match
            return true;
        }
        return null;
    }

}

我添加了与securityContext相关的所有代码,试图从menuBuilder开始使用它。现在这是我正在制作菜单的代码:

namespace PlantillaBundle\Menu;

use Knp\Menu\FactoryInterface;
use Symfony\Component\DependencyInjection\ContainerAware;

class MenuBuilder extends ContainerAware {

    public function mainMenu(FactoryInterface $factory, array $options)
    {
        // and here is where I need to access securityContext 
        // and in the near future EntityManger

        $user = $this->securityContext->getToken()->getUser();
        $logged_in = $this->securityContext->isGranted('IS_AUTHENTICATED_FULLY');

        $menu = $factory->createItem('root');
        $menu->setChildrenAttribute('class', 'nav');

        if ($logged_in)
        {
            $menu->addChild('Home', array('route' => 'home'))->setAttribute('icon', 'fa fa-list');
        }
        else
        {
            $menu->addChild('Some Menu');
        }

        return $menu;
    }     

}

但是这是完全错误的,因为我没有将securityContext传递给方法,我不知道该怎么做,而且我收到了这个错误:

  

在渲染模板期间抛出了异常   (“通知:未定义的属性:   PlantillaBundle \ Menu \ MenuBuilder :: $ securityContext in   /var/www/html/src/PlantillaBundle/Menu/MenuBuilder.php第12行“)in   /var/www/html/src/PlantillaBundle/Resources/views/menu.html.twig at   第2行。

选民在services.yml中定义如下:

plantilla.menu.voter.request:
    class: PlantillaBundle\Menu\RequestVoter
    arguments:
        - @service_container
        - @security.context
    tags:
        - { name: knp_menu.voter }

那么,我如何注入securityContext(我不会要求EntityManager,因为我认为将是相同的过程)并从menuBuilder访问它?

更新:重构代码

所以,按照@Cerad的建议我做了这个改动:

services.yml

services:
    plantilla.menu_builder:
        class: PlantillaBundle\Menu\MenuBuilder
        arguments: ["@knp_menu.factory", "@security.context"]

    plantilla.frontend_menu_builder:
        class: Knp\Menu\MenuItem # the service definition requires setting the class
        factory_service: plantilla.menu_builder
        factory_method: createMainMenu
        arguments: ["@request_stack"]
        tags:
            - { name: knp_menu.menu, alias: frontend_menu } 

MenuBuilder.php

namespace PlantillaBundle\Menu;

use Knp\Menu\FactoryInterface;
use Symfony\Component\HttpFoundation\RequestStack;

class MenuBuilder {

    /**
     * @var Symfony\Component\Form\FormFactory $factory
     */
    private $factory;

    /**
     * @var Symfony\Component\Security\Core\SecurityContext $securityContext
     */
    private $securityContext;

    /**
     * @param FactoryInterface $factory
     */
    public function __construct(FactoryInterface $factory, $securityContext)
    {
        $this->factory = $factory;
        $this->securityContext = $securityContext;
    }

    public function createMainMenu(RequestStack $request)
    {
        $user = $this->securityContext->getToken()->getUser();
        $logged_in = $this->securityContext->isGranted('IS_AUTHENTICATED_FULLY');

        $menu = $this->factory->createItem('root');
        $menu->setChildrenAttribute('class', 'nav');

        if ($logged_in)
        {
            $menu->addChild('Home', array('route' => 'home'))->setAttribute('icon', 'fa fa-list');
        }
        else
        {
            $menu->addChild('Some Menu');
        }

        return $menu;
    }

}

Abd ib我的模板只是渲染菜单{{ knp_menu_render('frontend_menu') }}但现在我松开了FontAwesome部分,在它工作之前,为什么?

2 个答案:

答案 0 :(得分:2)

您的菜单构建器为ContainerAware,因此我猜您应该通过SecurityContext访问$this->getContainer()->get('security.context')

您还没有为选民班级提供任何用例,因此我猜测您没有使用matchItem方法。

您绝对应该尝试重新构建服务,以便明确依赖性。

答案 1 :(得分:1)

根据您的评论请求,以下是您的菜单构建器的外观:

namespace PlantillaBundle\Menu;

use Knp\Menu\FactoryInterface;

class MenuBuilder {

    protected $securityContext;

    public function __construct($securityContext)
    {
        $this->securityContext = $securityContext;
    }
    public function mainMenu(FactoryInterface $factory, array $options)
    {
        // and here is where I need to access securityContext 
        // and in the near future EntityManger

        $user = $this->securityContext->getToken()->getUser();
        ...

// services.yml
plantilla.menu.builder:
    class: PlantillaBundle\Menu\MenuBuilder
    arguments:
        - '@security.context'

// controller
$menuBuilder = $this->container->get('plantilla.menu.builder');

请注意,由于您只需要安全上下文服务,因此无需使用构建器容器。您当然可以注入实体管理器。

================================

关于投票人的东西,现在你只是检查一下用户是否登录。所以没有选民的真正需要。但是假设某些用户(管理员等)可以访问其他菜单项。您可以将所有安全检查逻辑移动到选民。您的菜单构建器代码可能如下所示:

if ($this->securityContext->isGranted('view','homeMenuItem')
{
    $menu->addChild('Home', array('route' ...

换句话说,你可以获得更好的控制权来获取哪个菜单项。

但是,首先让你的MenuBuilder工作,然后根据需要添加选民的东西。