Role::getTable() method brokes withCount method
xenaio-daniil opened this issue · 1 comments
I needed to get ManyToMany relation of roles (structure of departments). To do this, I extended the \Spatie\Permission\Models\Role
class with methods:
class Department extends \Spatie\Permission\Models\Role
{
....
public function parents(): BelongsToMany
{
return $this->belongsToMany(
Department::class,
"department_hierarchy,
'child_id',
'parent_id'
);
}
public function children(): BelongsToMany
{
return $this->belongsToMany(
Department::class,
'department_hierarchy',
'parent_id',
'child_id'
);
}
....
}
and created migration:
Schema::create('department_hierarchy', function (Blueprint $table) {
$table->string("rel_id", 25)->primary();
$table->bigInteger("parent_id", false, true);
$table->bigInteger("child_id", false, true);
$table->boolean("is_direct")->default(false);
$table->foreign("parent_id")->references("id")->on("roles");
$table->foreign("child_id")->references("id")->on("roles");
});
Everything works well except methods like
\App\Models\Department::withCount('children')
and so on: it always returns parent_count = 0
.
The reason of this behavior is in overriding of method getTable
in Spatie\Permission\Models\Role
.
When extending Illuminate\Database\Eloquent\Model
:
echo \App\Models\Department::withCount('children')->toSql();
/**
SELECT `roles`.*, (
SELECT COUNT(*)
FROM `roles` AS `laravel_reserved_0`
INNER JOIN `department_hierarchy`
ON `laravel_reserved_0`.`id` = `department_hierarchy`.`child_id` <-- look at laravel_reserved_0
WHERE `roles`.`id` = `department_hierarchy`.`parent_id`) AS `children_count`
FROM `roles`
When extending \Spatie\Permission\Models\Role
:
echo \App\Models\Department::withCount('children')->toSql();
/**
SELECT `roles`.*, (
SELECT COUNT(*)
FROM `roles` AS `laravel_reserved_0`
INNER JOIN `department_hierarchy`
ON `roles`.`id` = `department_hierarchy`.`child_id` <------ laravel_reserved_0 != roles
WHERE `roles`.`id` = `department_hierarchy`.`parent_id`) AS `children_count`
FROM `roles`
withCount()
uses Model::table
field to temporary store alias name (laravel_reserved_0
).
And when (in Illuminate\Database\Eloquent\Model
) method Model::getTable()
returns $this->table
or magic name from class basename, your version returns only pre-configurated table name (config('permission.table_names.roles')
) or, if configuration not found, Model::getTable()
. So if permission.table_names.roles
is configured, it has no change to return $this->table.
I would recommend to rewrite this method like this:
public function getTable()
{
return $this->table ?? config('permission.table_names.roles', parent::getTable());
}
or for best practice remove method Role::getTable() and replace it with
public function __construct(array $attributes = [])
{
$attributes['guard_name'] = $attributes['guard_name'] ?? config('auth.defaults.guard');
parent::__construct($attributes);
$this->guarded[] = $this->primaryKey;
$this->table = config('permission.table_names.roles', parent::getTable()); // <---------------
}
Feel free to open a PR