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!

  • Steve Tredinnick

    Hi… Line 30 of adding navigation links, file to be edited is not but

    • flynsarmy

      Huh?

      • Steve Tredinnick

        lol… it removes the HTML comment lines thats why the important bits are blank 🙂

        Line 30 of adding navigation links, file to be edited is
        /resources/views/projects/show.blade.php not /resources/views/projects/create.blade.php

  • Marshall Telaumbanua

    hi, i’ve a problem in tutorial 3.
    Class “Form: not found”

    • flynsarmy

      You need to install illuminate/html which was the very first step of part 2. See my How to Install Illuminate/HTML In Laravel 5 tutorial.

      • Marshall Telaumbanua

        succeed to install illuminate/html,

        but the results in the browser like this

        • flynsarmy

          You’re using {{ }} instead of {!! !!}. Please read the instructions above carefully 🙂

  • Johannes Lochter

    “One minor modification to the Post and Task models are required for this to work:” — Something left after that?

    • flynsarmy

      Thanks. Removed.

  • Juarez Ribeiro

    When I go to the edit form, it doesn’t load the values? Why?

  • Duncan Stokes

    The “task created” message is not being passed back to the task list page.

    Following line of the store() function in TasksController needs updating from:

    return Redirect::route(‘projects.show’, $project->slug)->with(‘Task created.’);

    to:

    return Redirect::route(‘projects.show’, $project->slug)->with(‘message’, ‘Task created.’);

    • flynsarmy

      Thanks, Duncan. Post has been updated. Will update the git repo when I get back from holidays.

  • Ireco Gnizedyou

    I have followed tutorial step by step and wasn’t able to get any tasks value it always said “Your project has no tasks.”, finally I got github repo of this project but still shows “Your project has no tasks.”

    • flynsarmy

      Check your DB. Make sure there are rows in your tasks and projects tables. Make sure the project_id values are matching up correctly.

  • Friendbg

    Thanks for the great tutorial. I have problem with editing/creating tasks/projects. Always shows me : SQLSTATE[42S22]: Column not found: 1054 Unknown column ‘q’ in ‘field list’ (SQL: insert into `tasks` (`name`, `slug`, `description`, `q`, `project_id`, `updated_at`, `created_at`) values (ffff, fffff, fff, /projects/project-3/tasks, 3, 2015-04-15 11:08:23, 2015-04-15 11:08:23))

    I’m using mariadb, but I try to another linux box with mysql and it shows me the same. I look 4-5 times at each of the steps , but can’t find what I’m missing.

    My database looks :
    MariaDB [todo]> show tables;
    +—————–+
    | Tables_in_todo |
    +—————–+
    | migrations |
    | password_resets |
    | projects |
    | tasks |
    | users |
    +—————–+

    and my tasks and projects looks well too:

    +————-+——————+——+—–+———————+—————-+
    | Field | Type | Null | Key | Default | Extra |
    +————-+——————+——+—–+———————+—————-+
    | id | int(10) unsigned | NO | PRI | NULL | auto_increment |
    | project_id | int(10) unsigned | NO | MUL | 0 | |
    | name | varchar(255) | NO | | | |
    | slug | varchar(255) | NO | | | |
    | completed | tinyint(1) | NO | | 0 | |
    | description | text | NO | | NULL | |
    | created_at | timestamp | NO | | 0000-00-00 00:00:00 | |
    | updated_at | timestamp | NO | | 0000-00-00 00:00:00 | |
    +————-+——————+——+—–+———————+—————-+

    Can you help me.

    Thank you!

  • Lahm

    When I create a new task for a project, I get this error:

    SQLSTATE[23000]: Integrity constraint violation: 1048 Column ‘project_id’ cannot be null (SQL: insert into `tasks` (`name`, `slug`, `description`, `project_id`, `updated_at`, `created_at`) values (Easy task, easy-task, This task is very easy, , 2015-04-16 16:50:44, 2015-04-16 16:50:44))

    It seems like the ‘project_id’ is missing

    • CE

      I had the same problem…

      $input[‘project_id’] = $project->id

      … in the store method doesn’t seem to be working. I don’t think $project->id has a value.

      I added the following to my tasks/partials/_form.blade.php

      {!! Form::hidden(‘project_id’, $project->id) !!}

      This obviously isn’t the best way to do it as you could change the id via the inspector, but is a workaround for now.

  • Marc

    Hi,

    I have a problem… when I press delete in tasks. The page is reload but the task not delete.

    Thanks

  • Marcelo

    Hi there flynsarmy,
    Great tutorial, it helped me a lot.
    I noticed that the checkbox helper does not worked properly, you may uncheck it, but it does not update the database.
    How do i fix it ?

    • flynsarmy

      That’s a common issue with checkboxes. If unchecked, no POST value is sent, so the DB leaves the value as is. Add a hidden field with the same field name before it with a value of 0.

      Something like the following (untested):


      {!! Form::hidden('completed', 0) !!}
      {!! Form::checkbox('completed') !!}

      If you have any issues just google it.

  • Hey. Nice work here. Been following the tutorials, and everything was working fine until I updated the store, update and delete methods, and my pages went blank. I new laravel follower. What could be the issue?

    • flynsarmy

      Check your php error logs, turn on display_errors in php.ini, make sure laravel debug mode is on… All the usual steps.

  • flynsarmy

    If you type php artisan into the terminal do you get the usual list of commands? If you get nothing then a fatal error is occuring somewhere and you should have logs of it.

    • php artisan shows everything is fine.

      • flynsarmy

        No idea. Sorry. You’ll have to learn how to debug stuff. dd() in your routes file, in your controller etc.

        • Funny. I have just commented out protected $guarded = []; from both models, and everything is OK. Why?
          Once again, quite new to Laravel, but I have Codeigniter experience. Just trying to hack this quickly to get on a project. Your help is appreciated on this one.

  • Pawel

    You can add note that class FORM and HTML aren’t added . And you should add them to composer. sry for english.

    source:
    http://laravel.io/forum/09-20-2014-html-form-class-not-found-in-laravel-5

    By default in Laravel 5.0, Html and Form are not embedded anymore.

    Add the following lines in the require section of composer.json file and run composer update “illuminate/html”: “5.*”

    Register the service provider in config/app.php by adding the following value into the providers array:

    ‘IlluminateHtmlHtmlServiceProvider’

    Register facades by adding these two lines in the aliases array:

    ‘Form’=> ‘IlluminateHtmlFormFacade’, ‘HTML’=> ‘IlluminateHtmlHtmlFacade’

  • Bart

    Wouldn’t be more appropriate to add “fillable” properties instead of adding empty “guarded”?

  • Ivan

    Hi , great tutorial. What about user’s accounts, so that the app woud only show the singed in user stuff.

  • Eddy

    Hi, awesome tutorial. Was wondering how i can integrate this app wiith bootstrap framework.

    • add bootstrap css link in app.blade.php

      and just implement usual steps as how you integrate bootstrap css classes in php.

  • jef

    On the Products & Tasks Controller I’m getting an error of the below on the Destroy & Update functions (I comment out the Destroy and it does the same error on the Update, but I comment out the Destroy & Update and it doesn’t error out, however destroy & update are non-functional):

    Cannot make static method IlluminateDatabaseEloquentModel::destroy() non static in class AppProject
    ********************************************

    I added the: public static function destroy

    and that reverses the error to:

    Cannot make non static method IlluminateDatabaseEloquentModel::update() static in class AppProject

    Do you know how to fix this?

  • Rania Fathy

    hi am new in laravel and when i try to create form task it give me an error i thought the problem in store action but i’m not sure so please any help will be good for me

  • Jeremy Hermawan

    i was completed tutorial part 1 – tutorial part 3 and i still cant understand how to call data from database , call data from class , call data from php class (like $example) , and another function2 on laravel , can some one explain it for all function on laravel ? (ex : @extend for blablabla)

  • if anyone have problem related with form error. Please perform this step
    http://laravelcollective.com/docs/5.1/html

  • Kevin Bradshaw

    When I try to call the update method it updates all the Project resources in the database as opposed to the one I am trying to edit

  • Robert Schlick

    PLEASE DELETE

  • kaveh lorestani

    Hi. thx for nice tutorial. i was doing good until adding your div to app.blade.php. it’s funny but i can’t add it correctly into the file. can you show me your app.blad.php and i can find out where i was going wrong?

  • Salan Le

    When I go to http://localhost:8000/projects/project-1, then show the error “NotFoundHttpException in Handler.php line 113:
    No query results for model [AppProject].”
    I use laravel 5.3.
    Please help, I’m new in Laravel.

    • Xu Wang

      dont use slug just the old way: project/1/task/1