91

Creating a Basic ToDo Application in Laravel 5 – Part 2

Posted (Updated ) in PHP

Welcome back to my simple to-do application tutorial for Laravel 5. 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.

So far we have a working database complete with seed data and a bunch of routes for displaying, editing and deleting our projects and tasks. In this second chapter I’ll cover controllers, models (with relationships), views (including the blade templating language and layouts) and route model binding.

 

Laravel Form Helpers

In Laravel 4 we had a HTML package but it was removed for 5 to cut down on cruft. Add it back in by following the instructions in my How to Install Illuminate/HTML In Laravel 5 tutorial. In addition to HTML and Form facades, this package provides some handy helper functions such as link_to_route() which we’ll be using later.

 

Controllers and Blade View Layouts

Further details: See Resourceful Controllers Part 1 and Part 2, Blade 101

If you browse to /projects you’ll get an empty page. Why is that? Well let’s find out. Run php artisan route:list one more time and look at this line:

1
2
3
4
5
+--------+----------+----------------------------------------+------------------------+-------------------------------------------------+------------+
| Domain | Method   | URI                                    | Name                   | Action                                          | Middleware |
+--------+----------+----------------------------------------+------------------------+-------------------------------------------------+------------+
|        | GET|HEAD | projects                               | projects.index         | App\Http\Controllers\ProjectsController@index   |            |+--------+----------+----------------------------------------+------------------------+-------------------------------------------------+------------+

Looks like the /projects URL is loading ProjectsController’s index method. So open up /app/Http/controllers/ProjectsController.php and update the method to point to a view we’ll create:

1
2
3
4
public function index()
{
	return view('projects.index');}

We’re using Blade Templates in this tutorial so create a /resources/views/projects/index.blade.php file and enter some text in there. Hit /projects in your browser again. If everything is working correctly you should see the text you entered above. Do the same for the create controller method.

Showing the contents of a view is great, but if we have more than one page on our site we’ll want a consistent template across all pages. In other words, we need the view’s contents to sit inside a basic HTML template. This is done with controller layouts.

There are a few steps to implementing controller layouts:

  1. Create a layout view. Laravel actually ships with a pretty decent one called app.blade.php so we’ll save some time and just use that. Notice near the bottom the layout contains a @yield(‘content’) line. That’s the function that will load our actual content.
  2. Reference your layout in your view using @extends(‘app’) and wrap in a @section(‘content’) block like so:
    1
    2
    3
    4
    5
    
    @extends('app') 
    @section('content')    This is my /resources/views/projects/index.blade.php file!
    @endsection

In /resources/views/projects folder create a show.blade.php, index.blade.php and create.blade.php view with the above markup replacing the filename as necessary. With these in place, refresh /projects in your browser. You should now see the app.blade.php skeleton around your view contents.

Route Model Binding

Further Details: If you’re a laracasts member see Route Model Binding

By default Laravel will provide an ID value to various resourceful controller methods such as show(), edit(), update() and destroy(). This is fine but it adds alot of extra boilerplate we need to write – grabbing the model instance, checking if it exists etc. Thankfully Laravel provides something called route model binding that helps with this issue. Instead of providing an $id variable, the method will be given the $project or $task object instance instead.

Open up /app/Http/routes.php and add the following two lines:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Provide controller methods with object instead of ID
Route::model('tasks', 'Task');Route::model('projects', 'Project'); 
// Use slugs rather than IDs in URLs
Route::bind('tasks', function($value, $route) {
	return App\Task::whereSlug($value)->first();
});
Route::bind('projects', function($value, $route) {
	return App\Project::whereSlug($value)->first();
});
 
Route::resource('projects', 'ProjectsController');
Route::resource('projects.tasks', 'TasksController');

and in your TasksController and ProjectsController replace every method definition’s $id reference with Task $task and Project $project like so:

// public function edit($id)
public function edit(Project $project)

Don’t forget to add use App\Task and App\Project at the top of your respective controllers now that we’re referencing those models!

At this point you can also pass the object to its respective view in each controllers show, edit and update methods like so as we’ll be using them later:

1
2
3
4
public function edit(Project $project)
{
    return view('projects.show', compact('project'));}

The TasksController will also need some minor modifications. Because we’re using nested resources, php artisan route:list will tell us that task routes all include a {projects} mask in addition to the {tasks} mask that some of them receive. As a result the controller methods will be passed a Project instance as their first argument. So update them accordingly remembering to update method docs and pass the new $project variable.

By this point your controllers should look 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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// /app/Http/Controllers/ProjectsController.php
<?php namespace App\Http\Controllers;
 
use App\Project;
use App\Http\Requests;
use App\Http\Controllers\Controller;
 
use Illuminate\Http\Request;
 
class ProjectsController extends Controller {
 
	/**
	 * Display a listing of the resource.
	 *
	 * @return Response
	 */
	public function index()
	{
		$projects = Project::all();
		return view('projects.index', compact('projects'));
	}
 
	/**
	 * Show the form for creating a new resource.
	 *
	 * @return Response
	 */
	public function create()
	{
		return view('projects.create');
	}
 
	/**
	 * Store a newly created resource in storage.
	 *
	 * @return Response
	 */
	public function store()
	{
		//
	}
 
	/**
	 * Display the specified resource.
	 *
	 * @param  \App\Project $project
	 * @return Response
	 */
	public function show(Project $project)
	{
		return view('projects.show', compact('project'));
	}
 
	/**
	 * Show the form for editing the specified resource.
	 *
	 * @param  \App\Project $project
	 * @return Response
	 */
	public function edit(Project $project)
	{
		return view('projects.edit', compact('project'));
	}
 
