fbpx

Controllers and Validation in Laravel

by Wire Tech
Controllers and Validation

In this article we are going to learn about Controllers and Validation in Laravel,

In our previous article in this Laravel series, we started to build our first Laravel project, A simple library management system. We started with the very first steps starting from creating the project itself, till we managed to add books to the application, and display them in a web page.

Earlier we have already discussed about

Introduction To Laravel For Beginners

Ultimate Beginner’s Guide To Getting Started With Laravel

Introduction to Frontend Development For Beginners

Top 12 Best PHP Frameworks To Consider In 2022

However, we only built a very basic application. It doesn’t process the requests submitted by the user, or validate the data received. Which is not the right thing to do, users can make mistakes when they submit the data, and this application still has very limited functionalities.

One other point we need to handle is that we are currently handling the application logic in the routing file, which is not the ideal thing to do, we need to have the logic and data processing in a separate file, this should make maintaining the code, and updating it much easier and cleaner.

So how do we do this? By using Controllers.

Controllers

Controllers are the link between Models and Views. In our application for example, when the users add a new book, the data is received by the controller where it’s validated, processed and then passed to the model to be stored in the database, then the controller either replies back to the view with information about the created book, or redirects the user to another view.

Controllers can be generated using the php artisan command make:controller , so let’s create our BookController with the following command:

php artisan make:controller BookController

This command will create the required controller in the /app/Http/Controllers folder, note that the name of the controller must follow a certain format: the name of the model in single form, with the first letter of each word in capital, followed by the word Controller.

If your model name consists of two words, like “book information”, then the controller should be called “BookInformationController”.

Now open the BookController.php file, you will find it completely empty, now we need to move our logic from the routing file to this controller.

Our application currently has 2 functions only, adding the books to the database, and displaying a list of books. We need to code them in the controller.

Before you start coding these 2 functions, you need to know that Laravel has 7 standard methods for CRUD operations (CRUD = Create, Read, Update and Delete), these methods are:

  1. index() – Fetches all model records e.g. all the available books.
  2. show() – Fetches a single model e.g. a single book.
  3. create() – Shows the form used to create a model, in our application the create form.
  4. store() – Stores the model to the database e.g. saves the book submitted from the create form.
  5. edit() – to show the form to edit the model.
  6. update() – Used to edit models (books) in the database.
  7. destroy() – to delete a model (book in our case) from the database.

Moving the code from route file to controller

The current functionality of our application will require only 3 of these 7 methods, index() to list all the records, create() to show the “create” web page, and store() to save the book submitted in the database.

We will start with them, and later we will use the other methods when we advance more in the series.

Here is how the code will look like in the BookController file:

<?php

namespace App\Http\Controllers;

use App\Models\Book;
use Illuminate\Http\Request;

class BookController extends Controller
{
    public function index()
    {
        return view('index');
    }

    public function create()
    {
        return view('create');
    }

    public function store(Request $request)
    {
        Book::create([
            "name" => $request->name,
            "author" => $request->author,
            "isbn" => $request->isbn,
        ]);
        return redirect('/');
    }
}

If you check the 3 methods in the BookController file, you will find that we offloaded the code from the routing file here, the exact same code.

Only added them to the 3 methods mentioned above. Now the routing file web.php should do its main task only, route the requests to this controller, without any other code. Here is how it looks like now:

<?php

use App\Http\Controllers\BookController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::get('/', [BookController::class, 'index']);

Route::get('/create', [BookController::class, 'create']);

Route::post('/create', [BookController::class, 'store']);

If you study the routes, you can see that we removed the closures with the code we had earlier (in our previous article), and replaced them with an array pointing to the controller where the code was moved “BookController” and the method that handles this route in the controller.

Also note the difference between the second and third route. The second route is a GET request, it requests the page that we use to create books, where the third route is a POST request, where the user actually submits the data of the book.

Now we have a proper code. The web.php file only manages the routes. It doesn’t care what happens to the requests it receives, it just routes them to the relevant controller and method. The controller now is the brain of the operation. It receives the requests and handles them as required.

Moving code from views to controllers

