We start from the situation where we have this Permission Editor as a functionality inside of our Laravel application.
We have a typical MVC structure: routes, controllers, views, and validation.
routes/web.php:
1Route::resource('roles', \App\Http\Controllers\RoleController::class);2Route::resource('permissions', \App\Http\Controllers\PermissionController::class);
app/Http/Controllers/RoleController.php:
1namespace App\Http\Controllers; 2 3use Illuminate\Http\Request; 4use Spatie\Permission\Models\Role; 5use Spatie\Permission\Models\Permission; 6 7class RoleController extends Controller 8{ 9 public function index()10 {11 $roles = Role::withCount('permissions')->get();12 13 return view('roles.index', compact('roles'));14 }15 16 public function create() {17 $permissions = Permission::pluck('name', 'id');18 19 return view('roles.create', compact('permissions'));20 }21 22 public function store(Request $request)23 {24 $request->validate([25 'name' => ['required', 'string', 'unique:roles'],26 'permissions' => ['array'],27 ]);28 29 $role = Role::create(['name' => $request->input('name')]);30 31 $role->givePermissionTo($request->input('permissions'));32 33 return redirect()->route('roles.index');34 }35 36 public function edit(Role $role)37 {38 $permissions = Permission::pluck('name', 'id');39 40 return view('roles.edit', compact('role', 'permissions'));41 }42 43 public function update(Request $request, Role $role)44 {45 $request->validate([46 'name' => ['required', 'string', 'unique:roles,name,' . $role->id],47 'permissions' => ['array'],48 ]);49 50 $role->update(['name' => $request->input('name')]);51 52 $role->syncPermissions($request->input('permissions'));53 54 return redirect()->route('roles.index');55 }56 57 public function destroy(Role $role)58 {59 $role->delete();60 61 return redirect()->route('roles.index');62 }63}
resources/views/roles/index.blade.php:
1@extends('layouts.app') 2 3@section('content') 4 <h1 class="text-xl font-semibold text-gray-900">Roles</h1> 5 <a href="{{ route('roles.create') }}">Add Role</a> 6 7 <table class="min-w-full divide-y divide-gray-300"> 8 <thead class="bg-gray-50"> 9 <tr>10 <th>Name</th>11 <th>Permissions</th>12 <th scope="col"></th>13 </tr>14 </thead>15 <tbody class="divide-y divide-gray-200 bg-white">16 @forelse ($roles as $role)17 <tr>18 <td>{{ $role->name }}</td>19 <td>{{ $role->permissions_count }}</td>20 <td>21 <a href="{{ route('roles.edit', $role) }}">Edit</a>22 23 <form action="{{ route('roles.destroy', $role) }}"24 method="POST"25 onsubmit="return confirm('Are you sure?')"26 class="inline-block">27 @csrf28 @method('DELETE')29 <button type="submit">Delete</button>30 </form>31 </td>32 </tr>33 @empty34 <tr>35 <td colspan="3">No roles found.</td>36 </tr>37 @endforelse38 </tbody>39 </table>40@endsection
In the Blade snippet above, I intentionally skipped some <div>
parts and CSS classes, so you would focus on the structure and not how it looks visually.
The form to create the role:
resources/views/roles/create.blade.php:
1@extends('layouts.app') 2 3@section('content') 4 <h1 class="text-xl font-semibold text-gray-900">Create Role</h1> 5 6 @if ($errors->any()) 7 <div class="text-red-500 text-sm mb-4"> 8 <ul> 9 @foreach ($errors->all() as $error)10 <li>{{ $error }}</li>11 @endforeach12 </ul>13 </div>14 @endif15 16 <form action="{{ route('roles.store') }}" method="POST">17 @csrf18 <div>19 <label for="name">Name</label>20 <input type="text" name="name" id="name" value="{{ old('name') }}" required autofocus>21 </div>22 23 @if ($permissions->count())24 <div class="mt-4">25 <label for="permissions">Permissions</label>26 27 @foreach ($permissions as $id => $name)28 <input type="checkbox" name="permissions[]" id="permission-{{ $id }}"29 value="{{ $id }}" @checked(in_array($id, old('permissions', [])))>30 <label for="permission-{{ $id }}">{{ $name }}</label>31 <br />32 @endforeach33 </div>34 @endif35 36 <div class="mt-4">37 <button type="submit">Save</button>38 </div>39 </form>40@endsection
Again, some sections are "stripped down" for simplicity.
So, our goal is to turn these two Route Resources into a package, so developers would be able to run composer require our/package
and then navigate to, for example, /permission-editor
to see the same functionality.
Our package will have a prerequisite of spatie/laravel-permission
installed and configured, but we will get to that later in the tutorial. Now, let's start actually creating the package.
Povilas sir thank you so much for this tutorial I was trying to dive into package development but didn't know where to start. Hope this tutorial will make this journey easy
I second this message :)