Laravel 5.1 Beauty - Cleaning Up the Blog

Making the blog pages pretty with the Clean Blog template

Posted on May 24, 2015 in L5 Beauty
Note: this is the eleventh step in the tutorial.

In this chapter we’ll get the front end of our blog cleaned up. This includes both the index page showing the list of posts and the pages showing individual posts.

Chapter Contents

Using the Clean Blog Template

Clean Blog is a free blogging template provided by Start Bootstrap. We’ll use it as the basis of our blog pages.

Fetching Clean Blog with Bower

Set up bower to fetch the Clean Blog source with the command below:

Adding Clean Blog to Bower

/Code/l5beauty$ bower install clean-blog --save

There may be a couple warning errors about the clean blog repository not having bower.json set up correctly, but that’s okay.

Gulping Clean Blog’s Less Files

Edit your gulpfile.js and in the copyfiles task, at the bottom of the function, add the following lines.

Copying the Clean Blog Less Files

  // Copy clean-blog less files
  gulp.src("vendor/bower_dl/clean-blog/less/**")
      .pipe(gulp.dest("resources/assets/less/clean-blog"));

Now when you do a gulp copyfiles then the latest clean blog files will be copied.

We’ll use these files in a little bit as part of the blog’s CSS.

Copying Some Header Images

In order to have a few header images to play with we’ll copy the four header images that the Clean Blog template provides into our upload area. Depending on how you configured your Upload Manager these images will end up either on your local file system (in the public/uploads directory) or on the Amazon S3 servers.

Point your browser to http://l5beauty.app/admin and then click on the Uploads link in the menu bar. Upload the following files:

  • about-bg.jpg
  • contact-bg.jpg
  • home-bg.jpg
  • post-bg.jpg

The files are located in the img folder of the Clean Blog sources you just coped using Bower (which should be vendor/bower_dl/clean-blog).

Creating the BlogIndexData Job

The last time we touched the BlogController was back in Chapter 7 when we created the 10 Minute Blog. At that time we didn’t have the tagging feature with our blog.

If a tag is specified in the query string, we’ll need to gather the list of posts, filtering them so only posts with that tag displays. Rather than adding the logic to do this in the controller, let’s create a one-off job to gather the index data.

First, create the job class as instructed below.

Creating the BlogIndexData Job

vagrant@homestead:~/Code/l5beauty$ php artisan make:job BlogIndexData
Job created successfully.

Now edit the newly created BlogIndexData.php file. It’s in your app/Jobs directory.

Content of BlogIndexData.php

<?php

namespace App\Jobs;

use App\Post;
use App\Tag;
use Carbon\Carbon;
use Illuminate\Contracts\Bus\SelfHandling;

class BlogIndexData extends Job implements SelfHandling
{
  protected $tag;

  /**
   * Constructor
   *
   * @param string|null $tag
   */
  public function __construct($tag)
  {
    $this->tag = $tag;
  }

  /**
   * Execute the command.
   *
   * @return array
   */
  public function handle()
  {
    if ($this->tag) {
      return $this->tagIndexData($this->tag);
    }

    return $this->normalIndexData();
  }

  /**
   * Return data for normal index page
   *
   * @return array
   */
  protected function normalIndexData()
  {
    $posts = Post::with('tags')
      ->where('published_at', '<=', Carbon::now())
      ->where('is_draft', 0)
      ->orderBy('published_at', 'desc')
      ->simplePaginate(config('blog.posts_per_page'));

    return [
      'title' => config('blog.title'),
      'subtitle' => config('blog.subtitle'),
      'posts' => $posts,
      'page_image' => config('blog.page_image'),
      'meta_description' => config('blog.description'),
      'reverse_direction' => false,
      'tag' => null,
    ];
  }

