Managing permission changes in production Laravel apps using Migrations
So, i'm sitting here working on a new project on a weekend - and i need a roles and permissions library, and of course, someone on the LaravelUK Slack point out that spatie.be has one already out there, with tests and everything.
This article's code snippets are specific to this library, but the concept can be applied to any.
"But roles and permissions are super easy to create in laravel with Gates and Policies"
You aren't wrong. But persisting the roles and permissions to the DB, writing tests to make sure everything works as it should, and all the other guff associated with it can take time, and if there's already a package out there that can handle it, save yourself some of the that time by using it. It doesn't do everything i need, but it gets me more than 90% of the way there with little effort, and that's important to a freelancer like myself.
Initial roles and permissions can be set using a Seeder
So, after about 10 minutes, i was setup with this package, and set up a DatabaseSeeder
to add the initial roles and permissions:
This is fine during development, but what if i want to change or alter these roles or permissions later while the application is in production?
It could happen easily - adding a new feature maybe, or perhaps you just need to tweak who can do what in an application?
The config problem.
I've been here before - the roles and permissions are stored in database tables now, and cached so that they're faster in your application, so when you deploy your new feature, you have to update the database somehow.
Update the database and create the new roles/permissions manually.
Yuck, this is an awful way to do it, and has the most probability of something going wrong that has to be corrected manually.
Also - it won't clear the permission and role cache on the site for you.
Do it in Tinker.
If you have the CLI Tinker package installed, you can do it there:
_10> php artisan tinker_10> Psy Shell v0.8.17 (PHP 7.1.13 — cli) by Justin Hileman_10> >>> Spatie\Permission\Models\Role::findByName('super-admin')->givePermissionTo('do that thang');_10> >>> app()['cache']->forget('spatie.permission.cache');
But:
- Yeah, you have to type the full namespace of the class each time.
- It's pretty untenable if you have a lot of stuff to do.
- There's no auditable trail to when this was done - at least you know things in your seeders are done right at the start of your application (or later, see next point)
Make your seeders update-aware.
You COULD make it so that your seeders double check that something exists before creating them:
_27<?php_27_27use Illuminate\Database\Seeder;_27use Spatie\Permission\Models\Role;_27_27class RoleSeeder extends Seeder_27{_27 /**_27 * Run the database seeds._27 *_27 * @return void_27 */_27 public function run()_27 {_27_27 $role = Role::firstOrCreate(['name' => 'super-admin']);_27_27 // Sync will delete then re-add all the permissions._27 $role->syncPermissions([_27 'quiz.create'_27 'quiz.edit',_27 'quiz.delete',_27 'organisation.create',_27 // ...any new ones you want to add_27 ]);_27 }_27}
Now THIS is better - all we have to do is run the seeder again on it's own for our production app:
_10php artisan db:seed --class=RoleSeeder
Done! But there's an even nicer way, in my opinion...
Use migrations!
Migrations are mostly associated with database structure rather than content, but they are an effective way of managing any process where you need to manipulate something in your app in a way that respects the content and structure of the database you have currently. They're also quickly reversible, and very verbose in that you're laying out the exact changes you're making.
They're also SCM/GIT friendly, so you can track, comment on, and approve the changes you're making with your team if you have one, or a product manager.
So in this situation, say we want to add a new role called "student", we would generate a migration:
_10php artisan make:migration permissions_add_student_role
And in our up()
method we can add the new role and new permissions, or even alter old ones:
This will clear the cache for you since you're making changes using the model/trait methods, you can also see exact changes you made to the roles and permissions, and if you write a down method, you can reverse them:
_13public function down()_13{_13_13 // New student Role and permissions._13 $role = Role::findByName('student');_13 $role->revokePermission('quiz.view');_13 $role->revokePermission('quiz.results.view');_13 $role->delete();_13 _13 // Alter an old role._13 $role = Role::findByName('super-admin');_13 $role->givePermissionTo('quiz.delete'); // Maybe we don't want ANYONE to be able to delete quizzes anymore._13}
And there you go - role and permission changes that are auditable without reading commit messages, and can easily be reversed.
Got any feedback? Give me a shout on twitter.