	/**
	 * Update the specified resource in storage.
	 *
	 * @param  \App\Project $project
	 * @return Response
	 */
	public function update(Project $project)
	{
		//
	}
 
	/**
	 * Remove the specified resource from storage.
	 *
	 * @param  \App\Project $project
	 * @return Response
	 */
	public function destroy(Project $project)
	{
		//
	}
 
}
 
 
// /app/Http/Controllers/TasksController.php
<?php namespace App\Http\Controllers;
 
use App\Project;
use App\Task;
use App\Http\Requests;
use App\Http\Controllers\Controller;
 
use Illuminate\Http\Request;
 
class TasksController extends Controller {
 
	/**
	 * Display a listing of the resource.
	 *
	 * @param  \App\Project $project
	 * @return Response
	 */
	public function index(Project $project)
	{
		return view('tasks.index', compact('project'));
	}
 
	/**
	 * Show the form for creating a new resource.
	 *
	 * @param  \App\Project $project
	 * @return Response
	 */
	public function create(Project $project)
	{
		return view('tasks.create', compact('project'));
	}
 
	/**
	 * Store a newly created resource in storage.
	 *
	 * @param  \App\Project $project
	 * @return Response
	 */
	public function store(Project $project)
	{
		//
	}
 
	/**
	 * Display the specified resource.
	 *
	 * @param  \App\Project $project
	 * @param  \App\Task    $task
	 * @return Response
	 */
	public function show(Project $project, Task $task)
	{
		return view('tasks.show', compact('project', 'task'));
	}
 
	/**
	 * Show the form for editing the specified resource.
	 *
	 * @param  \App\Project $project
	 * @param  \App\Task    $task
	 * @return Response
	 */
	public function edit(Project $project, Task $task)
	{
		return view('tasks.edit', compact('project', 'task'));
	}
 
	/**
	 * Update the specified resource in storage.
	 *
	 * @param  \App\Project $project
	 * @param  \App\Task    $task
	 * @return Response
	 */
	public function update(Project $project, Task $task)
	{
		//
	}
 
	/**
	 * Remove the specified resource from storage.
	 *
	 * @param  \App\Project $project
	 * @param  \App\Task    $task
	 * @return Response
	 */
	public function destroy(Project $project, Task $task)
	{
		//
	}
 
}

If you refresh the url /projects/project-1 everything should still be working.

 

Displaying Our Models

Project listing page

It’s time to start listing our projects and tasks. Open /projects in your browser. Based on php artisan route:list this is our project listing page. Open /resources/views/projects/index.blade.php and set it to the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@extends('app')
 
@section('content')
    <h2>Projects</h2>     @if ( !$projects->count() )        You have no projects    @else        <ul>            @foreach( $projects as $project )                <li><a href="{{ route('projects.show', $project->slug) }}">{{ $project->name }}</a></li>            @endforeach        </ul>    @endif@endsection

There are a few things going on above:

  1. I’m using the blade templating language’s if and foreach control-flow functions as well as its print function (the double curly braces).
  2. I’m checking if there are any projects to show. If not, display a message saying so. If there are, list them all
  3. I’m calling the route() helper with a named route (You can see a list of your named routes with php artisan route:list) to link to each projects details page.

You’ll also need to pass the $projects variable to this view or you’ll get an undefined variable error. Open /app/Http/controllers/ProjectsController.php and update the index() method to:

1
2
3
4
5
public function index()
{
	$projects = Project::all();	return view('projects.index', compact('projects'));}

Refresh and you’ll now see a listing of your projects.

 

Model Relations – The Project Details page

Further Details: See Eloquent Relationships

On the project details page we need to display a list of the given projects tasks. To do that we need to define a one-to-many relationship in our Project model allowing it to grab its tasks.

Open /app/Project.php and add a tasks() method like so:

1
2
3
4
public function tasks()
{
	return $this->hasMany('App\Task');
}

Inversely we can also add a many-to-one relationship to our Task model:

1
2
3
4
public function project()
{
	return $this->belongsTo('App\Project');
}

To make sure it worked:

$ php artisan tinker
>App\Project::whereSlug('project-1')->first()->tasks->count();
5
>App\Project::whereSlug('project-2')->first()->tasks->count();
2
>App\Task::first()->project->name;
Project 1

Perfect! The view can now be updated (/resources/views/projects/show.blade.php):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@extends('app')
 
@section('content')
    <h2>{{ $project->name }}</h2>     @if ( !$project->tasks->count() )        Your project has no tasks.    @else        <ul>            @foreach( $project->tasks as $task )                <li><a href="{{ route('projects.tasks.show', [$project->slug, $task->slug]) }}">{{ $task->name }}</a></li>            @endforeach        </ul>    @endif@endsection

Click a project on the project listing page in your browser and your project will now display complete with its task listing.

Finally we have the task show page (/resources/views/tasks/show.blade.php). This one is very straightforward:

1
2
3
4
5
6
7
8
9
10
@extends('app')
 
@section('content')
    <h2>
        {!! link_to_route('projects.show', $project->name, [$project->slug]) !!} -
        {{ $task->name }}
    </h2>
 
    {{ $task->description }}
@endsection

Note: Be very careful when using model relationships. It’s easy to generate huge numbers of SQL queries if you’re not careful. This is called the N+1 problem. See the video below for details on what exactly the N+1 problem is, and how to protect against it.

 

Conclusion

Today we covered:

  • Route Model Binding
  • Models (with one-to-many relationships)
  • Controllers (with route model binding)
  • Views (with blade templating language and layouts)

We now have a working listing of projects and tasks. In the next lesson we’ll concentrate on editing, creating and deleting projects and tasks. Stay tuned!