Доступ к контейнеру или securityContext или EntityManager из MenuBuilder через RequestVoter

Я нашел этот фрагмент кода в 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 методу, и я не знаю, как это сделать, и я получаю эту ошибку:

Во время рендеринга шаблона возникло исключение ("Примечание: свойство Undefined: PlantillaBundle\Menu\MenuBuilder::$securityContext в /var/www/html/src/PlantillaBundle/Menu/MenuBuilder.php, строка 12") в / var/www/html/src/PlantillaBundle/Resources/views/menu.html.twig в строке 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 и до того, как она заработает, почему?


person ReynierPM    schedule 02.10.2014    source источник
comment
Простое создание MenuBuilder ContainerAware не приводит к автоматическому внедрению контейнера. Вам нужно сделать: $menuBuilder->setContainer($container) после создания экземпляра. Еще лучше сделать MenuBuilder службой и добавить вызовы: [['setContainer', ['@service_container']]] в определение службы. Наконец, по возможности избегайте внедрения контейнера и просто внедряйте необходимые вам службы, такие как контекст безопасности.   -  person Cerad    schedule 02.10.2014
comment
@Cerad, не могли бы вы оставить мне пример? Я не знаю, как добиться того, что вы сказали раньше! И да, я могу не вводить весь сервисный контейнер, вместо этого я должен вводить только @security.context и EntityManager позже, но будет хорошо, если я оставлю эту часть готовой отсюда   -  person ReynierPM    schedule 02.10.2014


Ответы (2)


В соответствии с вашим запросом на комментарий, вот как может выглядеть ваш конструктор меню:

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, а затем добавьте элементы избирателя, если это необходимо.

person Cerad    schedule 02.10.2014
comment
это работает, но, пожалуйста, взгляните на мое издание в основном посте о потере поддержки FontAwesome - person ReynierPM; 02.10.2014
comment
Нет подсказки. Я не использую KnpMenuBundle. - person Cerad; 02.10.2014

Ваш конструктор меню ContainerAware, поэтому я думаю, что в нем вы должны получить доступ к SecurityContext через $this->getContainer()->get('security.context').

И вы не предоставили никаких вариантов использования для класса избиратель, поэтому я предполагаю, что вы не используете метод matchItem.

Вы обязательно должны попытаться реструктурировать свои сервисы, чтобы зависимости были очевидны.

person kix    schedule 02.10.2014
comment
Не работает Attempted to call method "getContainer" on class "PlantillaBundle\Menu\MenuBuilder" in /var/www/html/src/PlantillaBundle/Menu/MenuBuilder.php line 12. Did you mean to call: "setContainer"? и как вы предлагаете мне реструктурировать зависимости? - person ReynierPM; 02.10.2014
comment
Поскольку я все еще учусь и пытаюсь кое-что понять в Symfony, я совсем не разбираюсь в Voters, поэтому, другими словами, я не знаю, как работает метод matchItem, не могли бы вы помочь мне понять эту часть с некоторыми пояснениями. ? - person ReynierPM; 02.10.2014
comment
Итак, вы реализовали интерфейс ContainerAware, поэтому должны знать, какое свойство или метод возвращает внедренный контейнер. О безопасности избирателей. Документ действительно довольно ясен, вы должны его проверить: symfony.com/doc /current/cookbook/security/ По сути, избиратель — это подчиненный класс SecurityContext, он сообщает, что должен возвращать $securityContext->isGranted() при получении объекта, известного избирателю. - person kix; 02.10.2014
comment
Также реализованный вами метод matchItem не имеет ничего общего с VoterInterface. Обратите внимание, что интерфейс имеет только три метода: github.com/symfony/symfony/blob/master/src/Symfony/Component/ - person kix; 02.10.2014
comment
Я думаю, вы говорите о чем-то совершенно отличном от того, что я говорю. Если вы внимательно посмотрите на класс RequestVoter, то увидите, что он наследуется от Knp\Menu\Matcher\Voter\VoterInterface, а не из этого Symfony\Component\Security\Core\Authorization\Voter\VoterInterface, так что да, KnpMenu Voter - person ReynierPM; 02.10.2014
comment
@ReynierPM, ой, плохо, конечно. - person kix; 02.10.2014