Hi. I have 2 Models for User. One for keeping all the relationships and scopes for the frontend and another for the admin area which has different scopes and so on.
The frontend user is located at App/Models/User.php and the Admin one App/Models/Admin/User.php
They are both almost identical bar the scopes and relationships.
What I'm trying to do is in the backend controllers use App/Models/Admin/User.php but when I call the role it doesn't get any results:
use App/Model/Admin/User;
$user = User::role('customer')->get();
But if I change the use App/Model/Admin/User; to use App/Model/User; it works fine.
Heres the model for Admin/User.php which is identical on the User.php:
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Cashier\Billable;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use Notifiable;
use HasRoles;
protected $guard_name = 'web';
Any help or info would be greatful
Oh the joys of polymorphic relations.
When you assign roles (or permissions for that matter) to a user, its model class name is recorded in the pivot table, essentially hard-linking it to that specific model.
So, if you assign roles to an App/Models/User' object, it recordsApp/Models/Userin the database, and therefore an attempt to view relations by another object such asApp/Models/Admin/User` will not return those roles assigned to the other object.
One way I've gotten around this is by reverting to v1 of this package, which doesn't use polymorphism. It's a simpler relation structure and skips this issue because it assumes only one type of user model. This has its pros and cons.
(Note: if you wanna give it a try, revert and delete the migrations for the v2 and rebuild all your roles/permissions assignments fresh in v1 -- the 2 schemas are incompatible with each other.)
Ah brilliant, I'll give that a shot and see how I get on. If not I'll have to keep it on v2 and just suck it up lol...
Many thanks for the reply.
Is there another way of achieving what @cloudwales described, other than reverting to v1 of the package?
Any help would be greatly appreciated. From what I've read, I'm not the only one looking for a workarround to this issue.
@laxsmi,
A few questions:
I don't know that there is a _high demand_ but I believe that #292 this fork for instance aims at something that could be achieved easily by extending a model, and scoping the new model based on roles or permissions.
My scenario is this one: I have different models (e.g. Student and Teacher) extending User. I was planning to restrict the scope of these new models to the users who have been assigned these roles (using Laravel global scopes). From these models, I would then be call Student::all() and all other methods without the need to re-write code like \App\User::role('admin')->get(); for every method within the model.
However, since the roles are assigned to the User model, I am not able to retrieve the users from another model...
I'm facing this issue within similar use cases. I have users and admins.
Admin is a model who extends User. User is the main model, predefined by Laravel.
class Admin extends User
{
/**
* The "booting" method of the model.
*
* @return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope('admin', function (Builder $builder) {
$builder->whereHas('roles', function ($query) {
$query->where('guard_name', 'admin');
$query->where('name', 'admin');
});
});
}
}
class User extends Authenticatable implements CanBeOwner
This is exactly what I was talking about. Thank you @drbyte for suggesting to reverting to the v1 of the package - I wasn't aware that it was still maintained, but this can be a viable solution. Since my project is also using Backpack, though, this doesn't work for me: the permissions module of Backpack requires the v2.
So I reverted to using the User model only, with local scopes:
public function scopeTeacher($query)
{
return User::role('teacher');
}
which allows me to do stuff like User::teacher()->get() whenever I need to perform operations on what would have been my Teacher model.
This isn't as clean as I'd like to be, but it works.
@laxsmi , your solution is better than you think. I'll refactor my code to be more like yours. Thank you.
@laxsmi thanks, We have the same query "Teacher" hahaha
There are a couple of methods to work around this.
First if you only have one entity that extends User functionality, you can add it to the morphMap array. Adding this to the boot() method in AppServiceProvider.php.
public function boot()
{
//
Relation::morphMap([
User::class => Teacher::class
]);
The other way, if you have more logical entities, like Admins, Managers, all that extend User class.
You could override the getMorphClass on the User model to always return User::class.
public function getMorphClass()
{
return self::class;
}
In theory, you could instead return a meaningful equivalent string and also add the Relation::morphMap for that string, but I haven't tested that one.
The only caveat is that this will override all MorphTo or MorphToMany relations to give the overridden specific string every time you're building a relation. So keep that in mind.
Most helpful comment
There are a couple of methods to work around this.
First if you only have one entity that extends User functionality, you can add it to the morphMap array. Adding this to the boot() method in AppServiceProvider.php.
The other way, if you have more logical entities, like Admins, Managers, all that extend User class.
You could override the getMorphClass on the User model to always return User::class.
In theory, you could instead return a meaningful equivalent string and also add the Relation::morphMap for that string, but I haven't tested that one.
The only caveat is that this will override all MorphTo or MorphToMany relations to give the overridden specific string every time you're building a relation. So keep that in mind.