Hi,
I have found a very strange behavior. I have a User model, a Role model, many other models like a Comments model or a Page model because a page can have comments and so on. However, I have simplified the problem and even created a new project to reproduce this problem. And I am facing the same problem as I am in my own project. I have also attached the repo so you can test it on your own (download link at the end of this text).
My problem is that a User has one Role which is assigned by the role_id in the User table. No problem so far. However, in my User model I have assigned a belongsTo relationship to the Role model to get the role of the user.
Why I am doing this? Because only people with certain roles should be able to see certain pages or comments for exmaple. So, I am basically checking if the user has the permission to see certain pages.
The problem I am facing now is that any user I choose, always has the superadmin role. The superadmin role is the first role along my two other roles in my Role table. Besides superadmin I also have to role admin and user. Please check out the repo, I have also created migrations and seeders so you can easily reproduce the problem.
But this problem only occurs when my Page model performs eager loading via the protected $with variable or in the controller when using $page->load('comments'). When eager loading is activated, the user, no matter which one, always has the first role which there is superadmin.
For me this is not normal and sounds like a bug? I am also very confused because I only have this problem with my role relationship and no other relationship. Maybe I cannot use the name role?
What I am basically trying to do is to check weather the user is admin or not via the ->isAdmin() function in my User model. Which also is very strange when I call the ->isAdmin() method via php artisan tinker or basically inside my view the user has the correct role. But why doesn't the user has the correct role when accessing it in a relationship?
Here you can download my repo. After you have downloaded it, please run the migrations and seeders and call the URI /page/1. There you can see a dump which says superadmin although User 2 has the role called user. Then switch the $with attribute in my Page model form comments to test and you can see the user will have the correct role as the role is resolved in the view itself.
Note 1: I wanted to keep the repo as small as possible. So, I have not implemented a login and authenticate feature. All queries are made on the User with the id 2. This user has the role called user. However, I have tested if it makes any difference if I am using the auth user or just any User from the database. It doesn't make any difference. So, it also has nothing to do with auth.
Note 2: Even when performing a refresh on my model the role relationship still is wrong!
Note 3: Why I even want to know if the user is admin or not? Because as I mentioned earlier, my page can have comments. Comments have a page status (publish, pending, draft, etc.) and I only want to show to normal users the published comments and to the admins the published and also pending comments so they can approve them. For this I need to perform a if-else statement and have to check (inside the relationship) via the isAdmin() function weather to load only published or also pending comments.
Note 4: The user is always the correct when. Even when doing this with auth()->user(). However, the role relation is wrong because it always is the first entry in my database which there is superadmin!
I am using PHP 7.4.11 and Laravel 8.15.0. I will post my code here as well:
Role migration:
Schema::create('roles', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name')->unique();
$table->string('label')->unique();
$table->timestamps();
});
User migration:
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('role_id')->index();
$table->string('name', 255)->unique();
$table->string('email')->unique()->nullable();
$table->timestamp('email_verified_at')->nullable();
$table->string('password')->nullable();
$table->timestamps();
$table->foreign('role_id')
->references('id')
->on('roles');
});
Page migration:
Schema::create('pages', function (Blueprint $table) {
$table->increments('id');
$table->unsignedBigInteger('user_id')->index();
$table->char('heading')->index();
$table->char('slug')->index()->unique();
$table->mediumText('content');
$table->timestamps();
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('restrict');
});
Role seeder:
\DB::table('roles')->delete();
\DB::table('roles')->insert(array (
0 =>
array (
'id' => 1,
'name' => 'superadmin',
'label' => 'Superadmin',
'created_at' => date(now()),
'updated_at' => date(now()),
),
1 =>
array (
'id' => 2,
'name' => 'admin',
'label' => 'Admin',
'created_at' => date(now()),
'updated_at' => date(now()),
),
2 =>
array (
'id' => 3,
'name' => 'user',
'label' => 'User',
'created_at' => date(now()),
'updated_at' => date(now()),
),
));
User seeder
\DB::table('users')->delete();
\DB::table('users')->insert(array (
0 =>
array (
'id' => 1,
'role_id' => 1,
'name' => 'MyName',
'email' => '[email protected]',
'email_verified_at' => date(now()),
'password' => 'mypassword',
'created_at' => date(now()),
'updated_at' => date(now())
),
1 =>
array (
'id' => 2,
'role_id' => 3,
'name' => 'ThisIsMyName',
'email' => '[email protected]',
'email_verified_at' => date(now()),
'password' => 'mypassword',
'created_at' => date(now()),
'updated_at' => date(now())
)
));
Page seeder:
\DB::table('pages')->delete();
\DB::table('pages')->insert(array (
0 =>
array (
'id' => 1,
'user_id' => 1,
'heading' => 'My Test Heading',
'slug' => 'my-test-heading',
'content' => 'This is my content',
'created_at' => date(now()),
'updated_at' => date(now())
),
1 =>
array (
'id' => 2,
'user_id' => 1,
'heading' => 'My Test Heading 1',
'slug' => 'my-test-heading-1',
'content' => 'This is my content',
'created_at' => date(now()),
'updated_at' => date(now())
))
);
Role model:
class Role extends Model
{
use HasFactory;
/**
* @Protected_variables
*/
protected $table = 'roles';
protected $guarded = ['id'];
/**
* @Public_variables
*/
/**
* @Relationships
*/
/**
* @Attributes
*/
/**
* @Custom_functions
*/
public static function getIdByName($name)
{
return Cache::rememberForever('getRoleIDByName.'.$name, function() use ($name)
{
return self::where('name', $name)->first()->id;
});
}
}
User model:
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
protected $table = 'users';
protected $guarded = ['id'];
protected $hidden = [
'password', 'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
public function role()
{
return $this->belongsTo('App\Models\Role', 'role_id', 'id');
}
public function hasRole($roleNames)
{
//dd($this->role->id);
$role = $this->refresh()->role->name;
if(is_array($roleNames)){
return in_array($role, $roleNames);
}
if(is_string($roleNames)){
return $role == $roleNames;
}
return false;
}
public function isAdmin($authorized = array('admin', 'superadmin'))
{
return $this->hasRole($authorized);
}
}
Page model
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Page extends Model
{
protected $table = 'pages';
protected $guarded = ['id'];
protected $with = ['comments'];
protected $casts = [
'publish_at' => 'datetime',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
public function test(){
return $this->belongsTo('App\Models\User');
}
public function comments()
{
$user = User::find(2);
if($user->isAdmin()){
dd("I am FALSELY admin!");
dump($user->role->name . " <- This is wrong!");
dump(json_encode($user->isAdmin()) . " <- This is wrong!");
}else{
return $this->belongsTo('App\Models\User');
}
}
public function user()
{
return $this->belongsTo('App\Models\User');
}
}
Kind regards