Laravel observer for pivot table Laravel observer for pivot table laravel laravel

Laravel observer for pivot table


As you already know, Laravel doesn't actually retrieve the models nor call save/update on any of the models when calling sync() thus no event's are created by default. But I came up with some alternative solutions for your problem.


1 - To add some extra functionality to the sync() method:

If you dive deeper into the belongsToMany functionality you will see that it tries to guess some of the variable names and returns a BelongsToMany object. Easiest way would be to make your relationship function to simply return a custom BelongsToMany object yourself:

public function products() {    // Product::class is by default the 1. argument in ->belongsToMany calll    $instance = $this->newRelatedInstance(Product::class);    return new BelongsToManySpecial(        $instance->newQuery(),        $this,        $this->joiningTable(Product::class), // By default the 2. argument        $this->getForeignKey(), // By default the 3. argument        $instance->getForeignKey(), // By default the 4. argument        null // By default the 5. argument    );}

Or alternatively copy the whole function, rename it and make it return the BelongsToManySpecial class. Or omit all the variables and perhaps simply return new BelongsToManyProducts class and resolve all the BelongsToMany varialbes in the __construct... I think you got the idea.

Make the BelongsToManySpecial class extend the original BelongsToMany class and write a sync function to the BelongsToManySpecial class.

public function sync($ids, $detaching = true) {    // Call the parent class for default functionality    $changes = parent::sync($ids, $detaching);    // $changes = [ 'attached' => [...], 'detached' => [...], 'updated' => [...] ]    // Add your functionality    // Here you have access to everything the BelongsToMany function has access and also know what changes the sync function made.    // Return the original response    return $changes}

Alternatively override the detach and attachNew functions for similar results.

protected function attachNew(array $records, array $current, $touch = true) {    $result = parent::attachNew($records, $current, $touch);    // Your functionality    return $result;}public function detach($ids = null, $touch = true)    $result = parent::detach($ids, $touch);    // Your functionality    return $result;}

If you want to dig deeper and want to understand what's going on under the hood then analyze the Illuminate\Database\Eloquent\Concerns\HasRelationship trait - specifically the belongsToMany relationship function and the BelongsToMany class itself.


2 - Create a trait called BelongsToManySyncEvents which doesn't do much more than returns your special BelongsToMany class

trait BelongsToManySyncEvents {    public function belongsToMany($related, $table = null, $foreignKey = null, $relatedKey = null, $relation = null) {        if (is_null($relation)) {            $relation = $this->guessBelongsToManyRelation();        }        $instance = $this->newRelatedInstance($related);        $foreignKey = $foreignKey ?: $this->getForeignKey();        $relatedKey = $relatedKey ?: $instance->getForeignKey();        if (is_null($table)) {            $table = $this->joiningTable($related);        }        return new BelongsToManyWithSyncEvents(            $instance->newQuery(), $this, $table, $foreignKey, $relatedKey, $relation        );    }}

Create the BelongsToManyWithSyncEvents class:

class BelongsToManyWithSyncEvents extends BelongsToMany {    public function sync($ids, $detaching = true) {        $changes = parent::sync($ids, $detaching);        // Do your own magic. For example using these variables if needed:        // $this->get() - returns an array of objects given with the sync method        // $this->parent - Object they got attached to        // Maybe call some function on the parent if it exists?        return $changes;    }}

Now add the trait to your class.


3 - Combine the previous solutions and add this functionality to every Model that you have in a BaseModel class etc. For examples make them check and call some method in case it is defined...

$functionName = 'on' . $this->foreignKey . 'Sync';if(method_exists($this->parent), $functionName) {    $this->parent->$functionName($changes);}

4 - Create a service

Inside that service create a function that you must always call instead of the default sync(). Perhaps call it something attachAndDetachProducts(...) and add your events or functionality


As I didn't have that much information about your classes and relationships you can probably choose better class names than I provided. But if your use case for now is simply to clear cache then I think you can make use of some of the provided solutions.