Dispatching events from an extended class - php

In a Laravel 5.5 project, I have a Person class and a Student class. The Student class extends the Person class. I have a bunch of stuff that needs to happen when a new person is created and a bunch of stuff that needs to happen when a new student (who is also a person of course) is created.
My classes look something like this...
class Person extends Model {
protected $dispatchesEvents = [
'created' => PersonJoins::class
];}
.
class Student extends Person {
protected $dispatchesEvents = [
'created' => StudentIsCreated::class,
];}
When a new Student instance is created, the StudentIsCreated event fires but the PersonJoins event does not.
A workaround is to change the 'created' in one of the models to 'saved' and then both events are triggered. From that, it seems obvious what is happening. The 'created' element in the $dispatchesEvents array on the Person model is being overwritten by the same on the Student model. Even just typing that, it seems the solution should be obvious but I just can't see it.
So, my question is this... How do I have an event triggered by 'created' on two models, one of which extends the other?
Thank you.
David.
EDIT:
After reading #hdifen answer. My Student model now looks like this...
class Student extends Person
{
protected static function boot()
{
parent::boot();
static::created(function($student) {
\Event::Fire('StudentCreated', $student);
});
}
}
and in App\Events\StudentCreated.php I have...
class StudentCreated
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($student)
{
echo ("\r\nStudentCreated event has fired");
$this->student = $student;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('Student-Is-Created-Channel');
}
}
But the event doesn't seem to be fired. Am I doing something wrong?

Yes you are correct the student model is overwriting the parent variable $dispatchesEvents.
I would recommend not using $dispatchedEvent as events are mainly used if you are doing something more complex that requires other parts of your code to react when they are fired.
The simple solution is to just manually hook into the created event in your model, in your case you want to create a grades model when a student is created?
protected static function boot()
{
parent::boot();
static::created(function($model) {
$grade = new Grade;
$model->grades->save($grade);
// Or if you really want to use an event something like
Event::fire('student.created', $student);
});
}
This post might give you an idea:
Laravel Model Events - I'm a bit confused about where they're meant to go.

Related

Optmize detach model with a event listener

I have a destroy function which allows me to detach models (polymorphic relationship).
public function destroy {
$vaccine = HealthItem::findOrFail($vaccine_id);
$vaccine->detachCategories();
$events = $vaccine->events()->get();
foreach ($events as $event) {
$event->detachCategories();
};
$vaccine->events()->delete();
$vaccine->delete();
}
Here, I detach an "event" model with "detachCategories" (a helper to help me detach my categories)
I collect them and I do a foreach. It works, it is well detached from my table categorizable.
BUT I don't think it's great, right?
I'm going to have to do it for all of my events, every time a model is linked to it and it'll do a lot. So, I tried to make it an event but without success.
My Event Model :
protected static function boot()
{
parent::boot();
static::deleting(function ($event) {
$event->categories()->detach();
});
}
I delete the event with the following line $vaccine->events()->delete();
How would you do it?
Laravel Events should get you there, and if its a big job and you have setup queue workers on your server, you should make sure the event listeners are queued.
The following solution should work:
You likely want to make a contract and a trait for this polymorphic relationship so that you can do better type hinting, and DRY up your code. You can put these in whatever folders make most sense for your project. Say:
interface CategorizableContract
{
// Relationship to access the models categories
}
trait HasCategories
{
// Implement methods above
/**
* Initialize the trait
*
* #return void
*/
protected function bootHasCategories()
{
static::deleting(function($categorizable) {
event(new DestroyCategorizable($categorizable));
});
}
}
Then in your models that have categories:
class Vaccine extends Model implements CategorizableContract
{
use HasCategories;
...
}
Now we can make an event in App\Events:
class DestroyCategorizable
{
use SerializesModels;
/** #var CategorizableContract */
public $categorizable;
/**
* Create an event for deleting categorizable models
*
* #param CategorizableContract $categorizable
*
* #return void
*/
public function __construct(CategorizableContract $categorizable)
{
$this->categorizable = $categorizable;
}
}
Now you can make an event listener, in App\Listeners, like so:
class DetachCategories implements ShouldQueue
{
public function handle(DestroyCategorizable $event)
{
$categorizable = $event->categorizable;
$class = new ReflectionClass($categorizable)
// Detach the categories
DB::table('categorizable')
->where('categorizable_type', $class->getName())
->where('categorizable_id', $categorizable->id)
->delete();
}
}
Now just register your listener in your EventServiceProvider, and away you go!
protected $listen = [
DestroyCategorizable::class => [
DetachCategories::class,
],
];
Now when you call delete on a model with categories, it will automatically detach them for you.
$vaccine->delete();
Even though the model is deleted, it was serialized in the event, so its id and class type will be available to detach the categories in the listener. Hope that helps :)

