2

Creating a Basic ToDo Application With Laravel 4 – Part 4

Posted December 2nd, 2013 (Updated 14 Mar 2014) in PHP

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

So far we’ve learned how to install and set up Laravel, set up some project and task nested resources and display them to the user. Create, edit and delete functionality has also been implemented. In this chapter we’ll finish things off by adding form validation and slug management.

 

Server Side Form Validation

As it stands our create and edit forms work but they’re not validated. Let’s fix that.

We’ll be using Ardent for validation so install it like so:

1
composer require "laravelbook/ardent":"2.4.*"

We need a place to display any generated errors. Open /app/views/layouts/main.blade.php and drop the following above @yield(‘main’):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
 
<div id="content">
	@if (Session::has('message'))
		<div class="flash alert">
			<p>{{ Session::get('message') }}</p>
		</div>
	@endif
	@if ($errors->any())		<ul>			{{ implode('', $errors->all('<li class="error">:message</li>')) }}		</ul>	@endif 
	@yield('main')
</div>
 
...

Next as per the documentation, our model must extend \LaravelBook\Ardent\Ardent and its validation rules need to be defined:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// /app/models/Project.php
class Project extends LaravelBookArdentArdent {
	public static $rules = array(		'name'			=> 'required|min:4',		'slug'			=> 'required',	); 
	...
 
// /app/models/Task.php
class Task extends LaravelBookArdentArdent {
	public static $rules = array(		'name'			=> 'required|min:4',		'slug'			=> 'required|unique',		'description'	=> 'required',	); 
	..

See Available Validation Rules for a complete list of rules available. The rules above are straight from the documentation except unique. Ardent doesn’t require the :table:id suffix on its unique validation rules however it does require some fancy rejigging of the controller methods as a result. See below.

Finally we will update the store() and update() controller methods to use the new functionality:

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
// /app/controllers/ProjectsController.php
public function store()
{
	$input = Input::all();
	$project = new Project($input); 	if ( $project->save() )		return Redirect::route('projects.index')->with('message', 'Project created.');	else		return Redirect::route('projects.create')->withInput()->withErrors( $project->errors() );}
 
public function update(Project $project)
{
	$input = Input::all();
	$project->fill($input); 	if ( $project->updateUniques() )		return Redirect::route('projects.show', $project->slug)->with('message', 'Project updated.');	else		return Redirect::route('projects.edit', array_get($project->getOriginal(), 'slug'))->withInput()->withErrors( $project->errors() );}
 
// /app/controllers/TasksController.php
public function store(Project $project)
{
	$input = Input::all();
	$input['project_id'] = $project->id;
	$task = new Task($input); 	if ( $task->save() )		return Redirect::route('projects.show', $project->slug)->with('message', 'Task created.');	else		return Redirect::route('projects.tasks.create', $project->slug)->withInput()->withErrors( $task->errors() );}
 
public function update(Project $project, Task $task)
{
	$input = Input::all();
	$task->fill($input); 	if ( $task->updateUniques() )		return Redirect::route('projects.tasks.show', [$project->slug, $task->slug])->with('message', 'Task updated.');	else		return Redirect::route('projects.tasks.edit', [$project->slug, array_get($task->getOriginal(), 'slug')])->withInput()->withErrors( $task->errors() );}

Things get a little complicated above – mostly because of our inclusion of the unique slug column.

The store() methods are relatively simple:

  1. Grab the submitted form data
  2. Drop the data into a new model instance
  3. Attempt to save and redirect to the appropriate route

The use of withInput() keeps any updated form details in the form on validation failure so the user doesn’t need to retype everything and withErrors() passes along the validation errors themselves.

The update() methods are a little more difficult.

  1. As in store() I grab the submitted form data
  2. Also as in store() I drop the data into the (this time pre-existing) model instance
  3. Now our models include a unique field – the slug field. As per the Ardent documentation on Updates with Unique Rules I must call updateUniques() on my updated model.
  4. If validation passes, all well and good. Redirect to the show page for that resource
  5. If validation fails, I can’t just redirect back to $project->slug because my model now includes updated details (which may different slug). As a result I need to grab the pre-filled slug from the model instance which I do with
    1
    
