Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

devhoussam123's avatar

Call to a member function map() on null

User.php

    protected static function booted()
    {
        static::created(function (User $user) {
            if (filament()->getCurrentPanel()->getId() === 'customer') {
                $user->assignRole('customer');
            }
            if (filament()->getCurrentPanel()->getId() === 'admin') {
                $user->assignRole('admin', 'super_admin');
            }
        });
    }

     public function canAccessPanel(Panel $panel): bool
    {
        if ($panel->getId() === 'customer' && $this->hasRole('customer')) {
            return true;
        }

        if ($panel->getId() === 'admin' && $this->hasRole('admin', 'super_admin')) {
            return true;
        }

        return false;
    }

Screenshot

0 likes
30 replies
tykus's avatar

Does the User model use the HasRoles trait; assuming you are using Spatie's Laravel Permission package

tykus's avatar

@devhoussam123 where is the map function called, in assignRole I guessed? So, why then is $this->roles evaluating as null?

If you have not implemented roles (as a relation, accessor or property) yourself, then it should be an empty Collection (not null).

devhoussam123's avatar

User.php

<?php

namespace App\Models;

use App\Enums\AccountStatus;
use App\Enums\UserRoles;
use Filament\Models\Contracts\FilamentUser;
use Filament\Models\Contracts\HasAvatar;
use Filament\Panel;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Storage;
use Laravel\Sanctum\HasApiTokens;
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable implements FilamentUser, HasAvatar, MustVerifyEmail
{
    use HasApiTokens;
    use HasFactory;
    use HasRoles;
    use Notifiable;
    use SoftDeletes;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'avatar_url',
        'name',
        'email',
        'password',
        'status',
        'roles',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
        'password' => 'hashed',
        'status' => AccountStatus::class,
        'roles' => UserRoles::class,
    ];

    /**
     * The "booted" method of the model.
     */
    protected static function booted(): void
    {
        static::created(function (User $user) {
            if (filament()->getCurrentPanel()->getId() === 'customer') {
                $user->assignRole('customer');
            }

            if (filament()->getCurrentPanel()->getId() === 'admin') {
                $user->assignRole(['admin', 'super_admin']);
            }
        });
    }

    public function canAccessPanel(Panel $panel): bool
    {
        if ($panel->getId() === 'customer' && $this->hasRole('customer')) {
            return true;
        }

        if ($panel->getId() === 'admin' && $this->hasRole(['admin', 'super_admin'])) {
            return true;
        }

        return false;
    }

    public function getFilamentAvatarUrl(): ?string
    {
        return $this->avatar_url ? Storage::url($this->avatar_url) : null;
    }
}

tykus's avatar

@devhoussam123 you have a roles property that is fillable (and cast as UserRoles) on the Model, so I assume you have a roles column on the table also?

This is exactly the problem I mentioned above; you are overriding a property that the package implements as a relationship. If you are using the package, then you should not need your own roles column.

Snapey's avatar

can you post the full error with stacktrace, or share the error using the share button on the error?

Snapey's avatar

@devhoussam123 So this is the offending line

$currentRoles = $this->roles->map(fn ($role) => $role->getKey())->toArray();

and I'm concerned that $this->roles is messed up by your roles cast

devhoussam123's avatar

Notice: When I remove the $table->string('roles')->default(UserRoles::Default); from users table it works

tykus's avatar

@devhoussam123 again, it is not necessary to use the package and your own role column

Why do you name the column roles anyway?

arthvrian's avatar

The documentation is clear about this.

https://spatie.be/docs/laravel-permission/v6/prerequisites

Must not have a [role] or [roles] property, nor a [roles()] method

Your User model/object MUST NOT have a role or roles property (or field in the database by that name), nor a roles() method on it. Those will interfere with the properties and methods added by the HasRoles trait provided by this package, thus causing unexpected outcomes when this package's methods are used to inspect roles and permissions.

Must not have a [permission] or [permissions] property, nor a [permissions()] method

Your User model/object MUST NOT have a permission or permissions property (or field in the database by that name), nor a permissions() method on it. Those will interfere with the properties and methods added by the HasPermissions trait provided by this package (which is invoked via the HasRoles trait).

// -----

just rename your column like

$table->string('current_role')->default(UserRoles::Default);
tykus's avatar

@arthvrian or role if it is expected to be a singular value property 🤷‍♂️

devhoussam123's avatar

after I update the migration table now I get '403 FORBIDDEN' when I try to get access to filament dashboard

User.php

    public function canAccessPanel(Panel $panel): bool
    {
        if ($panel->getId() === 'customer' && $this->hasRole([UserRoles::Customer]) && $this->hasVerifiedEmail()) {
            return true;
        }

        if ($panel->getId() === 'admin' && $this->hasRole([UserRoles::Admin, UserRoles::SuperAdmin]) && $this->hasVerifiedEmail()) {
            return true;
        }

        return false;
    }
devhoussam123's avatar

@tykus ### UserRoles.php

<?php

namespace App\Enums;

use Filament\Support\Contracts\HasColor;
use Filament\Support\Contracts\HasIcon;
use Filament\Support\Contracts\HasLabel;

enum UserRoles: string implements HasColor, HasIcon, HasLabel
{
    case Customer = 'customer';
    case Admin = 'admin';
    case SuperAdmin = 'super_admin';

    public const Default = self::Customer->value;

    public function getColor(): string | array | null
    {
        return match ($this) {
            self::Customer => 'primary',
            self::Admin => 'info',
            self::SuperAdmin => 'success',
        };
    }

    public function getIcon(): ?string
    {
        return match ($this) {
            self::Customer => 'bi-person',
            self::Admin => 'bi-person',
            self::SuperAdmin => 'bi-person',
        };
    }

    public function getLabel(): ?string
    {
        return match ($this) {
            self::Customer => __('Customer'),
            self::Admin => __('Admin'),
            self::SuperAdmin => __('Super Admin'),
        };
    }
}

tykus's avatar

@devhoussam123 (i) are they the values as the Role names in the package; (ii) is the role being assigned still and (iii) did you check which of the three conditions fails?

Snapey's avatar

use a different name in your model, eg current_role

devhoussam123's avatar

Now i WANT to be able to update the current role id from users table

Snapey's avatar

@devhoussam123 If you just rename the column, why would you not be able to update it like any column?

Please or to participate in this conversation.