  /**
   * Return data for a tag index page
   *
   * @param string $tag
   * @return array
   */
  protected function tagIndexData($tag)
  {
    $tag = Tag::where('tag', $tag)->firstOrFail();
    $reverse_direction = (bool)$tag->reverse_direction;

    $posts = Post::where('published_at', '<=', Carbon::now())
      ->whereHas('tags', function ($q) use ($tag) {
        $q->where('tag', '=', $tag->tag);
      })
      ->where('is_draft', 0)
      ->orderBy('published_at', $reverse_direction ? 'asc' : 'desc')
      ->simplePaginate(config('blog.posts_per_page'));
    $posts->addQuery('tag', $tag->tag);

    $page_image = $tag->page_image ?: config('blog.page_image');

    return [
      'title' => $tag->title,
      'subtitle' => $tag->subtitle,
      'posts' => $posts,
      'page_image' => $page_image,
      'tag' => $tag,
      'reverse_direction' => $reverse_direction,
      'meta_description' => $tag->meta_description ?: \
          config('blog.description'),
    ];
  }
}

__construct()
Just stash the tag passed to the constructor.
handle()
A simple method. If a value for $tag was passed during construction we call one method to gather the data, otherwise a different method is called.
normalIndexData()
This method returns the index data the normal way. That is, without a filter on the tag. The code is almmost identical to what we did with the 10 minute blog, but now any tags the posts have are Eager Loaded (the with() method does this.). Also, we do filter the query to not include any draft posts.
tagIndexData()
Here we first load the Tag and then filter posts to match the tag. This is accomplished with the whereHas() method. Don’t forget that line continuation character (the backslash) where ‘meta_description’ is returned should not be typed, instead continue typing the next line without hitting enter!

Notice all the extra data we’re returning? Before, in the 10 minute blog, we only returned the $posts to the view. But now we return all kinds of information. Shortly, you’ll see how this is used in the index view.

Eager Loading

Eager Loading helps with the n+1 query problem. It loads queries having this issue in two queries instead of many. This can drastically increase the application’s performance. See the Laravel Docs for a complete example.

Updating the BlogController

Update BlogController.php to match what’s below.

Updated BlogController

<?php
namespace App\Http\Controllers;

use App\Jobs\BlogIndexData;
use App\Http\Requests;
use App\Post;
use App\Tag;
use Illuminate\Http\Request;

class BlogController extends Controller
{
  public function index(Request $request)
  {
    $tag = $request->get('tag');
    $data = $this->dispatch(new BlogIndexData($tag));
    $layout = $tag ? Tag::layout($tag) : 'blog.layouts.index';

    return view($layout, $data);
  }

  public function showPost($slug, Request $request)
  {
    $post = Post::with('tags')->whereSlug($slug)->firstOrFail();
    $tag = $request->get('tag');
    if ($tag) {
        $tag = Tag::whereTag($tag)->firstOrFail();
    }

    return view($post->layout, compact('post', 'tag'));
  }
}

index()
Here we pull any $tag value from the request and use that BlogIndexData command just created to figure the data. Because we want the ability to have different index templates for different tags, we ask the Tag class for the template if a $tag is used, otherwise we go with the default.
showPost()
Any associated tags are Eager Loaded with the post. If there’s any $tag passed in the query string, we convert $tag over to the actual Tag record before passing it to the view.

Building the Assets

There’s a bit more of coding to do, and the views to create, but first let’s get all the blog’s assets in place.

Creating blog.js

Create blog.js in the resources/assets/js directory with the following content.

Content of blog.js

/*
 * Blog Javascript
 * Copied from Clean Blog v1.0.0 (http://startbootstrap.com)
 */

// Navigation Scripts to Show Header on Scroll-Up
jQuery(document).ready(function($) {
  var MQL = 1170;

  //primary navigation slide-in effect
  if ($(window).width() > MQL) {
    var headerHeight = $('.navbar-custom').height();
    $(window).on('scroll', {
      previousTop: 0
    },
    function() {
      var currentTop = $(window).scrollTop();

      //if user is scrolling up
      if (currentTop < this.previousTop) {
        if (currentTop > 0 && $('.navbar-custom').hasClass('is-fixed')) {
          $('.navbar-custom').addClass('is-visible');
        } else {
          $('.navbar-custom').removeClass('is-visible is-fixed');
        }
      //if scrolling down...
      } else {
        $('.navbar-custom').removeClass('is-visible');
        if (currentTop > headerHeight &&
          !$('.navbar-custom').hasClass('is-fixed')) {
            $('.navbar-custom').addClass('is-fixed');
        }
      }
      this.previousTop = currentTop;
    });
  }

  // Initialize tooltips
  $('[data-toggle="tooltip"]').tooltip();
});

