42

Creating a Basic ToDo Application in Laravel 5 – Part 3

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’ve learned how to install and set up Laravel, set up some project and task resources and displayed them to the user. In this chapter we’ll learn how to set up create, edit and delete pages/actions.

 

Before you Begin

If you’re a Laracasts member, watch the videos RESTful FormsCreate/Edit Forms and Form Requests & Controller Validation. These videos explain far better and in far more detail the concepts below. If you’re serious about learning Laravel I’d highly recommend you sign up.

 

Adding Navigation Links

Before we do anything it would make life a little easier to add create/edit/delete/back links to our projects and tasks pages.

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
<!-- /resources/views/projects/index.blade.php -->
@extends('app')
 
@section('content')
    <h2>Projects</h2>
 
    @if ( !$projects->count() )
        You have no projects
    @else
        <ul>
            @foreach( $projects as $project )
                <li>
                    {!! Form::open(array('class' => 'form-inline', 'method' => 'DELETE', 'route' => array('projects.destroy', $project->slug))) !!}
                        <a href="{{ route('projects.show', $project->slug) }}">{{ $project->name }}</a>
                        (
                            {!! link_to_route('projects.edit', 'Edit', array($project->slug), array('class' => 'btn btn-info')) !!},
                            {!! Form::submit('Delete', array('class' => 'btn btn-danger')) !!}
                        )
                    {!! Form::close() !!}
                </li>
            @endforeach
        </ul>
    @endif
 
    <p>
        {!! link_to_route('projects.create', 'Create Project') !!}
    </p>
@endsection
 
<!-- /resources/views/projects/show.blade.php -->
@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>
                    {!! Form::open(array('class' => 'form-inline', 'method' => 'DELETE', 'route' => array('projects.tasks.destroy', $project->slug, $task->slug))) !!}
                        <a href="{{ route('projects.tasks.show', [$project->slug, $task->slug]) }}">{{ $task->name }}</a>
                        (
                            {!! link_to_route('projects.tasks.edit', 'Edit', array($project->slug, $task->slug), array('class' => 'btn btn-info')) !!},
 
                            {!! Form::submit('Delete', array('class' => 'btn btn-danger')) !!}
                        )
                    {!! Form::close() !!}
                </li>
            @endforeach
        </ul>
    @endif
 
    <p>
        {!! link_to_route('projects.index', 'Back to Projects') !!} |
        {!! link_to_route('projects.tasks.create', 'Create Task', $project->slug) !!}
    </p>
@endsection

For the most part this should be pretty self explanatory. The only tricky concept is the delete link. Resource controllers require a HTTP DELETE method to be sent. This can’t be done with a standard link so a form submit to the given route is required. See Actions Handled by Resource Controller in the documentation for more information.

 

Creating the Add and Edit pages

With the listing pages all set up, we need to be able to add and edit projects and tasks. The create and edit forms will be pretty much identical so instead of duplicating them, we will inherit from a single form partial for each model.

I’ll begin with the project create/edit views:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- /resources/views/projects/create.blade.php -->
@extends('app')
 
@section('content')
    <h2>Create Project</h2>
 
    {!! Form::model(new App\Project, ['route' => ['projects.store']]) !!}
        @include('projects/partials/_form', ['submit_text' => 'Create Project'])
    {!! Form::close() !!}
@endsection
 
<!-- /resources/views/projects/edit.blade.php -->
@extends('app')
 
@section('content')
    <h2>Edit Project</h2>
 
    {!! Form::model($project, ['method' => 'PATCH', 'route' => ['projects.update', $project->slug]]) !!}
        @include('projects/partials/_form', ['submit_text' => 'Edit Project'])
    {!! Form::close() !!}
@endsection

Do the same for tasks but with updated routes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- /resources/views/tasks/create.blade.php -->
@extends('app')
 
@section('content')
    <h2>Create Task for Project "{{ $project->name }}"</h2>
 
    {!! Form::model(new App\Task, ['route' => ['projects.tasks.store', $project->slug], 'class'=>'']) !!}
        @include('tasks/partials/_form', ['submit_text' => 'Create Task'])
    {!! Form::close() !!}
@endsection
 
<!-- /resources/views/tasks/edit.blade.php -->
@extends('app')
 
@section('content')
    <h2>Edit Task "{{ $task->name }}"</h2>
 
    {!! Form::model($task, ['method' => 'PATCH', 'route' => ['projects.tasks.update', $project->slug, $task->slug]]) !!}
        @include('tasks/partials/_form', ['submit_text' => 'Edit Task'])
    {!! Form::close() !!}
@endsection

Now the are a few new concepts here:

Including a Partial

Firstly you’ll notice my use of blades @include method. This includes the form view that we will define later (for now, just create the files /resources/views/projects/partials/_form.blade.php and /resources/views/tasks/partials/_form.blade.php). Because the same form will be used on both create and edit pages we need its submit button to have a ‘Create Form’ and ‘Edit Form’ message appropriately so a submit_text variable is passed to the view.

Form Model Binding

