3

I have a Laravel 5.7 application using spatie/laravel-permission and normal IDs for models. I want to transition to using UUIDs and I've taken the steps mentioned in webpatser/laravel-uuid Readme file. Everything works with my own models, e.g. with User, Model A, Model B etc. and the relations seem fine, but I cannot seem to make the uuid's work with Spatie's permissions.

When I want to assign a role (and associated permissions) to an object, I get the following error. This happens when I try to register a User and assign him a Role.

As you can see in this image, the role_id from Spatie is transmitted into this query as an integer. Now it is 342, in other cases it is similar to 705293

As you can see in this image, the role_id from Spatie is transmitted into this query as an integer. Now it is 342, in other cases it is similar to 705293

I have defined a Uuids.php trait in my /app folder accordingly and added for all the models, including Permission.php and Role.php the following code:

public $incrementing = false;
protected $keyType = 'string';

use Uuids;

protected $casts = [
    'id' => 'string',
];

I know that this works with any other model and relation, but just not with Spatie's permissions and it seems that the role_id is converted differently in the internal functions (like assignRole('') from a 36chars string to something else. If I query the Roles or Permissions I get the correct string id.

Anything that I might be missing or does anyone knows a fix for this?

Later edit: this is my original migration for Spatie:

    <?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePermissionTables extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $tableNames = config('permission.table_names');
        $columnNames = config('permission.column_names');

        Schema::create($tableNames['permissions'], function (Blueprint $table) {
            $table->uuid('id');
            $table->primary('id');
            $table->string('name');
            $table->string('guard_name');
            $table->timestamps();
        });

        Schema::create($tableNames['roles'], function (Blueprint $table) {
            $table->uuid('id');
            $table->primary('id');
            $table->string('name');
            $table->string('guard_name');
            $table->timestamps();
        });

        Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames) {
            $table->uuid('permission_id');
            $table->string('model_type');
            $table->uuid($columnNames['model_morph_key']);
            $table->index([$columnNames['model_morph_key'], 'model_type', ]);

            $table->foreign('permission_id')
                ->references('id')
                ->on($tableNames['permissions'])
                ->onDelete('cascade');

            $table->primary(
                ['permission_id', $columnNames['model_morph_key'], 'model_type'],
                    'model_has_permissions_permission_model_type_primary'
            );
        });

        Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames) {
            $table->uuid('role_id');

            $table->string('model_type');
            $table->uuid($columnNames['model_morph_key']);
            $table->index([$columnNames['model_morph_key'], 'model_type', ]);

            $table->foreign('role_id')
                ->references('id')
                ->on($tableNames['roles'])
                ->onDelete('cascade');

            $table->primary(
                ['role_id', $columnNames['model_morph_key'], 'model_type'],
                    'model_has_roles_role_model_type_primary'
            );
        });

        Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames) {
            $table->uuid('permission_id');
            $table->uuid('role_id');

            $table->foreign('permission_id')
                ->references('id')
                ->on($tableNames['permissions'])
                ->onDelete('cascade');

            $table->foreign('role_id')
                ->references('id')
                ->on($tableNames['roles'])
                ->onDelete('cascade');

            $table->primary(['permission_id', 'role_id']);

            app('cache')->forget('spatie.permission.cache');
        });
    }

    //Reverse the migrations.
    public function down()
    {
        $tableNames = config('permission.table_names');

        Schema::drop($tableNames['role_has_permissions']);
        Schema::drop($tableNames['model_has_roles']);
        Schema::drop($tableNames['model_has_permissions']);
        Schema::drop($tableNames['roles']);
        Schema::drop($tableNames['permissions']);
    }
}

This is an example of how the Roles and permissions are stored (working)

enter image description here

Same is for Permissions. So their _id is correct. The issue is somewhere in Laravel or in Spatie that it sends another value to the DB when trying to add a Role to a Model.

apokryfos
  • 38,771
  • 9
  • 70
  • 114

3 Answers3

0

Updating the tables in the package is not recommended. So create a trait in the app folder:

<?php

namespace YourNamespace;

use Illuminate\Support\Str;

trait Uuids
{
    /**
    * Boot function from Laravel
    */
    protected static function boot()
    {
        parent::boot();

        static::creating(function ($model) {
            $model->incrementing = false;
            $model->{$model->getKeyName()} = Str::uuid()->toString();
        });
    }
}

Create a migration to update your table structure:

php artisan migrate:make change_primary_key_type_in_roles_table --table=roles

Put the following in to the migration:

public function up()
{
    Schema::table('roles', function (Blueprint $table) {
        $table->uuid('id')->change();
    });
}

public function down()
{
    Schema::table('roles', function (Blueprint $table) {
        $table->increments('id')->change();
    });
}

Use Uuid trait in your model:

<?php

namespace YourNamespace;

use Illuminate\Database\Eloquent\Model;

class Category extends Model
{
    use Uuids;

    protected $guarded = [''];
    protected $casts = [
        'id' => 'string',
    ];

Then do composer dumpautoload and php artisan migrate

Elisha Senoo
  • 3,489
  • 2
  • 22
  • 29
  • Not working, unfortunately. My Roles table has the $table->uuid('id'); primary key set from the beginning. The Roles and Permissions are added correctly in the Database when I seed it. The Permissions then are corretly assigned to the Roles when I seed them. The problem appears when I try to assign a role to a model, as the role_id is somehow converted in Laravel from the char(36) or not displayed as so. Just like in the screenshot :( – Rareș Popescu Nov 21 '18 at 12:27
0

Also facing this issue. solved by changing Permission.php and Role.php with the following code:

public $incrementing = false;
protected $keyType = 'string';

use Uuids;

protected $casts = [
    'id' => 'uuid',
];
0

If you want all the role/permission objects to have a UUID instead of an integer, you will need to Extend the default Role and Permission models into your own namespace in order to set some specific properties.

namespace App\Models;

use App\Traits\Uuid;
use Illuminate\Database\Eloquent\Model;
use Spatie\Permission\Models\Role as SpatieRole;

class Role extends SpatieRole
{
    use Uuid;
    protected $primaryKey = 'id';

    /**
    * The attributes that should be cast to native types.
    *
    * @var array
    */
    protected $casts = [
        'id' => 'string',
    ];
}

then update config in permission.php change the model used

'permission' => Spatie\Permission\Models\Permission::class,
to
'permission' => App\Models\Permission::class,

'role' => Spatie\Permission\Models\Role::class,
to
'role' => App\Models\Role::class,