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

Cushty's avatar
Level 4

shield vs default spatie permissions package?

Hi would you recommend using the shield plugin or install the spatie plugin in the default way? Thanks

0 likes
3 replies
newbie360's avatar

Are you mean this https://filamentphp.com/plugins/bezhansalleh-shield

that package is overkill for me

may be just add a role column into users table ?

// migration

        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->unsignedTinyInteger('role')->default(User::MEMBER_ROLE_ID);
// model

class User extends Authenticatable implements FilamentUser
{
    use HasFactory, Notifiable;

    const SUPER_ADMIN_ROLE_ID = 1;
    const MEMBER_ROLE_ID = 4;

    protected $fillable = [
        'name',
        'email',
        'role',
        'password',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];

    protected $attributes = [
        'role' => self::MEMBER_ROLE_ID,
    ];

    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }

    public function isSuperAdmin(): bool
    {
        return $this->role === self::SUPER_ADMIN_ROLE_ID;
    }

    public function canAccessPanel(Panel $panel): bool
    {
        return $this->isSuperAdmin();
    }
}

add a new config file

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Site Config
    |--------------------------------------------------------------------------
    */

    'user_roles' => [
        1 => 'super admin',
        2 => 'admin',
        3 => 'vip',
        4 => 'member',
    ],

];

add a seeder for dev

class UserSeeder extends Seeder
{
    public function run(): void
    {
        $users = [
            'admin',
            'test1',
            'test2',
            'test3',
        ];

        $roleKeys = array_keys(config('cfg.user_roles'));

        throw_if(
            count($users) != count($roleKeys),
            'The count of users and roles not matched.'
        );

        $users = array_combine($roleKeys, $users);

        foreach($users as $role => $userName) {
            User::factory()->create([
                'name' => $userName,
                'email' => "{$userName}@example.com",
                'role' => $role,
            ]);
        }
    }
}

and then add two state in to User factory

class UserFactory extends Factory
{
    /**
     * The current password being used by the factory.
     */
    protected static ?string $password;

    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        ...
    }

    /**
     * Indicate that the model's email address should be unverified.
     */
    public function unverified(): static
    {
        ...
    }

    /**
     * Indicate that the user factory use super admin role.
     */
    public function superAdminRole(): Factory
    {
        return $this->state(fn (array $attributes) => [
            'role' => User::SUPER_ADMIN_ROLE_ID,
        ]);
    }

    /**
     * Indicate that the user factory use random role.
     */
    public function randomRole(): Factory
    {
        return $this->state(fn (array $attributes) => [
            'role' => fake()->randomKey(config('cfg.user_roles')),
        ]);
    }
}

so you can call this to create user

User::factory()->superAdminRole()->create();
User::factory()->count(10)->superAdminRole()->create();

User::factory()->randomRole()->create();
User::factory()->count(10)->randomRole()->create();

finally write a test

Edited:

<?php

use App\Models\User;
use Filament\Pages\Auth\Login;
use Livewire\Livewire;
use function Pest\Laravel\{actingAs, get};

test('admin panel login screen can be rendered', function () {
    get('/admin')
        ->assertRedirect('/admin/login');

    get('/admin/login')
        ->assertOk()
        ->assertSeeLivewire(Login::class);
});

test('only super admin can authenticate using the admin panel login screen', function () {
    $component = Livewire::test(Login::class);

    $roles = array_keys(config('cfg.user_roles'));

    foreach ($roles as $role) {
        $user = User::factory()->create(['role' => $role]);

        $component
            ->fillForm([
                'email' => $user->email,
                'password' => 'password',
            ])
            ->call('authenticate');

        if ($user->role === 1) {
            $component
                ->assertHasNoFormErrors()
                ->assertRedirect('/admin');

            $this->assertAuthenticated();
            continue;
        }

        $component
            ->assertHasFormErrors()
            ->assertNoRedirect();

        $this->assertGuest();
    }
});

test('super admin can not authenticate with invalid password', function () {
    $superAdmin = User::factory()->superAdminRole()->create();

    Livewire::test(Login::class)
        ->fillForm([
            'email' => $superAdmin->email,
            'password' => 'wrong-password',
        ])
        ->call('authenticate')
        ->assertHasFormErrors()
        ->assertNoRedirect();

    $this->assertGuest();
});

test('super admin can logout', function () {
    $superAdmin = User::factory()->superAdminRole()->create();

    actingAs($superAdmin)
        ->post('/admin/logout')
        ->assertRedirect('/admin/login');

    $this->assertGuest();
});

Cushty's avatar
Level 4

So you wouldnt use spatie roles and permissions at all? I was thinking to use a simple enum for roles, but I was thinking just in case in the future we needed a more robust system spatie has more options.

newbie360's avatar

@Cushty Sorry i'm not fan for use Enum, and then the Filament Shield package added too many things is confuse me to learning Filament

Please or to participate in this conversation.