This code implement tooltips and will cause the navigation bar to appear when the user scrolls up.

It was copied from Clean Blog’s js/clean-blog.js file.

Creating blog.less

In the resources/assets/less directory create a file named blog.less with the following content.

Content of blog.less

@import "bootstrap/bootstrap";
@import "fontawesome/font-awesome";
@import "clean-blog/clean-blog";

@import "//fonts.googleapis.com/css?family=Lora:400,700,\
  400italic,700italic";
@import "//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,\
  600italic,700italic,800italic,400,300,600,700,800'";

.intro-header .post-heading .meta a,
article a {
  text-decoration: underline;
}

h2 {
  padding-top: 22px;
}
h3 {
  padding-top: 15px;
}

h2 + p, h3 + p, h4 + p {
  margin-top: 5px;
}

// Adjust position of captions
.caption-title {
  margin-bottom: 5px;
}
.caption-title + p {
  margin-top: 0;
}

// Change the styling of dt/dd elements
dt {
  margin-bottom: 5px;
}
dd {
  margin-left: 30px;
  margin-bottom: 10px;
}

This file pulls in Bootstrap, Font Awesome and Clean Blog. Then the fonts are imported and a touch of styling is added to a few elements. (Again, careful of those line continuation characters on the 4th and 5th @import lines.)

Updating gulpfile.js

Update gulpfile.js to match what’s below. The only changes at this point are the addition of the scripts for blog.js and blog.less.

Final Version of gulpfile.js

var gulp = require('gulp');
var rename = require('gulp-rename');
var elixir = require('laravel-elixir');

/**
 * Copy any needed files.
 *
 * Do a 'gulp copyfiles' after bower updates
 */
gulp.task("copyfiles", function() {

    // Copy jQuery, Bootstrap, and FontAwesome
    gulp.src("vendor/bower_dl/jquery/dist/jquery.js")
        .pipe(gulp.dest("resources/assets/js/"));

    gulp.src("vendor/bower_dl/bootstrap/less/**")
        .pipe(gulp.dest("resources/assets/less/bootstrap"));

    gulp.src("vendor/bower_dl/bootstrap/dist/js/bootstrap.js")
        .pipe(gulp.dest("resources/assets/js/"));

    gulp.src("vendor/bower_dl/bootstrap/dist/fonts/**")
        .pipe(gulp.dest("public/assets/fonts"));

    gulp.src("vendor/bower_dl/fontawesome/less/**")
        .pipe(gulp.dest("resources/assets/less/fontawesome"));

    gulp.src("vendor/bower_dl/fontawesome/fonts/**")
        .pipe(gulp.dest("public/assets/fonts"));

    // Copy datatables
    var dtDir = 'vendor/bower_dl/datatables-plugins/integration/';

    gulp.src("vendor/bower_dl/datatables/media/js/jquery.dataTables.js")
        .pipe(gulp.dest('resources/assets/js/'));

    gulp.src(dtDir + 'bootstrap/3/dataTables.bootstrap.css')
        .pipe(rename('dataTables.bootstrap.less'))
        .pipe(gulp.dest('resources/assets/less/others/'));

    gulp.src(dtDir + 'bootstrap/3/dataTables.bootstrap.js')
        .pipe(gulp.dest('resources/assets/js/'));

    // Copy selectize
    gulp.src("vendor/bower_dl/selectize/dist/css/**")
        .pipe(gulp.dest("public/assets/selectize/css"));

    gulp.src("vendor/bower_dl/selectize/dist/js/standalone/selectize.min.js")
        .pipe(gulp.dest("public/assets/selectize/"));

    // Copy pickadate
    gulp.src("vendor/bower_dl/pickadate/lib/compressed/themes/**")
        .pipe(gulp.dest("public/assets/pickadate/themes/"));

    gulp.src("vendor/bower_dl/pickadate/lib/compressed/picker.js")
        .pipe(gulp.dest("public/assets/pickadate/"));

    gulp.src("vendor/bower_dl/pickadate/lib/compressed/picker.date.js")
        .pipe(gulp.dest("public/assets/pickadate/"));

    gulp.src("vendor/bower_dl/pickadate/lib/compressed/picker.time.js")
        .pipe(gulp.dest("public/assets/pickadate/"));

    // Copy clean-blog less files
    gulp.src("vendor/bower_dl/clean-blog/less/**")
        .pipe(gulp.dest("resources/assets/less/clean-blog"));
});

