0

Creating a Basic ToDo Application in Laravel 5 – Part 1

Posted February 5th, 2015 (Updated 22 Feb 2015) in PHP

With the release of Laravel 5, there have been a bunch of backwards incompatible changes, new features added as well as the usual influx of new users so I thought I’d take the time to redo my basic to-do application with Laravel 5. The app covers a wide range of concepts, links to relevant learning material where possible and should make for a great introduction to the framework.

This tutorial is relatively long so I’ve broken it up into multiple posts.

  1. Part 1 – Installation, Database and Routes
  2. Part 2 – Listing Projects and Tasks
  3. Part 3 – Create/Edit/Delete
  4. Part 4 – Validation

The source for each part can also be found on GitHub.

Today will cover installation, configuration, artisan, migration, seeding and routes.

 

Before you Begin

Before you begin there are a few great resources you should check out.

You won’t get far in the Laravel world without hearing about Jeffrey Way. Jeffrey has perhaps done more for the Laravel community than any other non-core developer. He has produced high-quality, comprehensive video tutorials on almost every aspect of L5, many of which are free to view and aimed at beginners. I would highly recommend you check out the following pieces of his work:

  1. Laravel 5 Fundamentals – An absolute must-see series of free screencasts for new users of Laravel.
  2. What’s New in Laravel 5 – A great series by Matt Stauffer showing off some cool new stuff in Laravel.
  3. Laracasts – Mostly paid for videos of very high quality. New videos are regularly created and one a week is made free.

There are a few other places to find news and information:

  1. The Laravel Twitter feed – for the latest breaking news on L4 development.
  2. Taylor Otwell’s Twitter feed – the author of Laravel himself.
  3. Laravel.io – Weekly roundups  that gather the latest news and tutorials from around the web. They also do a weekly podcast covering their thoughts/concerns relevant to the platform.
  4. Laravel Packages Registry – good place to go to find some of the best Laravel packages
  5. Code Bright – An e-book provided free of charge by framework author Dayle Rees

If you’re coming from Laravel 4 I also recommend checking out Directory structure and namespace by Matt Stauffer.

 

Project Aim

Our to-do application will consist of one or more projects, each with its own list of tasks. You will be able to create, list, modify and delete both tasks and projects.

In this lesson we will go through:

  1. Installing and setting up Laravel
  2. Installing extra packages that will make development easier
  3. Using migrations and seeds
  4. Learning how to use resourceful controllers
  5. Learning how to use views (including the blade templating language and content layouts)
  6. Handling model relations

 

Installation

Installing Laravel is extremely quick and painless thanks to Composer. I’ll blaze through this section as it’s really been covered to death by this point.

Composer

Further Details: See Meet Composer

First you’ll need to install Composer if you haven’t already (you’ll only need to do this once):

1
2
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer

Laravel

Now for the laravel installer (you’ll only need to do this once):

1
composer global require "laravel/installer=~1.1"

and finally our project:

1
laravel new l5todo

Configuration

Laravel 5 uses a package called DotEnv that stores sensitive information in .env files which are loaded as PHP environment variables at runtime. Sounds complicated but it just means your sensitive credentials go into these files while the rest of your config remains in the standard config files.

Database

We need a database. Set one up for yourself in a DB of your choice then copy .env.example to .env and update accordingly:

1
2
3
4
DB_HOST=localhost
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

Finally if you’re not using MySQL open /config/database.php and change the default line:

1
'default' => 'mysql',

Remember to add your environment files to your .gitignore by adding a .env line!

 

Taking our First Steps

As mentioned above, our to-do application will comprise of one or more projects each with their own task list. We’re going to need Project and Task models, controllers, views, migrations, seeds (optional but useful) and routes. You probably already understand what most/all of these are, however if you don’t you should check out the video M-V-Huh? and Basic Model/Controller/View Workflow.

Let’s make our way through them one at a time.

 

Migrations

Further Details: See Migrations

We want to get our table schema set up in the database. It will look like the following:

Projects
+------------+------------------+------+-----+
| Field      | Type             | Null | Key |
+------------+------------------+------+-----+
| id         | int(10) unsigned | NO   | PRI |
| name       | varchar(255)     | NO   |     |
| slug       | varchar(255)     | NO   |     |
| created_at | timestamp        | NO   |     |
| updated_at | timestamp        | NO   |     |
+------------+------------------+------+-----+
 
Tasks
+-------------+------------------+------+-----+
| Field       | Type             | Null | Key |
+-------------+------------------+------+-----+
| id          | int(10) unsigned | NO   | PRI |
| project_id  | int(10) unsigned | NO   | MUL |
| name        | varchar(255)     | NO   |     |
| slug        | varchar(255)     | NO   |     |
| completed   | tinyint(1)       | NO   |     |
| description | text             | NO   |     |
| created_at  | timestamp        | NO   |     |
| updated_at  | timestamp        | NO   |     |
+-------------+------------------+------+-----+

