Symfony EventListener - symfony

I created some new events like app.client_enter or app.client_leave. Now I want to register a listener to listen on this events. If I add a listener in the same command, it's working.
ClientListener.php
namespace AppBundle\Service;
use AppBundle\Event\ClientEnterEvent;
class ClientListener {
public function onClientEnter(ClientEnterEvent $event) {
echo "It could be working";
}
}
service.yml (update)
services:
app.client_listener:
class: AppBundle\Service\ClientListener
tags:
- { name: kernel.event_listener, event: app.client_enter, method: onClientEnter }
ClientCommand.php
namespace AppBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use AppBundle\Event\ClientEnterEvent;
class ClientCommand extends ContainerAwareCommand {
protected function configure() { ... }
protected function execute(InputInterface $input, OutputInterface $output) {
$dispatcher = new EventDispatcher();
$dispatcher->dispatch('app.client_enter', new ClientEnterEvent("Maxi"));
}

It's name: kernel.event_listener for the tag

Thank you to all. I found the solution, in ContainerAwareCommand you have to use the service of event_dispatcher.
ClientCommand.php
namespace AppBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use AppBundle\Event\ClientEnterEvent;
class ClientCommand extends ContainerAwareCommand {
protected function configure() { ... }
protected function execute(InputInterface $input, OutputInterface $output) {
$dispatcher = $this->getContainer->get('event_dispatcher');
$dispatcher->dispatch('app.client_enter', new ClientEnterEvent("Maxi"));
}
After I used this service, my event trigger the listener.

Just a tip hoẃ to make this even better.
Since great Dependency Injection changes in Symfony 3.3+ you can delegate many error prone code to Symfony.
Simplify Service Registration
# app/config/services.yml
services:
_defaults:
autowire: true
AppBundle\:
resouce: '../../src/AppBundle'
It doesn't work for listeners, because of extra tags, but it does for subscribers - I recommend to use them to prevent any extra redundant config proramming.
Get Arguments The Clean Way - Via Constructor
Using that, you can use constructor injection in your commands out of the box.
use Symfony\Component\EventDispatcher\EventDispatcherInterface
class ClientCommand extends Command
{
/**
* #var EventDispatcherInterface
*/
private $eventDispatcher;
public function __construct(EventDispatcherInterface $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->eventDispatcher->dispatch(...);
}
}
To read more about DI changes, see this post with before/after examples.

Related

Generate Routes in Symfony Command Class

I am using Symfony 4.2.1 and I cannot somehow not use the generate() Method of router:
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Routing\RouterInterface;
class NewAcceptedOrderCommand extends Command
{
/**
* #var RouterInterface $router
*/
private $router;
public function __construct(
RouterInterface $router
) {
$this->router = $router;
parent::__construct();
}
protected function configure()
{
$this
->setName('address:new')
->setDescription('Get adresses by Status ID')
->setHelp('Get adresses by Status ID')
->addArgument('id', InputArgument::REQUIRED, 'Status ID');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$url = $this->router->generate('adresses_index');
// SOME more Code
}
}
I injected all sorts of other services and interfaces before (EntityManagerInterface, my Logservice etc.)
But I am always getting
Unable to generate a URL for the named route "adresses_index" as such
route does not exist.
But this route exists for sure I checked with php bin/console debug:router and also using it in other places. I am using the global approach from here: https://symfony.com/doc/current/console/request_context.html#configuring-the-request-context-globally
I was thinking to secure at the beginning and added the annotation "method" to every action - it has to be unset (ANY). It works fine afterwards.
Dont forget to configure your router
$context = $this->router->getContext();
$context->setHost($this->container->getParameter('router_host'));
$context->setScheme($this->container->getParameter('router_scheme'));

Fosuserbundle override event listener

I'm trying to override the LastLoginListener to add functionality to it.
I;m trying to do it as described here
It seems
In AppBundle\DependencyInjection\OverrideServiceCompilerPass.php
<?php
namespace AppBundle\DependencyInjection\Compiler;
use AppBundle\EventListener\LastLoginListener;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class OverrideServiceCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('"fos_user.security.interactive_login_listener');
$definition->setClass(LastLoginListener::class);
}
services.yml
services:
app.login_listener:
class: AppBundle\EventListener\LastLoginListener
arguments: []
tags:
- { name: kernel.event_subscriber }
The listener itself is copied from the bundle.
The autoloader expected class "AppBundle\DependencyInjection\OverrideServiceCompilerPass" to be defined in file "/vendor/composer/../../src/AppBundle/DependencyInjection/OverrideServiceCompilerPass.php". The file was found but the class was not in it, the class name or namespace probably has a typo.
in DebugClassLoader.php (line 261)
My goal is to add the ip address of the last login with the listener, but I'll need to create another to add a role and a registration date
I'm trying to do it "the right way" instead of doing something hackish
Its much better to use success_handler and failure_handler services.
# app/config/security.yml
firewalls:
main:
...
form_login:
...
success_handler: authentication_success_handler
failure_handler: authentication_failure_handler
Next you need to register your services and add arguments that fit your needs (probably #router and #doctrine.orm.entity_manager)
# app/config/services.yml
authentication_success_handler:
class: AppBundle\Handler\AuthenticationSuccessHandler
arguments: ['#router', '#doctrine.orm.entity_manager']
authentication_failure_handler:
class: AppBundle\Handler\AuthenticationFailureHandler
arguments: ['#router', '#doctrine.orm.entity_manager']
Then you need to create your services
// src/AppBundle/Handler/AuthenticationSuccessHandler.php
<?php
namespace AppBundle\Handler;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Router;
use Doctrine\Common\Persistence\ObjectManager;
class AuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface {
protected $router;
private $em;
public function __construct(Router $router, ObjectManager $em) {
$this->router = $router;
$this->em = $om;
}
public function onAuthenticationSuccess(Request $request, AuthenticationException $exception) {
// your code here - creating new object. redirects etc.
}
}
and
// src/AppBundle/Handler/AuthenticationFailureHandler.php
<?php
namespace AppBundle\Handler;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Router;
use Doctrine\Common\Persistence\ObjectManager;
class AuthenticationFailureHandler implements AuthenticationFailureHandlerInterface {
protected $router;
private $em;
public function __construct(Router $router, ObjectManager $em) {
$this->router = $router;
$this->em = $om;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception) {
// your code here - creating new object. redirects etc.
}
}
If you want to hook into another FOSUserBundle Controller use this

Definition : addMethodCall works but methods are never invoked

I want to use custom tags on my services, so I followed the instructions in the documentation: http://symfony.com/doc/2.8/components/dependency_injection/tags.html
I have a RulesHydrator class:
<?php
namespace TestBundle\Thruway;
class RulesHydrator
{
private $container;
private $manualChecks = [];
public function __construct($container)
{
$this->container = $container;
}
public function addManualCheck($service, $rule, $method)
{
echo 'addManualCheck invoked!'.PHP_EOL;
exit;
$this->manualChecks[$rule] = $service;
}
}
Here is the compiler pass:
<?php
namespace TestBundle\Thruway;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
class ThruwayCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->has('thruway.rules_hydrator')) {
return;
}
$definition = $container->findDefinition('thruway.rules_hydrator');
foreach ($container->findTaggedServiceIds('thruway.manual_check') as $id => $tags) {
foreach ($tags as $attributes) {
$definition->addMethodCall('addManualCheck', [new Reference($id), $attributes['rule'], $attributes['method']]);
}
}
}
}
Here is my bundle's class:
<?php
namespace TestBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use TestBundle\Thruway\ThruwayCompilerPass;
class TestBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new ThruwayCompilerPass());
}
}
My services.yml file look like this:
services:
thruway.rules_hydrator:
class: TestBundle\Thruway\RulesHydrator
thruway.customer_checker:
class: TestBundle\Thruway\MyChecker
tags:
- { name: thruway.manual_check, rule: some.rule1, method: someMethod1 }
- { name: thruway.manual_check, rule: some.rule2, method: someMethod2 }
The process method is called and the different calls to addMethodCall on my Definition object work fine (the property "calls" of the definition is correctly filled). The problem is that the calls to my method addManualCheck never occur. Any idea why?
The case could be that you did not instantiated the service. By default from what I remember the services are lazy loaded, and until you actually fetch one from the container, or it is injected into an other service, it will not be initialized.
Can you look into your appProdProjectContainer.php inside app/cache/prod for the "TestBundle\Thruway\MyChecker" and post back how it is used?
Also try a fast check by getting the thruway.customer_checker from the container.
A fast command like this could help
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class TestCommand extends ContainerAwareCommand
{
protected function configure()
{
$this->setName('service:test')->setDescription('Test service functionalities');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$service = $this->getContainer()->get('thruway.customer_checker');
}
}