/**
 * Default gulp is to run this elixir stuff
 */
elixir(function(mix) {

    // Combine scripts
    mix.scripts([
        'js/jquery.js',
        'js/bootstrap.js',
        'js/jquery.dataTables.js',
        'js/dataTables.bootstrap.js'
    ],
    'public/assets/js/admin.js', 'resources//assets');

    // Combine blog scripts
    mix.scripts([
        'js/jquery.js',
        'js/bootstrap.js',
        'js/blog.js'
    ], 'public/assets/js/blog.js', 'resources//assets');

    // Compile CSS
    mix.less('admin.less', 'public/assets/css/admin.css');
    mix.less('blog.less', 'public/assets/css/blog.css');
});

Run gulp twice. First gulp copyfiles to copy the needed clean-blog assets. Then just a plain gulp to combine everything.

The Blog Views

Let’s wrap up the views for showing the blog and individual posts.

You can delete the index.blade.php and post.blade.php files that are in the resources/views/blog directory. They are still there from the 10 Minute Blog chapter and we won’t need them any longer.

The blog.layouts.master view

Create a folder named layouts in the resources/views/blog directory and create a new file there named master.blade.php. Update this file to match the contents below.

Content of blog.layouts.mater view

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="{{ $meta_description }}">
  <meta name="author" content="{{ config('blog.author') }}">

  <title>{{ $title or config('blog.title') }}</title>

  {{-- Styles --}}
  <link href="/assets/css/blog.css" rel="stylesheet">
  @yield('styles')

  {{-- HTML5 Shim and Respond.js for IE8 support --}}
  <!--[if lt IE 9]>
  <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
  <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
  <![endif]-->
</head>
<body>
@include('blog.partials.page-nav')

@yield('page-header')
@yield('content')

@include('blog.partials.page-footer')

{{-- Scripts --}}
<script src="/assets/js/blog.js"></script>
@yield('scripts')

</body>
</html>

This is the basic layout we’ll use for other layouts.

The blog.layouts.index view

Create a index.blade.php view in the same folder with the contents below.

Content of blog.layouts.index view

@extends('blog.layouts.master')

@section('page-header')
  <header class="intro-header"
          style="background-image: url('{{ page_image($page_image) }}')">
    <div class="container">
      <div class="row">
        <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
          <div class="site-heading">
            <h1>{{ $title }}</h1>
            <hr class="small">
            <h2 class="subheading">{{ $subtitle }}</h2>
          </div>
        </div>
      </div>
    </div>
  </header>
@stop

@section('content')
  <div class="container">
    <div class="row">
      <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">

        {{-- The Posts --}}
        @foreach ($posts as $post)
          <div class="post-preview">
            <a href="{{ $post->url($tag) }}">
              <h2 class="post-title">{{ $post->title }}</h2>
              @if ($post->subtitle)
                <h3 class="post-subtitle">{{ $post->subtitle }}</h3>
              @endif
            </a>
            <p class="post-meta">
              Posted on {{ $post->published_at->format('F j, Y') }}
              @if ($post->tags->count())
                in
                {!! join(', ', $post->tagLinks()) !!}
              @endif
            </p>
          </div>
          <hr>
        @endforeach

        {{-- The Pager --}}
        <ul class="pager">

          {{-- Reverse direction --}}
          @if ($reverse_direction)
            @if ($posts->currentPage() > 1)
              <li class="previous">
                <a href="{!! $posts->url($posts->currentPage() - 1) !!}">
                  <i class="fa fa-long-arrow-left fa-lg"></i>
                  Previous {{ $tag->tag }} Posts
                </a>
              </li>
            @endif
            @if ($posts->hasMorePages())
              <li class="next">
                <a href="{!! $posts->nextPageUrl() !!}">
                  Next {{ $tag->tag }} Posts
                  <i class="fa fa-long-arrow-right"></i>
                </a>
              </li>
            @endif
          @else
            @if ($posts->currentPage() > 1)
              <li class="previous">
                <a href="{!! $posts->url($posts->currentPage() - 1) !!}">
                  <i class="fa fa-long-arrow-left fa-lg"></i>
                  Newer {{ $tag ? $tag->tag : '' }} Posts
                </a>
              </li>
            @endif
            @if ($posts->hasMorePages())
              <li class="next">
                <a href="{!! $posts->nextPageUrl() !!}">
                  Older {{ $tag ? $tag->tag : '' }} Posts
                  <i class="fa fa-long-arrow-right"></i>
                </a>
              </li>
            @endif
          @endif
        </ul>
      </div>

    </div>
  </div>
