Understand Laravel Migration using a PHP Application

laravel-php-tutorial-1.png

Take a look at some of the key considerations when making the switch from vanilla PHP to Laravel. Use a demo app to compare functionality. Examine security, database setup and more.

Introduction

Without a doubt, Laravel is the best PHP framework right now. It would be nice if all PHP applications are migrated to Laravel, though this is wishful thinking. However, if you are here then you must be interested in doing that. This article is going to explain how Laravel migration works

Our case study will be a Todo list app built in vanilla PHP. We are using a to-do application because a to-do’s are a good way to demonstrate CRUD (Create Read Update and Delete).

Here is a preview of our case study:

Demo

Prerequisites

To follow along in this tutorial you need to have a working knowledge of PHP and the Laravel framework.

What we will be considering:

  • Migrating databases
  • Data querying
  • Authentication
  • MVC
  • Routing
  • Security

Migrating databases

Starting with our database tables we’d normally write SQL queries to create or modify a table but in Laravel, we will use migrations.

A migration is a neat way of specifying your table structure without bothering writing a bunch of SQL queries. Even more, migrations work a little like Git as it allows you to make incremental changes, roll back changes, and also keep the database structure in sync when multiple teams are working on the same application.

Here is a sample SQL query you’d have had to write manually when creating the Todo application using vanilla PHP:

1CREATE TABLE `tasks` (
2      `id` int(11) NOT NULL,
3      `user_id` varchar(100) NOT NULL,
4      `title` text NOT NULL,
5      `completed` tinyint(1) NOT NULL,
6      `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
7    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;

In Laravel however, instead of creating and running this SQL query manually, we will create a new Laravel migration using the artisan CLI tool that comes bundled with Laravel:

1$ php artisan make:migration create_tasks_table --create="tasks"

When the command has run, you’ll get a new migration file in your database/migrations folder:

1<?php
2
3    use IlluminateSupportFacadesSchema;
4    use IlluminateDatabaseSchemaBlueprint;
5    use IlluminateDatabaseMigrationsMigration;
6
7    class CreateTasksTable extends Migration
8    {
9        /**
10         * Run the migrations.
11         *
12         * @return void
13         */
14        public function up()
15        {
16            Schema::create('tasks', function (Blueprint $table) {
17                $table->increments('id');
18                $table->timestamps();
19            });
20        }
21
22        /**
23         * Reverse the migrations.
24         *
25         * @return void
26         */
27        public function down()
28        {
29            Schema::dropIfExists('tasks');
30        }
31    }

There are two methods up() and down(). up() is always associated with making new tables or fields in the database. The down() method is there to undo, or rollback, whatever the up() method does.

A single table can have many migration files that run chronologically to make it whatever you want it to be. As long as you implement the up and down method properly you should be fine. With that said let’s modify our migration to match that of the SQL query we wrote earlier.

1[...]
2
3    public function up() {
4        Schema::create('tasks', function (Blueprint $table) {
5            $table->increments('id');
6            $table->integer('user_id')->nullable();
7            $table->string('title')->nullable();
8            $table->tinyInteger('completed')->nullable();
9            $table->timestamps();
10        });
11    }
12
13    public function down() {
14        Schema::dropIfExists('tasks');
15    }
16
17    [...]

As seen above, migrations are a way easier way of managing database structuring than doing it manually. To execute your pending migrations, run the command below:

1$ php artisan migrate

To learn more about migrations use the official Laravel documentation.

Database querying

Data is vital to most applications, and the way data is being manipulated needs to be taken into consideration. In order to fetch all tasks for a logged in user, we’d have had to use something like pdo to prepare our statement.

1$user = $_SESSION['user_id'];
2    $query = $pdo->prepare('SELECT * FROM todos WHERE user_id=? ORDER BY id DESC');
3    $query->execute([$user]);

While you are safe from SQL injection, you still need to validate and sanitize your user-inputted data. You can use a function like filter_var() to validate before inserting it into the database and htmlspecialchars() to sanitise after retrieving it.

Laravel however, uses an ORM (Object Relational Mapping) called Eloquent. Eloquent makes it easy to work with databases to do things like accessing the records, deleting records and more. Eloquent model work by automatically converting table columns into PHP class properties. Eloquent makes tasks like adding, deleting, and updating the database easy and without the need of writing complex queries.

Here is the same query above written in Eloquent:

1$tasks = Task::whereUserId(Auth::id())->get();

As you can see, we have reduced the code written to one line using Eloquent.

? To see the SQL query generated by Eloquent, you can replace get() with toSql() and dump the resulting string.

We can successfully fetch all tasks created by the logged in user with a single line of code using Laravel Eloquent, you can learn more about Eloquent here.

Authentication

Authentication is another critical part of most applications. Laravel makes it easy to set up an entire authentication system with one command.

In your Laravel project run the command:

1$ php artisan make:auth

This command will save you hours of work compared to writing everything from scratch using vanilla PHP.

In a vanilla PHP application, you’d need to write queries to store the registered user information in the database, then fetch and verify if a user exists with the credentials provided when the user is attempting to log in.

It will look something like this:

1<?php
2    require_once 'functions.php';
3
4    if ( isset($_POST['btnLogin'])) {
5        $email = $_POST['email'];
6        $pass = md5($_POST['pass']);
7        $query = $pdo->prepare('SELECT * FROM users WHERE email = ? AND password=?');
8        $query->execute([$email, $pass]);
9        $rows = $query->rowCount();
10
11        if( $rows > 0 ) {
12            $_SESSION['user_id'] = $email;
13
14            header('location: pages/home.php');
15        } else {
16            echo 'Invalid Credentials`;
17        }
18    }

