Skip to content

Laravel 4 tutorial – validation and front end – part 3

This is the 3rd part of the Laravel 4 tutorial series, and the source code will be available on Github, under the 0.3.0 tag.

Also check out the rest of the Laravel 4 series here:

Laravel 4 Validation

In the previous part we added some CRUD actions to our backend, and we’re now able to manage our pages and articles. This is all good, but one crucial thing is missing. Validation.

I really like the idea of services in apps, and I’ve been using this kind of separation for a while now, so it seems like the best idea to use a validation service for our models.

Some people like to do the validation inside the model, some do it in controller, but in my opinion it is better to take it out into a service. Jeffrey Way from Nettuts did a nice screencast on validation, and you will find it is very similar to his way, since I’ve been doing it almost the same in some of my previous apps.

So let’s dive right in. So the idea is to create a validator class for each form. The validation rules will be defined inside this validator class. Since we have 2 types of content, we’ll need 2 validator classes, and an additional one as an abstract class that will contain the validation logic.

So let’s create a directory app/services/validators, and put the app/services directory inside ourcomposer.json so that the classes will be autoloaded:

[code lang=”php”]
"autoload": {
"classmap": [
"app/services"
]
}
[/code]

Next we’ll create the abstract validator class:

app/services/validators/Validator.php

[code lang=”php”]
<?php namespace App\Services\Validators;

abstract class Validator {

protected $data;

public $errors;

public static $rules;

public function __construct($data = null)
{
$this->data = $data ?: \Input::all();
}

public function passes()
{
$validation = \Validator::make($this->data, static::$rules);

if ($validation->passes()) return true;

$this->errors = $validation->messages();

return false;
}

}
[/code]

The $data variable will contain the input data that will be validated, the $errors variable will contain the errors of course and the $rules variable will contain the validation rules, and those rules will be defined in the actual content validator classes that will extend this class.

Before I explain the methods, let’s create our content validator classes with the rules:

app/service/validators/ArticleValidator.php

[code lang=”php”]
<?php namespace App\Services\Validators;

class ArticleValidator extends Validator {

public static $rules = array(
‘title’ => ‘required’,
‘body’ => ‘required’,
);

}
[/code]

app/service/validators/PageValidator.php

[code lang=”php”]
<?php namespace App\Services\Validators;

class PageValidator extends Validator {

public static $rules = array(
‘title’ => ‘required’,
‘body’ => ‘required’,
);

}
[/code]

As you can see these classes simply contain the validation rules. You can read more on Laravel 4 validation in the docs. For now we’ll simply mark the title and body fields as required.

Now that we have our validators setup, we need to call them upon form submission. We’ll do that in our admin controllers. It’s done by simply creating an instance of our validator and running the passes() method.

app/controllers/admin/ArticlesController

[code lang=”php”]
<?php namespace App\Controllers\Admin;

use App\Models\Article;
use App\Services\Validators\ArticleValidator;
use Input, Notification, Redirect, Sentry, Str;

class ArticlesController extends \BaseController {

public function index()
{
return \View::make(‘admin.articles.index’)->with(‘articles’, Article::all());
}

public function show($id)
{
return \View::make(‘admin.articles.show’)->with(‘article’,Article::find($id));
}

public function create()
{
return \View::make(‘admin.articles.create’);
}

public function store()
{
$validation = new ArticleValidator;

if ($validation->passes())
{
$article = new Article;
$article->title = Input::get(‘title’);
$article->slug = Str::slug(Input::get(‘title’));
$article->body = Input::get(‘body’);
$article->user_id = Sentry::getUser()->id;
$article->save();

Notification::success(‘The article was saved.’);

return Redirect::route(‘admin.articles.edit’, $article->id);
}

return Redirect::back()->withInput()->withErrors($validation->errors);
}

public function edit($id)
{
return \View::make(‘admin.articles.edit’)->with(‘article’, Article::find($id));
}

public function update($id)
{
$validation = new ArticleValidator;

if ($validation->passes())
{
$article = Article::find($id);
$article->title = Input::get(‘title’);
$article->slug = Str::slug(Input::get(‘title’));
$article->body = Input::get(‘body’);
$article->user_id = Sentry::getUser()->id;
$article->save();

Notification::success(‘The article was saved.’);

return Redirect::route(‘admin.articles.edit’, $article->id);
}

return Redirect::back()->withInput()->withErrors($validation->errors);
}

public function destroy($id)
{
$article = Article::find($id);
$article->delete();

Notification::success(‘The article was deleted.’);

return Redirect::route(‘admin.articles.index’);
}

}
[/code]