We have one more bit of cleaning to do. As discussed earlier, controllers should be the brain of the application, it should be handling all logic and data processing.

Views on the other hand should handle only the frontend and have no data processing or interaction with the database. In our previous article when we created our 2 views, we had this part in the index.blade.php view, responsible for listing the books from the database.

@foreach(App\Models\Book::all() as $book)
           <tr>
               <td>{{$book->id}}</td>
               <td>{{$book->name}}</td>
               <td>{{$book->author}}</td>
               <td>{{$book->isbn}}</td>
           </tr>
       @endforeach

App\Models\Book::all(), this is not ideal, we shouldn’t fetch the data in the view, but the controllers should do that and pass this data to the views, so how should we do this?

In our controller, the index() method is responsible for loading the index.blade.php view, this means we should move this responsibility there, here is how it should be done.

class BookController extends Controller
{

    public function index()
    {
        $books = Book::all();
        return view('index', ['books' => $books]);
    }

// rest of the code here    
}   

What did we change here? We created a variable that fetches the books from the database, and we passed it to the index view.

Sending parameters to the views is as easy as assigning them to an array, where the keys are the name of the variable to be used in the view, and the values are the variable in the controllers.

Ideally both variables should have the same name. The block [‘books’ => $books] assigns a “books” variable in the view to the $books variable in the controller.

Now we need to reflect this change in the view.

@foreach($books as $book)
           <tr>
               <td>{{$book->id}}</td>
               <td>{{$book->name}}</td>
               <td>{{$book->author}}</td>
               <td>{{$book->isbn}}</td>
           </tr>
       @endforeach

The same block that iterates through the books, but we removed the Book:all() that is now already in the controller, and we replaced it with the $books variable we passed from the controller. Nothing will be changed in the frontend, users will not know the difference, but our code is cleaner.

To give you an example of how this clear separation of responsibility is important. Suppose in the future you want to give the user the choice of changing the order of the books in the list, either by book name or by author name.

This means you will have some extra code to select the sorting method, and fetch the books based on the user choice. You will need to change this code in the controller and it will be very easy to identify and change. You will not need to touch the view, it will remain unchanged.

As it will display whatever list of books it receives from the controller.

One trick to learn before we move to the next point. If all the variables you pass from the controllers to the view has the same name in both sides, you can shorten the code by using the compact function, instead of passing the variable as an array, just list them in the compact function and Laravel will know variable names are the identical in both side, this will make your code even shorter and easier.

class BookController extends Controller
{

    public function index()
    {
        $books = Book::all();
        return view('index', compact('books'));
    }

// rest of the code here    
}

Data Validation

Data validation is one of the most important aspects when you design an application. It makes sure that the data submitted by the user meets the requirements of your application.

The validation step is usually done in the controller, just after your controller receives the request from the user, and before it processes it further (saving to database for example).

Laravel provides us with a very efficient validation method which is built in its controllers, it gives you a wide range of validation rules that you can use to test the inputs received from the users that they meet your requirements.

Let’s use our small library application to explain how we can validate the user inputs. We currently have 3 fields for the books: name, author and ISBN, let’s define some rules for them and see how we can validate them.

Name: Required, should not exceed 255 characters.

Author: Required, should have a minimum of 5 characters.

ISBN: Required, unique in the database, should contain only digits and the size is exactly 13 digits.

So where do you think we need to validate the inputs? Have a look at the BookController and try to guess. If you guessed in the store() method then you are correct. This is where we save the books to the database. So we need to be sure that whatever data we receive in the request is validated before saving them.

public function store(Request $request)
{
   $request->validate([
       'name' => ['required', 'max:255'],
       'author' => ['required', 'min:5'],
       'isbn' => ['required', 'unique:books,isbn', 'digits:13']
   ]);

   Book::create([
       "name" => $request->name,
       "author" => $request->author,
       "isbn" => $request->isbn,
   ]);


   return redirect('/');

}

The validation code is straight forward. We used the validate method to check for our defined rules. If all the conditions and rules are met, the code will move forward and create the book in the database.

If any single rule is not valid, the validation method will return false and will not continue the data processing.

