Laravel 5.1 Beauty - The 10 Minute Blog

Creating a simple blogging system with test data in under 10 minutes.

Posted on March 22, 2015 in L5 Beauty
Note: this is the fifth step of the tutorial.

In this chapter we’ll turn the l5beauty project into a blog, complete with test data. Harnessing the power of Laravel 5.1 a blog can be created in less than 10 minutes. This time is from start to finish, without spending time reviewing the detailed discussions below. There’s not many bells or whistles, and no administration of the blog, but what do you expect for less than 10 minutes of development time.

Chapter Contents

Pre-work before the 10 Minute Blog

We’ve already completed most of the pre-work with the l5beauty project created in the last chapter.

All that is needed to create a blog in 10 minutes is a freshly created laravel project with the database created. No need for migrations to have been run, although it won’t hurt anything if they have been.

We’ll use the 10 Minute Blog as the starting point for a project that will grow into a full-featured blogging application. This application won’t use the password reset feature of Laravel 5.1 so delete the password resets migration as illustrated below.

Deleting the password resets migration

~/Code/l5beauty$ rm database/migrations/2014_10_12_100000_create_\
    password_resets_table.php

Don’t Forget the Line Continuation

The above code is actually typed in as one line. That backslash at the end of the first line is the line continuation character.

0:00 to 2:30 - Creating the Posts table

First, create a Post model within the Homestead VM.

Creating Post model

~/Code/l5beauty$ php artisan make:model --migration Post
Model created successfully.
Created Migration: 2015_03_22_210207_create_posts_table

This does two things:

  1. It creates the model class App\Post class in the app directory of the project.
  2. It creates a migration to create the posts table. This migration will end up in the database/migrations folder.

What is a Migration?

Think of migrations as version control for your database structure. Check out the documentation for migrations on the official Laravel 5.1 web site. Migrations and seeding are are explained well there.

Edit the migration file just created in the database/migrations directory to match what’s below.

The create_posts_table migration

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePostsTable extends Migration
{

  /**
   * Run the migrations.
   */
  public function up()
  {
    Schema::create('posts', function (Blueprint $table) {
      $table->increments('id');
      $table->string('slug')->unique();
      $table->string('title');
      $table->text('content');
      $table->timestamps();
      $table->timestamp('published_at')->index();
    });
  }

  /**
   * Reverse the migrations.
   */
  public function down()
  {
    Schema::drop('posts');
  }
}

We’ve added four additional columns here from the default ones provided when the migration was created.

slug
We’ll convert the title of each post to a slug value and use that as part of the URI to the post. This helps make the blog search engine friendly.
title
Every post needs a title.
content
Every post needs some content.
published_at
We want to control when the post is to be published.

Now that the migration is edited, run migrations to create the table.

Running the migrations

~/Code/l5beauty$ php artisan migrate
Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2015_03_22_210207_create_posts_table

Finally, edit app/Post.php to match what’s below.

First version of the Post model

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
  protected $dates = ['published_at'];

  public function setTitleAttribute($value)
  {
    $this->attributes['title'] = $value;

    if (! $this->exists) {
      $this->attributes['slug'] = str_slug($value);
    }
  }
}

Line 9
Tell Laravel 5.1 that the published_at column should be treated as a date.
Line 11
Whenever a value is assigned to the title property of this object, the setTitleAttribute() method will be called to do it. We assign the property as normal and if the row has not yet been saved to the database then we convert the title to a slug value and assign the slug.

2:30 to 5:00 - Seeding Posts with test data

Now that there’s a place to store posts for teh blog, let’s create some random data. To do this we’ll make use of Laravel 5.1’s Model Factories which is built on the excellent Faker library by Francois Zaninotto. (See github for information about Faker.)

Add the following lines to the ModelFactory.php file located in the database/factories directory.

The Post Model Factory

