I am upgrading from Symfony 3.3 to Symfony 4.
KNPMenu was running fine under Symfony 3.3, but now I am seeing this exception:
An exception has been thrown during the rendering of a template ("The
menu "main" is not defined.").
services.yaml
App\Menu\MenuBuilder:
public: true
tags:
- { name: app.menu_builder.admin, method: createAdminMenu, alias: admin }
- { name: app.menu_builder.calendar, method: createCalendarMenu, alias: calendar }
- { name: app.menu_builder.main, method: createMainMenu, alias: main }
- { name: app.menu_builder.trailer, method: createTrailerMenu, alias: trailer }
- { name: app.menu_builder.user, method: createUserMenu, alias: user }
MenuBuilder
Namespace App\Menu;
use Knp\Menu\FactoryInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\Security\Core\Security;
class MenuBuilder implements ContainerAwareInterface
{
use ContainerAwareTrait;
private $factory;
/**
* #param FactoryInterface $factory
*/
public function __construct( FactoryInterface $factory )
{
$this->factory = $factory;
}
I put a die('here'); in the __construct, it is never executed.
Using php bin/console debug:container menu yields:
Information for Service "App\Menu\MenuBuilder"
==============================================
---------------- -------------------------------------------------------------------------
Option Value
---------------- -------------------------------------------------------------------------
Service ID App\Menu\MenuBuilder
Class App\Menu\MenuBuilder
Tags app.menu_builder.admin (method: createAdminMenu, alias: admin)
app.menu_builder.calendar (method: createCalendarMenu, alias: calendar)
app.menu_builder.main (method: createMainMenu, alias: main)
app.menu_builder.trailer (method: createTrailerMenu, alias: trailer)
app.menu_builder.user (method: createUserMenu, alias: user)
Public yes
Synthetic no
Lazy no
Shared yes
Abstract no
Autowired yes
Autoconfigured yes
---------------- -------------------------------------------------------------------------
How can I get MenuBuilder __construct to execute?
Thank you all for your time.
This gist was helpful: https://gist.github.com/lsv/4d8044d21819f28f0dde52a3fb8211a0
This answer was helpful: How to avoid "knp_menu.factory" deprecation?
services.yaml
app.menu_builder:
class: App\Menu\MenuBuilder
app.menu.main:
class: Knp\Menu\MenuItem
factory: ['#app.menu_builder', 'createMainMenu']
arguments: { $options: [] }
tags:
- { name: knp_menu.menu, alias: main }
I used php bin/console debug:autowiring security to get the security authorization checker
Then I updated MenuBuilder.php like so:
Namespace App\Menu;
use Knp\Menu\FactoryInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class MenuBuilder
{
private $factory;
private $security;
/**
* #param FactoryInterface $factory
*/
public function __construct( FactoryInterface $factory, AuthorizationCheckerInterface $security )
{
$this->factory = $factory;
$this->security = $security;
}
Adding the SecurityAuthorizationChecker allows me to control the menu items offered with this statement:
if( $this->security->isGranted( 'IS_AUTHENTICATED_FULLY' ) )
Related
I'm asking you for help because I got an error while trying to create a log service for tracing.
I followed this tutorial to perform this task:
https://nehalist.io/logging-events-to-database-in-symfony/
No problem occurred when creating the Log entity and the dbHandler service seems to run.
When I introduce the argument in services.yaml (arguments): ["#doctrine.orm.entity_manager"] and I use it in the MenuController constructor parameters, that's where the problem occurs.
Here is the error message: "It's a requirement to specify a Metadata Driver and pass it to Doctrine\ORM\Configuration::setMetadataDriverImpl()"
Thanks for your help.
Code error
services.yaml
services:
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event
subscribers, etc.
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
- '../src/Tests/'
App\Controller\:
resource: '../src/Controller/'
tags: ['controller.service_arguments']
arguments: ['#monolog.logger.db']
dbHandler:
class: App\Service\DbHandler
arguments: ["#doctrine.orm.entity_manager"]
monolog.yaml
monolog:
channels: ['db']
handlers:
dbHandler:
type: service
id: dbHandler
DbHandler.php
<?php
namespace App\Service;
use App\Entity\Log;
use Monolog\Handler\AbstractProcessingHandler;
use Doctrine\ORM\EntityManagerInterface;
class DbHandler extends AbstractProcessingHandler {
private $em;
public function __construct(EntityManagerInterface $em) {
parent::__construct();
$this->em = $em;
}
protected function write($record):void {
$log = new Log();
$log->setMessage($record['message']);
$log->setLevel($record['level']);
$log->setLevelName($record['level_name']);
$log->setContext($record['context']);
$this->em->persist($log);
$this->em->flush();
}
}
MenuController.php
namespace App\Controller;
use Psr\Log\LoggerInterface;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class MenuController extends AbstractController
{
private $logger;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
/**
* #Route("/menu", name="menu")
* #IsGranted("ROLE_USER")
*/
public function menu(LoggerInterface $logger )
{
$logger->info('Test');
}
}
I'm trying to change the db connection based on current subdomain.
So far I've managed to create a kernel.request listener and injecting the entity manager.
services.yaml
services:
app.listener.currentSubdomain:
class: App\EventListener\CurrentSubdomainListener
arguments: ['#doctrine.orm.entity_manager']
tags:
- { name: kernel.event_listener, event: kernel.request }
CurrentSubdomainListener.php
<?php
namespace App\EventListener;
use Doctrine\ORM\EntityManager;
use Symfony\Component\HttpKernel\Event\RequestEvent;
class CurrentSubdomainListener{
protected $entityManager;
public function __construct(EntityManager $entityManager){
$this->entityManager = $entityManager;
}
public function onKernelRequest(RequestEvent $event){
//Change entity manager connection
}
}
How can I set the entity manager connection inside onKernelRequest(), so that when I auto wire it into any controllers it'll automatically use the connection I set?
I wanted to set password automatically when one register the form. So I use REGISTRATION_INITIALIZE to trigger the event. Unfortunately it's not working.
Listener:
<?php
namespace Acme\UserBundle\EventListener;
use Doctrine\ORM\EntityManager;
use FOS\UserBundle\Event\UserEvent;
use FOS\UserBundle\FOSUserEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class RegistrationListener implements EventSubscriberInterface
{
/**
* #var EntityManager
*/
private $em;
/**
* #param \Doctrine\ORM\EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->em = $entityManager;
}
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_INITIALIZE => 'onRegistrationInit',
);
}
public function onRegistrationInit(UserEvent $userEvent)
{
$user = $userEvent->getUser();
$user->setPassword('abcdeffffff');
}
Services:
#src/Acme/UserBundle/Resources/config/services.yml
services:
acme_user.registration:
class: Acme\UserBundle\EventListener\RegistrationListener
arguments:
entityManager: "#doctrine.orm.entity_manager"
tags:
- { name: kernel.event_subscriber}
So the password is not setting and it shows the password should not be blank.
What am I doing wrong? Any help!
EDIT:
The problem was I was defining service at the wrong place.
Instead of src/Acme/UserBundle/Resources/config/services.yml, It should be app/config/services.yml.
I saw src/Acme/UserBundle/Resources/config/services.ymlin http://symfony.com/doc/master/bundles/FOSUserBundle/controller_events.html, But was not working for me!
Maybe I am wrong, but I think your service definition uses features from Symfony 3.3 but you are using Symfony 3.2.8, e.g.
New in version 3.3: The ability to configure an argument by its name ($adminEmail) was added in Symfony 3.3. Previously, you could configure it only by its index (2 in this case) or by using empty quotes for the other arguments.
Try updating your service definition to:
#src/Acme/UserBundle/Resources/config/services.yml
services:
acme_user.registration:
class: Acme\UserBundle\EventListener\RegistrationListener
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: kernel.event_subscriber}
And a remark: as you are actually using a subscriber you should rename the class RegistrationListener to RegistrationSubscriber.
I'm following the FOSUserbundle Official documentation in order to change the redirection after a password resetting procedure. I created a new class named PasswordResettingListener and add it to services.yml
The class is executes up to the getSubscribedEvents() method but Symfony never calls my specific method that performs the redirect. Am I misunderstanding something?
src/AppBundle/EventListener/PasswordResettingListener.php:
<?php
// src/AppBundle/EventListener/PasswordResettingListener.php
namespace AppBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class PasswordResettingListener implements EventSubscriberInterface {
private $router;
public function __construct(UrlGeneratorInterface $router){
$this->router = $router;
}
/**
* CALLED!
*/
public static function getSubscribedEvents() {
return array(
FOSUserEvents::RESETTING_RESET_SUCCESS => 'onPasswordResettingSuccess',
);
}
public function onPasswordResettingSuccess(FormEvent $event) {
die("alone"); //never called
$url = $this->router->generate('tarifs'); //nevercalled
die("die alone !!!!!"); // never called
$event->setResponse(new RedirectResponse($url)); // ""
}
}
The service is recorded here (/app/config/services.yml):
# app/config/services.yml
services:
app.form.registration:
class: AppBundle\Form\RegistrationType
tags:
- { name: form.type, alias: app_user_registration }
app.password_resetting:
class: AppBundle\EventListener\PasswordResettingListener
arguments: [#router]
tags:
- { name: kernel.event_subscriber }
src/AppBundle/EventListener/PasswordResettingListener.php:
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class PasswordResettingListener implements EventSubscriberInterface
{
private $router;
private $security;
public function __construct(UrlGeneratorInterface $router, AuthorizationChecker $security) {
$this->router = $router;
$this->security = $security;
}
public static function getSubscribedEvents() {
return [
FOSUserEvents::RESETTING_RESET_SUCCESS => 'onPasswordResettingSuccess',
];
}
public function onPasswordResettingSuccess(FormEvent $event) {
$url = $this->router->generate('homepage');
$event->setResponse(new RedirectResponse($url));
}
}
and app/config/services.yml services:
acme_user.password_resetting:
class: src/AppBundle/EventListener/PasswordResettingListener
arguments: [ "#router", "#security.authorization_checker" ]
tags:
- { name: kernel.event_subscriber }
You can use FOSUser's routing in order to change the redirect.
When importing it's routes, you have two options: either you add all by adding
fos_user:
resource: "#FOSUserBundle/Resources/config/routing/all.xml"
or each of them, by path. This is the one for the user's profile:
fos_user_profile:
resource: "#FOSUserBundle/Resources/config/routing/profile.xml"
prefix: /profile
Just change it's prefix by your desired path and that's all.
PS: I tried to use Event Listener as well but it didn't worked.
Good luck!
I setup a listener class where i'll set the ownerid column on any doctrine prePersist. My services.yml file looks like this ...
services:
my.listener:
class: App\SharedBundle\Listener\EntityListener
arguments: ["#security.context"]
tags:
- { name: doctrine.event_listener, event: prePersist }
and my class looks like this ...
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\SecurityContextInterface;
class EntityListener
{
protected $securityContext;
public function __construct(SecurityContextInterface $securityContext)
{
$this->securityContext = $securityContext;
}
/**
*
* #param LifecycleEventArgs $args
*/
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
$entity->setCreatedby();
}
}
The result of this is the following error.
ServiceCircularReferenceException: Circular reference detected for service "doctrine.orm.default_entity_manager", path: "doctrine.orm.default_entity_manager -> doctrine.dbal.default_connection -> my.listener -> security.context -> security.authentication.manager -> fos_user.user_manager".
My assumption is that the security context has already been injected somewhere in the chain but I don't know how to access it. Any ideas?
I had similar problems and the only workaround was to pass the whole container in the constructor (arguments: ['#service_container']).
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\DependencyInjection\ContainerInterface;
class MyListener
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
// ...
public function prePersist(LifeCycleEventArgs $args)
{
$securityContext = $this->container->get('security.context');
// ...
}
}
As of Symfony 2.6 this issue should be fixed. A pull request has just been accepted into the master. Your problem is described in here.
https://github.com/symfony/symfony/pull/11690
As of Symfony 2.6, you can inject the security.token_storage into your listener. This service will contain the token as used by the SecurityContext in <=2.5. In 3.0 this service will replace the SecurityContext::getToken() altogether. You can see a basic change list here: http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements#deprecated-the-security-context-service
Example usage in 2.6:
Your configuration:
services:
my.entityListener:
class: App\SharedBundle\Listener\EntityListener
arguments:
- "#security.token_storage"
tags:
- { name: doctrine.event_listener, event: prePersist }
Your Listener
namespace App\SharedBundle\Listener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class EntityListener
{
private $token_storage;
public function __construct(TokenStorageInterface $token_storage)
{
$this->token_storage = $token_storage;
}
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entity->setCreatedBy($this->token_storage->getToken()->getUsername());
}
}
For a nice created_by example, you can use https://github.com/hostnet/entity-blamable-component/blob/master/src/Listener/BlamableListener.php for inspiration. It uses the hostnet/entity-tracker-component which provides a special event that is fired when an entity is changed during your request. There's also a bundle to configure this in Symfony2
https://github.com/hostnet/entity-tracker-component
https://github.com/hostnet/entity-tracker-bundle
There's a great answer already in this thread but everything changes. Now there're entity listeners classes in Doctrine:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#entity-listeners-class
So you can add an annotation to your entity like:
/**
* #ORM\EntityListeners({"App\Entity\Listener\PhotoListener"})
* #ORM\Entity(repositoryClass="App\Repository\PhotoRepository")
*/
class Photo
{
// Entity code here...
}
And create a class like this:
class PhotoListener
{
private $container;
function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/** #ORM\PreRemove() */
public function preRemoveHandler(Photo $photo, LifecycleEventArgs $event): void
{
// Some code here...
}
}
Also you should define this listener in services.yml like that:
photo_listener:
class: App\Entity\Listener\PhotoListener
public: false
autowire: true
tags:
- {name: doctrine.orm.entity_listener}
I use the doctrine config files to set preUpdate or prePersist methods:
Project\MainBundle\Entity\YourEntity:
type: entity
table: yourentities
repositoryClass: Project\MainBundle\Repository\YourEntitytRepository
fields:
id:
type: integer
id: true
generator:
strategy: AUTO
lifecycleCallbacks:
prePersist: [methodNameHere]
preUpdate: [anotherMethodHere]
And the methods are declared in the entity, this way you don't need a listener and if you need a more general method you can make a BaseEntity to keep that method and extend the other entites from that. Hope it helps!
Symfony 6.2.4
Add this in your Entity :
#[ORM\EntityListeners(["App\Doctrine\MyListener"])]
Add this in your services.yaml:
App\Doctrine\MyListener:
tags: [doctrine.orm.entity_listener]
Then you can do this :
<?php
namespace App\Doctrine;
use App\Entity\MyEntity;
use Symfony\Component\Security\Core\Security;
class MyListener
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function prePersist(MyEntity $myEntity)
{
//Your stuff
}
}
Hope it helps.