3 use Illuminate\Database\Migrations\Migration;
4 use Illuminate\Database\Query\Builder;
5 use Illuminate\Database\Schema\Blueprint;
6 use Illuminate\Support\Facades\DB;
7 use Illuminate\Support\Facades\Schema;
9 class FlattenEntityPermissionsTable extends Migration
18 // Remove entries for non-existing roles (Caused by previous lack of deletion handling)
19 $roleIds = DB::table('roles')->pluck('id');
20 DB::table('entity_permissions')->whereNotIn('role_id', $roleIds)->delete();
22 // Create new table structure for entity_permissions
23 Schema::create('new_entity_permissions', function (Blueprint $table) {
25 $table->unsignedInteger('entity_id');
26 $table->string('entity_type', 25);
27 $table->unsignedInteger('role_id')->index();
28 $table->boolean('view')->default(0);
29 $table->boolean('create')->default(0);
30 $table->boolean('update')->default(0);
31 $table->boolean('delete')->default(0);
33 $table->index(['entity_id', 'entity_type']);
36 // Migrate existing entity_permission data into new table structure
38 $subSelect = function (Builder $query, string $action, string $subAlias) {
39 $sub = $query->newQuery()->select('action')->from('entity_permissions', $subAlias)
40 ->whereColumn('a.restrictable_id', '=', $subAlias . '.restrictable_id')
41 ->whereColumn('a.restrictable_type', '=', $subAlias . '.restrictable_type')
42 ->whereColumn('a.role_id', '=', $subAlias . '.role_id')
43 ->where($subAlias . '.action', '=', $action);
44 return $query->selectRaw("EXISTS({$sub->toSql()})", $sub->getBindings());
47 $query = DB::table('entity_permissions', 'a')->select([
48 'restrictable_id as entity_id',
49 'restrictable_type as entity_type',
51 'view' => fn(Builder $query) => $subSelect($query, 'view', 'b'),
52 'create' => fn(Builder $query) => $subSelect($query, 'create', 'c'),
53 'update' => fn(Builder $query) => $subSelect($query, 'update', 'd'),
54 'delete' => fn(Builder $query) => $subSelect($query, 'delete', 'e'),
55 ])->groupBy('restrictable_id', 'restrictable_type', 'role_id');
57 DB::table('new_entity_permissions')->insertUsing(['entity_id', 'entity_type', 'role_id', 'view', 'create', 'update', 'delete'], $query);
59 // Drop old entity_permissions table and replace with new structure
60 Schema::dropIfExists('entity_permissions');
61 Schema::rename('new_entity_permissions', 'entity_permissions');
65 * Reverse the migrations.
69 public function down()
71 // Create old table structure for entity_permissions
72 Schema::create('old_entity_permissions', function (Blueprint $table) {
73 $table->increments('id');
74 $table->integer('restrictable_id');
75 $table->string('restrictable_type', 191);
76 $table->integer('role_id')->index();
77 $table->string('action', 191)->index();
79 $table->index(['restrictable_id', 'restrictable_type']);
82 // Convert newer data format to old data format, and insert into old database
84 $actionQuery = function (Builder $query, string $action) {
85 return $query->select([
86 'entity_id as restrictable_id',
87 'entity_type as restrictable_type',
89 ])->selectRaw("? as action", [$action])
90 ->from('entity_permissions')
91 ->where($action, '=', true);
94 $query = $actionQuery(DB::query(), 'view')
95 ->union(fn(Builder $query) => $actionQuery($query, 'create'))
96 ->union(fn(Builder $query) => $actionQuery($query, 'update'))
97 ->union(fn(Builder $query) => $actionQuery($query, 'delete'));
99 DB::table('old_entity_permissions')->insertUsing(['restrictable_id', 'restrictable_type', 'role_id', 'action'], $query);
101 // Drop new entity_permissions table and replace with old structure
102 Schema::dropIfExists('entity_permissions');
103 Schema::rename('old_entity_permissions', 'entity_permissions');