Laravel 5.8 syncing / attaching / detaching events - php

Laravel 5.8 is supposed to dispatch the syncing, attaching and detaching events (https://laravel.com/docs/5.8/releases search for Intermediate Table / Pivot Model Events section).
UPDATE: the release notes have been update after posting this question (more info: https://github.com/laravel/framework/issues/28050 - https://github.com/laravel/docs/pull/5096).
I tried it out but the following code throws the exception:
Call to undefined method App\ProjectUser::syncing()
NOTE: since Laravel 5.8 is supposed to dispatch the syncing event I don't want to use an external package.
class User extends Model
{
public function projects()
{
return $this->belongsToMany(\App\Project::class)->using(\App\ProjectUser::class);
}
}
class Project extends Model
{
public function users()
{
return $this->belongsToMany(\App\User::class)->using(\App\ProjectUser::class);
}
}
class ProjectUser extends Pivot
{
public static function boot()
{
parent::boot();
static::syncing(function ($item) {
dd('syncing event has been fired!');
});
}
}
// web.php
$project = \App\Project::first();
$project->users()->sync([1,2]);
I tried to move the boot method from ProjectUser to User and Project but I get the same exception.

On Laravel 5.8, when you are using the methods sync, attach or detach is going to be fired the appropriate model events (creating, updating, saving, ...) for the called action. Note that using sync, attach or detach is not going to fire any event like syncing, attaching or detaching.
More specifically, the sequence of events fired for each element passed to the sync method are:
saving
creating
created
saved
The sequence of events fired for each element passed to the attach method are:
saving
creating
created
saved
The sequence of events fired for each element passed to the detach method are:
deleting
deleted
So if you want to observe the syncing operation you actually have to observe the saving (or saved) event from the pivot model (in this case ProjectUser):
class ProjectUser extends Pivot
{
public static function boot()
{
parent::boot();
static::saving(function ($item) {
// this will die and dump on the first element passed to ->sync()
dd($item);
});
}
}
A working example https://github.com/danielefavi/laravel-issue-example
More info on this issue https://github.com/laravel/framework/issues/28050
The release notes were misleading and they have been changed https://github.com/laravel/docs/pull/5096.

If detach method called without ids (for detach all relations), events are not firing
https://github.com/laravel/framework/pull/27571#issuecomment-493451259
i tried many different way for the solve this need, but it is impossible without use external package or override many method.
I choose chelout/laravel-relationship-events package.
It's look clean and understable. And use with trait.

Related

Pass variable to laravel model boot

I use laravel 5.8 for my application. I have variables call "lot" and when I delete one, I want to perform other actions.
So I use deleting function on my model, everything works OK.
But know I have a function on my controller to delete many "lots" and I want actions to be perform only when all "lots" have been delete and not on every "lots" delete.
So I wonder if there is a way to achieve this ? Maybe we can pass a variable to boot functions to trigger or not the function ?
My model looks like this :
protected static function boot()
{
parent::boot();
static::deleted(function($modele) {
Etage::doesntHave('lots')->delete();
}
}
Model events are been designed to work for single models.
You should use Laravel Events instead, not tied to Models.
In EventServiceProvider.php register a new Event/Listener in the $listens array, like
LotsDeleted::class => PerformOtherAction::class
Create those classes with php artisan event:generate
Then, when you have finished deleting all your Lot objects, trigger the event with
event(new LotsDeleted());
The handle() function of your listener will be called and you can perform other actions.

Laravel model event won't fire

I'm using ajax to update my model User, the ajax part works fine since the data is updated successfully in the database, inside my controller action the update performed by :
$user->update($data);
The part that doesn't work:
I've used boots method updated inside my model like :
class User extends BaseModel
{
...
public static function boot()
{
parent::boot();
self::updated(function($model){
Log::info("updated");
dd($model);
});
}
}
The event was never reached I'm not sure why.
Problem:
I'm trying to perform an action after the model update but the event doesn't fire.
Here's what the manual states with update()
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.
You need to use save to trigger events. Something like:
$user->fill($data);
$user->save();
This of course is assuming that $user is a model and not a query builder instance.
You are accessing the function statically:
instead try using
self::updated(function($model){
Log::info("updated");
dd($model);
});

Laravel 5.1 Model Delete Event

I have a load of Models. Users can comment on some of the model records. For example, a user can comment on a Notice model record, or a user can comment on a Calendar Model record.
I've created a trait called Commentable. This contains all the methods needed to retrieve comments based on a model, to add / delete comments, create the comment create form and so on.
Whenever I want a model to be commentable, all I need to do is use that trait within the model.
Because that comment is a polymorphic relationship with the model record, I can't do an onCascade = delete migration.
Whenever the parent model is deleted (such as a notice or calendar item), then I want all associated records to be deleted as well, but I'd prefer not to have to rely on the developer writing the deleteRelatedComments() method call into an overridden delete function.
I thought I would create a service provider that listened for any model deletion event, check to see if that model was commentable, and delete any associated comments, but the event doesn't fire correctly.
This is my service provider code:
Model::deleting(function (Model $record) {
if(in_array('App\Libraries\Traits\Commentable', class_uses($record))) {
$record->deleteRelatedComments();
}
return true;
});
As you can see, all it does is check the deleted model to see if it uses the Commentable trait. If it does, it calls the deleteRelatedComments() method.
My Question
Is there any way to automatically delete related polymorphic related content on the deletion of it's parent record:
If I delete a Notice record, is there any way to delete any associated polymorphic Comment records?
I have previously created a service provider to listen for any Model deletes using Model Events as specified at http://laravel.com/docs/5.1/eloquent#events
However, this doesn't work if you're trying to listen to the Model class instead of a specific class such as Notice.
Instead, when you delete each model, a eloquent.deleting event is fired. I can then manually create a listener within the EventServiceProvider to listen for any model being deleted, and perform my checks accordingly, but without having the rely on the user overloading the delete() method and manually deleting the polymorphic relations.
Answer
If you want to delete polymorphic content automatically when you delete a model, make sure you either implement an interface or use a trait in the model.
For example, I want to delete all polymorphic comments when I delete the parent model record (If I delete a Notice record delete all polymorphic comments).
I created a Commentable trait which I use in any model (but you can just as easily use an empty interface on your model).
Then, in your EventServiceProvider.php alter the boot() method accordingly:
public function boot(DispatcherContract $events)
{
parent::boot($events);
/**
* If you use a trait in your model:
*/
$events->listen('eloquent.deleting*', function ($record) {
if (in_array('App\Libraries\Traits\Commentable', class_uses($record))) {
$record->deleteRelatedComments();
}
});
/**
* Or if you use an interface:
*/
$events->listen('eloquent.deleting*', function ($record) {
if ($record instanceof SomeInterface) {
$record->deleteSomePolymorphicRelation();
{
});
}
Use case:
Commentable Trait
namespace App\Libraries\Traits;
trait Commentable
{
public function comments()
{
return $this->morphMany(Comment::class, 'content');
}
public function deleteRelatedComments()
{
$this->comments()->delete();
}
}
Commentable Model
class Notice extends Model
{
use Commentable;
/** ... **/
/**
* NOTE: You do not need to overload the delete() method
*/
}
EventServiceProvider.php
public function boot(DispatcherContract $events)
{
parent::boot($events);
$events->listen('eloquent.deleting*', function ($record) {
if (in_array('App\Libraries\Traits\Commentable', class_uses($record))) {
$record->deleteRelatedComments();
}
});
}
Now, the developer creating a new content type that wants it to be commentable doesn't need to worry about creating the relations, managing the relation methods, or even deleting the polymorphic contents by overloading delete(). To make a model entirely commentable, they just need to use the Commentable trait and the framework handles everything.
This method can be used to automatically delete any polymorphic record, as long as the parent model implements some interface or uses a related trait.

Using `boot()` on a model conflicts with RevisionableTrait

I'm using the Revisionable package in my laravel app to log edits to a model.
In addition I have also implemented an observer class to listen for specific model events (update, delete, create etc) and perform actions (such as clear caches etc). This observer is instantiated in the model using the boot() method as follows:
class Client {
use \Venturecraft\Revisionable\RevisionableTrait;
public static function boot()
{
parent::boot();
Client::observe(new App\Observers\ClientObserver);
}
}
What I find is that when I define a boot() method in my model the Revisionable Trait stops working and does not log changes - presumably because it too uses a boot method that is being overridden by the one in the model.
How would I fix this to allow listening for model events as well as utilizing the Revisionable package?
this link helped me
https://github.com/VentureCraft/revisionable/issues/175
I used in laravel 5.1 i hope it will work to you
use RevisionableTrait, UuidTrait {
UuidTrait::boot insteadof RevisionableTrait;
}

Eager loading has some unexpected side effects on Model events/ booting in Laravel

I'm trying to create some tests.
Here's my Test Class:
class ExampleTest extends TestCase {
public function setUp()
{
parent::setUp();
Artisan::call('migrate');
$this->seed();
Auth::loginUsingId(1);
}
public function testActionUpdateNew()
{
$action = new Action(Array());
$action->save();
var_dump($action->id);
Action::with('reponses','contact','user','etudiant','entreprise','etude')->findOrFail($action->id);
}
public function testEtudes()
{
$etudes=Etude::all()->toArray();
$this->assertCount(10, $etudes, "Nombre d'études incorrectes");
$numEtudes=count($etudes);
//Buggy part
$etude= Etude::create(Array());
var_dump($etude->id);
$etudes=Etude::all()->toArray();
$this->assertCount(11, $etudes, "Nombre d'études incorrectes");
//10+1 should equal to 11 but it hasnt updated
}
}
The test that is not passing is the second one: I count the number of eloquent Objects Etudes, which are of 10 at the beginning, I then add one etude to the database (using Etude::create()) , the object is created, because $etude->id gives out a real number. Howewer, the number of Etude hasn't updated.
The problem does go away when I remove the 'etude' from the eager loading in Action::with('reponses',...)
Here is the etudes relationship in the Action class:
public function etude() {
return $this->belongsTo('Etude');
}
Do you guys have any idea if eager-loading in laravel can have such strange behavior and how to fix that ?
EDIT
I found out that calling with('etude') had the action to remove the events registered to the Eloquent Model:
boot Method of Etude:
public static function boot()
{
parent::boot();
static::creating(function($etude)
{
var_dump("creating etude"); //This doesn't get executed even when I run Etude::create(Array());
}
);
}
So If I add Etude::boot() at the beginning of testEtudes, it works again. This is still strange.
Does eager loading has any effect on events or the boot method ? Or is the boot method not called automatically after each test ?
In Laravel tests, the event dispatcher is reset between each test, but the models are still only booted once as they live a pretty independent life. This means that between each test, the model listeners are erased but never re-registered. The solution is to not use boot() for registering model events, but rather but them in a separate file - either a service provider or a file included from app/start/global.php (app/events.php is a common one).

Categories