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');
}
}
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', '[email protected]')->name('todos.index');
Route::post('todos', '[email protected]')->name('todos.store');
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']
];
}
}
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);
}
}
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);
}
}
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);
}
}
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');
}
}
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>
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.