Code Happy: The Blog Tutorial

← Back to Index

Please note that this chapter was written for VERSION 3 of the Laravel PHP Framework.

It's finally time to write our first full application. If you haven't read the other chapters you might find this a little tricky. Let's have a look at what we are going to be using to create the blog.

  • Routes
  • Migrations
  • Eloquent
  • Blade Templates
  • Authentication
  • Filters

Let's have a look at the plan Stan, and see exactly what we are going to be building.

Oh yeah, if you would like to see what we are making then you will find the source code for this tutorial on github.

The Design

We are going to build a three page blog. The first page will consist of a listing of blog posts. Similar to the front of a Wordpre.. Wordpush blog. The next page will be the full-view page. It is the page you reach when you click an article that you want to read. The final page will be the page where we write our posts.

Normally you would have the ability to edit or delete posts, but we can add all that stuff later. I'm not counting the usual login page in that count, since it is used with any application that implements authentication.

Let's think about the database schema for a moment. We know that we are going to need a table to store our blog posts. We will also need a table to store our users. We are also going to need some form of relationship between them both. Let's map this out and think about the fields.

Table : posts

| Field       | Type           |
|-------------|----------------|
| id          | INTEGER        |
| title       | VARCHAR(128)   |
| body        | TEXT           |
| author_id   | INTEGER        |
| created_at  | DATETIME       |
| updated_at  | DATETIME       |

Table : users

| Field       | Type           |
|-------------|----------------|
| id          | INTEGER        |
| username    | VARCHAR(128)   |
| nickname    |    VARCHAR(128)   |
| password      | VARCHAR(64)    |
| created_at  | DATETIME       |
| updated_at  | DATETIME       |

Ok that looks good. We can use the author_id field on the Post object to reference the User object, which represents the post's author.

Right, to work!

Basic Setup

First of all you will need to setup your database and session driver, you should know how to do this by now. I will be using mySQL as my database as always.

Now let's create migrations for our tables, again using Artisan as we did in the migrations chapter.

php artisan migrate:make create_users

Now our schema for up() along with default user account.

<?php

Schema::create('users', function($table) {
    $table->increments('id');
    $table->string('username', 128);
    $table->string('nickname', 128);
    $table->string('password', 64);
    $table->timestamps();
});

DB::table('users')->insert(array(
    'username'  => 'admin',
    'nickname'  => 'Admin',
    'password'  => Hash::make('password')
));

and let's update our down() method just in case.

<?php

Schema::drop('users');

Let's also create our posts migration. I bet you didn't see that coming?

php artisan migrate:make create_posts

The schema for up..

<?php

Schema::create('posts', function($table) {
    $table->increments('id');
    $table->string('title', 128);
    $table->text('body');
    $table->integer('author_id');
    $table->timestamps();
});

and let's tear down this table in down() too.

<?php

Schema::drop('posts');

Let's run our migrations and get those tables in place.

php artisan migrate:install
php artisan migrate

Finally let's update our authentication configuration in application/config/auth.php to use fluent as an authentication driver, and username as a login identifier.

<?php

return array(

    'driver' => 'fluent',
    'username' => 'username',
    'model' => 'User',
    'table' => 'users',
);

There, now we are ready to create our Eloquent models.

Eloquent Models

Right, you know exactly how to create Eloquent models, so let's just have a look at the source.

<?php

class User extends Eloquent
{
    public function posts()
    {
        return $this->has_many('Post');
    }
}

class Post extends Eloquent
{
    public function author()
    {
        return $this->belongs_to('User', 'author_id');
    }
}

Nice and simple, we have our User object which has_many posts, and our Post object which belongs_to a user.

Routes

Ok, we have our tables, we have models, and since we are now following my workflow let's create placeholders for the routes we will need.

<?php

Route::get('/', function() {
    // this is our list of posts
});

Route::get('view/(:num)', function($post) {
    // this is our single view
});

Route::get('admin', function() {
    // show the create new post form
});

Route::post('admin', function() {
    // handle the create new post form
});

Route::get('login', function() {
    // show the login form
});

Route::post('login', function() {
    // handle the login form
});

