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 have a Lumen controller class that fires an event when a user is created. I am using an event dispatcher. The event gets fired as it should, but the listener does not handle the event.
I am sure I have followed every step of the Lumen documentation.
// UserController.php
class UserController extends ApiController
{
protected $event = null;
public function __construct(Dispatcher $event)
{
$this->event = $event;
}
/**
* Store a newly created resource in storage.
*
* #param Request $request
* #return Response
*/
public function store(Request $request)
{
$this->acceptContentType($request, 'json');
$this->input = $request->json()->all();
$this->withEncryptedParameters();
$this->validateParameterNames(array_keys($this->validationRules));
$this->validateParameterContent($this->validationRules);
$roles = $this->getRelationInput('roles');
$user = User::create($this->input);
$this->addRelation($user, $roles, Role::class, 'roles');
$this->event->fire(new UserCreated($user));
return $this->respondCreated($user->id);
}
}
So I basically want to store a user into the database and fire an event when that happens.
// UserCreated.php
class UserCreated extends Event
{
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
}
The event is fired correctly, so if I put an "echo" or a "var_dump" into the event's constructor, I can see that it works. If I so the same for the listener, it does not react.
// UserCreatedEmail.php
class UserCreatedEmail extends Listener
{
public function handle(UserCreated $event)
{
echo 'Hello?';
}
}
I have registered it in the EventServiceProvider.
// EventServiceProvider.php
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* #var array
*/
protected $listen = [
UserCreated::class => [
UserCreatedEmail::class
]
];
}
And uncommented it in the bootstrap area.
// bootstrap/app.php
$app->register(WISSIT\UserService\Providers\EventServiceProvider::class);
I have absolutely no idea why is doesn't work. I could use "$event->listen" but then it would also listen when I use testing. According to the Lumen documentation, it should also work without that.
And yes, the namespaces are set correctly. And no, I do not want to use Facades.
In bootstrap/app.php under 'Register Service Providers' comment out the registration of the service provider.
// $app->register(App\Providers\EventServiceProvider::class);
into
$app->register(App\Providers\EventServiceProvider::class);
I think I already got this problem, so this is how I resolve this:
In your EventServiceProvider change the event class and listener class to a real path, dont use ::class in EventServiceProvider. I.e:
// EventServiceProvider.php
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* #var array
*/
protected $listen = [
'WISSIT\UserService\Events\UserCreated' => [
'WISSIT\UserService\Listeners\UserCreatedEmail'
]
];
}
Okay, so it appears like the event is only listened when using \Illuminate\Contracts\Event\Dispatcher in the controller. I used \Illuminate\Events\Dispatcher. I don't know why that is the case, but it worked.
I had a similar issue when working with Lumen and event listeners. The fired event never reached my custom listener and I was struggling for a day to figure out where the problem is.
At the end I figured out, that I had a wrong signature of handle method on my listener. It was my mistake, but Dispatcher did not notify me about that issue. When I changed the method to accept given arguments, it just started working.
I think the problem lies in the Illuminate\Events\Dispatcher in the fire method. Function call_user_func_array returns false if the signature of the method is wrong, but dispatcher just breaks out of the loop on error. And does not notify user about an issue.
In a Symfony2 application, I have an entity that needs to be populated on pre-persist with various context properties (like user id, what page it was called from, etc.)
I figured that to do this, I need to add a doctrine event listener that has access to "service_container", and the best way to give such access is to pass "service_container" as an argument to this listener.
I have a specific entity that I want to listen to, and I do not want to trigger the listener to events with any other entity.
We can add an entity-specific listener, documentation is found here:
http://docs.doctrine-project.org/en/latest/reference/events.html#entity-listeners
- but this does not provide example of how to pass an argument (I use PHP annotations to declare the listener).
I also tried to use JMSDiExtraBundle annotations, like in the example below:
http://jmsyst.com/bundles/JMSDiExtraBundle/master/annotations#doctrinelistener-or-doctrinemongodblistener
- but this way requires to declare the listener as non-entity-specific
Is there any way to make a listener for one entity only, and have it have access to container?
One of the ways similar to doctrine docs through dependency injection:
<?php
namespace AppBundle\EntityListener;
use AppBundle\Entity\User;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Psr\Log\LoggerInterface;
use Symfony\Component\Routing\RouterInterface;
class UserListener {
/**
* #var LoggerInterface
*/
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function postPersist(User $user, LifecycleEventArgs $args)
{
$logger = $this->logger;
$logger->info('Event triggered');
//Do something
}
}
services:
user.listener:
class: AppBundle\EntityListener\UserListener
arguments: [#logger]
tags:
- { name: doctrine.orm.entity_listener }
And dont forget add listener to entity mapping:
AppBundle\Entity\User:
type: entity
table: null
repositoryClass: AppBundle\Entity\UserRepository
entityListeners:
AppBundle\EntityListener\UserListener: ~
I would simply check entity type from the event. If you check type inside or outside the subscriber, it has the same performance cost. And simple type condition is fast enough.
namespace App\Modules\CoreModule\EventSubscriber;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Events;
class SetCountryToTaxSubscriber implements EventSubscriber
{
/**
* {#inheritdoc}
*/
public function getSubscribedEvents()
{
return [Events::prePersist];
}
public function prePersist(LifecycleEventArgs $lifecycleEventArgs)
{
$entity = $lifecycleEventArgs->getEntity();
if ( ! $entity instanceof Tax) {
return;
}
$entity->setCountry('myCountry');
}
}
So the way I see it is that a good Laravel application should be very model- and event-driven.
I have a Model called Article. I wish to send email alerts when the following events happen:
When an Article is created
When an Article is updated
When an Article is deleted
The docs say I can use Model Events and register them within the boot() function of App\Providers\EventServiceProvider.
But this is confusing me because...
What happens when I add further models like Comment or Author that need full sets of all their own Model Events? Will the single boot() function of EventServiceProvider just be absolutely huge?
What is the purpose of Laravel's 'other' Events? Why would I ever need to use them if realistically my events will only respond to Model CRUD actions?
I am a beginner at Laravel, having come from CodeIgniter, so trying to wrap my head around the proper Laravel way of doing things. Thanks for your advice!
In your case, you may also use following approach:
// Put this code in your Article Model
public static function boot() {
parent::boot();
static::created(function($article) {
Event::fire('article.created', $article);
});
static::updated(function($article) {
Event::fire('article.updated', $article);
});
static::deleted(function($article) {
Event::fire('article.deleted', $article);
});
}
Also, you need to register listeners in App\Providers\EventServiceProvider:
protected $listen = [
'article.created' => [
'App\Handlers\Events\ArticleEvents#articleCreated',
],
'article.updated' => [
'App\Handlers\Events\ArticleEvents#articleUpdated',
],
'article.deleted' => [
'App\Handlers\Events\ArticleEvents#articleDeleted',
],
];
Also make sure you have created the handlers in App\Handlers\Events folder/directory to handle that event. For example, article.created handler could be like this:
<?php namespace App\Handlers\Events;
use App\Article;
use App\Services\Email\Mailer; // This one I use to email as a service class
class ArticleEvents {
protected $mailer = null;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function articleCreated(Article $article)
{
// Implement mailer or use laravel mailer directly
$this->mailer->notifyArticleCreated($article);
}
// Other Handlers/Methods...
}
Recently I came to same problem in one of my Laravel 5 project, where I had to log all Model Events. I decided to use Traits. I created ModelEventLogger Trait and simply used in all Model class which needed to be logged. I am going to change it as per your need Which is given below.
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Event;
/**
* Class ModelEventThrower
* #package App\Traits
*
* Automatically throw Add, Update, Delete events of Model.
*/
trait ModelEventThrower {
/**
* Automatically boot with Model, and register Events handler.
*/
protected static function bootModelEventThrower()
{
foreach (static::getModelEvents() as $eventName) {
static::$eventName(function (Model $model) use ($eventName) {
try {
$reflect = new \ReflectionClass($model);
Event::fire(strtolower($reflect->getShortName()).'.'.$eventName, $model);
} catch (\Exception $e) {
return true;
}
});
}
}
/**
* Set the default events to be recorded if the $recordEvents
* property does not exist on the model.
*
* #return array
*/
protected static function getModelEvents()
{
if (isset(static::$recordEvents)) {
return static::$recordEvents;
}
return [
'created',
'updated',
'deleted',
];
}
}
Now you can use this trait in any Model you want to throw events for. In your case in Article Model.
<?php namespace App;
use App\Traits\ModelEventThrower;
use Illuminate\Database\Eloquent\Model;
class Article extends Model {
use ModelEventThrower;
//Just in case you want specific events to be fired for Article model
//uncomment following line of code
// protected static $recordEvents = ['created'];
}
Now in your app/Providers/EventServiceProvider.php, in boot() method register Event Handler for Article.
public function boot(DispatcherContract $events)
{
parent::boot($events);
$events->subscribe('App\Handlers\Events\ArticleEventHandler');
}
Now create Class ArticleEventHandler under app/Handlers/Events directory as below,
<?php namespace App\Handlers\Events;
use App\Article;
class ArticleEventHandler{
/**
* Create the event handler.
*
* #return \App\Handlers\Events\ArticleEventHandler
*/
public function __construct()
{
//
}
/**
* Handle article.created event
*/
public function created(Article $article)
{
//Implement logic
}
/**
* Handle article.updated event
*/
public function updated(Article $article)
{
//Implement logic
}
/**
* Handle article.deleted event
*/
public function deleted(Article $article)
{
//Implement logic
}
/**
* #param $events
*/
public function subscribe($events)
{
$events->listen('article.created',
'App\Handlers\Events\ArticleEventHandler#created');
$events->listen('article.updated',
'App\Handlers\Events\ArticleEventHandler#updated');
$events->listen('article.deleted',
'App\Handlers\Events\ArticleEventHandler#deleted');
}
}
As you can see from different answers, from different Users, there are more than 1 way of handling Model Events. There are also Custom events That can be created in Events folder and can be handled in Handler folder and can be dispatched from different places. I hope it helps.
I found this the cleanest way to do what you want.
1.- Create an observer for the model (ArticleObserver)
use App\Article;
class ArticleObserver{
public function __construct(Article $articles){
$this->articles = $articles
}
public function created(Article $article){
// Do anything you want to do, $article is the newly created article
}
}
2.- Create a new ServiceProvider (ObserversServiceProvider), remember to add it to you config/app.php
use App\Observers\ArticleObserver;
use App\Article;
use Illuminate\Support\ServiceProvider;
class ObserversServiceProvider extends ServiceProvider
{
public function boot()
{
Article::observe($this->app->make(ArticleObserver::class));
}
public function register()
{
$this->app->bindShared(ArticleObserver::class, function()
{
return new ArticleObserver(new Article());
});
}
}
You can opt for the Observer approach to deal with Model Events. For example, here is my BaseObserver:
<?php
namespace App\Observers;
use Illuminate\Database\Eloquent\Model as Eloquent;
class BaseObserver {
public function saving(Eloquent $model) {}
public function saved(Eloquent $model) {}
public function updating(Eloquent $model) {}
public function updated(Eloquent $model) {}
public function creating(Eloquent $model) {}
public function created(Eloquent $model) {}
public function deleting(Eloquent $model) {}
public function deleted(Eloquent $model) {}
public function restoring(Eloquent $model) {}
public function restored(Eloquent $model) {}
}
Now if I am to create a Product Model, its Observer would look like this:
<?php
namespace App\Observers;
use App\Observers\BaseObserver;
class ProductObserver extends BaseObserver {
public function creating(Eloquent $model)
{
$model->author_id = Sentry::getUser()->id;
}
public function created(Eloquent $model)
{
if(Input::hasFile('logo')) Image::make(Input::file('logo')->getRealPath())->save(public_path() ."/gfx/product/logo_{$model->id}.png");
}
public function updating(Eloquent $model)
{
$model->author_id = Sentry::getUser()->id;
}
public function updated(Eloquent $model)
{
if(Input::has('payment_types')) $model->paymentTypes()->attach(Input::get('payment_types'));
//Upload logo
$this->created($model);
}
}
Regarding listeners, I create an observers.php file inside Observers dir and I include it from the AppServiceProvider. Here is a snippet from within the observers.php file:
<?php
\App\Models\Support\Ticket::observe(new \App\Observers\Support\TicketObserver);
\App\Models\Support\TicketReply::observe(new \App\Observers\Support\TicketReplyObserver);
All of this is regarding Model Events.
If you need to send an e-mail after a record is created, it would be cleaner to use the Laravel 'other' Events, as you will have a dedicated class to deal with just that, and fire it, when you wish, from the Controller.
The 'other' Events will have much more purpose as the more automated your app becomes, think of all the daily cronjobs you will need at some point. There will be no more cleaner way to deal with that other than 'other' Events.
You've tagged this question as Laravel 5, so I would suggest not using model events as you'll end up with lots of extra code in your models which may make things difficult to manage in future. Instead, my recommendation would be to make use of the command bus and events.
Here's the docs for those features:
http://laravel.com/docs/5.0/bus
http://laravel.com/docs/5.0/events
My recommendation would be to use the following pattern.
You create a form which submits to your controller.
Your controller dispatches the data from the request generated to a command.
Your command does the heavy lifting - i.e. creates an entry in the database.
Your command then fires an event which can be picked up by an event handler.
Your event handler does something like send an email or update something else.
There are a few reasons why I like this pattern: Conceptually your commands handle things that are happening right now and events handle things that have just happened. Also, you can easily put command and event handlers onto a queue to be processed later on - this is great for sending emails as you tend not to want to do that in real time as they slow the HTTP request down a fair bit. You can also have multiple event handlers for a single event which is great for separating concerns.
It would be difficult to provide any actual code here as your question more about the concepts of Laravel, so I'd recommend viewing these videos so you get a good idea of how this pattern works:
This one describes the command bus:
https://laracasts.com/lessons/laravel-5-events
This one describes how events work:
https://laracasts.com/lessons/laravel-5-commands
You can have multiple listeners on an event. So you may have a listener that sends an email when an article is updated, but you could have a totally different listener that does something totally different—they’ll both be executed.
1) You may create an event listener for each new Model (ArticleEventSubscriber,CommentEventSubscriber) at boot method:
EventServiceProvider.php
public function boot(DispatcherContract $events)
{
parent::boot($events);
$events->subscribe('App\Listeners\ArticleEventListener');
$events->subscribe('App\Listeners\CommentEventListener');
}
or you may also use $subscribe property
protected $subscribe = [
'App\Listeners\ArticleEventListener',
'App\Listeners\CommentEventListener',
];
There are many ways to listen and handle events. Take a look to current master documentation for discovering more ways(like usings closures) to do so : Laravel Docs (master) and this other answer
2) Model events are just events provided by default by Eloquent.
https://github.com/illuminate/database/blob/491d58b5cc4149fa73cf93d499efb292cd11c88d/Eloquent/Model.php#L1171
https://github.com/illuminate/database/blob/491d58b5cc4149fa73cf93d499efb292cd11c88d/Eloquent/Model.php#L1273
I might come after the battle, but If you do not want all the fuss of extending classes or creating traits, you might want to give a try to this file exploration solution.
Laravel 5.X solution
Beware the folder you choose to fetch the models should only contain models to make this solution to work
Do not forget to add the use File
app/Providers/AppServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use File;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
$model_location = base_path() . '/app'; // Change to wherever your models are located at
$files = File::files( $model_location );
foreach( $files as $data ) {
$model_name = "App\\" . pathinfo($data)['filename'];
$model_name::creating(function($model) {
// ...
});
$model_name::created(function($model) {
// ...
});
$model_name::updating(function($model) {
// ...
});
$model_name::updated(function($model) {
// ...
});
$model_name::deleting(function($model) {
// ...
});
$model_name::deleted(function($model) {
// ...
});
$model_name::saving(function($model) {
// ...
});
$model_name::saved(function($model) {
// ...
});
}
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
}
Hope it helps you write the less code possible!
Laravel 6, the shortest solution
BaseSubscriber class
namespace App\Listeners;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Str;
/**
* Class BaseSubscriber
* #package App\Listeners
*/
abstract class BaseSubscriber
{
/**
* Returns the first part of an event name (before the first dot)
* Can be a class namespace
* #return string
*/
protected abstract function getEventSubject(): string;
/**
* Register the listeners for the subscriber.
* #param Dispatcher $events
*/
public function subscribe($events)
{
$currentNamespace = get_class($this);
$eventSubject = strtolower(class_basename($this->getEventSubject()));
foreach (get_class_methods($this) as $method) {
if (Str::startsWith($method, 'handle')) {
$suffix = strtolower(Str::after($method, 'handle'));
$events->listen("$eventSubject.$suffix", "$currentNamespace#$method");
}
}
}
}
OrderEventSubscriber class. Handlers for Order model events
use App\Models\Order;
/**
* Class OrderEventSubscriber
* #package App\Listeners
*/
class OrderEventSubscriber extends BaseSubscriber
{
/**
* #return string
*/
protected function getEventSubject(): string
{
return Order::class; // Or just 'order'
}
/**
* #param Order $order
*/
public function handleSaved(Order $order)
{
// Handle 'saved' event
}
/**
* #param Order $order
*/
public function handleCreating(Order $order)
{
// Handle 'creating' event
}
}
ModelEvents trait. It goes to your models, in my case - App\Model\Order
namespace App\Traits;
use Illuminate\Database\Eloquent\Model;
/**
* Trait ModelEvents
* #package App\Traits
*/
trait ModelEvents
{
/**
* Register model events
*/
protected static function bootModelEvents()
{
foreach (static::registerModelEvents() as $eventName) {
static::$eventName(function (Model $model) use ($eventName) {
event(strtolower(class_basename(static::class)) . ".$eventName", $model);
});
}
}
/**
* Returns an array of default registered model events
* #return array
*/
protected static function registerModelEvents(): array
{
return [
'created',
'updated',
'deleted',
];
}
}
Register the subscriber in a service provider, e.g AppServiceProvider
/**
* #param Dispatcher $events
*/
public function boot(Dispatcher $events)
{
$events->subscribe(OrderEventSubscriber::class);
}
How just add the ModelEvents trait into your model, adjust the events you want to register instead of default ones:
protected static function registerModelEvents(): array
{
return [
'creating',
'saved',
];
}
Done!
I want to do some post-processing after sending a response object in my Symfony controller. Problem is, the post-processing requires other methods contained in my controller object. I'd like to do something like this:
public function testAction() {
$dispatcher = new EventDispatcher();
$dispatcher->addListener('kernel.terminate', function (Event $event) {
$controller->get('logger');
$logger->info('hello');
});
return new Response();
}
How can I inject the $controller variable into my kernel.terminate post-processing?
it seems you need only the container in your service. To get the container injected into your event listener I prefer to create a separate EventListener which you have to register in your container see code:
First create event listener class:
<?php
namespace Acme\DemoBundle\Listener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\DependencyInjection\ContainerInterface;
class RequestListener
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$logger = $this->container->get('logger')->getToken();
$logger->info('.....');
}
}
As you can see, we have now the service container injected and we are able to use it.
Next you have to register the service and inject the service container:
services:
acme.demo.listener.request:
class: Acme\DemoBundle\Listener\RequestListener
arguments: [ #service_container ]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
Notice in your case you have to select the event you wanna inject to. In my case I used the kernel.request event. You have to select the kernel.terminate event.
That can also be helpful: http://symfony.com/doc/current/cookbook/service_container/event_listener.html