$factory->define(App\Post::class, function ($faker) {
  return [
    'title' => $faker->sentence(mt_rand(3, 10)),
    'content' => join("\n\n", $faker->paragraphs(mt_rand(3, 6))),
    'published_at' => $faker->dateTimeBetween('-1 month', '+3 days'),
  ];
});
Line 1
We’re defining the factory for the App\Post class. The function gets an instance of a Faker object.
Line 2
The factory function returns an array of column values.
Line 3
For the title, we’ll use a random sentence between 3 and 10 words long.
Line 4
For the content, we’ll use between 3 and 6 random paragraphs.
Line 5
And for published_at, we’ll use a random time between a month ago and 3 days from now.

Next edit the DatabaseSeeder.php file located in the database/seeds folder to match what’s below.

Contents of DatabaseSeeder.php

<?php

use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;

class DatabaseSeeder extends Seeder
{
  /**
   * Run the database seeds.
   */
  public function run()
  {
    Model::unguard();

    $this->call('PostTableSeeder');
  }

}

class PostTableSeeder extends Seeder
{
  public function run()
  {
    App\Post::truncate();

    factory(App\Post::class, 20)->create();
  }
}

Line 15
Call the seeder for the posts table
Line 20
Yeah, stuffing another class into this file. Sometimes rules are made to be broken.
Line 24
Truncate any existing records in the posts table.
Line 26
Call the Post model factory, 20 times, creating the resulting rows in the database.

Finally, to get the random data into the database, use artisan to seed it.

Seeding the database

~/Code/l5beauty$ php artisan db:seed
Seeded: PostTableSeeder

Once artisan db:seed is executed there will be 20 rows of data in the posts database. You can use your favorite database client to view it, but I’m not going to here because we’re on a tight schedule.

5:00 to 5:30 - Creating configuration

We may want to have some configuration options with our blog. Things like the title and number of posts per page. So let’s quickly set that up.

Create a new file in the config directory called blog.php with the contents below.

Contents of config/blog.php

<?php
return [
    'title' => 'My Blog',
    'posts_per_page' => 5
];

Within our Laravel 5.1 application it’ll be easy to refer to these settings using the config() function. For instance, config('blog.title') will return the title.

The Timezone

This is a good time to update the config/app.php file if you wish to change your app’s timezone from UTC to your local timezone.

5:30 to 7:30 - Creating the routes and controller

Next edit the app/Http/routes.php file, to match what’s below.

The Initial Routes

<?php

get('/', function () {
    return redirect('/blog');
});

get('blog', 'BlogController@index');
get('blog/{slug}', 'BlogController@showPost');

Line 3
When a GET request to http://l5beauty.app/ is made, we return a redirect to the user, redirecting them to http://l5beauty.app/blog.
Line 7
When a GET request to http://l5beauty.app/blog is made, the index() method of the BlogController class will called and the result returned to the user. (Actually, the full class name of the controller is App\Http\Controllers\BlogController.)
Line 8
When a GET request to http://l5beauty.app/blog/ANYTHING-HERE is made, then the showPost() method of BlogController is called. The value ANYTHING-HERE will be passed to the showPost() method as an argument.

Next we’ll create BlogController.

First, use artisan to create an empty controller.

Creating an empty BlogController

~/Code/l5beauty$ php artisan make:controller BlogController --plain
Controller created successfully.

(The --plain option is used to create an empty class without the standard RESTful methods.)

A new file named BlogController.php will be created in the app/Http/Controllers directory. Edit it to match what’s below.

Initial version of BlogController.php

<?php

namespace App\Http\Controllers;

use App\Post;
use Carbon\Carbon;

class BlogController extends Controller
{
    public function index()
    {
        $posts = Post::where('published_at', '<=', Carbon::now())
            ->orderBy('published_at', 'desc')
            ->paginate(config('blog.posts_per_page'));

        return view('blog.index', compact('posts'));
    }

    public function showPost($slug)
    {
        $post = Post::whereSlug($slug)->firstOrFail();

        return view('blog.post')->withPost($post);
    }
}