First a migration must be set up:

1
php artisan make:migration create_projects_and_tasks_tables --create="projects"

We’re creating both tables in the one migration so that they can be removed in reverse order to avoid an integrity constraint violation. Open /database/migrations/<date>_create_projects_and_tasks_tables.php and set it up as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php
 
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
 
class CreateProjectsAndTasksTables extends Migration {
 
	/**
	 * Run the migrations.
	 *
	 * @return void
	 */
	public function up()
	{
		Schema::create('projects', function(Blueprint $table)
		{
			$table->increments('id');
			$table->string('name')->default('');			$table->string('slug')->default('');			$table->timestamps();
		});
 
		Schema::create('tasks', function(Blueprint $table) {			$table->increments('id');			$table->integer('project_id')->unsigned()->default(0);			$table->foreign('project_id')->references('id')->on('projects')->onDelete('cascade');			$table->string('name')->default('');			$table->string('slug')->default('');			$table->boolean('completed')->default(false);			$table->text('description')->default('');			$table->timestamps();		});	}
 
	/**
	 * Reverse the migrations.
	 *
	 * @return void
	 */
	public function down()
	{
		Schema::drop('tasks');		Schema::drop('projects');
	}
 
}

Perform the migration:

1
php artisan migrate

If you check the database, your tables should now be all set up.

 

Seeds

Further details: See Database Seeding

We’ll seed some projects/tasks to have something to work with when we finally get to the browser. Create /database/seeds/ProjectsTableSeeder.php and TasksTableSeeder.php like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// /database/migrations/seeds/ProjectsTableSeeder.php
<?php
 
use Illuminate\Database\Seeder;
 
class ProjectsTableSeeder extends Seeder {
 
    public function run()
    {
        // Uncomment the below to wipe the table clean before populating
        DB::table('projects')->delete();
 
        $projects = array(
            ['id' => 1, 'name' => 'Project 1', 'slug' => 'project-1', 'created_at' => new DateTime, 'updated_at' => new DateTime],
            ['id' => 2, 'name' => 'Project 2', 'slug' => 'project-2', 'created_at' => new DateTime, 'updated_at' => new DateTime],
            ['id' => 3, 'name' => 'Project 3', 'slug' => 'project-3', 'created_at' => new DateTime, 'updated_at' => new DateTime],
        );
 
        // Uncomment the below to run the seeder
        DB::table('projects')->insert($projects);
    }
 
}
 
// /database/migrations/seeds/TasksTableSeeder.php
<?php
 
use Illuminate\Database\Seeder;
 
class TasksTableSeeder extends Seeder {
 
    public function run()
    {
        // Uncomment the below to wipe the table clean before populating
        DB::table('tasks')->delete();
 
        $tasks = array(
            ['id' => 1, 'name' => 'Task 1', 'slug' => 'task-1', 'project_id' => 1, 'completed' => false, 'description' => 'My first task', 'created_at' => new DateTime, 'updated_at' => new DateTime],
            ['id' => 2, 'name' => 'Task 2', 'slug' => 'task-2', 'project_id' => 1, 'completed' => false, 'description' => 'My first task', 'created_at' => new DateTime, 'updated_at' => new DateTime],
            ['id' => 3, 'name' => 'Task 3', 'slug' => 'task-3', 'project_id' => 1, 'completed' => false, 'description' => 'My first task', 'created_at' => new DateTime, 'updated_at' => new DateTime],
            ['id' => 4, 'name' => 'Task 4', 'slug' => 'task-4', 'project_id' => 1, 'completed' => true, 'description' => 'My second task', 'created_at' => new DateTime, 'updated_at' => new DateTime],
            ['id' => 5, 'name' => 'Task 5', 'slug' => 'task-5', 'project_id' => 1, 'completed' => true, 'description' => 'My third task', 'created_at' => new DateTime, 'updated_at' => new DateTime],
            ['id' => 6, 'name' => 'Task 6', 'slug' => 'task-6', 'project_id' => 2, 'completed' => true, 'description' => 'My fourth task', 'created_at' => new DateTime, 'updated_at' => new DateTime],
            ['id' => 7, 'name' => 'Task 7', 'slug' => 'task-7', 'project_id' => 2, 'completed' => false, 'description' => 'My fifth task', 'created_at' => new DateTime, 'updated_at' => new DateTime],
        );
 
        //// Uncomment the below to run the seeder
        DB::table('tasks')->insert($tasks);
    }
 
}

Also don’t forget to add your seed classes to /database/seeds/DatabaseSeeder.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
 
class DatabaseSeeder extends Seeder {
 
