00. Understanding Laravel - Introduction

00. Understanding Laravel - Introduction

An extension to the CodeIgniter framework that would provide certain features out of the box such as basic authentication and authorization for a side project that a .NET programmer, Taylor Otwell was doing back in 2011, would grow to become the most used open-source PHP framework for novice and expert programmers alike. With an endless list of built-in tools, a plethora of resources, topped with a vibrant community, support, and ecosystem, Laravel allows a PHP developer to rapidly develop web applications using an elegant syntax, share it with other developers and deploy to a destination of choice without breaking a sweat. A PHP framework for web artisans it is.

Out of the box, Laravel supports testing, routing, authentication, authorization, mailing, eventing, asynchronous job handling, periodic task scheduling, web socket connections, a powerful templating engine for view generation as the framework follows the model view controller design pattern and if you wanna connect to your databases, the active record Eloquent ORM topped with database migrations makes everything a breeze. At its core an intelligent IoC container allows for dependency injection, making the framework itself very customizable as you can swap the default implementations with your custom if you wish to. Laravel has already laid the foundation — freeing you to create without sweating the small things.

The internet does a pretty good job of explaining the features outlined above, and I do not think there is much I can do to add to that. To that end, I thought it would be better if we quickly got our hands dirty and kill a puppy developing yet another to-do list application. My hopes are this will be enough to give you a rough overview of Laravel features and development workflow. For brevity I will not be explaining some of the features here as that would just make the post too long, we will discuss those features in greater depth in the upcoming posts. To follow along make sure you’ve installed Laravel by following through the installation guide on the official Laravel docs.

Throughout this guide, I will deliberately leave out some minor details that may cause errors when running the code in your environment. I’m sure it’s nothing that you won’t be able to fix on your own. My hope is that getting these errors and fixing them yourself will enhance your learning experience. If you do get stuck and need assistance feel free to DM me on Twitter, as always, don’t forget to google.

Creating a new project

To create a new Laravel project, open your terminal and simply navigate to the directory where you want to put your code, in my case this was $HOME/Documents/code and execute the laravel new todo command. This will create a directory named todo containing a fresh Laravel installation with all of Laravel’s dependencies already installed. When Laravel is done creating the application navigate to the newly created todo directory.

$ cd $HOME/Documents/code
$ laravel new todo --auth
$ cd todo

Writing the tests

Everyone tests their applications, whether you use print or log statements to view the results of your method calls, manually test the entire user flow in your browser or view JSON responses from your API inside Postman. As the application grows in size, however, manual testing methods become tedious, and refactoring existing codebases becomes scary as you will not be sure which parts of your application will break by changing a certain part of your codebase. Test-Driven Development allows the developer to be confident that their code still works, even after aggressive refactoring that changes the entire application architecture, as long as the API does not change. Automated testing allows us to introduce new features and edit existing codebases with speed and without worry, and Laravel is built with automated testing in mind.

When users add items to their to-do list we will store them in the database, and show them a list of their items on the homepage. We can verify this behavior by using tests. To create a new test execute the make:test artisan command inside the newly created todo application directory. The command below will create a new class CreateTodoTest inside the ./tests/Feature directory.

$ php artisan make:test CreateTodoTest

Inside the CreateTodoTest class, add the following code. It just verifies that when a user submits the form using HTTP method POST to the /todos URI then we should have an entry in the database matching the submitted data and we should see the newly added item on the returned page. To execute the tests execute the test artisan command, of course, if we run it now the test will fail, telling us exactly why it failed and thus guiding our development direction.

$ php artisan test
<?php

namespace Tests\Feature;

use App\User;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\DatabaseMigrations;

class CreateTodoTest extends TestCase
{
    use DatabaseMigrations;

    /**
     * A logged in user can create a todo item
     *
     * @return void
     */
    public function test_a_user_can_create_a_todo_item()
    {
        $user = User::create([
            'name' => 'Berzel Best',
            'email' => '[email protected]',
            'password' => '123456789'
        ]);

        $response = $this->actingAs($user)->post('todos', [
            'description' => 'Make the bed'
        ]);

        $this->assertDatabaseHas('todos', [
            'user_id' => $user->id,
            'description' => 'Make the bed'
        ]);

        $response->assertSee('Make the bed');

    }
}
tests/Feature/CreateTodoTest.php

The above command will fail, telling us something about the route todos not being defined. Let’s work to make the test pass.

The routes

The mechanism that allows Laravel to decide which method is going to handle which request is routing. When a user sends a GET request to the /todos URI we want to show them a list of to-do items, similarly, when they send a POST request, with a new to-do item to the same URI we will store the new item in the database and show them a list with the new item added. We define our routes inside the routes/web.php file. Add the following code to that file.

<?php

use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('todos', 'TodoController@index')->name('todos.index');
Route::post('todos', 'TodoController@store')->name('todos.store');

routes/web.php

The above snippet tells Laravel that when it receives a GET request to the /todos endpoint then it should call the index method of the TodoController class. Similarly, the store method of the TodoController class will be called when the application receives a POST request to the /todos endpoint.

Creating the controller

Controller methods are the final destination of our requests and Laravel makes it very easy to create them using the make:controller artisan command. Before creating our controller though let’s create a request class CreateTodoRequest that will be responsible for validating the submitted request data and checking if the user making the request is allowed to perform that action in the first place. Using artisan you create a form request by executing the following command.

$ php artisan make:request CreateTodoRequest

This will create a CreateTodoRequest class inside the ./app/Http/Requests directory. Open that file and add the following code.

<?php

namespace App\Http\Requests;

use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Http\FormRequest;

class CreateTodoRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return Auth::check();
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'description' => ['required', 'max:126']
        ];
    }
}

