In this lesson, let's build a page to manage categories.
Navigation Link and Resource Controller
Let's start by adding a link in the navigation. In Breeze, navigation is found in the resources/views/layouts/navigation.blade.php
View file.
For the link, Breeze uses Blade components for better re-usability. We won't cover Blade components in this course, so we will create our link with a regular a
HTML tag, taking the CSS classes from the nav-link
component.
resources/views/components/nav-link.blade.php:
@props(['active']) @php$classes = ($active ?? false) ? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out' : 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out'; @endphp <a {{ $attributes->merge(['class' => $classes]) }}> {{ $slot }}</a>
So, we add the navigation link.
resources/views/layouts/navigation.blade.php:
// ... <!-- Navigation Links --><div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex"> <x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')"> {{ __('Dashboard') }} </x-nav-link> <a href="{{ route('categories.index') }}" class="inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out"> Categories </a></div> // ...
Why does Route have the name of categories.index
? You might think you should create a CategoryController
and a GET Route and point to that Controllers index()
method. But in this case, no.
Because here we will have a CRUD with actions for the list, create, edit, and delete, we will use a Resource Controller. Resource Controller can be created using the same make:controller
artisan command and adding a --resource
additional option. Another option that can be passed is --model
and specifying the Model name. This way, controller methods will have type-hinted models in the methods where they are needed.
php artisan make:controller CategoryController --resource --model=Category
With the artisan command in the CategoryController.php
, we have created seven methods:
-
index()
is used to show a list of categories. -
create()
for showing the create form andstore()
for saving record into database. -
show()
for showing individual records. -
edit()
for showing the edit form andupdate()
for updating data in the database. -
destroy()
for deleting a record.
Now we can add a Route::resource()
Route with a name of categories
and provide the Controller.
routes/web.php:
// ... Route::resource('categories', \App\Http\Controllers\CategoryController::class);
The resource
method for the Route has a specific rule for the Route names. The names are a Route name dot method name, so this is where the categories.index
comes from. This is how Routes would look like:
Verb | URI | Action | Route Name |
---|---|---|---|
GET | /categories |
index | categories.index |
GET | /categories/create |
create | categories.create |
POST | /categories |
store | categories.store |
GET | /categories/{category} |
show | categories.show |
GET | /categories/{category}/edit |
edit | categories.edit |
PUT/PATCH | /categories/{category} |
update | categories.update |
DELETE | /categories/{category} |
destroy | categories.destroy |
You can also check available Routes using an artisan command php artisan route:list
.
Let's return some string in the Controllers index()
method.
app/Http/Controllers/CategoryController.php:
class CategoryController extends Controller{ public function index() { return 'aaa'; } // ...}
Refresh the home page and see the Categories
link in the navigation.
After pressing on the categories link, we can see the string is returned.
Listing Categories
Now, let's build the actual page to show categories. In the Controller, we need to do the same as in other lessons: get all the records and pass them to the View.
app/Http/Controllers/CategoryController.php:
class CategoryController extends Controller{ public function index() { return 'aaa'; $categories = Category::all(); return view('categories.index', compact('categories')); } // ...}
If you want to add View files in a sub-folder when returning View, you must add a dot between the folder and the View file. Create an index.blade.php
View file inside the resources/views/categories
folder and copy the content of dashboard.blade.php
into the newly created View file.
Let's change the text from Dashboard
to Categories
, and instead of You're logged in!
let's put a table.
resources/views/categories/index.blade.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ __('Dashboard') }} {{ __('Categories') }} </h2> </x-slot> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="p-6 text-gray-900"> {{ __("You're logged in!") }} <table> <thead> <tr> <th>Name</th> <th></th> </tr> </thead> <tbody> @foreach($categories as $category) <tr> <td>{{ $category->name }}</td> <td> <a href="{{ route('categories.edit', $category) }}">Edit</a> </td> </tr> @endforeach </tbody> </table> </div> </div> </div> </div></x-app-layout>
For the edit link, again, we use the Route Model binding. For now, let's add a category manually in the DB. On the categories page, we can see the unstyled table.
After clicking on the edit link, it will show an empty page because we still need to build it. Let's make the edit page. We must return the view in the Controller and pass the category variable.
app/Http/Controllers/CategoryController.php:
class CategoryController extends Controller{ // ... public function edit(Category $category) { return view('categories.edit', compact('category')); } // ...}
Create the edit View file. We will add a form in the View file, and for now, just to test if it's working, the page will just show the category name.
resources/views/categories/edit.blade.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ __('Category Edit') }} </h2> </x-slot> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="p-6 text-gray-900"> <form> {{ $category->name }} </form> </div> </div> </div> </div></x-app-layout>
After visiting the edit page, we should see the name of the category shown.
Category Edit Page Form
Now, let's build the form. The method for the form will be POST, and the action will be another Route from the resource categories.update
with a $category
as a parameter.
For the update form, the actual method is PUT or PATCH, which must be defined in the form using @method
Blade directive.
Also, every form must have the @csrf
Blade directive for the cross-site protection.
resources/views/categories/edit.blade.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ __('Category Edit') }} </h2> </x-slot> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="p-6 text-gray-900"> <form method="POST" action="{{ route('categories.update', $category) }}"> @csrf @method('PUT') {{ $category->name }} </form> </div> </div> </div> </div></x-app-layout>
And let's add the input with the submit button.
resources/views/categories/edit.blade.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ __('Category Edit') }} </h2> </x-slot> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="p-6 text-gray-900"> <form method="POST" action="{{ route('categories.update', $category) }}"> @csrf @method('PUT') <div> <div> <label for="name">Name:</label> </div> <input type="text" name="name" id="name" value="{{ $category->name }}"> </div> <div> <button type="submit"> Save </button> </div> </form> </div> </div> </div> </div></x-app-layout>
We have the unstyled edit form.
We can take styles from the Blade components after we installed Laravel Breeze. The styles for the input can be taken from resources/views/components/text-input.blade.php
and the button from the resources/views/components/primary-button.blade.php
files.
resources/views/categories/edit.blade.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ __('Category Edit') }} </h2> </x-slot> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="p-6 text-gray-900"> <form method="POST" action="{{ route('categories.update', $category) }}"> @csrf @method('PUT') <div> <div> <label for="name">Name:</label> </div> <input type="text" name="name" id="name" value="{{ $category->name }}" class="border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"> </div> <div> <button type="submit" class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150"> Save </button> </div> </form> </div> </div> </div> </div></x-app-layout>
Now we have form with some styles.
Submit The Form
Let's take care of the submission. The categories.update
Route goes to the update()
method in the Controller. The category is passed using Route Model Binding by default.
We can call the update()
method on the category and pass the fields as an array. We get the name from the Request class, which is injected into the method: that's another cool Laravel automation.
app/Http/Controllers/CategoryController.php:
use Illuminate\Http\Request; class CategoryController extends Controller{ // ... public function update(Request $request, Category $category) { $category->update([ 'name' => $request->input('name'), ]); } // ...}
After the update, the user should be redirected somewhere. In this case, we will redirect to the categories.index
Route.
app/Http/Controllers/CategoryController.php:
class CategoryController extends Controller{ // ... public function update(Request $request, Category $category) { $category->update([ 'name' => $request->input('name'), ]); return redirect()->route('categories.index'); } // ...}
If you try to update the category now, you will get the mass assignment error.
When using update()
or create()
methods on the Model, all fields must be added to the $fillable
property as an array.
The
id
andtimestamps
fields are fillable by default.
app/Models/Category.php:
class Category extends Model{ use HasFactory; protected $fillable = ['name']; }
Let's also add fillable fields to the Post Model.
app/Models/Post.php:
class Post extends Model{ use HasFactory; protected $fillable = ['title', 'text', 'category_id']; }
The category update should work, and you should be redirected to the categories list page.
Create Category Page
Next, let's build the Create category page. On the index page, let's add a link.
resources/views/categories/index.blade.php:
<x-app-layout> // ... <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="p-6 text-gray-900"> <a href="{{ route('categories.create') }}">Add new category</a> <table> // ... </table> </div> </div> </div> </div></x-app-layout>
We need to return the View in the create()
method.
app/Http/Controllers/CategoryController.php:
class CategoryController extends Controller{ // ... public function create() { return view('categories.create'); } // ...}
Create the create.blade.php
View file and put the content from the edit.blade.php
. The form will be identical, with some minor changes.
resources/views/categories/create.blade.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ __('Category Edit') }} {{ __('Category Create') }} </h2> </x-slot> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="p-6 text-gray-900"> <form method="POST" action="{{ route('categories.update', $category) }}"> <form method="POST" action="{{ route('categories.store') }}"> @csrf @method('PUT') <div> <div> <label for="name">Name:</label> </div> <input type="text" name="name" id="name" value="{{ $category->name }}" class="border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"> <input type="text" name="name" id="name" class="border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"> </div> <div> <button type="submit" class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150"> Save </button> </div> </form> </div> </div> </div> </div></x-app-layout>
The Route for creating a new record is categories.store
. The method is POST, so we don't need to tell Laravel what method this is. We must remove the value from the input because we don't have anything yet.
In the Controller, we need to call the create()
Eloquent method on the Model and pass the fields as an array, similar to how it was done with the update.
After creating the record, it's the same: we need to redirect the user somewhere.
app/Http/Controllers/CategoryController.php:
class CategoryController extends Controller{ // ... public function store(Request $request) { Category::create([ 'name' => $request->input('name'), ]); return redirect()->route('categories.index'); } // ...}
We can create a new category from the create page.
Deleting the Category
The last CRUD action is deleting, which in the Controller is the destroy()
method. The delete is different because it isn't a link but a POST form with the DELETE method.
Also, for the delete button, we have added the JavaScript confirmation.
resources/views/categories/index.blade.php:
// ...<tbody> @foreach($categories as $category) <tr> <td>{{ $category->name }}</td> <td> <a href="{{ route('categories.edit', $category) }}">Edit</a> <form method="POST" action="{{ route('categories.destroy', $category) }}"> @csrf @method('DELETE') <button type="submit" onclick="return confirm('Are you sure?')">Delete</button> </form> </td> </tr> @endforeach</tbody>// ...
We have the delete button, and we can see the confirmation on click.
Let's implement the destroy()
method in the Controller. It's the same as we had with the update()
. We have a category from the Route Model Binding, which means we can call the delete()
Eloquent method on the category.
class CategoryController extends Controller{ // ... public function destroy(Category $category) { $category->delete(); return redirect()->route('categories.index'); }}
After confirming the deletion, the category gets deleted, and the user is returned to the categories list page.
So yeah, now we have a complete CRUD of one Model of Category!
No comments or questions yet...