I have two models User and Role many to many relationship through a pivot table. User:
public static function boot(): void
{
parent::boot();
self::observe(new UserObserver());
}
public function roles(): belongsToMany
{
return $this->belongsToMany(
Role::class,
'role_user_table',
'user_id',
'role_id'
)->using(RoleUser::class);
}
RoleUser model
class RoleUser extends Pivot
{
use CrudTrait;
use HasFactory;
protected $table = 'role_user_table';
public $incrementing = true;
protected $fillable = [
'id',
'user_id',
'role_id',
];
}
UserObserver
public function saved(User $model): void
{
$this->model = $model;
if ($this->model->all_roles) {
$roles= Role::pluck('id')->toArray();
$this->model->roles()->syncWithoutDetaching($roles);
}
}
I see in the logs (and auto-increment in the staging table) that the observer adds rows to the pivot table and then deletes them. I can't understand why this happens. In this case, if you save the result of syncWithoutDetaching into a variable and output it via dd(). The arrays are returned correct.
Solution:
As I already wrote in the comments, it was probably worth pointing out that I use a crud backpack. In the process of clarifying the strange behavior of the observer, I noticed in the logs that the observer is working correctly and regardless of what synchronization method I use(sync, attach, detach etc), all records remain in place. The first thing I did was to use the capabilities of the backpack and place the saved method inside the setupCreateOperation method as indicated in the documentation. It worked exactly the same as a simple observer - it added and then deleted rows.
public function setupCreateOperation()
{
User::saved(function($entry) {
$roles= Role::pluck('id')->toArray();
$entry->roles()->syncWithoutDetaching($roles);
});
}
Then I began to understand the store and update methods. By placing my synchronization code AFTER the default create and update traits, my code worked as it should. Apparently these traits somehow preserve the state of the columns and an attempt to change the values before they are executed encounters a rollback. Quite strange behavior. And this is the second time I’ve met him. But last time we managed to bypass them without understanding how it works.
public function store()
{
$response = $this->traitStore();
// do something after save
$this->syncRoles($this->data['entry']->id, $this->data['entry']->all_roles);
return $response;
}
public function udate(UserRequest $request)
{
$response = $this->traitUpdate();
// do something after update
$this->syncRoles($request['id'], $request['all_roles']);
return $response;
}
public function syncRoles(int $id, int $all_roles): void
{
if ($all_roles) {
$roles = Role::pluck('id')->toArray();
User::find($id)->role()->syncWithoutDetaching($roles);
}
}