Fire Laravel events on touch

this is my scenario:
I'm using Laravel 5.5.x.
I have two models, linked in one to many way.
class Artwork extends Model
{
//It has timestamps
protected $table = 'artworks';
protected $fillable = [
'artwork_category_id'
];
public function artworkCategory()
{
return $this->belongsTo('App\Models\ArtworkCategory');
}
}
class ArtworkCategory extends Model
{
use SoftDeletes;
protected $touches = ["artworks"];
/**
* #var string
*/
protected $table = 'artwork_categories';
protected $fillable = [
'category_name',
'acronym',
'deleted_at'
];
public function artworks()
{
return $this->hasMany('App\Models\Artwork');
}
}
Touch works correctly, so when I update an artwork category the related artworks updated_at field is updated.
But I need to listen the "touch" event on each artwork.
I've tried inserting "updated" listener on boot method in AppServiceProvider, but it is not fired.
Artwork::updated(function ($model){
\Log::debug("HERE I AM");
});
I've tried using an observer, but no luck.
class ArtworkObserver
{
public function updated(Artwork $artwork)
{
dd($artwork);
}
}
Boot method in AppServiceProvider:
Artwork::observe(ArtworkObserver::class)
Question:
Could somebody show me the right way to do it? Or tell me where am I wrong?
I was not good enough to find an example that helps me how to do it.
Update
I need to achieve this because I have to "fire" Scout to save updated data on Elasticsearch on Artwork index.
Most probably $touches uses mass update, and if you check Events section of Eloquent you'll find following:
When issuing a mass update via Eloquent, the saved and updated model events will not be fired for the updated models. This is because the models are never actually retrieved when issuing a mass update.
The best that I can think of is that, you update Artworks manually (instead of $touches) when a ArtworkCategory is updated:
class ArtworkCategory extends Model
{
use SoftDeletes;
protected $fillable = [
'category_name',
'acronym',
'deleted_at'
];
public function artworks()
{
return $this->hasMany('App\Models\Artwork');
}
public static function boot()
{
parent::boot();
static::updated(function($artworkCategory)
{
$artworkCategory->artworks->each(function($artwork) {
$artwork->setUpdatedAt($artwork->freshTimestamp());
$artwork->save(); /// Will trigger updated on the artwork model
});
});
}
}

Linking models to parent in Laravel constructor

Maybe I'm searching it all wrong but I haven't been able to figure out an answer.. Say I have a model Building, which always has n Floor(s)
I would like to write a constructor for Building, in which I could specify a number of Floor(s) to be created. The problem is that I can't link back a Floor to the Building because when the constructor for Building is being called, it doesn't have a primary key yet...
Basically, my code looks like this but doesn't work:
class Building extends Model {
public function __construct($nbFloors) {
for($i=0; $i<$nbFloors; $i++) {
$foo = new Floor();
$foo->building_id = $this->id;
$foo->save();
}
}
}
What would be the correct solution to achieve something like that?
The primary key will never be available in the constructor and your constructor's definition is not compatible with Model which expects an array of attributes as the first argument.
You're performing too much logic in your constructor, a constructor is meant to just instantiate an object and its dependencies, not perform business logic. By doing this in your constructor, you're actually going to be attempting to create new floors EVERY time your Model is instantiated which includes when your model is retrieved from the database.
I'd recommend adding a new method like:
public function createWithFloors($n) {
$this->save();
...
}
Now, you can use the model as it's expected and call the create method:
$building = new Building(['name' => 'Empire State']);
$building->createWithFloors(102);
Besides the solutions already suggested, you could create an event that is fired when a Building is created. A listener could then store your Floors. For event reference, have a look at the documentation.
First, create an event called BuildingCreated with php artisan make:event BuildingCreated and use below code:
namespace App\Events;
use App\Building;
use Illuminate\Queue\SerializesModels;
class BuildingCreated extends Event
{
use SerializesModels;
public $building;
public function __construct(Building $building)
{
$this->building = $building;
}
}
Then, register the event within your Building model:
use App\Events\BuildingCreated;
class Building
{
protected $dispatchesEvents = [
'created' => BuildingCreated::class,
];
}
Next, you will need a listener that creates the floors. Create it with php artisan make:listener AddFloorsToNewBuilding and adapt it as you need:
namespace App\Listeners;
use App\Building;
use App\Events\BuildingCreated;
class AddFloorsToNewBuilding
{
public function handle(BuildingCreated $event)
{
$floors = ...;
$event->building->floors()->saveMany($floors);
$event->building->save();
}
}
Lastly, have the listener listen for the event by adding it to the $listen array in the EventServiceProvider:
class EventServiceProvider
{
protected $listen = [
\App\Events\BuildingCreated::class => [
\App\Listeners\AddFloorsToNewBuilding::class,
],
];
}
since, you can't bind Floor to a building that is not created yet, you should make the "new floors number" an attribute of the Building instance. Then you overload the save method to create the new floors.
class Building extends Model {
/** number of floors to be created on save
* #var int
*/
private $newFloorsCount;
/**
* Building constructor.
* #param array $attributes
* #param int $nbFloors
*/
public function __construct(array $attributes = [], $nbFloors = 0) {
parent::__construct($attributes);
$this->newFloorsCount = $nbFloors;
}
/**
* #param array $options
* #return bool
*/
public function save(array $options = [])
{
$return = parent::save($options);
for($i=0; $i<$this->newFloorsCount; $i++) {
$foo = new Floor();
$foo->building_id = $this->id;
$foo->save();
}
return $return;
}
}
now you can just do
$building = new Building([],5);
$building->save();