@stop

The blog.layouts.index view will show the blog index pages. It wraps the page-header in it’s own section. The content loops through the $posts and displays navigation afterward.

The blog.layouts.post view

Next we’ll create the view to show a particular post. Create post.blade.php view in resources/views/blog/layouts with the contents below.

Content of blog.layouts.post view

@extends('blog.layouts.master', [
  'title' => $post->title,
  'meta_description' => $post->meta_description ?: config('blog.description'),
])

@section('page-header')
  <header class="intro-header"
          style="background-image: url('{{ page_image($post->page_image) }}')">
    <div class="container">
      <div class="row">
        <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
          <div class="post-heading">
            <h1>{{ $post->title }}</h1>
            <h2 class="subheading">{{ $post->subtitle }}</h2>
            <span class="meta">
              Posted on {{ $post->published_at->format('F j, Y') }}
              @if ($post->tags->count())
                in
                {!! join(', ', $post->tagLinks()) !!}
              @endif
            </span>
          </div>
        </div>
      </div>
    </div>
  </header>
@stop

@section('content')

  {{-- The Post --}}
  <article>
    <div class="container">
      <div class="row">
        <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
          {!! $post->content_html !!}
        </div>
      </div>
    </div>
  </article>

  {{-- The Pager --}}
  <div class="container">
    <div class="row">
      <ul class="pager">
        @if ($tag && $tag->reverse_direction)
          @if ($post->olderPost($tag))
            <li class="previous">
              <a href="{!! $post->olderPost($tag)->url($tag) !!}">
                <i class="fa fa-long-arrow-left fa-lg"></i>
                Previous {{ $tag->tag }} Post
              </a>
            </li>
          @endif
          @if ($post->newerPost($tag))
            <li class="next">
              <a href="{!! $post->newerPost($tag)->url($tag) !!}">
                Next {{ $tag->tag }} Post
                <i class="fa fa-long-arrow-right"></i>
              </a>
            </li>
          @endif
        @else
          @if ($post->newerPost($tag))
            <li class="previous">
              <a href="{!! $post->newerPost($tag)->url($tag) !!}">
                <i class="fa fa-long-arrow-left fa-lg"></i>
                Next Newer {{ $tag ? $tag->tag : '' }} Post
              </a>
            </li>
          @endif
          @if ($post->olderPost($tag))
            <li class="next">
              <a href="{!! $post->olderPost($tag)->url($tag) !!}">
                Next Older {{ $tag ? $tag->tag : '' }} Post
                <i class="fa fa-long-arrow-right"></i>
              </a>
            </li>
          @endif
        @endif
      </ul>
    </div>

  </div>
@stop

Like the blog.layouts.index view this one has a page-header and a content section.

That’s it for the layouts.

The blog.partials.page-nav view

Create a partials directory in the resources/views/blog folder. Inside it, create a page-nav.blade.php file with the following.

Content of the blog.partials.page-nav

{{-- Navigation --}}
<nav class="navbar navbar-default navbar-custom navbar-fixed-top">
  <div class="container-fluid">
    {{-- Brand and toggle get grouped for better mobile display --}}
    <div class="navbar-header page-scroll">
      <button type="button" class="navbar-toggle" data-toggle="collapse"
              data-target="#navbar-main">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="/">{{ config('blog.name') }}</a>
    </div>

    {{-- Collect the nav links, forms, and other content for toggling --}}
    <div class="collapse navbar-collapse" id="navbar-main">
      <ul class="nav navbar-nav">
        <li>
          <a href="/">Home</a>
        </li>
      </ul>
    </div>
  </div>
