We are running a huge platform that has a single database for multiple frontends. Now we are about to try to identify our slow queries and get an better idea of from what page our traffic comes from.
I had the idea to inject the page name as a comment in every sql query to be able to see it when looking at the database using SHOW FULL PROCESSLIST
At the end it should look like this: /*PAGE NAME*/ SHOW FULL PROCESSLIST
If I do this in sequel pro it seems that the comment gets listed then:
How can I update every doctrine query using a listener/subscriber to inject a custom comment?
Doctrine DBAL allows you to define your own Connection class.
doctrine:
dbal:
wrapper_class: App\DBAL\MyConnectionWrapper
You could implement a child class of Doctrine\DBAL\Connection and override executeQuery() according to your needs.
class MyConnectionWrapper extends Connection
{
public function executeQuery($sql, array $params = [], $types = [], ?QueryCacheProfile $qcp = null)
{
$sql = '/*PAGE NAME*/ '.$sql;
return parent::executeQuery($sql, $params, $types, $qcp);
}
}
Please check this Symfony documentation : Doctrine Event Listeners and Subscribers To understand the following code
Here is how i did it for each update, in order to update the update time:
<?php
namespace App\EventListener;
use App\Entity\AbstractEntity;
use App\Entity\User;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Events;
use Doctrine\Persistence\Event\LifecycleEventArgs;
use Exception;
use Stringable;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use function is_object;
class DatabaseActivitySubscriber implements EventSubscriber
{
/**
* #var TokenStorageInterface
*/
private TokenStorageInterface $tokenStorage;
/**
* #var null|User
*/
private ?User $user;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
$this->user = null;
}
/**
* #return array|string[]
*/
public function getSubscribedEvents()
{
return [
Events::prePersist,
Events::preUpdate,
];
}
/**
* Initiate the name of the user creating the object with "LastName FirstName (id)"
*
* #param LifecycleEventArgs $args
* #throws Exception
*/
public function prePersist(LifecycleEventArgs $args)
{
$object = $args->getObject();
if ($object instanceof AbstractEntity && $this->getUser() instanceof User) {
$object->setCreateUser($this->getUser()->getLastName() . ' ' . $this->getuser()->getLastName() . ' (' . $this->getuser()->getId() . ')');
$object->setCreateDate();
}
}
/**
* #return string|Stringable|UserInterface|null|User
*/
private function getUser()
{
if ($this->user instanceof User){
return $this->user;
}
$token = $this->tokenStorage->getToken();
if (null === $token) {
return null;
}
if (!is_object($user = $token->getUser())) {
return null;
}
$this->user = $user;
return $this->user;
}
/**
* #param LifecycleEventArgs $args
* #throws Exception
*/
public function preUpdate(LifecycleEventArgs $args)
{
$object = $args->getObject();
if ($object instanceof AbstractEntity && $this->getUser() instanceof User) {
$object->setUpdateUser($this->getuser()->getLastName() . ' ' . $this->getuser()->getLastName() . ' (' . $this->getuser()->getId() . ')');
$object->setUpdateDate();
}
}
}
And add in service.yaml:
App\EventListener\DatabaseActivitySubscriber:
tags:
- { name: 'doctrine.event_subscriber' }
Related
I'm currently migrating a ZF2 application to ZF3.
Mostly everything is going smoothly but I'm stuck on one thing.
In my Module.php, I have an ACL management using 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
}
}
My issue here is that I'm still using the getServiceManager and therefore getting the deprecated warning : Usage of Zend\ServiceManager\ServiceManager::getServiceLocator is deprecated since v3.0.0;
Basically, I just need to inject dependencies into Module.php.
I guess otherwise I would have to move the checkAcl to the Controller directly and inject the ACL in them ? Not sure what is the proper way of doing this.
Any feedback on this would be greatly appreciated.
Regards,
Robert
To solve the issue you should use a Listener class and Factory. It would also help you with more separation of concerns :)
You seem quite capable of figuring stuff out, judging by your code. As such, I'm just going to give you an example of my own, so you should fill yours in with your own code (I'm also a bit lazy and do not wish to rewrite everything when I can copy/paste my code in ;) )
In your 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,
],
],
The Factory
<?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);
}
}
The Listener
<?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;
}
}
The important bits:
The Listener class must implement ListenerAggregateInterface
Must be activated in the listeners key of the module configuration
That's it really. You then have the basic building blocks for a Listener.
Apart from the attach function you could take the rest and make that into an abstract class if you'd like. Would save a few lines (read: duplicate code) with multiple Listeners.
NOTE: Above example uses the normal EventManager. With a simple change to the above code you could create "generic" listeners, by attaching them to the SharedEventManager, like so:
/**
* #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) {...}
I have backend on SonataAdminBundle 2.4.*#dev version.
Before update version and symfony core from 2.7 to 2.8 my code worked.
Explain:
I have an list of subscribers that have a flag isNew for separate export of new or old users. By default is 1, on export need to change it to 0 if in list exist new users.
But now it doesn't work. Because if filter of grid is set by this field isNew and export, in DB field is changed before, and later
return $this->get('sonata.admin.exporter')->getResponse(
$format,
$filename,
$this->admin->getDataSourceIterator()
);
getDataSourceIterator take data from DB not from result. So there is no more new users and file is empty.
How to update data after export, have any idea?
UPDATE:
Export function:
/**
* Export data to specified format.
*
* #param Request $request
*
* #return Response
*
* #throws AccessDeniedException If access is not granted
* #throws \RuntimeException If the export format is invalid
*/
public function exportAction(Request $request = null)
{
$request = $this->resolveRequest($request);
$this->admin->checkAccess('export');
$format = $request->get('format');
$allowedExportFormats = (array) $this->admin->getExportFormats();
if (!in_array($format, $allowedExportFormats)) {
throw new \RuntimeException(
sprintf(
'Export in format `%s` is not allowed for class: `%s`. Allowed formats are: `%s`',
$format,
$this->admin->getClass(),
implode(', ', $allowedExportFormats)
)
);
}
$filename = sprintf(
'export_%s_%s.%s',
strtolower(substr($this->admin->getClass(), strripos($this->admin->getClass(), '\\') + 1)),
date('Y_m_d_H_i_s', strtotime('now')),
$format
);
//my code to update field isNew of subscribers
$this->get('cfw.subscription')->processExportEmails($controller->admin->getFilterParameters());
return $this->get('sonata.admin.exporter')->getResponse(
$format,
$filename,
$this->admin->getDataSourceIterator()
);
}
Ok, I found an solution, maybe someone know better, please post here. I solved by update data in listener onTerminate. Look code:
service config:
sonata.admin.subscription.listener:
class: SiteBundle\Listener\ExportSubscriptionListener
arguments: [#service_container]
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
- { name: kernel.event_listener, event: kernel.terminate, method: onKernelTerminate }
in controller I deleted overridden method exportAction() and added little method
/**
* #return AdminInterface
*/
public function getAdmin()
{
return $this->admin;
}
ExportSubscriptionListener:
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Sonata\AdminBundle\Controller\CRUDController;
use SiteBundle\Controller\SubscriptionAdminController;
class ExportSubscriptionListener
{
/**
* #var CRUDController
*/
protected $controller;
/**
* #var ContainerInterface
*/
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function onKernelController(FilterControllerEvent $event)
{
$this->controller = $event->getController();
}
/**
* Update new subscribers on export
*/
public function onKernelTerminate()
{
$controller = $this->controller[0];
if (!$controller instanceof SubscriptionAdminController) {
return;
}
if ($this->controller[1] !== 'exportAction') {
return;
}
//here we update in service data
/** $var SubscriptionAdminController $controller */
$this->container->get('service.subscription')->processExportEmails($controller->getAdmin()->getFilterParameters());
}
}
and update of data in service:
/**
* #param array $subscriptions
*/
public function processExportEmails(array $filterParameters)
{
$criteria = [];
if(!empty($filterParameters['locale']) && $filterParameters['locale']['value'] !== "") {
$criteria['locale'] = $filterParameters['locale']['value'];
}
if(!empty($filterParameters['new']) && $filterParameters['new']['value'] !== "") {
$criteria['new'] = $filterParameters['new']['value'];
}
$subscriptionRepository = $this->getRepositorySubscription();
$subscriptions = null;
if (count($criteria) > 0) {
$subscriptions = $subscriptionRepository->findBy($criteria);
} else {
$subscriptions = $subscriptionRepository->findAll();
}
/** #var array|null $subscriptions */
foreach ($subscriptions as $subscription) {
/** #var Subscription $subscription */
if ($subscription->isNew()) {
$subscription->setNew(false);
$this->getEntityManager()->persist($subscription);
}
}
$this->getEntityManager()->flush();
}
Im trying to insert data into database in onKernelRequest using doctrine
//** src/MPN/CRMBundle/Listener/AnalyticsListener.php
namespace MPN\CRMBundle\Listener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use MPN\CRMBundle\Manager\AnalyticsManager;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Response;
use Doctrine\ORM\EntityManager;
use vRonn\StatisticsBundle\Entity\Statistics as Statistics;
class AnalyticsListener
{
/**
* #var AnalyticsManager
*/
private $am;
private $em;
public function __construct(AnalyticsManager $am)
{
$this->am = $am;
}
public function onKernelRequest(GetResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
$request = $event->getRequest();
$session = $request->getSession();
$uri = $request->getRequestUri();
$route = $request->get('_route');
if (!preg_match('#^/(app_dev\.php/)?(admin|js|media|_[^/]+)/?.*#', $uri)) {
// add page view count
$this->am->addPageView();
//Here is how i try to insert data
$d = new Statistics();
$d->setType(1);
$d->setName('blabla');
$d->setValue('val');
$em = $this->am->em->getRepository('vRonnStatisticsBundle:Statistics');
$em->persist($d);
$em->flush();
if (!$session->has('visitor') || $session->get('visitor') < time()) {
$session->set('visitor', time() + (24 * 60 * 60));
$this->am->addVisitor();
}
$this->am->save();
}
}
but i get error below
Undefined method 'persist'. The method name must start with either
findBy or findOneBy!
Here is the manager which i take the entity manager from, i take entity from here because i tried both $this->get('doctrine') or $this->getDoctrine() inside onKernelRequest but error
//** src/MPN/CRMBundle/Manager/AnalyticsManager.php
namespace MPN\CRMBundle\Manager;
use Doctrine\ORM\EntityManager;
use MPN\CRMBundle\Entity\Analytics;
use MPN\CRMBundle\Service\DateTimeBuilder;
class AnalyticsManager
{
/**
* #var EntityManager
*/
public $em;
/**
* #var DateTimeBuilder
*/
private $dateTimeBuilder;
/**
* #var array
*/
private $analytics;
public function __construct(EntityManager $em, DateTimeBuilder $dateTimeBuilder)
{
$this->em = $em;
$this->dateTimeBuilder = $dateTimeBuilder;
$this->setup();
}
/**
* Flushes the data to the database.
*
* #return void
*/
public function save()
{
$this->em->flush();
}
}
It looks like you're setting $em to a repository, not an entity manager. If you just do $em = $this->am->em it oughta work.
This question already has answers here:
Override method parameter with child interface as a new parameter
(2 answers)
Closed 8 years ago.
I try to use a subclass in a method with typesafe parameter. Don't know if I'm missing something or if the solution is to just don't make the parameter typesafe. What is the cleanest design option for this situation?
Look at following code:
<?php
class MyObject {
public $name;
}
class MySubObject extends MyObject {
public $subProperty;
}
abstract class Renderer {
protected $someProperty;
public abstract function render(MyObject $obj);
}
class RendererA extends Renderer {
public function render(MyObject $obj) {
return $this->someProperty . ': ' . $obj->name;
}
}
// This is the problematic case ------|
class RendererB extends Renderer { // |
public function render(MySubObject $obj) {
return $this->someProperty . ': ' . $obj->name . ' ' .$obj->subProperty;
}
}
/* EOF */
Take a look at this:
https://github.com/symfony/Routing/blob/2.4/Router.php#L225
In this code, the Router will match a request, however it has two ways of doing this depending on the type of Matcher it has an instance of.
That case uses interfaces to differentiate between the object type, however to perform the same technique to your case try this:
class MyObject {
public $name;
}
class MySubObject extends MyObject {
public $subProperty;
}
class Renderer {
public function render(MyObject $obj) {
if($obj instanceof MySubObject) {
return $this->someProperty . ': ' . $obj->name . ' ' .$obj->subProperty;
}
return $this->someProperty . ': ' . $obj->name;
}
}
Assuming that is the only variation of functionality, you don't need an AbstractRenderer.
Since MySubObject extends MyObject it is still type safe to use the extending class as an argument.
EDIT:
This example uses a basic form of the visitor pattern.
The Renderer essentially becomes a parent manager of multiple potential handlers.
The input is handled differently by drivers added at the configuration stage depending of the input's attributes (class in this case, but there are more advanced forms of criteria).
This example can be executed outside of namespaces.
interface TemplateInterface
{
/**
* #return string
*/
public function getName();
}
interface DriverInterface
{
/**
* #param TemplateInterface $template
* #return string
*/
public function doRender(TemplateInterface $template);
}
class Renderer
{
/**
* #var array
*/
protected $drivers;
protected $property = 'Sample Property';
/**
* Constructor
*/
public function __construct()
{
$this->drivers = array();
}
/**
* #param TemplateInterface $template
* #return string
*/
public function render(TemplateInterface $template)
{
$class = get_class($template);
if(false === isset($this->drivers[$class])) {
throw new InvalidArgumentException(sprintf('Renderer Driver supporting class "%s" is not present.', $class));
}
return sprintf('%s: %s', $this->property, $this->drivers[$class]->doRender($template));
}
/**
* #param DriverInterface $driver
* #param $class
* #return $this
*/
public function addDriver(DriverInterface $driver, $class)
{
$this->drivers[$class] = $driver;
return $this;
}
}
class MyTemplate implements TemplateInterface
{
public function getName()
{
return 'my_template';
}
}
class MyOtherTemplate implements TemplateInterface
{
public function getName()
{
return 'my_other_template';
}
public function getOtherProperty()
{
return 'this is another property';
}
}
class MyDriver implements DriverInterface
{
public function doRender(TemplateInterface $template)
{
return $template->getName();
}
}
class MyOtherDriver implements DriverInterface
{
public function doRender(TemplateInterface $template)
{
if(false === $template instanceof MyOtherTemplate) {
throw new InvalidaArgumentException('OtherDriver::doRender argument must be an instance of MyOtherTemplate');
}
return sprintf('%s %s', $template->getName(), $template->getOtherProperty());
}
}
$renderer = new Renderer();
$renderer
->addDriver(new MyDriver(), 'MyTemplate')
->addDriver(new MyOtherDriver(), 'MyOtherTemplate')
;
echo '<pre>';
echo $renderer->render(new MyTemplate()).PHP_EOL;
echo $renderer->render(new MyOtherTemplate());
This is a more advanced example.
This time you don't specify the class of the template, instead you only add the driver, but this driver interface required the supports method be implemented. The renderer will loop through each driver asking them if they support the template.
The first driver to return true will render and return the template.
interface DriverInterface
{
/**
* #param TemplateInterface $template
* #return string
*/
public function doRender(TemplateInterface $template);
/**
* #param TemplateInterface $template
* #return bool
*/
public function supports(TemplateInterface $template);
}
class Renderer
{
/**
* #var array
*/
protected $drivers;
protected $property = 'Sample Property';
/**
* Constructor
*/
public function __construct()
{
$this->drivers = array();
}
/**
* Returns the rendered template, or false if the template was not supported by any driver.
*
* #param TemplateInterface $template
* #return string|false
*/
public function render(TemplateInterface $template)
{
$class = get_class($template);
foreach($this->drivers as $driver) {
if($driver->supports($template)) {
return sprintf('%s: %s', $this->property, $driver->doRender($template));
}
}
return false;
}
/**
* #param DriverInterface $driver
* #param $class
* #return $this
*/
public function addDriver(DriverInterface $driver)
{
$this->drivers[] = $driver;
return $this;
}
}
I'm trying to get the access_control parameters which are located in my security.yml as an array in my custom service.
Just like with getting the role_hierarchy parameters I thought it would work with the following code:
$accessParameters = $this->container->getParameter('security.access_control');
Unfortunately this was not the case. Can someone tell how to get the parameters?
There's no way to get the access_control parameter from the container.
This is because this parameter is only used to create request matchers which will be registered as AccessMap later given in the AccessListener, and then are left over without registering it into the container.
You can try something hacky to get these matchers back by getting them like
$context = $this->get("security.firewall.map.context.main")->getContext();
$listener = $context[0][5];
// Do reflection on "map" private member
But this is kind of an ugly solution.
Another way I can see on how to get them is to parse again the security file
use Symfony\Component\Yaml\Yaml;
$file = sprintf("%s/config/security.yml", $this->container->getParameter('kernel.root_dir'));
$parsed = Yaml::parse(file_get_contents($file));
$access = $parsed['security']['access_control'];
If you want to register this configuration into a service, you can do something like
services.yml
services:
acme.config_provider:
class: Acme\FooBundle\ConfigProvider
arguments:
- "%kernel.root_dir%"
acme.my_service:
class: Acme\FooBundle\MyService
arguments:
- "#acme.config_provider"
Acme\FooBundle\ConfigProvider
use Symfony\Component\Yaml\Yaml;
class ConfigProvider
{
protected $rootDir;
public function __construct($rootDir)
{
$this->rootDir = $rootDir;
}
public function getConfiguration()
{
$file = sprintf(
"%s/config/security.yml",
$this->rootDir
);
$parsed = Yaml::parse(file_get_contents($file));
return $parsed['security']['access_control'];
}
}
Acme\FooBundle\MyService
class MyService
{
protected $provider;
public function __construct(ConfigProvider $provider)
{
$this->provider = $provider;
}
public function doAction()
{
$access = $this->provider->getConfiguration();
foreach ($access as $line) {
// ...
}
}
}
Necro, but still relevant. This is an improvement on Touki's answer above, where we don't reparse the access_control definitions, but rather use the already configured security token, firewall and access map to work out the answer.
.../services.yml
...
My\Application\AuthenticationBundle\Security\AccessControlHelper:
class: My\Application\AuthenticationBundle\Security\AccessControlHelper
arguments:
$securityContext: "#security.context"
$firewall: '#security.firewall.map'
$accessDecisionManager: '#security.access.decision_manager'
$accessMap: '#security.access_map'
...
src/My/Application/AuthenticationBundle/Security/AccessControlHelper.php
declare(strict_types=1);
namespace My\Application\AuthenticationBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Http\AccessMapInterface;
use Symfony\Component\Security\Http\Firewall\AccessListener;
use Symfony\Component\Security\Http\FirewallMapInterface;
class AccessControlHelper
{
/**
* #var SecurityContextInterface
*/
protected $securityContext;
/**
* #var FirewallMapInterface
*/
protected $firewallMap;
/**
* #var AccessDecisionManagerInterface
*/
protected $accessDecisionManager;
/**
* #var AccessMapInterface
*/
protected $accessMap;
public function __construct(
SecurityContextInterface $securityContext,
FirewallMapInterface $firewallMap,
AccessDecisionManagerInterface $accessDecisionManager,
AccessMapInterface $accessMap
)
{
$this->securityContext = $securityContext;
$this->firewallMap = $firewallMap;
$this->accessDecisionManager = $accessDecisionManager;
$this->accessMap = $accessMap;
}
public function isRequestAccessible(Request $request): bool
{
$token = $this->securityContext->getToken();
if (!$token || false == $token->isAuthenticated()) {
return false;
}
list($listeners) = $this->firewallMap->getListeners($request);
if ($listeners) {
foreach ($listeners as $listener) {
if ($listener instanceof AccessListener) {
/**
* Logic here is much inspired by the AccessListener->handle(...) method.
*/
list($attributes) = $this->accessMap->getPatterns($request);
if (null === $attributes) {
continue;
}
return boolval($this->accessDecisionManager->decide($token, $attributes, $request));
}
}
}
return true;
}
public function isUriAccessible(string $uri)
{
return $this->isRequestAccessible(Request::create($uri));
}
}
Sample usage:
use My\Application\AuthenticationBundle\Security\AccessControlHelper;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
$container = ...; // #var ContainerInterface
$accessControlHelper = $container->get(AccessControlHelper::class);
$accessControlHelper->isRequestAccessible(new Request("/foo"));
$accessControlHelper->isUriAccessible("/foo");