I'm learning Symfony and I'm trying to create: a new service and Event
My service send emails
config.yml
parameters:
MyService.class: Acme\UserBundle\Services\sendEmail
MyService.transport: sendmail
service.yml
services:
MyService:
class: %MyService.class%
arguments: [#mailer]
sendEmail.php
class sendEmail {
private $mail;
public function __construct ($mail) {
$this->mail = $mail;
}
public function sendMail () {
$msg = \Swift_Message::newInstance()
->setSubject('Hi')
->setFrom('xxx#example.com')
->setTo('cc#gmail.com')
->setBody('ok');
$this->mail->send($msg);
}
}
My Event
I have created these class
UserEvent.php
<?php
namespace Acme\UserBundle\Event;
final class UserEvents {
const NEW_USER='new.user';
}
newUserEvent.php
<?php
/**
* EVENT DISPATCHER
*/
namespace Acme\UserBundle\Event;
use Acme\UserBundle\Entity\User;
use Symfony\Component\EventDispatcher\Event;
class NewUserEvent extends Event {
protected $user;
public function __construct (User $user) {
$this->user = $user;
}
public function getUser () {
return $this->user;
}
}
newUserListener.php
<?php
namespace Acme\UserBundle\Event;
use Acme\UserBundle\Services\sendEmail;
class NewUserListener {
public function sendEmailToUsers(NewUserEvent $event,sendEmail $service)
{
// ... send email to users
}
}
in my controller
$em = $this->getEm();
$dispatcher = new EventDispatcher();
// attach listener
$listner = new NewUserListener();
$dispatcher->addListener(UserEvents::NEW_USER,array($listner,'sendEmailToUsers'));
$user = $em->getRepository('AcmeUserBundle:User')->findOneBy(array('username' => 'alex')); //mock
$event = new NewUserEvent($user,$this->get('MyService'));
$dispatcher->dispatch(UserEvents::NEW_USER,$event);
return new Response('hi');
I'd like to use my service inside my event by I have this error
ContextErrorException: Catchable Fatal Error: Argument 2 passed to
Acme\UserBundle\Event\NewUserListener::sendEmailToUsers() must be an
instance of Acme\UserBundle\Services\sendEmail, none given in
Acme/UserBundle/Event/NewUserListener.php
There are many points here :
You create a new event dispatcher in your controller, but the event dispatcher service is already available in a standard Symfony app, you should use it and register your listener with the kernel.event_listener tag for your service :
http://symfony.com/doc/current/cookbook/service_container/event_listener.html
$disptacher = $this->container->get('event_dispatcher');
Secondly, nowhere you inject your service in your listener ?
Related
How can I mock a service in a functional test use-case where a "request"(form/submit) is being made. After I make the request all the changes and mocking I made to the container are lost.
I am using Symfony 4 or 5. The code posted here can be also found here: https://github.com/klodoma/symfony-demo
I have the following scenario:
SomeActions service is injected into the controller constructor
in the functional unit-tests I try to mock the SomeActions functions in order to check that they are executed(it sends an email or something similar)
I mock the service and overwrite it in the unit-tests:
$container->set('App\Model\SomeActions', $someActions);
Now in the tests I do a $client->submit($form); which I know that it terminates the kernel.
My question is: HOW can I inject my mocked $someActions in the container after $client->submit($form);
Below is a sample code I added to the symfony demo app
https://github.com/symfony/demo
in services.yaml
App\Model\SomeActions:
public: true
SomeController.php
<?php
namespace App\Controller;
use App\Model\SomeActions;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* Controller used to send some emails
*
* #Route("/some")
*/
class SomeController extends AbstractController
{
private $someActions;
public function __construct(SomeActions $someActions)
{
//just dump the injected class name
var_dump(get_class($someActions));
$this->someActions = $someActions;
}
/**
* #Route("/action", methods="GET|POST", name="some_action")
* #param Request $request
* #return Response
*/
public function someAction(Request $request): Response
{
$this->someActions->doSomething();
if ($request->get('send')) {
$this->someActions->sendEmail();
}
return $this->render('default/someAction.html.twig', [
]);
}
}
SomeActions
<?php
namespace App\Model;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
class SomeActions
{
private $mailer;
public function __construct(MailerInterface $mailer)
{
$this->mailer = $mailer;
}
public function doSomething()
{
echo 'doSomething';
}
public function sendEmail()
{
echo 'sendEmail';
$email = (new Email())
->from('hello#example.com')
->to('you#example.com')
->subject('Time for Symfony Mailer!')
->text('Sending emails is fun again!')
->html('<p>See Twig integration for better HTML integration!</p>');
$this->mailer->send($email);
}
}
SomeControllerTest.php
<?php
namespace App\Tests\Controller;
use App\Model\SomeActions;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class SomeControllerTest extends WebTestCase
{
public function testSomeAction()
{
$client = static::createClient();
// gets the special container that allows fetching private services
$container = self::$container;
$someActions = $this->getMockBuilder(SomeActions::class)
->disableOriginalConstructor()
->getMock();
//expect that sendEmail will be called
$someActions->expects($this->once())
->method('sendEmail');
//overwrite the default service: class: Mock_SomeActions_e68f817a
$container->set('App\Model\SomeActions', $someActions);
$crawler = $client->request('GET', '/en/some/action');
//submit the form
$form = $crawler->selectButton('submit')->form();
$client->submit($form);
//after submit the default class injected in the controller is "App\Model\SomeActions" and not the mocked service
$response = $client->getResponse();
$this->assertResponseIsSuccessful($response);
}
}
The solution is to disable the kernel reboot:
$client->disableReboot();
It makes sense if ones digs deep enough to understand what's going on under the hood;
I am still not sure if there isn't a more straight forward answer.
public function testSomeAction()
{
$client = static::createClient();
$client->disableReboot();
...
For my project, I need to redirect the user after registration. In order to achieve that, I created an EventListener as described below :
My Event Listener :
namespace UserBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class RegistrationConfirmListener implements EventSubscriberInterface
{
private $router;
public function __construct(UrlGeneratorInterface $router)
{
$this->router = $router;
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_CONFIRM => 'onRegistrationConfirm'
);
}
public function onRegistrationConfirm(GetResponseUserEvent $event)
{
$url = $this->router->generate('standard_user_registration_success');
$event->setResponse(new RedirectResponse($url));
}
}
I registered it as a service in my service.yml :
services:
rs_user.registration_complet:
class: UserBundle\EventListener\RegistrationConfirmListener
arguments: [#router]
tags:
- { name: kernel.event_subscriber }
And I need to use it in my RegistrationController but I don't understand how to trigger it.
Here in my registerAction :
public function registerAction(Request $request)
{
$em = $this->get('doctrine.orm.entity_manager');
//Form creation based on my user entity
$user = new StandardUser();
$form = $this->createForm(RegistrationStandardUserType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user ->setEnabled(true);
$em ->persist($user);
$em ->flush();
if ($user){
$dispatcher = $this->get('event_dispatcher');
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_CONFIRM);
}
}
return $this->render('UserBundle:Registration:register.html.twig', array(
'form' => $form->createView()
));
}
I don't understand the Symfony2 documentation about the subject neither what I need to pass to the ->dispatch() function to trigger my event.
[EDIT]
I get this error when I register my user :
Type error: Argument 1 passed to
UserBundle\EventListener\RegistrationConfirmListener::onRegistrationConfirm()
must be an instance of UserBundle\EventListener\GetResponseUserEvent,
instance of Symfony\Component\EventDispatcher\Event given
500 Internal Server Error - FatalThrowableError
Your listener declares that it is subscribed to FOSUserEvents::REGISTRATION_CONFIRM but you are dispatching FOSUserEvents::REGISTRATION_COMPLETED. To trigger it you need to dispatch a FOSUserEvents::REGISTRATION_CONFIRM event
edit to match your edit, you need to pass the event in your services tags:
- { name: 'kernel.event_subscriber', event: 'fos_user.registration.confirm'}
This is the first time ever I am working with creating custom event dispatcher and subscriber so I am trying to wrap my head around it and I cant seem to find out why my custom event is not being dispatched.
I am following the documentation and in my case I need to dispatch an event as soon as someone registers on the site.
so inside my registerAction() I am trying to dispatch an event like this
$dispatcher = new EventDispatcher();
$event = new RegistrationEvent($user);
$dispatcher->dispatch(RegistrationEvent::NAME, $event);
This is my RegistrationEvent class
namespace AppBundle\Event;
use AppBundle\Entity\User;
use Symfony\Component\EventDispatcher\Event;
class RegistrationEvent extends Event
{
const NAME = 'registration.complete';
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function getUser(){
return $this->user;
}
}
This is my RegistrationSubscriber class
namespace AppBundle\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class RegistrationSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
KernelEvents::RESPONSE => array(
array('onKernelResponsePre', 10),
array('onKernelResponsePost', -10),
),
RegistrationEvent::NAME => 'onRegistration'
);
}
public function onKernelResponsePre(FilterResponseEvent $event)
{
// ...
}
public function onKernelResponsePost(FilterResponseEvent $event)
{
// ...
}
public function onRegistration(RegistrationEvent $event){
var_dump($event);
die;
}
}
After doing this, I was hoping that the registration process would stop at the function onRegistration but that did not happen, I then looked at the Events tab of the profiler and I do not see my Event listed their either.
What am I missing here? A push in right direction will really be appreciated.
Update:
I thought i need to register a service for the custom event so I added the following code inside services.yml
app.successfull_registration_subscriber:
class: AppBundle\Event\RegistrationSubscriber
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: kernel.event_subscriber}
Inside the Event tab of profiler I do see my custom event being listed but it still does not dispatch.
By creating your own EventDispatcher instance you dispatch an event that can never be listened to by other listeners (they are not attached to this dispatcher instance). You need to use the event_dispatcher service to notify all listeners you have tagged with the kernel.event_listener and kernel.event_subscriber tags:
// ...
class RegistrationController extends Controller
{
public function registerAction()
{
// ...
$this->get('event_dispatcher')->dispatch(RegistrationEvent::NAME, new RegistrationEvent($user););
}
}
Duplicate of dispatcher doesn't dispatch my event symfony
With auto-wiring, it is now better to inject the EventDispatcherInterface
<?php
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
//...
class DefaultController extends Controller
{
public function display(Request $request, EventDispatcherInterface $dispatcher)
{
//Define your event
$event = new YourEvent($request);
$dispatcher->dispatch(YourEvent::EVENT_TO_DISPATCH, $event);
}
}
I'm trying to use https://github.com/fervo/FervoDeferredEventBundle in order to have async events to store data in my DB. The event is added to the RabbitMQ Queue correctly but the event listener and the event itself is never executed.
I'm trying in both ways: first letting the listener to do the job and also dispatching manually the deferevent.
Any idea?
Thank you!
My code:
in my Controller:
$event = new DeferEvent('save.data', new SaveDataEvent($data));
$this->get('event_dispatcher')->dispatch('fervo.defer', $event);
SaveDataEvent.php
<?php
namespace AppBundle\Event;
use Symfony\Component\EventDispatcher\Event;
class SaveDataEvent extends Event
{
protected $data;
public function __construct($data)
{
$this->data = $data;
}
public function getData()
{
return $this->data;
}
public function saveData()
{
$data = $this->getData();
// do more stuff
}
}
SaveDataListener.php
<?php
namespace AppBundle\EventListener;
use AppBundle\Event\SaveDataEvent;
class SaveDataListener
{
/**
* #var SaveDataEvent
*/
public function onSendData(SaveDataEvent $event)
{
$data = $event->saveData();
}
}
services.yml
app.save_data_listener:
class: AppBundle\EventListener\SaveDataListener
tags:
-
name: kernel.event_listener #fervo_deferred_event.listener
event: send.data
Reading the docs, you should fix the services.yml:
app.save_data_listener:
class: AppBundle\EventListener\SaveDataListener
tags:
name: fervo_deferred_event.listener
event: fervo.defer
Replace fervo.defer with the first value passed to dispatch() method if you want, it's the name of the dispatched event.
I have a program with two bundles. One of them (CommonBundle) dispatches an event "common.add_channel", while a service on the other one (FetcherBundle) was supposed to be listening to it. On the profiler, I can see the event common.add_channel in the "Not Called Listeners" section. I don't get why symfony is not registering my listener.
This is my action, inside CommonBundle\Controller\ChannelController::createAction:
$dispatcher = new EventDispatcher();
$event = new AddChannelEvent($entity);
$dispatcher->dispatch("common.add_channel", $event);
This is my AddChannelEvent:
<?php
namespace Naroga\Reader\CommonBundle\Event;
use Symfony\Component\EventDispatcher\Event;
use Naroga\Reader\CommonBundle\Entity\Channel;
class AddChannelEvent extends Event {
protected $_channel;
public function __construct(Channel $channel) {
$this->_channel = $channel;
}
public function getChannel() {
return $this->_channel;
}
}
This was supposed to be my listener (FetcherService.php):
<?php
namespace Naroga\Reader\FetcherBundle\Service;
class FetcherService {
public function onAddChannel(AddChannelEvent $event) {
die("It's here!");
}
}
And here's where I register my listener (services.yml):
kernel.listener.add_channel:
class: Naroga\Reader\FetcherBundle\Service\FetcherService
tags:
- { name: kernel.event_listener, event: common.add_channel, method: onAddChannel }
What am I doing wrong? Why isn't symfony calling the event listener when I dispatch common.add_channel?
The new event dispatcher doesn't know anything about the listeners set on another dispatcher.
In your controller, you need to access the event_dispatcher service. A Compiler Pass of the Framework Bundle attached all listeners to this dispatcher. To get the service, use the Controller#get() shortcut:
// ...
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class ChannelController extends Controller
{
public function createAction()
{
$dispatcher = $this->get('event_dispatcher');
// ...
}
}