I use Symfony 4 with messenger and I use a worker who consumes my messages as a long-running process.
I have a bug with doctrine if I delete my post and recreate a new one and I dispatch my message. $post have the old data and not the new one.
I have tried a lot of things and nothing work, it works when I restart my worker.
class ChannelMessageHandler implements MessageHandlerInterface
{
private $channel;
private $bus;
public function __construct(ChannelService $channel, MessageBusInterface $commandBus)
{
$this->channel = $channel;
$this->bus = $commandBus;
}
public function __invoke(ChannelMessage $channelMessage)
{
$error = $this->channel->handleChannel($channelMessage->getUser(), $channelMessage->getService());
if ($error) {
throw new Exception($error[0]);
}
$this->bus->dispatch(new FeedMessage($channelMessage->getUser(), $channelMessage->getService()));
}
}
}
My MessageHandler call a service :
class ChannelService implements ContainerAwareInterface
{
use ContainerTrait;
protected $em;
protected $logger;
public function __construct(EntityManagerInterface $entityManager, LoggerInterface $logger)
{
$this->em = $entityManager;
$this->logger = $logger;
}
public function handleChannel($userId, $serviceName)
{
$user = $this->em->getRepository('App:User\Authentication\User')->findOneById($userId);
$post = $user->getPost();
return $this->getUserAnalyticBy($post, $serviceName);
}
thanks a lot
Related
When the user logs in to the system, I need to fill a class variable (Login-> testInfo) with information, but in the controller the variable always returns null.
Here is a generic example.
The Login class
class Login extends UserInterface
{
private $testInfo = null;
public function setTestInfo(string $testInfo)
{
$this->testInfo = $testInfo;
}
public function getTestInfo() : ?string
{
return $this->testInfo;
}
}
The Authenticator:
class FormAuthenticator extends AbstractFormLoginAuthenticator
{
...
public function getUser($credentials, UserProviderInterface $userProvider)
{
$user = $this->entityManager->getRepository(Login::class)->findByUsername(credentials['username']);
if (!$user)
{
throw new CustomUserMessageAuthenticationException('Username could not be found.');
}
//this prints NULL
dd($user->getTestInfo());
$user->setTestInfo('testing the string');
//this prints 'testing the string'
dd($user->getTestInfo());
return $user;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
//this prints 'testing the string'
dd($token->getUser()->getTestInfo());
}
...
}
The Controller Class:
class MyController extends AbstractController
{
private $login = null;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->login = $tokenStorage->getToken() ? $tokenStorage->getToken()->getUser() : null;
}
public function home()
{
//this prints null
dd($this->login->getTestInfo());
}
}
If $user goes to the tokenStorage with the new value ('testing the string'), why, when I try to use it on the controller, does the variable always return null? what am I doing wrong?
Is testInfo a transient variable? Because you gotta know that there is UserProvider that tries to refresh user from token (maybe it could be "changed" somehow between requests). I'm pretty sure you're losing those infos right in this process.
Are you sure your controller constructor isn't being executed too soon, prior to the authentication success event writing the token to the token storage service? I'd dd() the token in the constructor to verify if the token and Login instance are present at that point.
You may need to use setContainer() instead of __construct() in your controller to retrieve the authenticated token, which would look something like this:
private $tokenStorage = null;
private $login = null;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
/**
* #param ContainerInterface $container Symfony service container interface
* #return ContainerInterface|null
*/
public function setContainer(\Psr\Container\ContainerInterface $container): ?\Psr\Container\ContainerInterface
{
if ($this->tokenStorage instanceof TokenStorageInterface && $this->tokenStorage->getToken() instanceof TokenInterface && $this->tokenStorage->getToken()->getUser() instanceof Login) {
$this->login = $this->tokenStorage->getToken()->getUser();
}
return $container;
}
Changes to my Entities are logged using an EventSubscriber for Doctrine lifecycle events. I want to log a request id alognside the entity change log entries to see what's happened in one single user action.
Adding the request id is as easy as this:
class RequestIdSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [KernelEvents::REQUEST => 'addRequestId'];
}
public function addRequestId(GetResponseEvent $event): void
{
$request = $event->getRequest();
$request->attributes->set('RequestId', Uuid::uuid1()->toString());
}
}
The problem is that the request does not seem to be available in the Doctrine EventSubscribers in a reliable way:
class EntityEventSubscriber implements EventSubscriber
{
public function __construct(DelayedEventDispatcher $dispatcher, RequestStack $requestStack, LoggerInterface $logger)
{
$this->dispatcher = $dispatcher;
$this->inventory = new EntityInventory();
$this->requestStack = $requestStack;
$this->logger = $logger;
}
public function getSubscribedEvents(): array
{
return [
Events::postUpdate,
];
}
public function postUpdate(LifecycleEventArgs $args): void
{
// works
$this->logger->debug($this->requestStack->getCurrentRequest()->get('RequestId'));
$entity = $args->getEntity();
$changes = $this->inventory->getChangeSet($entity);
$event = new EntityUpdatedEvent($entity, $changes);
$this->triggerAuditLogEvent($event);
}
public function triggerAuditLogEvent(EntityEvent $event): void
{
// request.CRITICAL: Uncaught PHP Exception Error: "Call to a member function get() on null"
$event->setRequestId($this->requestStack->getCurrentRequest()->get('RequestId'));
$this->dispatcher->dispatch(FhrEvents::GENERIC_ENTITY_EVENT, $event);
}
}
So what really bothers me is that the request seems to be available in one method and if I call the next one, it's already gone.
I'm in a symfony 2.8 project. Another developer wrote a Voter to check if a user has the permissions or not for a specific task. I already use this function in a controller without problems but now I'm writing a service to get a dynamic menu and I don't know how to access to the method isGranted:
Error: Attempted to call an undefined method named "isGranted" of
class.
namespace NameSpaceQuestion\Question\Service;
use Doctrine\ORM\EntityManager;
class MenuBuilder
{
private $areasTools;
public $menuItems = array();
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function createMenuCourse($course,$mode,$user)
{
$repository = $this->em->getRepository('eBundle:AreasTools');
$areas = $repository->findAll();
//Retrieve every area from the db
$itemsMenu = array();
foreach ($areas as $area) {
//If the user has permissions the area is included in the menu and proceed to check the tools that belong to the current area
if($this->isGranted( $mode,$area,$user ) ){
$itemsMenu[$area->getName()] = array();
$toolsPerCourse = $this->em->getRepository('eBundle:CourseTool')->findByAreaToolAndCourse($area, $course);
foreach ($toolsPerCourse as $toolCourse) {
//If the user has permissions the tool is included in the menu under the respective Area
if( ($this->isGranted( $mode,$toolCourse,$user ) )){
array_push($itemsMenu[$area->getName()], $toolCourse->getName());
}
}
}
}
return $itemsMenu;
}
}
You'll have to use Dependency Injection to get the AuthorizationChecker in your MenuBuilder class. You can read about it here: http://symfony.com/doc/current/cookbook/security/securing_services.html
Because it looks like you're already injecting the EntityManager, just add the AuthorizationChecker to your code:
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class MenuBuilder
{
protected $authorizationChecker;
public function __construct(EntityManager $em, AuthorizationCheckerInterface $authorizationChecker)
{
$this->authorizationChecker = $authorizationChecker;
$this->em = $em;
}
function createMenuCourse()
{
if ( $this->authorizationChecker->isGranted('EDIT',$user) ) {
//build menu
}
}
}
First thing to understand is that the Symfony base controller has a number of helper functions such as isGranted implemented. But of course, if you are not inside of a controller then you don't have access to them. On the other hand, it's instructive to look at the base class and copy out the needed functionality.
The isGranted functionality relies on a authorization checker service which you will need to inject into your menu builder resulting in something like:
class MenuBuilder
{
private $em;
private $authorizationChecker;
public function __construct(
EntityManager $em,
$authorizationChecker // security.authorization_checker
) {
$this->em = $em;
$this->authorizationChecker = $authorizationChecker;
}
protected function isGranted($attributes, $object = null)
{
return $this->authorizationChecker->isGranted($attributes, $object);
}
Darn it. Stephan beat me by one minute. Must learn to type faster.
I'm using Symfony Service Configurator in my project to configure a service after its instantiation (Docs),
in Configure method I need the current user logged so I inject the container and I tried to get the token form Security.context service but I got always NULL.
I tried also to inject only Security.context in my Configurator construct but I got same result.
Any ideas pls
Thanks.
class MyConfigurator
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function configure()
{
$user = $this->container->get('security.context')->getToken();
var_dump($user); // = NULL
}
}
I resolve the problem by getting the UserId from the session and fetch the current User from Database.
The UserId is set previously by a AuthenticationListener in my project.
So I modify my Configurator construct to be like this:
/**
* #param EntityManager $em
* #param Session $session
*/
public function __construct(EntityManager $em, Session $session)
{
$this->em = $em;
$this->session = $session;
}
A better way should be:
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
class MyConfigurator
{
private $tokenStorage;
public function __construct(EntityManager $em, TokenStorage $tokenStorage)
{
$this->em = $em;
$this->tokenStorage= $tokenStorage;
}
public function configure()
{
$user = $this->tokenStorage->getToken()->getUser();
}
....
I have a project with a lot of tests class like
class MyTest extends BaseTestCase
{
public function __construct()
{
parent::__construct();
$this->em = $this->get('doctrine')->getManager();
}
public function setUp() {
$this->init();
//load sql data for the tests
$path = $this->get('kernel')->locateResource('#Bundle/Data/Test.sql');
$content_file_sql_data = file_get_contents($path);
$stmt = $this->em->getConnection()->prepare($content_file_sql_data);
$stmt->execute();
$stmt->closeCursor();
}
/*
* Then we do a lot of tests using the database
*/
}
They all extends my BaseTestCase:
abstract class BaseTestCase extends \PHPUnit_Framework_TestCase {
protected $_container;
protected $kernel;
public function __construct() {
parent::__construct();
$this->kernel = new \AppKernel("test", true);
$this->kernel->boot();
$this->_container = $this->kernel->getContainer();
$this->init();
}
//empty the database before each test class
public function init() {
$this->_application = new Application($this->kernel);
$this->_application->setAutoExit(false);
//rebuild and empty the database
$this->runConsole("doctrine:schema:drop", array("--force" => true));
$this->runConsole("doctrine:schema:create");
}
Since I have a lot of tests, i have recently got some errors PDOException: SQLSTATE[08004] [1040] Too many connections. It's like phpunit never close database connection, and around 100 tests I get this error for all the other tests.
How can i fix it?
I tried to put a last test doing $this->em->close() at the end of each test class but it didn't solve it
Some additional information: i'm pretty sure I don't have an issue with ONE test, because if I change the order of the test suite, the error appears around the same amount of tests class passed
My solution was to override shutdown method in my Bundle class:
public function shutdown()
{
if ('test' == $this->container->getParameter('kernel.environment')) {
/* #var EntityManager $em */
$em = $this->container->get('doctrine.orm.default_entity_manager');
$em->getConnection()->close();
}
}