app/controllers/admin/PagesController

[code lang=”php”]
<?php namespace App\Controllers\Admin;

use App\Models\Page;
use App\Services\Validators\PageValidator;
use Input, Notification, Redirect, Sentry, Str;

class PagesController extends \BaseController {

public function index()
{
return \View::make(‘admin.pages.index’)->with(‘pages’, Page::all());
}

public function show($id)
{
return \View::make(‘admin.pages.show’)->with(‘page’, Page::find($id));
}

public function create()
{
return \View::make(‘admin.pages.create’);
}

public function store()
{
$validation = new PageValidator;

if ($validation->passes())
{
$page = new Page;
$page->title = Input::get(‘title’);
$page->slug = Str::slug(Input::get(‘title’));
$page->body = Input::get(‘body’);
$page->user_id = Sentry::getUser()->id;
$page->save();

Notification::success(‘The page was saved.’);

return Redirect::route(‘admin.pages.edit’, $page->id);
}

return Redirect::back()->withInput()->withErrors($validation->errors);
}

public function edit($id)
{
return \View::make(‘admin.pages.edit’)->with(‘page’, Page::find($id));
}

public function update($id)
{
$validation = new PageValidator;

if ($validation->passes())
{
$page = Page::find($id);
$page->title = Input::get(‘title’);
$page->slug = Str::slug(Input::get(‘title’));
$page->body = Input::get(‘body’);
$page->user_id = Sentry::getUser()->id;
$page->save();

Notification::success(‘The page was saved.’);

return Redirect::route(‘admin.pages.edit’, $page->id);
}

return Redirect::back()->withInput()->withErrors($validation->errors);
}

public function destroy($id)
{
$page = Page::find($id);
//$page->delete();

Notification::success(‘The page was deleted.’);

return Redirect::route(‘admin.pages.index’);
}

}
[/code]

As you can see the controllers are almost identical for now, we’ll expand that in later part of the tutorial.

So let’s quickly go over what happens here. We created a new instance of the validator ($validation = new PageValidator), and then call the passes() which runs the validation and returns a boolean value.

If the validation fails the error messages are available through $validation->errors. You can of course create a getter for the errors, but I like to keep it simple.

The abstract validator class looks for the input data (Input::all()) if no data is passed when instantiating the class, so this cleans up our controller code a bit.

The passes() method simply creates an instance of the Laravel validator, runs the validation and returns true if the validation passes, or stores the errors inside the $errors variable and returns false.

Front end

Now that we have our basic backend setup and working, we need to somehow access the entered articles and pages on the front end. For now we just display the Laravel welcome page when hitting the home page.

I like to create a directory inside the public folder: public/site

And inside this directory, for now we’ll create 2 more directories called views and assets, which is pretty self-explanatory.

My way is to create 3 directories under assets: css, img and js. And also 1 directory under the views dir called **_partials**.

A lot of people are familiar with the basic template hierarchy in WordPress so we’ll do something similar in our theme views. You can also use Blade layouts like we did in our backend, but the theme will be very simple so there’s really no need for it.

The first view that we’ll create is the home page view, and 2 partials, header and footer. Our files should look something like this:

public/site/views/_partials/header.blade.php

[code lang=”php”]
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Laravel 4 Tutorial</title>
</head>
<body>
<div id="layout">
<header>
<h1><a href="{{ URL::route(‘home’) }}">Laravel 4 Tutorial</a></h1>
</header>

<hr>
[/code]

public/site/views/_partials/footer.blade.php

[code lang=”php”]
<hr>

<footer>
<p>© 2013, Boris Strahija</p>
</footer>
</div><!–/#layout–>
</body>
</html>
[/code]

public/site/views/index.blade.php

[code lang=”php”]
@include(‘site::_partials/header’)

<h2>Home</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Minima, temporibus, accusamus, nostrum veritatis doloremque officiis quaerat nulla eius laudantium pariatur iste maxime quam nemo eligendi dignissimos iure consequuntur voluptas nihil!</p>
<p>Qui, veniam, ipsum, reprehenderit, facilis dolore excepturi ipsam doloribus consequatur blanditiis quae nobis fugiat accusamus voluptate quibusdam asperiores architecto ratione! Unde, rem a blanditiis nostrum et explicabo qui est doloremque?</p>