Line 12
Here we start an Eloquent query stating we want all posts not scheduled in the future.
Line 13
We’ll order the posts with the most recently published first.
Line 14
And we’ll tap into Eloquent’s powerful pagination feature to only return the maximum number of posts per page we’ve set up in the configuration.
Line 16
Return the blog\index.blade.php view (yet to be created), passing it the $posts variable.
Line 21
Here we fetch a single post from the slug and throw an exception if it’s not found.
Line 23
The ->withPost() method is another way to pass variables to the view. In this case we’re passing the $post variable. It’ll be available to the view as $post

You can check the application’s routing table with the following artisan command.

Showing the routes

~/Code/l5beauty$ php artisan route:list
+----------+-------------+----------------------------------------------+
| Method   | URI         | Action                                       |
+----------+-------------+----------------------------------------------+
| GET|HEAD | /           | Closure                                      |
| GET|HEAD | blog        | App\Http\Controllers\BlogController@index    |
| GET|HEAD | blog/{slug} | App\Http\Controllers\BlogController@showPost |
+----------+-------------+----------------------------------------------+

(Empty columns omitted in above table.)

7:30 to 10:00 - Creating the views

All that’s left to do is to create two views, one to show the index and one to show a post.

Create a new directory named blog in the resources/views directory.

Then create a new file, index.blade.php. Since this file ends with .blade.php it is a Blade template, which is Laravel 5’s built in templating engine. Make index.blade.php match the content below.

Content of index.blade.php

<html>
<head>
  <title>{{ config('blog.title') }}</title>
  <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css"
        rel="stylesheet">
</head>
<body>
  <div class="container">
    <h1>{{ config('blog.title') }}</h1>
    <h5>Page {{ $posts->currentPage() }} of {{ $posts->lastPage() }}</h5>
    <hr>
    <ul>
      @foreach ($posts as $post)
        <li>
          <a href="/blog/{{ $post->slug }}">{{ $post->title }}</a>
          <em>({{ $post->published_at->format('M jS Y g:ia') }})</em>
          <p>
            {{ str_limit($post->content) }}
          </p>
        </li>
      @endforeach
    </ul>
    <hr>
    {!! $posts->render() !!}
  </div>
</body>
</html>
Line 3
Here we use two curly braces to surround the output of whatever value is assigned in the blog config file. Note that two curly braces will escape the output, making it safe for HTML.
Line 4
We’ll use bootstrap to provide just a touch of styling
Line 10
The $posts variable is actually a pagination object which basically means it’s a collection but also has methods for determining the page metrics.
Line 13
Here’s the blade syntax for a foreach loop
Line 16
Since we specified published_at is a date column, we can use the format() method here to show the date just how we want it.
Line 24
Two interesting things about this line. First, notice how we use the a single curly brace with double exclamation marks before and after a call to the render() method. Unlike the double curly brace syntax, this does not escape the output. The second thing is the render() method itself. We use this to display next and previous links.

The last step in the 10 minute blog is to create the view to display posts. Create a new file named post.blade.php in the resources/views/blog directory and match its contents to what’s below.

Content of post.blade.php

<html>
<head>
  <title>{{ $post->title }}</title>
  <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css"
        rel="stylesheet">
</head>
<body>
  <div class="container">
    <h1>{{ $post->title }}</h1>
    <h5>{{ $post->published_at->format('M jS Y g:ia') }}</h5>
    <hr>
    {!! nl2br(e($post->content)) !!}
    <hr>
    <button class="btn btn-primary" onclick="history.go(-1)">
      « Back
    </button>
  </div>
</body>
</html>

Nothing to explain here because everything should be clear.

Congratulations

Point your browser to http://l5beauty.app and browse around a bit.

You have created a simple and clean blog in just a few minutes. Isn’t the power of Laravel 5.1 amazing?

Does this really only take 10 minutes?

Yes. I timed myself, worked slowly and methodically, copying and pasting most of the code. It took a total of 7 minutes and 8 seconds.

Recap

In this chapter we created a working blog with test data in less than 10 minutes.

‘Nuf said.

comments powered by Disqus