Admin panels are an essential part of many web apps and online platforms. Although there are many admin packages available, including Laravel’s own Spark package, we experienced that it’s also very easy to create an admin panel from scratch.
Especially for new projects that only need a limited set of admin functionality, the Laravel framework has enough features to quickly build an admin panel yourself. The main advantages: you know every detail of the admin panel and once the project is growing and more functionality is required, it can be easily extended.
We had a look at the admin panels we built ourselves, and identified three major topics that are important in all of them:
- Authentication
- CRUD operations, for example to manage the customers of the platform
- User roles and privileges: some actions may only be performed by specific users
In this part of the blog series we focus on the second topic.
Database, model and controller
Let’s continue by adding some functionality to the admin panel. We consider the case where you would like to create and edit customers (or any other entity) using typical create, read, update and delete (CRUD) operations.
We start with the generation of the Customer model. The -mcr flag is used to directly create a migration and controller related to the customer model. Only one command is needed:
php artisan make:model Customer -mcr
The migration already contains some boilerplate to quickly generate a customers table, including a primary key and timestamps for created_at and updated_at columns.
The flag ‘c’ indicates that a controller should be created called CustomerController, and the ‘r’ indicates that the controller should be pre-filled with the typical methods for CRUD. Now, we add the very basics to CustomerController, resulting in the following code (I removed most of the doc blocks for clarity):
<?php namespace App\Http\Controllers; use App\Customer; use Illuminate\Http\Request; class CustomerController extends Controller { //Display a listing of the resource. public function index() { return view('customers.index')->with('customers',Customer::all()); } //Show the form for creating a new resource. public function create() { $customer = new Customer(); return view('customers.create')->with(compact('customer')); } //Store a newly created resource in storage. public function store(Request $request) { Customer::create($request->all()); return redirect(route('customer-index'))->with('message','Customer successfully created.'); } //Display the specified resource. public function show(Customer $customer) { // In an admin panel, I usually don't need this method. It's often more efficient to // show the customer data in the edit view. } //Show the form for editing the specified resource. public function edit(Customer $customer) { return view('customers.edit')->with(compact('customer')); } //Update the specified resource in storage. public function update(Request $request, Customer $customer) { $customer->update($request->all()); return redirect(route('customer-index'))->with('message','Customer successfully updated.'); } //Remove the specified resource from storage. public function destroy(Customer $customer) { $customer->delete(); return redirect(route('customer-index'))->with('message','Customer successfully removed.'); } }
The parts in blue are the only pieces of code added to the default Laravel boilerplate. Now, we have a fully functional CRUD controller. A few things to note:
- In the create method, we create an empty instance of the Customer model. Although not strictly necessary, this enables us to use the same form to create and edit a customer. In both cases we have a Customer model available to bind to the form. With this approach, we can also pre fill some attributes in the create view. This might be useful, for example, if a customer has an attribute ‘active’ that we would like to display in a checked checkbox (a checkbox is by default unchecked). Hence, we can use: $customer = new Customer([‘active’ => 1]). I also use this approach a lot to pre fill dates (e.g. set a deadline or expiration date on a ‘to do’ model instance).
- Currently, there is no validation when the customer data is saved to the database. Validation is easily added using Form Requests, thereby only having a minor effect on the code in the controller. Details can be found in Validation of request in Laravel.
- We use named routes in our redirects (e.g. route(‘customer-index’)) to make sure that changes in urls won’t affect the rest of the codebase.
- The methods only contain code that is necessary for basic CRUD functionality, but of course it is easily extended to your liking (e.g. with pagination).
Two more parts are needed to finish the CRUD setup: routes and views.
Routes
The next step is to add the corresponding routes to the routes/web.php file. Add the following lines of code:
Route::get('customers','CustomerController@index')->name('customer-index'); Route::get('customers/create','CustomerController@create')->name('customer-create'); Route::post('customers','CustomerController@store')->name('customer-store'); Route::get('customers/{customer}/edit','CustomerController@edit')->name('customer-edit'); Route::patch('customers/{customer}','CustomerController@update')->name('customer-update'); Route::delete(‘customers/{customer},’CustomerController@destroy’)->name(‘customer-destroy’);
Note that we use {customer} in the definition of the edit, update and destroy routes. This is Laravel’s route model binding. For example, for the route customers/2/edit the customer model with id = 2 is directly available in the edit method of the CustomerController.
Views
The last thing we need to do, is create the views. In an admin panel, forms are usually an important part of the views. Before we continue, we include the Forms and Html package of the Laravel collective. Just follow the instructions in the documentation of the package.
Next, in the resources/views directory, create a new directory called ‘customers’. Here we create 4 blade views:
index.blade.php
This is an overview of all customers. Of course you can decide yourself how this page should look, but assume that we want to display the name and phone number of each customer in a table, where the customer name should link to the edit page of the corresponding customer. Keep in mind that we use the bootstrap CSS framework, so if we add the correct CSS classes, we get a nice layout out of the box. This is an example of code that can be used:
<table class="table table-striped"> <thead> <tr> <th>Name</th> <th>Phonenumber</th> </tr> </thead> <tbody> @forelse ($customers as $customer) <tr> <td> <a href="{{ route('customer-edit', ['id' => $customer->id]) }}" title="Edit customer details"> {{ $customer->name }} </a> </td> <td> {{ $customer->phonenumber }} </td> </tr> @empty <tr> <td colspan="2">There are no customers yet.</td> </tr> @endforelse </tbody> </table>
The forelse loop is a blade directive, which will check if there are any customers and if so, loop through the customers. If there are no customers, the code in the empty block is executed and a message is displayed instead.
form.blade.php
The views to create a new customer or edit an existing one mainly contain a form to fill in the customer attributes. The form field are included in a blade partial, form.blade.php. We add the following lines of code, where we again make use of the bootstrap CSS classes:
<div class="form-group row"> {!! Form::label(’name’, ‘Customer name’ ,['class' => 'col-sm-6']) !!} <div class="col-sm-6"> {!! Form::text(‘name’, null, ['class'=>'form-control','id'=>’name’]) !!} </div> </div> <div class="form-group row"> {!! Form::label(’phonenumber’, ‘Customer phonenumber’ ,['class' => 'col-sm-6']) !!} <div class="col-sm-6"> {!! Form::text(‘phonenumber’, null, ['class'=>'form-control','id'=>’phonenumber’]) !!} </div> </div>
The submit button is left out of the form partials, because we like to use a different button text.
create.blade.php
This view will contain the form to create a new customer and contains the following code:
{!! Form::model($customer, ['url' => route('customer-store') ]) !!} @include('customers.form') {!! Form::submit(‘Save customer’,['class'=>'form-control btn btn-primary']) !!} {!! Form::close() !!}
Here we use the form partial that we just created, so this form will show a field for name and phone number. Additionally, we add a submit button.
We use form model binding, as indicated by the Form::model call. We have a $customer object, because we created an empty customer instance in the controller, which we send to this view. If any customer attributes would already have been filled in, those attributes would now be displayed in the form. For the form input field with name=”phonenumber”, $customer->phonenumber would be displayed, etc.
edit.blade.php
This view looks very similar to the create view, basically because the create and edit view both receive an instance of the customer model. We post to a different route, and we use a different text in our submit button.
{!! Form::model($customer,['url' => route('customer-update',['id' => $customer->id]) ]) !!} {{ method_field('PATCH') }} @include('customers.form') {!! Form::submit(‘Update customer’,['class'=>'form-control btn btn-primary']) !!} {!! Form::close() !!}
Note that we add a method field ‘PATCH’ because html forms do not support patch actions. This translates to a hidden input that will be recognized by Laravel. When posting this form, it will be treated as a patch request.
Summary
With the creation of the views, the setup for basic CRUD operations is finished. When you use the boilerplate code that’s included in Laravel and strategically use blade partials, you only have to write a limited amount of code yourself to create, read, update and delete the entities of your platform. In the next part of this series, we finish our basic admin panel by managing the user roles and privileges.