The forms require different HTML <form> tags so rather those were split out from the _form partial and placed directly in the views. The Add form is a simple POST request to the projects.store named route and the Edit form is a PATCH to projects.update. This may seem confusing but it’s just the way RESTful controllers work.

Also notice the use of Form::model(). This is called called form model binding and though this doesn’t do much now, it will be used to automatically populate the edit form later when we add the fields with using Form::input().

CSRF Protection

Form helpers provide alot of functionality for free. If you go to /projects/create and view page source you’ll see something like the following:

1
2
3
<form method="POST" action="http://l4todo.localhost.com/projects" accept-charset="UTF-8">
	<input name="_token" type="hidden" value="Y8uOo7SeD5tQZExezDf5a7UwiYR4P6qIHEUKJNxI">
</form>

See the _token field? This is a CSRF token automatically generated by the {{ Form::model() }} call which prevents cross-site request forgery. Suffice to say it’s a good thing and we didn’t even have to do anything special to get it!

 

Create the Edit Forms

We need form markup for our projects and tasks. Thanks to form model binding, we can just use Laravel’s Form helpers to output all the fields we need.

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
<!-- /resources/views/projects/partials/_form.blade.php -->
<div class="form-group">
    {!! Form::label('name', 'Name:') !!}
    {!! Form::text('name') !!}
</div>
<div class="form-group">
    {!! Form::label('slug', 'Slug:') !!}
    {!! Form::text('slug') !!}
</div>
<div class="form-group">
    {!! Form::submit($submit_text, ['class'=>'btn primary']) !!}
</div>
 
 
<!-- /resources/views/tasks/partials/_form.blade.php -->
<div class="form-group">
    {!! Form::label('name', 'Name:') !!}
    {!! Form::text('name') !!}
</div>
 
<div class="form-group">
    {!! Form::label('slug', 'Slug:') !!}
    {!! Form::text('slug') !!}
</div>
 
<div class="form-group">
    {!! Form::label('completed', 'Completed:') !!}
    {!! Form::checkbox('completed') !!}
</div>
 
<div class="form-group">
    {!! Form::label('description', 'Description:') !!}
    {!! Form::textarea('description') !!}
</div>
 
<div class="form-group">
    {!! Form::submit($submit_text) !!}
</div>

That’s about it. In your browser you should now be able to browse to your add and edit pages. How easy was that!

 

Making the Forms Work

We have project and task add and edit forms displaying and a pseudo-form for deleting them. Now to make everything work as advertised.

Firstly add to the top of your controllers:

1
2
use Input;
use Redirect;

Now for the store, update and destroy methods.

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
// ProjectsController
public function store()
{
	$input = Input::all();
	Project::create( $input );
 
	return Redirect::route('projects.index')->with('message', 'Project created');
}
 
public function update(Project $project)
{
	$input = array_except(Input::all(), '_method');
	$project->update($input);
 
	return Redirect::route('projects.show', $project->slug)->with('message', 'Project updated.');
}
 
public function destroy(Project $project)
{
	$project->delete();
 
	return Redirect::route('projects.index')->with('message', 'Project deleted.');
}
 
// TasksController
public function store(Project $project)
{
	$input = Input::all();
	$input['project_id'] = $project->id;
	Task::create( $input );
 
	return Redirect::route('projects.show', $project->slug)->with('message', 'Task created.');
}
 
public function update(Project $project, Task $task)
{
	$input = array_except(Input::all(), '_method');
	$task->update($input);
 
	return Redirect::route('projects.tasks.show', [$project->slug, $task->slug])->with('message', 'Task updated.');
}
 
public function destroy(Project $project, Task $task)
{
	$task->delete();
 
	return Redirect::route('projects.show', $project->slug)->with('message', 'Task deleted.');
}

Again the above is pretty much self explanatory and boilerplate-free thanks to route model binding and Laravel’s beautiful expressive syntax.

If you submitted one of the forms now, you’d likely see an error MassAssignmentException: _token. A mass assignment is where you pass an array of data to Model::create() or Model::update() the way we’re doing in our controllers, instead of setting one field at a time. To fix this simply add an empty guarded property to each model.

1
2
3
4
5
6
7
8
class Project extends Model {
 
    protected $guarded = [];
 
 
class Task extends Model {
 
    protected $guarded = [];

 

Flash Messages

Further Details: See Flash Messaging

One thing to note from the above is my use of the with() function. with() passes a flash (one time use) variable to the session which can then be read on the next page load. We now need to check for that message and display it to the user. Open /resources/views/app.blade.php and add the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
...
 
<div class="content">
	@if (Session::has('message'))		<div class="flash alert-info">			<p>{{ Session::get('message') }}</p>		</div>	@endif 
	@yield('content')
</div>
 
...

Try creating a project. The message will display as it should however refresh the page and it will be gone.

 

Conclusion

Today things got a little more interesting. We added create and edit forms and delete functionality, we learned about CSRF and Form Model Binding and even a few more blade and Eloquent functions.

With everything now working, have a play around. Create, edit and delete your resources. Remember you can always php artisan migrate:refresh –seed when you’re done and your database will be instantly reset.

In the next and final lesson we’ll put on the finishing touches including form validation and slug management. Stay tuned!