Back

Eager loading roles with users in Spatie Laravel permissions

Error: Class name must be a valid object or a string in file … HasRelationships.php

TL;DR?

in config/auth.php add this to the guards array

'sanctum' => [
  'driver' => 'sanctum',
  'provider' => 'users'
],

Explanation

This error occurs when attempting to eager load roles with their assigned users from Spatie’s laravel-permissions library like this

<?php

use Spatie\Permission\Models\Role;

Role::with('users')->get();

What can be more confusing is that this code works fine in the Laravel PsySH powered Repl Tinker, see this StackOverflow post

The problem is that if you’re using the first party Sanctum package, its service provider sets an authentication guard with a null value for the provider, see source code here

class SanctumServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        config([
            'auth.guards.sanctum' => array_merge([
                'driver' => 'sanctum',
                'provider' => null, // <=== This is the issue
            ], config('auth.guards.sanctum', [])),
        ]);

        if (! app()->configurationIsCached()) {
            $this->mergeConfigFrom(__DIR__.'/../config/sanctum.php', 'sanctum');
        }
    }

This causes Laravel-Permission’s getModelForGuard() helper function here

function getModelForGuard(string $guard)
{
    return collect(config('auth.guards'))
        ->map(fn ($guard) => isset($guard['provider']) ? config("auth.providers.{$guard['provider']}.model") : null)
        ->get($guard);
}

to return nothing when middleware requires the Sanctum guard to be authenticated, hence the eager load fails here

<?php
/**
 * A role belongs to some users of the model associated with its guard.
 */
public function users(): BelongsToMany
{
    return $this->morphedByMany(
        // Error occurs here
        getModelForGuard($this->attributes['guard_name'] ?? config('auth.defaults.guard')),
        'model',
        config('permission.table_names.model_has_roles'),
        app(PermissionRegistrar::class)->pivotRole,
        config('permission.column_names.model_morph_key')
    );
}

After manually inserting the guard in auth/config.php with a proper provider, eager loading works again

'guards' => [
    ...

    'sanctum' => [
        'driver' => 'sanctum',
        'provider' => 'users'
    ]
],

Get in touch_