The code above simulates a basic login using vanilla PHP, and as usual, you have to write your own queries, manage your sessions, handle the connection to the database. It get’s tiring.

When migrating your application to Laravel all you have to do to set up authentication is run the command below:

1$ php artisan make:auth

This will generate an authentication scaffold with views and routes for you.

The views generated are for login and registration along with forgotten password, which can be found in the resources/views/auth folder. The controllers for login, registration, and reset password can be found in app/Http/Controllers/Auth folder.

Let’s take a look at Laravel’s registration controller:

1// [...]
2
3    protected $redirectTo = '/home';
4
5    public function __construct()
6    {
7        $this->middleware('guest');
8    }
9
10    protected function validator(array $data)
11    {
12        return Validator::make($data, [
13            'name' => 'required|string|max:255',
14            'email' => 'required|string|email|max:255|unique:users',
15            'password' => 'required|string|min:6|confirmed',
16        ]);
17    }
18
19    public function create(array $data)
20    {
21
22        return User::create([
23            'name' => $data['name'],
24            'email' => $data['email'],
25            'password' => Hash::make($data['password']),
26        ]);
27    }
28
29    //  [...]

The protected variable $redirectTo holds the URL your application will redirect to after a successful registration. The constructor specifies the middleware your registration has to run first before continuing to the controller.

Middlewares provide a convenient mechanism for filtering HTTP requests entering your application.

Laravel includes a middleware that verifies the user of your application is authenticated. If the user is not authenticated, the middleware will redirect the user to the login screen. However, if the user is authenticated, the middleware will allow the request to proceed further into the application.

In this case, $this->middleware('guest'); allows users that are not logged in or authenticated to access the registration page, you can read more about middlewares here.

The validator() function validates every field of the incoming registration data. For example, 'name' => 'required|string|max:255' validates that the name field is not empty, is a string, and does not exceed 255 characters. You can learn more about Validation in Laravel here.

Finally the create() function uses the User model to store the validated data to the database. The Hash facade provides secure bcrypt and argon2 hashing for storing user passwords. Bcrypt will be used for registration and authentication by default. You can learn more about hashing here and you can read more about Laravel Auth here.

Architecture (MVC)

MVC diagram

MVC is an acronym for Model View Controller, it’s a way to separate your application’s logic and data model away from the view. It helps to make our code organization clean and improves our application development time.

When developing vanilla PHP applications, chances are you have to figure out how you want to organize your application. With Laravel everything has been organized to make it easier to find whatever you are looking for easily. It has also been arranged to make it easy to follow coding principles like SOLID.

You can read about the Laravel file structure here.

Model

The Model is simply the object that interfaces between the data store and your controller. In our application, we use the Task model to fetch data from the database using Eloquent, you can learn more about that here.

Here is our Task model:

1<?php
2
3    namespace App;
4
5    use IlluminateDatabaseEloquentModel;
6
7    class Task extends Model
8    {
9        protected $table = 'tasks';
10
11        protected $fillable = ['title', 'user_id', 'completed', 'created_at'];
12    }

The $table property can be used to specify the database the Model should interact with. This is optional if the name of the class is the singular word for the table. For example, if the class is named Task and there is a tasks table, Eloquent will make the connection automatically.

The $fillable property specifies columns in the table we want to be fillable using Eloquent. This is especially useful when we want to store multiple rows at once using Eloquent. We need to specify the columns manually to avoid the mass assignment vulnerability.

View

The view is what’s presented to the users. This is how users interact with our application, from the buttons, text fields and so on. The view is mostly made up of HTML and CSS.

In Laravel, views are blade templates. Blade is an amazing templating engine that makes it easy to work with variables passed on to the view. This templating syntax is easy to learn and pick up. With Blade, the need to use PHP in your views is reduced to zero.

The views are stored in the resources/views directory.

Here is our view:

1@extends('layouts.app')
2
3    @section('content')
4    <div class="container">
5        <div class="row justify-content-center">
6            <div class="col-md-8">
7                <div class="card">
8                    <div class="card-header">Add a task</div>
9
10                    <div class="card-body">
11                        @if (session('status'))
12                            <div class="alert alert-success" role="alert">
13                                {{ session('status') }}
14                            </div>
15                        @endif
16                        <form method="post" action="/task/add">
17                          @csrf
18                          <input type="text" name="title" required/>
19                          <button type="submit" class="btn-primary">add task</button>
20                        </form>
21                    </div>
22
23                    @foreach ($tasks as $task)
24                      @if ($task['completed'] == 1)
25                        <div class="card-body">
26                          <strike>{{$task['title']}}</strike> <button class="btn-warning" disabled>completed</button>
27                        </div>
28                      @else
29                      <div class="card-body">
30                        {{$task['title']}} <a href="/task/delete/{{$task['id']}}"><button class="btn-danger">delete task</button></a> <a href="/task/complete/{{$task['id']}}"><button class="btn-success">Mark as completed</button></a>
31                      </div>
32                      @endif
33                    @endforeach
34                </div>
35            </div>
36        </div>
37    </div>
38    @endsection