Open your browser, and go to the create book page. Now fill in the form with data that will meet all the defined rules, click submit. You will be redirected to the index page, and your book will be listed there.

Now let’s try to make a mistake, head again to the create page, fill in the form, but this time let’s keep the name field empty, and the isbn field has some alphabet not digits. And press submit, what happens? Nothing. You will stay on the same page.

All the fields are cleared and nothing happens. If you move to the index page, you will not find this book added. Ok so the book wasn’t added, which is the intended outcome, but the user was not notified what went wrong. We need to tell them what went wrong.

This field is required, and that field should meet certain conditions. And this is the role of the error bag.

Laravel automatically passes the validation errors back to the submitting form, all you need to do is just display them to the user. We need to add validation error messages to the create view.

The Blade directive @error can help us in this, you need to define the name of the field that you look for its error, and if this field has an error, the code enclosed in this directive will be displayed. Here is how we can do it in our create form

<form action="/create" method="post">
        @csrf
        <div style="margin: 10px">
            <label for="name">Name</label>
            <input type="text" name="name" id="name">
            @error('name')
                <div style="color: red">{{ $message }}</div>
            @enderror
        </div>
        <div style="margin: 10px">
            <label for="author">Author</label>
            <input type="text" name="author" id="author">
            @error('author')
                <div style="color: red">{{ $message }}</div>
            @enderror
        </div>
        <div style="margin: 10px">
            <label for="isbn">ISBN</label>
            <input type="text" name="isbn" id="isbn">
            @error('isbn')
               <div style="color: red">{{ $message }}</div>
            @enderror
        </div>
        <button type="submit" style="margin: 10px">submit</button>
    </form>

Now, let’s repeat the last scenario again and repeat the same mistakes, her is how our form will look like with the errors:

Controllers and Validation

The validation method in our controller provided us with accurate error messages, and the errors were displayed correctly. But something annoying happened here. The fields are cleared.

The user inputs were not included again in the field, this is inconvenient and needs to be addressed to provide a better user experience.

This is where the old() method comes to help, it carries the old values for each field, either filled or empty. So you can retrieve it and fill in the form fields. Let’s add them to the code.

<form action="/create" method="post">
        @csrf
        <div style="margin: 10px">
            <label for="name">Name</label>
            <input type="text" name="name" id="name" 
                 value="{{ old('name') }}">
            @error('name')
                <div style="color: red">{{ $message }}</div>
            @enderror
        </div>
        <div style="margin: 10px">
            <label for="author">Author</label>
            <input type="text" name="author" id="author" 
                value="{{ old('author') }}">
            @error('author')
            <div style="color: red">{{ $message }}</div>
            @enderror
        </div>
        <div style="margin: 10px">
            <label for="isbn">ISBN</label>
            <input type="text" name="isbn" id="isbn" 
                value="{{ old('isbn') }}">
            @error('isbn')
            <div style="color: red">{{ $message }}</div>
            @enderror
        </div>
        <button type="submit" style="margin: 10px">submit</button>
    </form>

The highlighted code blocks will now refill the fields with the old values, so the user doesn’t have to fill them again, he will just need to fix his mistake. Repeat the scenario with mistakes again, here is a screenshot for our final form.

Controllers and Validation

Laravel provides us with lots of validation rules to check all different types of inputs. You can check for valid email format, confirm the input is a date, maybe in a defined date range, and you can even require certain fields based on the value for another field. We will provide a deeper coverage for validation later in the series, but for now you can have a look at all available validation rules in the official Laravel documentation in this link.

Conclusion

We explored the concept of controllers, and why it is a good idea to separate the logic from the views and routes and keep them in the controller. We also validated the user’s inputs before we save them to the database to avoid unintentional mistakes.

However our application is still missing some important features. What if we need to delete a book from our library, or edit a book to change its data. This is what we will explore in the next articles when we talk about models. So follow us.

You may also like

Unlock the Power of Technology with Tech-Wire: The Ultimate Resource for Computing, Cybersecurity, and Mobile Technology Insights

Copyright @2023 All Right Reserved