    array_get($task->getOriginal(), 'slug')

Hopefully this is all understandable. Give the create and edit forms a try and enter both valid and invalid data. Make sure everything works as expected.

Optional Extra: Use Ardents Model Relationship Fields

You could easily argue against doing so, but I like to implement Ardent’s model relationship fields. As apposed to Laravel’s one-method-per-relation setup, Ardent offers use of a single $relationData array that keeps all relationships for a model in one tidy place.

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
class Project extends LaravelBookArdentArdent {
	public static $relationsData = array(		'tasks' =&gt; array(self::HAS_MANY, 'Task'),	); 
	protected $fillable = ['name', 'slug'];
 
	public static $rules = array(
		'name'			=&gt; 'required|min:4',
		'slug'			=&gt; 'required',
	);
}
 
class Task extends LaravelBookArdentArdent {
	public static $relationsData = array(		'project' =&gt; array(self::BELONGS_TO, 'Project'),	); 
	protected $fillable = ['project_id', 'name', 'slug', 'completed', 'description'];
 
	public static $rules = array(
		'name'			=&gt; 'required|min:4',
		'slug'			=&gt; 'required|unique',
		'description'	=&gt; 'required',
	);
}

 

Better Slug Management

We’re still writing our slug fields by hand and they currently have no validation on them. This needs to change.

A quick check of the Laravel Packages Repository brings up a package Eloquent-Sluggable. Perfect.

Installation

To install just follow the installations on the page linked above:

1
composer require "cviebrock/eloquent-sluggable":"1.0.*"

Add the service provider and facade in /app/config/app.php:

1
2
3
4
5
6
7
8
9
'providers' => array(
	...
	'CviebrockEloquentSluggableSluggableServiceProvider',
),
 
'aliases' => array(
	...
	'Sluggable'       => 'CviebrockEloquentSluggableFacadesSluggable',
),

and set up the config files

1
php artisan config:publish cviebrock/eloquent-sluggable

Configuration

Both our resources will generate slugs based on their name field. Open /app/config/packages/cviebrock/eloquent-sluggable/config.php and set build_from to ‘name’.

Open our two model files /app/models/Project.php and /app/models/Task.php and drop the following line in to make them automatically sluggable:

public static $sluggable = array();

Note the array is empty because the resources can both safely fall back to the default config options set earlier.

We don’t need or want to manually set slugs or even see them and will instead just let Eloquent-Sluggable handle all the heavy lifting. So open up /app/views/projects/partials/_form.blade.php and /app/views/tasks/partials/_form.blade.php and delete the <li> for the slug.

Finally we need to make Eloquent-Sluggable set the slug on model creation. Usually this is automatic but due to a conflict with Ardent we instead simply remove Ardent’s slug validation – Eloquent Sluggable will handle unique checks for us. Update your models 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
class Project extends LaravelBookArdentArdent {
	public static $relationsData = array(
		'tasks' => array(self::HAS_MANY, 'Task'),
	);
 
	protected $fillable = ['name', 'slug'];
 
	public static $sluggable = array();
 
	public static $rules = array(
		'name'			=> 'required|min:4'
	);
}
 
class Task extends LaravelBookArdentArdent {
	public static $relationsData = array(
		'project' => array(self::BELONGS_TO, 'Project'),
	);
 
	protected $fillable = ['project_id', 'name', 'slug', 'completed', 'description'];
 
	public static $sluggable = array();
 
	public static $rules = array(
		'name'			=> 'required|min:4',
		'description'	=> 'required',
	);
}

And we’re done. Try creating some new projects and tasks – even with identical names. Eloquent-Sluggable will detect the duplicate and add an appropriate suffix accordingly.

 

Conclusion

Over the court of this tutorial we’ve learned how to install and configure Laravel 4, add extra packages, handle seeds and migrations, generate and display resources, add create/edit/display pages for those resources and finally today we set up form validation and slug management. Along the way we even picked up a few more advanced concepts such as route model binding and CSRF protection. Despite its seemingly simple appearance at first a to-do application generally makes for a great starter app for any new PHP framework and Laravel has made the process of creating ours painless and fun. Happy coding!

  • janily chen

    this tutorial is very useful for me.
    could you share the program’s source code?

    thanks!

    • flynsarmy

      Sure. I planned on making a github repo with a tagged version for each blog post but haven’t had the time yet. In the meantime you can download the zip from here. It was written for 4.0 and may need a bit of modification to work with 4.1.