Route::get('logout', function() {
    // logout from the system
});

Right, those are done. As you can see I am using GET and POST to show and handle forms with the same URI.

Views

Let's start by creating a blade layout as a wrapper for our application.

templates/main.blade.php

<!DOCTYPE HTML>
<html lang="en-GB">
<head>
    <meta charset="UTF-8">
    <title>Wordpush</title>
    {{ HTML::style('css/style.css') }}
</head>
<body>
    <div class="header">
        <h1>Wordpush</h1>
        <h2>Code is Limmericks</h2>
    </div>

    <div class="content">
        @yield('content')
    </div>
</body>
</html>

As you can see we have a very simple HTML5 template, with a CSS style-sheet (which we will not be covering, by all means use it to make your blog pretty.. but this is not a design book) and a yielded content area for our page content.

We are going to need a login form so that our post authors can create new entries. I am going to steal this view from the last tutorial and hack it a bit to work with blade layouts. This is actually good practice. Re-use anything you can and eventually you will have built up your own Laravel development toolkit.

pages/login.blade.php

@layout('templates.main')

@section('content')
    {{ Form::open('login') }}

        <!-- check for login errors flash var -->
        @if (Session::has('login_errors'))
            <span class="error">Username or password incorrect.</span>
        @endif

        <!-- username field -->
        <p>{{ Form::label('username', 'Username') }}</p>
        <p>{{ Form::text('username') }}</p>

        <!-- password field -->
        <p>{{ Form::label('password', 'Password') }}</p>
        <p>{{ Form::password('password') }}</p>

        <!-- submit button -->
        <p>{{ Form::submit('Login') }}</p>

    {{ Form::close() }}
@endsection

As you can see, our login form is using our newly created blade layout. Let's also create our 'Create New Post' form.

pages/new.blade.php

<?php

@layout('templates.main')

@section('content')
    {{ Form::open('admin') }}

        <!-- title field -->
        <p>{{ Form::label('title', 'Title') }}</p>
        {{ $errors->first('title', '<p class="error">:message</p>') }}
        <p>{{ Form::text('title', Input::old('title')) }}</p>

        <!-- body field -->
        <p>{{ Form::label('body', 'Body') }}</p>
        {{ $errors->first('body', '<p class="error">:message</p>') }}
        <p>{{ Form::textarea('body', Input::old('body')) }}</p>

        <!-- submit button -->
        <p>{{ Form::submit('Create') }}</p>

    {{ Form::close() }}
@endsection

Get Coding

Finally it's time to get our hands dirty! Let's start with the authentication routine. First we need to link our login route to the login form view.

<?php

Route::get('login', function() {
    return View::make('pages.login');
});

There, that wasn't so hard. Now let's handle the authentication in the POST route in the usual way.

<?php

Route::post('login', function() {

    $userdata = array(
        'username' => Input::get('username'),
        'password' => Input::get('password')
    );

    if ( Auth::attempt($userdata) )
    {
        return Redirect::to('admin');
    }
    else
    {
        return Redirect::to('login')
            ->with('login_errors', true);
    }
});

Now we can login to our system. If you have any trouble understanding these topics please refer to the previous chapters, we aren't covering anything new here.

Let's create the logout route, so that we can test the login process.

<?php

Route::get('logout', function() {
    Auth::logout();
    return Redirect::to('/');
});

Let's add a small profile section to the header of our main template. We can use this to login or logout from the system.

<div class="header">
    @if ( Auth::guest() )
        {{ HTML::link('admin', 'Login') }}
    @else
        {{ HTML::link('logout', 'Logout') }}
    @endif
    <hr />

    <h1>Wordpush</h1>
    <h2>Code is Limmericks</h2>
</div>

Now to add the auth filter to both admin routes. You will notice that admin is the only route that needs protecting, since we want people to be able to browse our blog but not write anything.

<?php

Route::get('admin', array('before' => 'auth', 'do' => function() {
    // show the create new post form
}));

Route::post('admin', array('before' => 'auth', 'do' => function() {
    // handle the create new post form
}));