You can read more about Laravel Blade here.

Controller

The controller is the decision maker and the middleman between the model and the view. The controller loads and passes data to the view most times from the model. It also adds event listeners to the view and updates the model when the user manipulates the view, you can learn more about that here.

Here is our TaskController:

1<?php
2
3    namespace AppHttpControllers;
4
5    use AppTask;
6    use IlluminateHttpRequest;
7    use IlluminateSupportFacadesAuth;
8
9    class TaskController extends Controller
10    {
11        /**
12         * Create a new controller instance.
13         *
14         * @return void
15         */
16        public function __construct()
17        {
18            $this->middleware('auth');
19        }
20
21        /**
22         * Show the application dashboard.
23         *
24         * @return IlluminateHttpResponse
25         */
26        public function index()
27        {
28            $tasks = Task::whereUserId(Auth::id())->get();
29
30            return view('home', compact('tasks'));
31        }
32
33        public function create (Request $request) 
34        {
35          $task = Task::create([
36            'user_id' => Auth::id(),
37            'title' => $request->get('title'),
38            'completed' => 0,
39          ]);
40
41          return redirect('/tasks');
42        }
43
44        public function completed ($id) 
45        {
46          $task = Task::find($id);
47          $task->completed = 1;
48          $task->save();
49
50          return redirect('/tasks');
51        }
52
53        public function delete ($id) 
54        {
55          $task = Task::find($id);
56          $task->delete();
57
58          return redirect('/tasks');
59        }
60    }

Routing

Routing helps map URL paths to several parts of our application. In vanilla PHP this may be how you might choose to handle routing:

1<?php
2    $request_uri = explode('?', $_SERVER['REQUEST_URI'], 2);
3
4    switch ($request_uri[0]) {
5        case '/':
6            require './index.php';
7            break;
8
9        case '/register':
10            require './register.php';
11            break;
12
13        default:
14            header('HTTP/1.0 404 Not Found');
15            require './404.php';
16            break;
17    }

The code above illustrates a basic router for a vanilla PHP application, while this can handle most basic operations it’s not enough for dynamic routing, route groups, subdomain routing and so on.

Laravel, however, has a neat routing system that makes it easier without having to build a new router from the ground up. Routes file are in routes/web.php file:

1Route::get('/', function () {
2        return view('welcome');
3    });
4
5    Auth::routes();
6
7    Route::get('/tasks', 'TaskController@index')->name('home');
8
9    Route::post('/task/add', 'TaskController@create');
10
11    Route::get('/task/delete/{id}', 'TaskController@delete');
12
13    Route::get('/task/complete/{id}', 'TaskController@completed');

You can call a view directly or a controller that processes the logic before loading the view. Routes can be grouped, dynamic, assume multiple HTTP methods, prefixed and so on.

Additionally, Laravel provides API routes, which can be found in routes/api.php. All API routes URL’s are prefixed with /api.

You can learn more about routing in Laravel here.

Security

Laravel provides a bunch of security features that most developers might not take account of when building web applications. Vanilla PHP does not come by default with the security features we will highlight below. If you are going to be migrating to Laravel then you should take advantage of the security features highlighted below:

CSRF (Cross Site Request Forgery)

CSRF is when an authenticated user is tricked into making valid requests on behalf of a malicious hacker. It is by no means a trivial attack and can get very complicated and expose your entire application to attacks of varying degrees.

Laravel uses CSRF tokens in order to restrict malicious attackers from generating such forged requests. This is done by generating and adding a valid token that should be added in each request whether it’s coming from a form or whether it’s an AJAX request. Laravel then compares this token automatically with the value, which it has saved additionally to that particular user’s session. When the token doesn’t match the one stored, the request is considered to be invalid.

Related: CSRF in Laravel: how VerifyCsrfToken works

Protection against SQL injection

Laravel uses PDO parameter binding to fight against SQL injection. This type of binding the parameters ensures that the data passed from users in request variables are directly not utilized in SQL queries.

Protection against XSS (Cross Site Scripting)

XSS attacks happen when a user uses the input fields to add JavaScript code to your app’s database. The attack is successful when the data is fetched from the database and displayed to the user. If the script executes when viewed, then the attacker can add more complex code to steal cookies and more.

Because Laravel uses the Blade templating engine, you are safe from these types of attacks by default.

Conclusion

Laravel is a great framework, it simplifies a lot of boring tasks and has a lot of features you don’t even know you need until you start using them, there’s really no reason why you shouldn’t be using Laravel. You can learn more about Laravel from its official website.

The repository to the entire application is here.