@include(‘site::_partials/footer’)
[/code]

You can see here that we are referencing the site:: namespace for our views, which doesn’t work for now.

We also need to create routes that will display the content in the right view. So how do we do that? What I like to do is treat this site directory (theme) as a package, and in Laravel a package has it’s service provider.

I recently wrote an article on modules in Laravel 4 where I leveraged these service providers to register and boot up non-composer packages. You can find the article here if you’re interested: http://creolab.hr/2013/05/modules-in-laravel-4/

So to do something similar for our theme, I’ll create a class called SiteServiceProvider, but we also need to add the public/site directory to our composer.json file for autoloading:

composer.json

[code lang=”php”]
"autoload": {
"classmap": [
"public/site"
]
}
[/code]

public/site/SiteServiceProvider.php

[code lang=”php”]
<?php namespace App\Site;

class SiteServiceProvider extends \Illuminate\Support\ServiceProvider {

public function boot()
{
$this->package(‘app/site’, ‘site’, public_path() . ‘/site’);
}

public function register()
{
require public_path() . ‘/site/routes.php’;
}

}
[/code]

As you can see the service provider registers our site as a package, but we also include the filepublic/site/routes.php which doesn’t exist yet, so we need to create it. But first let’s delete our route for the home page from app/routes.php. To load our home page template we need to add the route:

public/site/routes.php

[code lang=”php”]
<?php

Route::get(‘/’, array(‘as’ => ‘home’, function() {
return View::make(‘site::index’);
}));
[/code]

So now when we hit the home page, we should get our index template.

But we also need templates for displaying a single page, a list of articles and a single article. For now this will be enough, so let’s go ahead and create those views:

public/site/views/page.blade.php

[code lang=”php”]
@include(‘site::_partials/header’)

<h2>{{ $entry->title }}</h2>
{{ $entry->body }}

@include(‘site::_partials/footer’)
[/code]

public/site/views/article.blade.php

[code lang=”php”]
@include(‘site::_partials/header’)

<h2>{{ $entry->title }}</h2>
<h4>Published at {{ $entry->created_at }} • by {{ $entry->author->first_name }}</h4>
{{ $entry->body }}

@include(‘site::_partials/footer’)
[/code]

public/site/views/articles.blade.php

[code lang=”php”]
@include(‘site::_partials/header’)

<h2>Articles</h2>

<ul>
@foreach ($entries as $entry)
<li>
<h3>{{ $entry->title }}</h3>
<h5>Created at {{ $entry->created_at }} • by {{ $entry->author->email }}</h5>
{{ Str::limit($entry->body, 100) }}
</li>
@endforeach
</ul>

@include(‘site::_partials/footer’)
[/code]

To actually display some content we need to create some pages and articles. I updated our content seeder class, but if you prefer to do it manually fell free to do so.

If you prefer the seeder, take a look at https://github.com/bstrahija/l4-site-tutorial/blob/master/app/database/seeds/ContentSeeder.php since I will be working with this data.

Now, when we hit the home page, we want to display the Welcome page which I defined in our content seeder. This page has the slug welcome so we’ll need to modify our home page route to fetch the page:

public/site/routes.php

[code lang=”php”]
<?php

Route::get(‘/’, array(‘as’ => ‘home’, function() {
return View::make(‘site::index’)
->with(‘entry’, App\Models\Page::where(‘slug’, ‘welcome’)->first());
}));
[/code]

This should work, but as you can see we are referencing our Page model with the full namespace path. To keep it cleaner we can define an alias in our app/config/app.php file. Just add the following lines under the aliases config::

app/config/app.php

[code lang=”php”]
‘Article’ => ‘App\Models\Article’,
‘Page’ => ‘App\Models\Page’,
[/code]

So now we can cleanup our route like this:

public/site/routes.php

[code lang=”php”]
Route::get(‘/’, array(‘as’ => ‘home’, function() {
return View::make(‘site::index’)
->with(‘entry’, Page::where(‘slug’, ‘welcome’)->first());
}));
[/code]

And now that we pass the welcome page to the view, we can adapt our view like this:

public/site/views/index.blade.php

[code lang=”php”]
@include(‘site::_partials/header’)

<h2>{{ $entry->title }}</h2>
{{ $entry->body }}

@include(‘site::_partials/footer’)
[/code]

Now that we have our welcome page setup, we need to define the routes for our articles and pages. The URL’s will look like this:

  • Article list: /blog
  • Single article: /blog/article-slug
  • Single page: /page-slug