Let's also attach the create new post form to the admin GET route while we are here. We should also hand the currently logged in user to this view. That way we can use the user objects id field to identify the author.

<?php

Route::get('admin', array('before' => 'auth', 'do' => function() {
    $user = Auth::user();
    return View::make('pages.new')->with('user', $user);
}));

Now that we have a handle on our currently logged in user, let's add that information to the create new post view to identify our author. A hidden field should do the trick.

<?php

...

{{ Form::open('login') }}

    <!-- author -->
    {{ Form::hidden('author_id', $user->id) }}

    <!-- title field -->
    <p>{{ Form::label('title', 'Title') }}</p>
    <p>{{ Form::text('title') }}</p>

...

We can now identify our author, and the create new post page is ready. We don't need to link to it, we want it hidden. We can simply hit the URL /admin if we want to create a new post.

Let's handle a new post creation.

<?php

Route::post('admin', function() {

    // let's get the new post from the POST data
    // this is much safer than using mass assignment
    $new_post = array(
        'title'     => Input::get('title'),
        'body'      => Input::get('body'),
        'author_id' => Input::get('author_id')
    );

    // let's setup some rules for our new data
    // I'm sure you can come up with better ones
    $rules = array(
        'title'     => 'required|min:3|max:128',
        'body'      => 'required'
    );

    // make the validator
    $v = Validator::make($new_post, $rules);

    if ( $v->fails() )
    {
        // redirect back to the form with
        // errors, input and our currently
        // logged in user
        return Redirect::to('admin')
                ->with('user', Auth::user())
                ->with_errors($v)
                ->with_input();
    }

    // create the new post
    $post = new Post($new_post);
    $post->save();

    // redirect to viewing our new post
    return Redirect::to('view/'.$post->id);

});

Now we should be able to create some blog posts, go ahead! Write a few articles so we have something to view in our list all posts view.

Speaking of which, let's get to work on that.

<?php

Route::get('/', function() {
    // lets get our posts and eager load the
    // author
    $posts = Post::with('author')->all();

    return View::make('pages.home')
        ->with('posts', $posts);
});

We also need a view to list all of our blog posts, here we go..

pages/home.blade.php

<?php

@layout('templates.main')

@section('content')
    @foreach ($posts as $post)
        <div class="post">
            <h1>{{ HTML::link('view/'.$post->id, $post->title) }}</h1>
            <p>{{ substr($post->body,0, 120).' [..]' }}</p>
            <p>{{ HTML::link('view/'.$post->id, 'Read more &rarr;') }}</p>
        </div>
    @endforeach
@endsection

Finally we need a full view for our blog posts, let's call it..

pages/full.blade.php

<?php

@layout('templates.main')

@section('content')
    <div class="post">
        <h1>{{ HTML::link('view/'.$post->id, $post->title) }}</h1>
        <p>{{ $post->body }}</p>
        <p>{{ HTML::link('/', '&larr; Back to index.') }}</p>
    </div>
@endsection

We also need to add a new route to enable the view..

Route::get('view/(:num)', function($post) {
    $post = Post::find($post);
    return View::make('pages.full')
        ->with('post', $post);
});

Now we have a fully working blog, with just the basics. Let's take a quick look at what we could do to improve it. Go ahead and try to add some of these features to test your Laravel skills.

The Future

Pagination

We could add some pagination to the posts list page, which would come in handy when the post count gets lengthy. Laravel let's you paginate Eloquent models easily, so this should be a walk in the park!

Edit / Delete Posts

Adding the functionality to edit or remove posts wouldn't be a lot of work, and would allow for maintenance to be performed on the blog.

Post Slugs

We could create post slugs to use instead of post ids in our URLs, this will result in better search engine optimisation. We would need to create a slug from the post title upon saving a new article, and then detect the slug from a route parameter.

My books are available online for free to encourage learning. However, if you'd like for me to keep writing, then please consider buying a digital copy over at Leanpub.com.

It's available in PDF, ePub, and Kindle format, and contains a bunch of extras that you won't find on the site. I have a full-time job, and I write my books in my spare time. Please consider buying a copy so that I can continue to write new books from the comfort of my sofa!