Iam bulk-inserting large database in chunks with this command:
DB::table($table)->insert($chunk);
But want I want is before actual insert operation, I want to be able to modify $chunk array for each table to add/remove certain attributes going into database. In order to do so, I setup saving event in my model:
public static function boot()
{
parent::boot();
static::saving(function ($model) {
Log::info('saving');
return true;
});
}
However, it seems events don't work for Model::insert operations.
Can anybody tell how can I achieve this ?
PS: I can't use save() (though saving event would work with it) method as it would only allow me to save one record at a time whereas I need to do bulk insert of each chunk.
Thanks
In this case you have to create your own Event/Listner.
Something like this :
php artisan make:event SomeEventName
//App\Events\SomeEventName
class SomeEventName extends Event
{
use SerializesModels;
public $chunk;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(&$chunk)
{
$this->chunk = &$chunk;
}
}
Then you make your listener (you can create a separate file according to laravel docs) or just put it on boot Model method
\Event::listen('App\Events\SomeEventName', function($event) {
$event->chunk = ['hello new world']; // this will replace the old chunk
});
and then use it like this :
$chunk = ['hello old world'];
event(new App\Events\SomeEventName($chunk));
dd($chunk);
DB::table($table)->insert($chunk);
Related
In saving a model, I have an error thats the slug attribute doesn't have a default value.
I had created a setSlugAttribute mutators but it gived me the error again.
//Controller save method inside
* * *
Task::create($request->all());
* * *
//Task model
public function setSlugAttribute(){
$this->attributes['slug'] = Str::slug($this->title, '-');
}
How can I fix it? It does fix by using observe(saving), doesn't it? Another idea?
I have created an TaskObserver and I set it in ServiceProvider.
In the observer, updated(Task $task) and updating(Task $task) methods didn't work !
But the created method works.
//Update method inside:
$array = $request->all(['title', 'description', 'deadline', 'budget','guest_token']);
$task = Task::where('id',$request->id)->update($array);
//I am waiting working of udate observer but it don't
//TaskObserver
public function updating(Task $task)
{
dd("updating");
}
public function updated(Task $task)
{
dd("updated");
}
I have solved this problem by doing the following:
//when I assigned it to a variable and then calling update method, it worked
$task = Task::where('id',$request->id)->first();
$update = $task->update($request->all(['title', 'description', 'deadline', 'budget','guest_token']));
So every time a Task is created or updated you want the slug column to be auto-populated from title column.
Accessors are not good for this. What you want is observers. For observers you have two choice: closure based or class based. Considering your use case is not too complex, I'd choose closure based observers.
You need two events creating and saving to handle it when the model is first creating, and when model is updating. So your task model should look like this:
<?php
class Task extends Model
{
protected static function creating()
{
static::creating(function ($task) {
$task->slug = Str::slug($task->title, '-');
});
static::saving(function ($task) {
$task->slug = Str::slug($task->title, '-');
});
}
}
This should do the trick.
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 :)
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;
}
I have model events:
protected static function boot()
{
parent::boot();
static::creating(function ($questionnaire) {
// Same code here
});
static::updating(function ($questionnaire) {
// Same code here
});
}
Is there a way of combining creating and updating together or is it better to put the same code in some sort of partial to reuse in each event?
https://laravel.com/docs/5.6/eloquent#events
When a new model is saved for the first time, the creating and created events will fire. If a model already existed in the database and the save method is called, the updating / updated events will fire. However, in both cases, the saving / saved events will fire.
The saving event is fired when a model is being created or being updated.
The saved method can be used to handle both the created and updated events:
protected static function boot()
{
parent::boot();
static::saved(function ($questionnaire) {
// Code here
});
}
However, in the cases, when more than these two events should run the same code, you can use closures:
protected static function boot()
{
parent::boot();
$closure = function ($questionnaire) {
// Code here
};
static::created($closure);
static::updated($closure);
static::deleted($closure);
}
You can go about it like so...
// store the events you want to capture
protected static $updateOnEvents = ['saved','deleted',...];
protected static function booted()
{
// loop through them and apply the logic
foreach (static::$updateOnEvents as $event) {
static::$event(function($questionnaire){
// your code here
});
}
}
Note that the booted method is available from Laravel 7.x, if you are using a lower version, you can use booting...
I have a laravel project, and I need to make some calculations immediately after I save a model and attach some data to it.
Is there any event that is triggered in laravel after calling attach (or detach/sync)?
No, there are no relation events in Eloquent. But you can easily do it yourself (Given for example Ticket belongsToMany Component relation):
// Ticket model
use App\Events\Relations\Attached;
use App\Events\Relations\Detached;
use App\Events\Relations\Syncing;
// ...
public function syncComponents($ids, $detaching = true)
{
static::$dispatcher->fire(new Syncing($this, $ids, $detaching));
$result = $this->components()->sync($ids, $detaching);
if ($detached = $result['detached'])
{
static::$dispatcher->fire(new Detached($this, $detached));
}
if ($attached = $result['attached'])
{
static::$dispatcher->fire(new Attached($this, $attached));
}
}
event object as simple as this:
<?php namespace App\Events\Relations;
use Illuminate\Database\Eloquent\Model;
class Attached {
protected $parent;
protected $related;
public function __construct(Model $parent, array $related)
{
$this->parent = $parent;
$this->related = $related;
}
public function getParent()
{
return $this->parent;
}
public function getRelated()
{
return $this->related;
}
}
then a basic listener as a sensible example:
// eg. AppServiceProvider::boot()
$this->app['events']->listen('App\Events\Relations\Detached', function ($event) {
echo PHP_EOL.'detached: '.join(',',$event->getRelated());
});
$this->app['events']->listen('App\Events\Relations\Attached', function ($event) {
echo PHP_EOL.'attached: '.join(',',$event->getRelated());
});
and usage:
$ php artisan tinker
>>> $t = Ticket::find(1);
=> <App\Models\Ticket>
>>> $t->syncComponents([1,3]);
detached: 4
attached: 1,3
=> null
Of course you could do it without creating Event objects, but this way is more convenient, flexible and simply better.
Steps to solve your problem:
Create custom BelongsToMany relation
In BelongsToMany custom relation override attach, detach, sync and updateExistingPivot methods
In overriden method dispatch desired events.
Override belongsToMany() method in Model and return your custom relation not default relation
and that's it. I created package that already doing that: https://github.com/fico7489/laravel-pivot
Laravel 5.8 now fires events on ->attach()
Check out: https://laravel.com/docs/5.8/releases
And search for: Intermediate Table / Pivot Model Events
https://laracasts.com/discuss/channels/eloquent/eloquent-attach-which-event-is-fired?page=1
Update:
From Laravel 5.8 Pivot Model Events are dispatched like normal model.
https://laravel.com/docs/5.8/releases#laravel-5.8
You just need to add using(PivotModel::class) to your relation and events will work on the PivotModel.
Attach($id) will dispatch Created and Creating
Detach($id) will dispatch Deleting and Deleted,
Sync($ids) will dispatch the needed events too [Created,Creating,Deleting,Deleted]
Only dispatch() with out id doesn't dispatch any event until now.