</nav>

The menu on the navbar at top will only have a single option, Home.

Finally, create a page-footer.blade.php file in the same directory with the content below.

Content of blog.partials.page-footer

<hr>
<footer>
  <div class="container">
    <div class="row">
      <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
        <p class="copyright">Copyright © {{ config('blog.author') }}</p>
      </div>
    </div>
  </div>
</footer>

That should wrap up the views.

Adding a Few Model Methods

In order for the views to work, there’s a few Model methods that need to be added.

Update the Tag Model

Update app/Tag.php, adding the method below to the Tag class.

Updates to the Tag Model

  /**
   * Return the index layout to use for a tag
   *
   * @param string $tag
   * @param string $default
   * @return string
   */
  public static function layout($tag, $default = 'blog.layouts.index')
  {
      $layout = static::whereTag($tag)->pluck('layout');

      return $layout ?: $default;
  }

The layout() method returns a Tag’s layout, or if there isn’t a tag, or the tag doesn’t have a layout, then a default value is returned.

Update the Post Model

Update app/Post.php, adding the use statement and the four methods below to the Post class.

Updates to the Post Model

<?php
// Add the following use statement with the others at the top of the file
use Carbon\Carbon;

// Add the 4 methods below to the class
  /**
   * Return URL to post
   *
   * @param Tag $tag
   * @return string
   */
  public function url(Tag $tag = null)
  {
    $url = url('blog/'.$this->slug);
    if ($tag) {
      $url .= '?tag='.urlencode($tag->tag);
    }

    return $url;
  }

  /**
   * Return array of tag links
   *
   * @param string $base
   * @return array
   */
  public function tagLinks($base = '/blog?tag=%TAG%')
  {
    $tags = $this->tags()->lists('tag');
    $return = [];
    foreach ($tags as $tag) {
      $url = str_replace('%TAG%', urlencode($tag), $base);
      $return[] = '<a href="'.$url.'">'.e($tag).'</a>';
    }
    return $return;
  }

  /**
   * Return next post after this one or null
   *
   * @param Tag $tag
   * @return Post
   */
  public function newerPost(Tag $tag = null)
  {
    $query =
      static::where('published_at', '>', $this->published_at)
        ->where('published_at', '<=', Carbon::now())
        ->where('is_draft', 0)
        ->orderBy('published_at', 'asc');
    if ($tag) {
      $query = $query->whereHas('tags', function ($q) use ($tag) {
        $q->where('tag', '=', $tag->tag);
      });
    }

    return $query->first();
  }

  /**
   * Return older post before this one or null
   *
   * @param Tag $tag
   * @return Post
   */
  public function olderPost(Tag $tag = null)
  {
    $query =
      static::where('published_at', '<', $this->published_at)
        ->where('is_draft', 0)
        ->orderBy('published_at', 'desc');
    if ($tag) {
      $query = $query->whereHas('tags', function ($q) use ($tag) {
        $q->where('tag', '=', $tag->tag);
      });
    }

    return $query->first();
  }

url()
This method returns the url to the particular post with an optional tag in the query string. The blog.layouts.index view uses it to link to a post details page.
tagLinks()
This method returns an array of links, each link going to the index page for a particular tag the post has been, uh, tagged with.
newerPost()
Returns the next Post coming after $this or null if there are no newer posts.
olderPost()
Returns the previous Post coming before $this or null if there are no older posts.

Updating the Blog Config

Update the config/blog.php file so it looks similar to the one below. Note, use your own values here.

The Blog Config File

<?php
return [
  'name' => "L5 Beauty",
  'title' => "Laravel 5.1 Beauty",
  'subtitle' => 'A clean blog written in Laravel 5.1',
  'description' => 'This is my meta description',
  'author' => 'Chuck Heintzelman',
  'page_image' => 'home-bg.jpg',
  'posts_per_page' => 10,
  'uploads' => [
    'storage' => 'local',
    'webpath' => '/uploads/',
  ],
];

Again, use your own values here. Especially the uploads section. If you’re using Amazon S3 this will look different.

Updating our Sample Data

Back in Chapter 10, when we created the 10 Minute Blog, we set up the Database Seeder to generate random data using Model Factories.

