I have a pivot table that connects users to workspaces. On the pivot table, I also have a column for role, which defines the users role for that workspace. Can I provide Accesso
This is a difficult question. The solutions I can think of are smelly and may cause some problems later on.
I am going to extend on Patricus's answer to make it work.
I was going to comment on Patricus's answer but there is simply too much to explain. To make his solution work with attach
and sync
we must do some ugly things.
The Problem
First let's identify the problem with his solution. His getters and setters do work but the belongsToMany relationship doesn't use the Pivot model when running sync
, attach
, or detach
. This means every time we call one of these with the $attributes
parameter the non-mutated data will be put into the database column.
// This will skip the mutator on our extended Pivot class
$user->workspaces()->attach($workspace, ['role' => 'new role value']);
We could just try to remember that every time we call one of these we can't use the second parameter to attach the mutated data and just call updateExistingPivot
with the data that must be mutated. So an attach
would be what Patricus stated:
$user->workspaces()->attach($workspace);
$user->workspaces()->updateExistingPivot($workspaceId, ['role' => 'new role value']);
and we could never use the correct way of passing the pivot attributes as the attach
methods second parameter shown in the first example. This will result in more database statements and code rot because you must always remember not to do the normal way. You could run into serious problems later on if you assume every developer, or even yourself, will just know not to use the attach
method with the second parameter as it was intended.
The Solution (untested and imperfect)
To be able to call attach
with the mutator on the pivot columns you must do some crazy extending. I haven't tested this but it may get you on the right path if you feel like giving it a try. We must first create our own relationship class that extends BelongsToMany
and implements our custom attach
method:
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class UserWorkspaceBelongsToMany extends BelongsToMany {
public function attach($id, array $attributes = [], $touch = true)
{
$role = $attributes['role'];
unset($attributes['role']);
parent::attach($id, $attributes, $touch);
$this->updateExistingPivot($id, ['role' => $role], $touch);
}
// You will need sync here too
}
Now we have to make each Model::belongsToMany
use our new UserWorkspaceBelongsToMany
class instead of the normal BelongsToMany
. We do this by mocking the belongsToMany
in our User and Workspace class:
// put this in the User and Workspace Class
public function userWorkspaceBelongsToMany($related, $table = null, $foreignKey = null, $otherKey = null, $relation = null)
{
if (is_null($relation)) {
$relation = $this->getBelongsToManyCaller();
}
$foreignKey = $foreignKey ?: $this->getForeignKey();
$instance = new $related;
$otherKey = $otherKey ?: $instance->getForeignKey();
if (is_null($table)) {
$table = $this->joiningTable($related);
}
$query = $instance->newQuery();
return new UserWorkspaceBelongsToMany($query, $this, $table, $foreignKey, $otherKey, $relation);
}
As you can see, we are still calling the database more but we don't have to worry about someone calling attach
with the pivot attributes and them not getting mutated.
Now use that inside your models instead of the normal belongsToMany:
class User extends Model {
public function workspaces() {
return $this->userWorkspaceBelongsToMany('App\Models\Workspace')->withPivot('role');
}
}
class Workspace extends Model {
public function users() {
return $this->userWorkspaceBelongsToMany('App\Models\User')->withPivot('role');
}
}