Let’s first deal with the list of articles (and keep in mind I will be doing most of the things directly inside the routes file, some people prefer controller but I guess for simple stuff this is ok):

public/site/routes.php

[code lang=”php”]
Route::get(‘blog’, array(‘as’ => ‘article.list’, function() {
return View::make(‘site::articles’)
->with(‘entries’, Article::orderBy(‘created_at’, ‘desc’)->get());
}));
[/code]

We want to order the articles by date, so that the newest article is always on top. Now go ahead and hit the URL /blog and you should get a list of article through the template articles.blade.php.

They’re not linked to the single page yet, well get to that immediately:

public/site/routes.php

[code lang=”php”]
Route::get(‘blog/{slug}’, array(‘as’ => ‘article’, function($slug) {
return View::make(‘site::article’)
->with(‘entry’, Article::where(‘slug’, $slug)->first());
}));
[/code]

If you used my content seeder you should be able to go to the URL: /blog/first-post and get the article displayed in the article.blade.php template.

The list of articles is that useful without actually linking to the articles details, and that’s why we created named routes. You can read more on that in the Laravel 4 docs: http://laravel.com/docs/routing#named-routes

The name for the single article route is article so to link our articles we need to adapt our articles.blade.php view:

public/site/views/articles.blade.php

[code lang=”php”]
@include(‘site::_partials/header’)

<h2>Articles</h2>

<ul>
@foreach ($entries as $entry)
<li>
<h3><a href="{{ URL::route(‘article’, $entry->slug) }}">{{ $entry->title }}</a></h3>
<h5>Created at {{ $entry->created_at }} • by {{ $entry->author->email }}</h5>
{{ Str::limit($entry->body, 100) }}
</li>
@endforeach
</ul>

@include(‘site::_partials/footer’)
[/code]

And on the details page we’ll add a “back” link to the list of articles:

public/site/views/article.blade.php

[code lang=”php”]
@include(‘site::_partials/header’)

<h2>{{ $entry->title }}</h2>
<h4>Published at {{ $entry->created_at }} • by {{ $entry->author->email }}</h4>
{{ $entry->body }}

<hr>

<a href="{{ URL::route(‘article.list’) }}">« Back to articles</a>

@include(‘site::_partials/footer’)
[/code]

The final thing we have left is the route to the pages. There was one problem I found here. Since the route is the slug of the page, and the admin route is also considered a slug, I got an error. So until I find a better solution I added where statement at the end of the route that ignores the adminroute:

public/site/routes.php

[code lang=”php”]
Route::get(‘{slug}’, array(‘as’ => ‘page’, function($slug) {
return View::make(‘site::page’)
->with(‘entry’, Page::where(‘slug’, $slug)->first());
}))->where(‘slug’, ‘^((?!admin).)*$’);
[/code]

The page.blade.php template is already setup so when hitting /about-us you should see the content from that page.

The basic front end functionality for Laravel 4 site is now ready, but we don’t have our pages linked.

To conclude this part of the tutorial we’ll also create a navigation partial. So go ahead and create the file:

public/site/views/_partials/navigation.blade.php

[code lang=”php”]
<nav>
<ul>
<li><a href="{{ URL::route(‘home’) }}">Home</a></li>
<li><a href="{{ URL::route(‘page’, ‘about-us’) }}">About us</a></li>
<li><a href="{{ URL::route(‘article.list’) }}">Blog</a></li>
<li><a href="{{ URL::route(‘page’, ‘contact’) }}">Contact</a></li>
</ul>
</nav>
[/code]

The navigation is hardcoded for now, but for simple websites this is fine. Keep in mind that I’m working with named routes, you need to follow the route creation exactly if you want the same result, or else you’ll get an exception.

Finally we’ll include the partial in our header:

public/site/views/_partials/header.blade.php

[code lang=”php”]
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Laravel 4 Tutorial</title>
</head>
<body>
<div id="layout">
<header>
<h1><a href="{{ URL::route(‘home’) }}">Laravel 4 Tutorial</a></h1>

@include(‘site::_partials.navigation’)
</header>

<hr>
[/code]

Try clicking on the links and see if everything works fine.

What’s next?

In the next part we’ll cover custom error pages for our theme, adding some style to the theme and image uploading and resizing. Until then, express your thoughts in the comments section below.

The fourth part on Laravel 4 images, themes and error pages…