	/**
	 * Run the database seeds.
	 *
	 * @return void
	 */
	public function run()
	{
		Model::unguard();
 
		$this->call('ProjectsTableSeeder');		$this->call('TasksTableSeeder');	}
 
}

Now we seed:

1
2
3
4
composer dump-autoload
php artisan db:seed
# or
php artisan migrate:refresh --seed

Your database should now be seeded!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
mysql> select * from projects;
+----+-----------+-----------+---------------------+---------------------+
| id | name      | slug      | created_at          | updated_at          |
+----+-----------+-----------+---------------------+---------------------+
|  1 | Project 1 | project-1 | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 |
|  2 | Project 2 | project-2 | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 |
|  3 | Project 3 | project-3 | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 |
+----+-----------+-----------+---------------------+---------------------+
 
mysql> select * from tasks;
+----+------------+--------+--------+-----------+----------------+---------------------+---------------------+
| id | project_id | name   | slug   | completed | description    | created_at          | updated_at          |
+----+------------+--------+--------+-----------+----------------+---------------------+---------------------+
|  1 |          1 | Task 1 | task-1 |         0 | My first task  | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 |
|  2 |          1 | Task 2 | task-2 |         0 | My first task  | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 |
|  3 |          1 | Task 3 | task-3 |         0 | My first task  | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 |
|  4 |          1 | Task 4 | task-4 |         1 | My second task | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 |
|  5 |          1 | Task 5 | task-5 |         1 | My third task  | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 |
|  6 |          2 | Task 6 | task-6 |         1 | My fourth task | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 |
|  7 |          2 | Task 7 | task-7 |         0 | My fifth task  | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 |
+----+------------+--------+--------+-----------+----------------+---------------------+---------------------+

 

Models

To work with our projects and tasks tables we need equivalent Models so create them now:

1
2
php artisan make:model Project
php artisan make:model Task

That was easy!

 

Artisan – Tinker

Now that we have information in the database it would be a great time to learn about one of artisan’s handy features – tinker. As explained in the informative article Tinkering with Tinker Like an Artisan, tinker provides a command line for interacting with your Laravel installation. As an example, let’s use it to retrieve the number of projects currently in the database:

$ php artisan tinker
>App\Project::count();    
3
>App\Task::count();
7

As you can see tinker has the potential to be quite useful. I’ll be referencing it a few times in this tutorial.

 

Controllers

We’ve gotten to the point now where we can start hitting the browser. To do that we need to set up some Controllers and Routes to point to them. First up the controllers:

1
2
php artisan make:controller ProjectsController
php artisan make:controller TasksController

 

Nested Resources

Further Details: See Nested Resources

Begin by adding the Project and Task resources to /app/Http/routes.php:

1
2
3
4
5
6
7
8
9
10
11
Route::get('/', 'WelcomeController@index');
 
//Route::get('home', 'HomeController@index');
//
//Route::controllers([
//	'auth' => 'Auth\AuthController',
//	'password' => 'Auth\PasswordController',
//]);
 
Route::resource('projects', 'ProjectsController');Route::resource('tasks', 'TasksController');

Let’s now look at a neat little artisan feature – route:list. In your command line enter the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
php artisan route:list
+--------+----------+--------------------------+------------------+-------------------------------------------------+------------+
| Domain | Method   | URI                      | Name             | Action                                          | Middleware |
+--------+----------+--------------------------+------------------+-------------------------------------------------+------------+
|        | GET|HEAD | /                        |                  | App\Http\Controllers\WelcomeController@index    |            |
|        | GET|HEAD | projects                 | projects.index   | App\Http\Controllers\ProjectsController@index   |            |
|        | GET|HEAD | projects/create          | projects.create  | App\Http\Controllers\ProjectsController@create  |            |
|        | POST     | projects                 | projects.store   | App\Http\Controllers\ProjectsController@store   |            |
|        | GET|HEAD | projects/{projects}      | projects.show    | App\Http\Controllers\ProjectsController@show    |            |
|        | GET|HEAD | projects/{projects}/edit | projects.edit    | App\Http\Controllers\ProjectsController@edit    |            |
|        | PUT      | projects/{projects}      | projects.update  | App\Http\Controllers\ProjectsController@update  |            |
|        | PATCH    | projects/{projects}      |                  | App\Http\Controllers\ProjectsController@update  |            |
|        | DELETE   | projects/{projects}      | projects.destroy | App\Http\Controllers\ProjectsController@destroy |            |
|        | GET|HEAD | tasks                    | tasks.index      | App\Http\Controllers\TasksController@index      |            |
|        | GET|HEAD | tasks/create             | tasks.create     | App\Http\Controllers\TasksController@create     |            |
|        | POST     | tasks                    | tasks.store      | App\Http\Controllers\TasksController@store      |            |
|        | GET|HEAD | tasks/{tasks}            | tasks.show       | App\Http\Controllers\TasksController@show       |            |
|        | GET|HEAD | tasks/{tasks}/edit       | tasks.edit       | App\Http\Controllers\TasksController@edit       |            |
|        | PUT      | tasks/{tasks}            | tasks.update     | App\Http\Controllers\TasksController@update     |            |
|        | PATCH    | tasks/{tasks}            |                  | App\Http\Controllers\TasksController@update     |            |
|        | DELETE   | tasks/{tasks}            | tasks.destroy    | App\Http\Controllers\TasksController@destroy    |            |
+--------+----------+--------------------------+------------------+-------------------------------------------------+------------+