RenderView in Symfony Command usage

How can I use $this->renderView inside a symfony Command (not inside a controller)? I new about the function "renderView" but what do I have to setup to use it wihtin a command?
Thank you in advance an regards
Your command class must extends the ContainerAwareCommand abstract class and then you can do:
$this->getContainer()->get('templating')->render($view, $parameters);
When it comes to commands that extend ContainerAwareCommand the proper way to obtain the container is by getContainer() unlike in controller shortcut.
In Symfony 4 I could not get $this->getContainer()->get('templating')->render($view, $parameters); to work.
I set the namespace use for Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand and extended ContainerAwareCommand class EmailCommand extends ContainerAwareCommand
I get an exception thrown
[Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException]
You have requested a non-existent service "templating".
For Symfony 4, this is the solution I came up with.
First I installed Twig.
composer require twig
Then created my own twig service.
<?php
# src/Service/Twig.php
namespace App\Service;
use Symfony\Component\HttpKernel\KernelInterface;
class Twig extends \Twig_Environment {
public function __construct(KernelInterface $kernel) {
$loader = new \Twig_Loader_Filesystem($kernel->getProjectDir());
parent::__construct($loader);
}
}
Now my email command looks like this.
<?php
# src/Command/EmailCommand.php
namespace App\Command;
use Symfony\Component\Console\Command\Command,
Symfony\Component\Console\Input\InputInterface,
Symfony\Component\Console\Output\OutputInterface,
App\Service\Twig;
class EmailCommand extends Command {
protected static $defaultName = 'mybot:email';
private $mailer,
$twig;
public function __construct(\Swift_Mailer $mailer, Twig $twig) {
$this->mailer = $mailer;
$this->twig = $twig;
parent::__construct();
}
protected function configure() {
$this->setDescription('Email bot.');
}
protected function execute(InputInterface $input, OutputInterface $output) {
$template = $this->twig->load('templates/email.html.twig');
$message = (new \Swift_Message('Hello Email'))
->setFrom('emailbot#domain.com')
->setTo('someone#somewhere.com')
->setBody(
$template->render(['name' => 'Fabien']),
'text/html'
);
$this->mailer->send($message);
}
}
Yet another one: rely on dependency injection, i.e. inject ContainerInterface
namespace AppBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Psr\Container\ContainerInterface;
class SampleCommand extends Command
{
public function __construct(ContainerInterface $container)
{
$this->templating = $container->get('templating');
parent::__construct();
}
protected function configure()
{
$this->setName('app:my-command')
->setDescription('Do my command using render');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$data = retrieveSomeData();
$csv = $this->templating->render('path/to/sample.csv.twig',
array('data' => $data));
$output->write($csv);
}
private $templating;
}
This relies on Symfony to inject the container, which in turn is used to retrieve either templating or twig or whatever you need for your custom command.

