I have a question about Laravel's Event Handlers and Listeners. I have no idea where to start.
I would like to know what exactly are Events and when to use them. Also I would like to know what the best way to organize events and listeners is, and where to place them (in which folder).
Any help would be appreciated ;)
I've recently implemented a feed for actions, e.g. when a post is created, a new user is registered, or whatever. Every action fires an event and for every event there's a listener, which saves something like "User XY just registered!" in the database.
Very basic version:
// app/controllers/RegistrationController.php
class RegistrationController {
public function register($name) {
User::create([
'name' => $name
});
Event::fire('user.registered', [$name]);
}
}
// app/events.php
Event::listen('user.registered', function($name) {
DB::table('feed')->insert(
[
'action' => 'User ' . $name . ' just registered!'
// ...
}
);
});
To use the events.php file, add the following line to your app/start/global.php
require app_path().'/events.php';
Now you can put all events in the events.php.
But if you're going to have a lot of events, you shouldn't put all of your events in a single file. See Event Subscribers.
Related
I'm curious if you guys know any easy and reliable way to track the source of what dispatched a Laravel job.
My use case for this is that I often have to debug failing jobs and knowing why, where and how they were dispatched would be very useful, e.g. could add it as metadata to Sentry reports.
I mean adding a property to a job would probably do it, but I'm wondering if there's some way that would not involve code changes in individual jobs.
I think you're looking for Job Events (Laravel documentation)
You could add this to your app/Providers/AppServiceProvider.php - boot() method or you could create one QueueJobProvider for example and add your provider in config/app.php inside the 'providers' key.
With this events you can have the payload as you want and more proprities. You can learn more about this issue in Laravel docs here
use Illuminate\Queue\Events\JobProcessed;
use Illuminate\Queue\Events\JobProcessing;
use Illuminate\Queue\Events\JobFailed;
use Illuminate\Queue\Events\JobExceptionOccurred;
Queue::before(function (JobProcessing $event) {
// \Log::info($event);
// \Log::info($event->job->payload());
});
Queue::after(function (JobProcessed $event) {
// \Log::info($event);
// \Log::info($event->job->payload());
});
Queue::failing(function (JobFailed $event) {
// \Log::info($event);
// \Log::info($event->job->payload());
});
Queue::exceptionOccurred(function (JobExceptionOccurred $event) {
// \Log::info($event);
// \Log::info($event->job->payload());
});
let's me explain example
User click button
run function trigger in Controller
User wait 30sec because MyModel::doSomeThing take long time to process
MyModel::doSomeThing do many thing. I don't want user to wait for it.
Is it possible to run MyModel::doSomeThing by don't care about result and return to user immediately?
function trigger(Request $request){
$id= $request->get('id');
MyModel::doSomeThing($id); // this one take 30 sec.
return response()->json([], 200);
}
If the result of doSomeThing() method isn't necessary for your response & can be done in background, I suggest using Events and Listeners, which will use queues to run in the background, and the user won't need to wait for this procces to finish. The process is fairly simple. Create event and it's listened with these two commands:
php artisan make:event YourEvent
php artisan make:listener YourListener --event=YourEvent
After that, register your event and listener in the App\Providers\EventServiceProvider, under the $listen array:
protected $listen = [
YourEvent::class => [
YourListener::class,
],
];
Now, when you have that sorted out, you need to build your event instance. Inside your newly created method, in the construct method, add this:
public $yourModel;
public function __construct(YourModel $yourModel)
{
$this->yourModel = $yourModel;
}
After you created your model, time to edit your listener, which will hanlde all the logic ghat you need. Inside this handle method, you will have the access to $yourModel instance that we defined in our event:
public function handle(YourEvent $event)
{
// Access your model using $event->yourModel...
YourModel::doSomeThing($event->yourModel);
}
The only thing left do to is to make your listener queueable. You can do this by adding implements ShouldQueue your listened definition:
class YourListener implements ShouldQueue
{
//
}
Now when we have everything setup, you can change your controller code to call this newly created event, and let the queue handle all the logic:
function trigger(Request $request){
$id= $request->get('id');
YourEvent::dispatch($id); //Calling event which will handle all the logic
return response()->json([], 200);
}
And that should be it. I haven't tested this code, so if you encounter any problems, let me know.
in Laravel 5.6
I have an event named DocumentSend,
And i have many Listeners Like (SendEmail, SendNotification, SendSMS),
listeners are optional (depends on document type and defined by user),
now the question is:
How can i call for example DocumentSend event with just SendSMS listener or DocumentSend with all the listeners?
I hope you get my mean,and tell me the best practice for my issue.
Thanks in advance
Well, the simple answers is - you can't. When you fire event all registered listeners will listen to this event and all of them will be launched.
However nothing stops you to prevent running code from listener.
For example you can fire event like this:
event(new DocumentSend($document, true, false, false));
and define constructor of DocumentSend like this:
public function __construct($document, $sendEmail, $sendNotification, $sendSms)
{
$this->document = $document;
$this->sendEmail = $sendEmail;
$this->sendNotification = $sendNotification;
$this->sendSms = $sendSms;
}
and now in each listener you can just verify correct variable, so for example in SendEmail listener in handle you can do it like this:
public function handle(DocumentSend $event)
{
if (!$event->sendSms) {
return;
}
// here your code for sending
}
similar you can do for other listeners.
Of course this is just example - you don't have to use 4 variables. You can set some properties to $document only to mark how it should be sent.
At the moment I don't have any queuing functionality in my Cakephp aplication. I will need that in the near future. An upload will result in a batchjob that uses external API with usage limitations, so it would be best if it was handeled in a seperate threat with a queue.
I don't have any experience with this, so I'm going to try a different, but easier, example.
User actions result in e-mails being send. At the moment, the loading of the page is delayed by the (rather long) time it takes the server to send the e-mail. I'd like to use the Event system to fix this. (I am aware I can also do this using this the afterRender function, or dispatch it to a shellTask, but that way I don't learn anything)
From the example page:http://book.cakephp.org/2.0/en/core-libraries/events.html
I've found this example:
// Cart/Model/Order.php
App::uses('CakeEvent', 'Event');
class Order extends AppModel {
public function place($order) {
if ($this->save($order)) {
$this->Cart->remove($order);
$this->getEventManager()->dispatch(new CakeEvent('Model.Order.afterPlace', $this, array(
'order' => $order
)));
return true;
}
return false;
}
}
Let's say the function was called by a controller action:
public function place_order() {
$result = $this->Order->place($this->request->data);
$this->set('result', $result);
}
Now my question... Will the corresponding view be rendered after all the dispatched events completes? or will the Model function just trigger the event and then forget about it?
The last option seems more logical to me (which also resembles the mentioned jQuery functionality in the article)
The problem is that If this were true, I don't understand the later example:
In the example about using results:
// Using the event result
public function place($order) {
$event = new CakeEvent('Model.Order.beforePlace', $this, array('order' => $order));
$this->getEventManager()->dispatch($event);
if (!empty($event->result['order'])) {
$order = $event->result['order'];
}
if ($this->Order->save($order)) {
// ...
}
// ...
}
if the event was just triggered (and then forgot about) there is no way you can asume it has modified the passed event object on the next line of code!
I would like to use cake as much as possible, but I'm not sure if I can get my desired background behavior without shellTasks and external queue. Any tips about these Cake Events?
Cake Events are triggered synchronously. When an event is triggered, all available listeners are called, before proceeding with other instructions.
You can imagine it on your second example as:
public function place($order) {
$event = new CakeEvent('Model.Order.beforePlace', $this, array('order' => $order));
$this->getEventManager()->dispatch($event); // -> all listeners are called at this point
// ... here you can assume your $event was modified
if (!empty($event->result['order'])) {
$order = $event->result['order'];
}
if ($this->Order->save($order)) {
// ...
}
// ...
}
I made the authorization and authentication via facebook like here:
http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html
and it works
Now I want to make my own event, this event will do something when the user authenticates using facebook. For example-will redirect the user to the home page.
I did it like this
http://symfony.com/doc/current/components/event_dispatcher/introduction.html
So I have this class
http://pastebin.com/2FTndtL4
I do not know how to implement it, what am I supposed to pass as an argument to the constructor
It's really simple. Symfony 2 event system is powerful, and service tags will do the job.
Inject the dispatcher into the class where you want to fire the event. The service id is event_dispatcher;
Fire the event with $this->dispatcher->dispatch('facebook.post_auth', new FilterFacebookEvent($args)) when needed;
Make a service that implements EventSubscriberInterface, defining a static getSubscribedEvents() method. Of course you want to listen to facebook.post_auth event.
So your static method will look like:
static public function getSubscribedEvents()
{
return array(
'facebook.post_auth' => 'onPostAuthentication'
);
}
public function onPostAuthentication(FilterFacebookEvent $event)
{
// Do something, get the event args, etc
}
Finally register this service as a subscriber for the dispatcher: give it a tag (eg. facebook.event_subscriber), then make a RegisterFacebookEventsSubscribersPass (see this tutorial). You compiler pass should retrieve all tagged services and inside the loop should call:
$dispatcher = $container->getDefinition('event_dispatcher');
$subscribers = $container->findTaggedServiceIds('facebook.event_subscriber');
foreach($subscribers as $id => $attributes) {
$definition->addMethodCall('addSubscriber', array(new Reference($id)));
}
This way you can quick make a subscriber (for logging, for example) simply tagging your service.
Event object is just some kind of state/data storage. It keeps data that can be useful for dispatching some kind of events via Subscribers and/or Listeners. So, for example, if you wanna pass facebook id to your Listener(s) - Event is the right way of storing it. Also event is the return value of dispatcher. If you want to return some data from your Listener/Subscriber - you can also store it in Event object.