You’ll notice that both projects and tasks are top level urls. In our to-do app, tasks belong to projects though, so it makes sense for URLs to be nested more like /projects/1/tasks/3 instead of just /tasks/3. This can be accomplished using something called nested resources. As with most things in Laravel, the modification required is quick and simple. Open /app/Http/routes.php and make the following change:

1
2
// Route::resource('tasks', 'TasksController');
Route::resource('projects.tasks', 'TasksController');

That’s it. Do another php artisan route:list and see what you have now:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
php artisan route:list
+--------+----------+----------------------------------------+------------------------+-------------------------------------------------+------------+
| Domain | Method   | URI                                    | Name                   | Action                                          | Middleware |
+--------+----------+----------------------------------------+------------------------+-------------------------------------------------+------------+
|        | GET|HEAD | /                                      |                        | App\Http\Controllers\WelcomeController@index    |            |
|        | GET|HEAD | projects                               | projects.index         | App\Http\Controllers\ProjectsController@index   |            |
|        | GET|HEAD | projects/create                        | projects.create        | App\Http\Controllers\ProjectsController@create  |            |
|        | POST     | projects                               | projects.store         | App\Http\Controllers\ProjectsController@store   |            |
|        | GET|HEAD | projects/{projects}                    | projects.show          | App\Http\Controllers\ProjectsController@show    |            |
|        | GET|HEAD | projects/{projects}/edit               | projects.edit          | App\Http\Controllers\ProjectsController@edit    |            |
|        | PUT      | projects/{projects}                    | projects.update        | App\Http\Controllers\ProjectsController@update  |            |
|        | PATCH    | projects/{projects}                    |                        | App\Http\Controllers\ProjectsController@update  |            |
|        | DELETE   | projects/{projects}                    | projects.destroy       | App\Http\Controllers\ProjectsController@destroy |            |
|        | GET|HEAD | projects/{projects}/tasks              | projects.tasks.index   | App\Http\Controllers\TasksController@index      |            |
|        | GET|HEAD | projects/{projects}/tasks/create       | projects.tasks.create  | App\Http\Controllers\TasksController@create     |            |
|        | POST     | projects/{projects}/tasks              | projects.tasks.store   | App\Http\Controllers\TasksController@store      |            |
|        | GET|HEAD | projects/{projects}/tasks/{tasks}      | projects.tasks.show    | App\Http\Controllers\TasksController@show       |            |
|        | GET|HEAD | projects/{projects}/tasks/{tasks}/edit | projects.tasks.edit    | App\Http\Controllers\TasksController@edit       |            |
|        | PUT      | projects/{projects}/tasks/{tasks}      | projects.tasks.update  | App\Http\Controllers\TasksController@update     |            |
|        | PATCH    | projects/{projects}/tasks/{tasks}      |                        | App\Http\Controllers\TasksController@update     |            |
|        | DELETE   | projects/{projects}/tasks/{tasks}      | projects.tasks.destroy | App\Http\Controllers\TasksController@destroy    |            |
+--------+----------+----------------------------------------+------------------------+-------------------------------------------------+------------+

 

Setting Slug-based URLs

We’ve almost got our routes perfect however in their current state we’ll have URLs like /projects/1/tasks/2. It would be much better for our visitors if the model IDs were replaced with their respective slug fields instead. So we’d get for example /projects/my-first-project/tasks/buy-milk.

Open /app/Http/routes.php and drop the following in:

1
2
3
4
5
6
Route::bind('tasks', function($value, $route) {
	return App\Task::whereSlug($value)->first();
});
Route::bind('projects', function($value, $route) {
	return App\Project::whereSlug($value)->first();
});

the above will override the default behavior for the tasks and projects wildcards in php artisan routes.

 

Conclusion

Today we:

  • installed and configured Laravel
  • created two resources
  • set up our migrations
  • added some data seeds
  • configured our URL structure

We laid the groundwork for the next lesson by creating all the components required for the frontend to function. We now have data in our database and routes to hit. In the next lesson we’ll get started on the frontend of the site!