В настоящее время я переношу приложение ZF2 на ZF3. В основном все идет гладко, но я зациклился на одном.
В моем Module.php у меня есть управление ACL с помощью zend-permissions-acl.
class Module
{
protected $defaultLang = 'fr';
public function onBootstrap(MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
if (!$e->getRequest() instanceof ConsoleRequest){
$eventManager->attach(MvcEvent::EVENT_RENDER_ERROR, array($this, 'onRenderError'));
$eventManager->attach(MvcEvent::EVENT_RENDER, array($this, 'onRender'));
$eventManager->attach(MvcEvent::EVENT_FINISH, array($this, 'onFinish'));
$this->initAcl($e);
$eventManager->attach('route', array($this, 'checkAcl'));
}
}
public function checkAcl(MvcEvent $e) {
$app = $e->getApplication();
$sm = $app->getServiceManager();
$route = $e -> getRouteMatch() -> getMatchedRouteName();
$authService = $sm->get('AuthenticationService');
$jwtService = $sm->get('JwtService');
$translator = $sm->get('translator');
$identity = null;
try {
$identity = $jwtService->getIdentity($e->getRequest());
} catch(\Firebase\JWT\ExpiredException $exception) {
$response = $e->getResponse();
$response->setStatusCode(401);
return $response;
}
if (is_null($identity) && $authService->hasIdentity()) { // no header being passed on... we try to use standard validation
$authService->setJwtMode(false);
$identity = $authService->getIdentity();
}
$userRole = 'default';
$translator->setLocale($this->defaultLang);
if (!is_null($identity))
{
$userRole = $identity->getType();
//check if client or prospect
if ($userRole >= User::TYPE_CLIENT)
{
$userManagementRight = UserRight::CREATE_USERS;
if ($identity->hasRight($userManagementRight))
$userRole = 'userManagement';
}
$translator->setLocale($identity->getLang());
}
if (!$e->getViewModel()->acl->isAllowed($userRole, null, $route)) {
$response = $e -> getResponse();
$response->setStatusCode(403);
return $response;
}
public function initAcl(MvcEvent $e) {
//here is list of routes allowed
}
}
Моя проблема здесь в том, что я все еще использую getServiceManager и поэтому получаю устаревшее предупреждение: Usage of Zend\ServiceManager\ServiceManager::getServiceLocator is deprecated since v3.0.0;
По сути, мне просто нужно ввести зависимости в Module.php. Я предполагаю, что в противном случае мне пришлось бы переместить checkAcl непосредственно в контроллер и ввести в них ACL? Не уверен, как это сделать правильно.
Мы будем благодарны за любые отзывы по этому поводу.
С уважением,
Роберт






Чтобы решить эту проблему, вы должны использовать класс Listener и Factory. Это также поможет вам разделить проблемы :)
Судя по вашему коду, вы, кажется, вполне способны во всем разбираться. Поэтому я просто собираюсь привести вам свой собственный пример, поэтому вы должны заполнить ваш собственный код (я также немного ленив и не хочу все переписывать, когда я могу скопировать / вставить свой код. в ;) )
В вашем module.config.php:
'listeners' => [
// Listing class here will automatically have them "activated" as listeners
ActiveSessionListener::class,
],
'service_manager' => [
'factories' => [
// The class (might need a) Factory
ActiveSessionListener::class => ActiveSessionListenerFactory::class,
],
],
Фабрика
<?php
namespace User\Factory\Listener;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityManager;
use Interop\Container\ContainerInterface;
use User\Listener\ActiveSessionListener;
use Zend\Authentication\AuthenticationService;
use Zend\ServiceManager\Factory\FactoryInterface;
class ActiveSessionListenerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
/** @var ObjectManager $entityManager */
$entityManager = $container->get(EntityManager::class);
/** @var AuthenticationService $authenticationService */
$authenticationService = $container->get(AuthenticationService::class);
return new ActiveSessionListener($authenticationService, $entityManager);
}
}
Слушатель
<?php
namespace User\Listener;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityManager;
use User\Entity\User;
use Zend\Authentication\AuthenticationService;
use Zend\EventManager\Event;
use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\ListenerAggregateInterface;
use Zend\Mvc\MvcEvent;
/**
* Class ActiveSessionListener
*
* @package User\Listener
*
* Purpose of this class is to make sure that the identity of an active session becomes managed by the EntityManager.
* A User Entity must be in a managed state in the event of any changes to the Entity itself or in relations to/from it.
*/
class ActiveSessionListener implements ListenerAggregateInterface
{
/**
* @var AuthenticationService
*/
protected $authenticationService;
/**
* @var ObjectManager|EntityManager
*/
protected $objectManager;
/**
* @var array
*/
protected $listeners = [];
/**
* CreatedByUserListener constructor.
*
* @param AuthenticationService $authenticationService
* @param ObjectManager $objectManager
*/
public function __construct(AuthenticationService $authenticationService, ObjectManager $objectManager)
{
$this->setAuthenticationService($authenticationService);
$this->setObjectManager($objectManager);
}
/**
* @param EventManagerInterface $events
*/
public function detach(EventManagerInterface $events)
{
foreach ($this->listeners as $index => $listener) {
if ($events->detach($listener)) {
unset($this->listeners[$index]);
}
}
}
/**
* @param EventManagerInterface $events
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
$events->attach(MvcEvent::EVENT_ROUTE, [$this, 'haveDoctrineManagerUser'], 1000);
}
/**
* @param Event $event
*
* @throws \Doctrine\Common\Persistence\Mapping\MappingException
* @throws \Doctrine\ORM\ORMException
*/
public function haveDoctrineManagerUser(Event $event)
{
if ($this->getAuthenticationService()->hasIdentity()) {
// Get current unmanaged (by Doctrine) session User
$identity = $this->getAuthenticationService()->getIdentity();
// Merge back into a managed state
$this->getObjectManager()->merge($identity);
$this->getObjectManager()->clear();
// Get the now managed Entity & replace the unmanaged session User by the managed User
$this->getAuthenticationService()->getStorage()->write(
$this->getObjectManager()->find(User::class, $identity->getId())
);
}
}
/**
* @return AuthenticationService
*/
public function getAuthenticationService() : AuthenticationService
{
return $this->authenticationService;
}
/**
* @param AuthenticationService $authenticationService
*
* @return ActiveSessionListener
*/
public function setAuthenticationService(AuthenticationService $authenticationService) : ActiveSessionListener
{
$this->authenticationService = $authenticationService;
return $this;
}
/**
* @return ObjectManager|EntityManager
*/
public function getObjectManager()
{
return $this->objectManager;
}
/**
* @param ObjectManager|EntityManager $objectManager
*
* @return ActiveSessionListener
*/
public function setObjectManager($objectManager)
{
$this->objectManager = $objectManager;
return $this;
}
}
Важные биты:
Listenerдолжен реализовать ListenerAggregateInterfacelisteners конфигурации модуляЭто действительно так. Затем у вас есть основные строительные блоки для слушателя.
Помимо функции attach, вы можете взять остальное и превратить его в абстрактный класс, если хотите. Сохранит несколько строк (читай: дублированный код) с несколькими слушателями.
ЗАМЕТКА: В приведенном выше примере используется обычный EventManager. С помощью простого изменения в приведенном выше коде вы можете создать «общие» слушатели, подключив их к SharedEventManager, например:
/**
* @param EventManagerInterface $events
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
$sharedManager = $events->getSharedManager();
$sharedManager->attach(SomeClass::class, EventConstantClass::SOME_STRING_CONSTANT, [$this, 'callbackFunction']);
}
public function callbackFunction (MvcEvent $event) {...}
Еще раз спасибо, это сработало очень хорошо. Единственная проблема, которая у меня была, заключалась в том, что приоритет был установлен на 1000, из-за чего мое событие произошло без RouteMatched. Верните значение по умолчанию 1, и все остальное пройдет гладко.
Да, это просто приоритет, который вы можете себе дать. Для меня это ActiveSessionListener. На раннем этапе я хочу знать, кто использует мое приложение, поэтому оно запускается раньше с этим приоритетом. С приоритетом 999 я получил прослушиватель авторизации, чтобы убедиться, что пользователь, определенный с приоритетом 1000, проверяется на разрешение на маршруте ;-)
Большое спасибо за это, это потрясающий ответ. Завтра протестирую, но я считаю, что это именно то, что мне нужно! :-)