But now, the database has changed.

Let’s update the seeder and factories and re-seed the database to populate tags and other fields.

Updating the Database Seeders

In the database/seeds directory there’s currently a single file named DatabaseSeeder.php. Edit this to match what’s below and then create the additional two files after it.

Content of DatabaseSeeder.php

.langage=php

<?php

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

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

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

    Model::reguard();
  }
}

In the same directory create TagTableSeeder.php with the following content.

Content of TagTableSeeder

<?php

use App\Tag;
use Illuminate\Database\Seeder;

class TagTableSeeder extends Seeder
{
  /**
   * Seed the tags table
   */
  public function run()
  {
    Tag::truncate();

    factory(Tag::class, 5)->create();
  }
}

Next created PostTableSeeder.php in the same directory with the following.

Content of PostTableSeeder.php

<?php

use App\Post;
use App\Tag;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class PostTableSeeder extends Seeder
{
  /**
   * Seed the posts table
   */
  public function run()
  {
    // Pull all the tag names from the file
    $tags = Tag::lists('tag')->all();

    Post::truncate();

    // Don't forget to truncate the pivot table
    DB::table('post_tag_pivot')->truncate();

    factory(Post::class, 20)->create()->each(function ($post) use ($tags) {

      // 30% of the time don't assign a tag
      if (mt_rand(1, 100) <= 30) {
        return;
      }

      shuffle($tags);
      $postTags = [$tags[0]];

      // 30% of the time we're assigning tags, assign 2
      if (mt_rand(1, 100) <= 30) {
        $postTags[] = $tags[1];
      }

      $post->syncTags($postTags);
    });
  }
}

This last seeder is a tiny bit longer because we randomly tie some of the tags to the posts.

Updating the Model Factories

Now update the Model Factories by editing ModelFactory.php in the database/factories directory to match what’s below.

Content of ModelFactory.php

<?php

$factory->define(App\User::class, function ($faker) {
  return [
    'name' => $faker->name,
    'email' => $faker->email,
    'password' => str_random(10),
    'remember_token' => str_random(10),
  ];
});

$factory->define(App\Post::class, function ($faker) {
  $images = ['about-bg.jpg', 'contact-bg.jpg', 'home-bg.jpg', 'post-bg.jpg'];
  $title = $faker->sentence(mt_rand(3, 10));
  return [
    'title' => $title,
    'subtitle' => str_limit($faker->sentence(mt_rand(10, 20)), 252),
    'page_image' => $images[mt_rand(0, 3)],
    'content_raw' => join("\n\n", $faker->paragraphs(mt_rand(3, 6))),
    'published_at' => $faker->dateTimeBetween('-1 month', '+3 days'),
    'meta_description' => "Meta for $title",
    'is_draft' => false,
  ];
});

$factory->define(App\Tag::class, function ($faker) {
  $images = ['about-bg.jpg', 'contact-bg.jpg', 'home-bg.jpg', 'post-bg.jpg'];
  $word = $faker->word;
  return [
    'tag' => $word,
    'title' => ucfirst($word),
    'subtitle' => $faker->sentence,
    'page_image' => $images[mt_rand(0, 3)],
    'meta_description' => "Meta for $word",
    'reverse_direction' => false,
];
});

Seeding the Database

Now seed the database. First we’ll dump the autoloader to make sure the new Seed classes are known.

Dumping the Autoloader

~/Code/l5beauty% composer dumpauto
Generating autoload files

Then seed the database from the Homestead VM.

Seeding the Database

vagrant@homestead:~/Code/l5beauty$ php artisan db:seed
Seeded: TagTableSeeder
Seeded: PostTableSeeder

The Blog Is Completely Usable

Browse around. Look at posts, navigate back and forth. You’ll see screens similar to the one below.

Figure 13.1 - Example Home Page

Example Home Page

Recap

This chapter focused on cleaning up the blog pages. To do this we used the Clean Blog template by StartBootstrap as a model. Clean Blog was pulled in with Bower, then we made use of much of the assets as we built the index and post pages.

The blogging application is complete and usable. It’s time to start adding a few featurs to our blog.

In the next chapter we’ll add a “Contact Us” form as we delve into Laravel 5.1 Queues.

comments powered by Disqus