app/Requests/CreateTodoRequest.php

The authorize method is used to determine if the user is authorized to make the request, in our case only logged in users are allowed to create a new to-do item. The method Auth::check() returns true if the user is authenticated. The rules method returns the rules that should be applied to the request, in our case we won’t allow users to post to the /todos endpoint without providing the description field, and if provided the field should only be a maximum of 126 characters.

Now that our request class is created lets create the controller using the make:controller artisan command and add the index and store methods as required by our routes/web.php route definitions.

$ php artisan make:controller TodoController
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests\CreateTodoRequest;

class TodoController extends Controller
{
    /**
     * Create a new controller instance
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     * Show a list of all the todo items
     *
     * @param Request $request
     * @return void
     */
    public function index(Request $request)
    {
        return view('todos')
                ->with('todos', $request->user->todos);
    }

    /**
     * Save a new todo item to storage
     *
     * @param CreateTodoRequest $request
     * @return void
     */
    public function store(CreateTodoRequest $request)
    {
        $request->user->todos()->create($request->all());

        return view('todos')
                ->with('todos', $request->user()->todos);
    }
}
app/Http/Controllers/TodoController.php

Line 17, $this->middleware(‘auth’) makes sure that all our controller methods can only be called by a user who is logged in.

The models

The Active Record implementation Eloquent ORM that comes with Laravel allows us to access data in the database through objects of a defined class. The database table corresponds to a class and an object instance is tied to a particular row in the database. In our setup, we will have two tables in our database users and todos, for keeping track of registered users and each user’s to-do items respectively. The classes that will correspond to these tables are the User class and the Todo class each of these classes will extend the base Model Eloquent class. Laravel comes with a User model already defined for us so all we have to do is to create the Todo model using the make:model artisan command. The -m option allows us to generate a migration that we can use to define the schema of our todos table.

$ php artisan make:model Todo -m

A user will have many to-do items stored in the database and inversely every to-do item will belong to exactly one user i.e the user who created the to-do item. Let’s define this relationship in our model classes.

<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<string>
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array<string>
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array<string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    /**
     * This users todo items
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function todos()
    {
        return $this->hasMany(Todo::class);
    }
}
app/User.php

Inside the Todo class lets add the following code.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Todo extends Model
{
     /**
     * The attributes that are mass assignable.
     *
     * @var array<string>
     */
    protected $fillable = [
        'description'
    ];

     /**
     * This todo items' owner
     *
     * @var \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}
app/Todo.php

If you check inside the database/migrations directory you will see that the create_todos_table migration has been created. It’s prefixed with a timestamp of sorts, along with other default migrations that Laravel comes with. Open that migration file and add the following code.

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTodosTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('todos', function (Blueprint $table) {
            $table->id();
            $table->unsignedInteger('user_id');
            $table->string('description',125);
            $table->boolean('done')->default(false);
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('todos');
    }
}
database/migrations/2020_06_14_123456_create_todos_table.php

The up method is called when we run our migrations using the migrate artisan command. As you can see inside that we are creating a todos table with a primary key column id, a foreign key user_id that keeps track of the user who created the todo item, a description for the actual todo item and a done column which indicates whether the task is completed or not: by default, it is set to false. The down method is called when we are rolling back our migrations, in this case, the todos table will be dropped if it exists.

If you want to run the migrations yourself using the $ php artisan migrate command, you should first make sure to connect your application to a database by specifying your database credentials and which database to connect to inside the .env file that comes with Laravel. If you’re running tests Laravel will use an in-memory SQLite database to make the tests run faster, you can check the provided phpunit.xml file provided by Laravel.

The views

If you take a look at both the index and store method of our controller you will see that they both return views. Views are the HTML that will be rendered by the browser. Creating views with Laravel is very painless, in our case we are returning a view called todos so let’s go ahead and create the todos.blade.php file inside the ./resources/views directory and add the following code.

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Awesome Todos</title>
</head>
<body>
    <h1>Awesome Todos</h1>
    <ul>
        @foreach ($todos as $todo)
            <li>
                <input 
                    type="checkbox" 
                    disabled {{ $todo->done ? 'checked' : null }} /> 
                {{ $todo->description }}
            </li>
        @endforeach
    </ul>
    <form action={{ route('todos.store') }} method="post">
        @csrf
        <input 
            type="text" 
            name="description" 
            id="description" 
            placeholder="New task" />
        <button type="submit">Add</button>
    </form>
</body>
</html>
resources/views/todos.blade.php

View files in Laravel have the .blade.php extension as Laravel uses the Blade templating engine for view files. As you can see we are looping over the todos list which is available to the view since we added it with the view()->with(‘todos’…) method in our controller, and displaying the description of the todo together with a checkbox that will be checked if the task is done. For the action property of the form we are using the route() method to generate the URL to the named route todos.store, we defined a name for this route inside the routes/web.php file before.

Are we done yet

It has been a very short journey and we’ve accomplished a lot in a few minutes. Just to verify if everything works fine simply run the $ php artisan test command and now it should pass verifying that everything is working according to our specification. Alternatively you could manually test your application by running the $ php artisan serve command, opening your browser and navigating to http://localhost:8000/todos URL. Feel free to add more features to the app like task completion and deletion if you wish to do so.

Conclusion

Laravel is very mature framework with a lot of resources online to help you in your development carrier. @Jeffrey Way does a really good job providing the most concise Laravel video tutorials on Laracasts. If you wanna up your game Adam Wathan created a test driven laravel video course. Shuan from the net ninja created a very beginner friendly series on laravel on youtube. There's a lot of resources online to help you in your development journey and on top of that I'm going to go over the most used laravel features in the upcoming posts. But before we do that let's try and explain the Laravel's service container to a five year old.