Yii2: Can I attach events from outside the model class?

I am currently writing a class that caches model data for a select field.
Now obviously, if any model that is affecting this select field gets inserted, updated or deleted, the cache must be refreshed.
To handle this, I'd like to use the model events of Yii2. For example, if EVENT_AFTER_INSERT is triggered in the model Album, I want to execute the code to refresh the cache of the album select data.
Now I could do this the classical way and add an event to the model Album like this:
class Album extends ActiveRecord {
public function init(){
$this->on(self::EVENT_AFTER_INSERT, [$this, 'refresh_cache']);
$this->on(self::EVENT_AFTER_UPDATE, [$this, 'refresh_cache']);
$this->on(self::EVENT_AFTER_DELETE, [$this, 'refresh_cache']);
}
// ...
}
That would work, yes. Problem is, I'd need to include this code in any model I'd like to create a select field from at any point of development. It's not such a big deal, but you can easily forget it while coding and if the behavior needs to change at some point, you need to update a whole bunch of models.
Now here is my question: Is there any possibility to add events to a model from another component? My idea would be to create a component, that knows about all used select data caches and adds the necessary model events accordingly. any idea how to achieve this or something similar?
you just need create a behaviour and attach it to your various models. see the basic guide and speciffically the Behavior::events() use case
so i went ahead and wrote an example
class RefreshCacheBehavior extends \yii\base\Behavior
{
public function events() {
return [
\yii\db\ActiveRecord::EVENT_AFTER_INSERT => 'refreshCache',
\yii\db\ActiveRecord::EVENT_AFTER_UPDATE => 'refreshCache',
\yii\db\ActiveRecord::EVENT_AFTER_DELETE => 'refreshCache',
];
}
/**
* event handler
* #param \yii\base\Event $event
*/
public function refreshCache($event) {
// model that triggered the event will be $this->owner
// do things with Yii::$app->cache
}
}
class Album extends ActiveRecord {
public function behaviors() {
return [
['class' => RefreshCacheBehavior::className()],
];
}
// ...
}
Is there any possibility to add events to a model from another component?
Yes! You can use class level event handlers. The line of code below shows how to do that.
Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
Yii::debug(get_class($event->sender) . ' is inserted');
});
You can use same code in your init method and bind it to your class method instead of that closure.
I would create a class implementing BootstrapInterface and add it to config. Then I would handle those class level events there!
Do yourself a favour and read about events in the Guide as well as the API Documentation
on() is a public method, so you can always attach event to already instantiated object. This may be useful if you're using some kind of factory do build your objects:
public function createModel($id) {
$model = Album::findOne($id);
if ($model === null) {
// some magic
}
$model->on(Album::EVENT_AFTER_INSERT, [$this, 'refresh_cache']);
$model->on(Album::EVENT_AFTER_UPDATE, [$this, 'refresh_cache']);
$model->on(Album::EVENT_AFTER_DELETE, [$this, 'refresh_cache']);
return $model;
}

Laravel Model Events - I'm a bit confused about where they're meant to go

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!

Categories