Symfony2 getdoctrine outside of Model/Controller

I'm trying to getDoctrine() outside of the controller.
I've created this service:
config/services.yml
services:
update_command:
class: project\projBundle\Command\Update
arguments: ['#doctrine.orm.entity_manager']
and in my app/config/config.yml
imports:
- { resource: "#projectprojBundle/Resources/config/services.yml" }
so and the class that I want to use:
namespace project\projBundle\Command;
use Doctrine\ORM\EntityManager;
class Update {
protected $em;
public function __construct(EntityManager $em) {
$this->em = $em;
}
but every time I want to do this: (I'm doing this right?)
$up = new Update();
i got this error:
Catchable Fatal Error: Argument 1 passed to ...\Update::__construct() must be an instance of Doctrine\ORM\EntityManager, none given, called in .../Update.php line 7
Simple solution
If you're implementing a Symfony command (that can be executed in a cron tab), you can access the service container from the command.
<?php
namespace MyProject\MyBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class UpdateCommand extends ContainerAwareCommand
{
protected $em;
protected function configure()
{
$this->setName('myproject:mybundle:update') ;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->em = $this->getContainer()->get('doctrine.orm.entity_manager');
}
}
That way, you get the entity manager from a command and don't need to declare this class as a service. You can therefore remove the configuration you added in the services.yml file.
An other solution (cleaner)
This solution allows better separation of concerns and can therefore be easily unit tested and reused in other parts of your Symfony application (not only as a command).
Move all the logic part of your "update" command to a dedicated class that you will declare as a service:
<?php
namespace MyProject\MyBundle\Service;
use Doctrine\ORM\EntityManager;
class MyUpdater
{
protected $em;
public function __construct($em)
{
$this->em = $em;
}
public function runUpdate()
{
// All your logic code here
}
}
Declare it as a service in your services.yml file:
services:
myproject.mybundle.myupdater:
class: MyProject\MyBundle\Service\MyUpdater
arguments: ['#doctrine.orm.entity_manager']
Simply call your service from your command :
<?php
namespace MyProject\MyBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class UpdateCommand extends ContainerAwareCommand
{
protected function configure()
{
$this->setName('myproject:mybundle:update') ;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$myUpdater = $this->getContainer()->get('myproject.mybundle.myupdater');
$myUpdater->runUpdate();
}
}
You have to either inject your newly created #update_command service or get it from the container in order to have the #doctrine.orm.entity_manager service injected automatically.
You're just creating the object with no argument, not a service. Update expects to retrieve an entity manager instance but you don't provide it.
$up = new Update();
In a ContainerAware class like a controller get your service like this:
$up = $this->container->get('update_command');
Otherwise turn the class where you want to use the update_command into a service aswell and inject #update_command as you did with the entity manager in the service itself.
remove below codes in app/config/config.yml, your services.yml will be autoload...
imports:
- { resource: "#projectprojBundle/Resources/config/services.yml" }
in a Action new a instance you can do:
$up = $this->get('update_command');

Resources