一步一步用laravel开发回风博客系统-09-首页皮肤

原作者网站:http://laravelcoding.com/blog?tag=L5+Beauty
可以参考中文站点:http://laravelacademy.org/resources/blog

我是基于5.2的,而且有些东西我觉得有必要有的没必要,所以思路是跟着以上两个参考着搞,具体还是有区别的,我最终代码放在了Github:https://github.com/wedojava/hfblog.dev

由于正在补英语,所以有些我能看懂的就没从原作者那里翻译。

之前,我们实现了tag标签的增删改查,post的增删改查以及和tag的关联与同步,本节,我们让首页面美观起来。我们选择的是 CleanBlog

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

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

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

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

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

php artisan make:job BlogIndexData

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\Jobs\Job;
use App\Post;
use Carbon\Carbon;
use Illuminate\Contracts\Bus\SelfHandling;

class BlogIndexData extends Job implements SelfHandling
{

protected $tag;
/**
* Create a new job instance.
*
* @return void
*/

public function __construct($tag)
{

$this->tag = $tag;
}

/**
* Execute the job.
*
* @return void
*/

public function handle()
{

if ($this->tag) {
return $this->tagIndexData($this->tag);
}

return $this->normalIndexData();
}

protected function normalIndexData()
{

$post = Post::with('tags')
->where('publish_at', '<=', Carbon::now())
->where('is_draft', 0)
->orderBy('publish_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_directions ?
'reverse_direction' => false,
'tag' => null,
];
}

protected function tagIndexData($tag)
{

$tag = Tag::where('tag', $tag)->firstOrFail();
$reverse_direction = (bool)$tag->reverse_direction;

$posts = Post::where('publish_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 ‘metadescription’ is returned should not be typed, instead continue typing the next line without hitting enter!

Notice all the extra data we’re returning? Before, 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\Http\Controllers\Controller;
use App\Http\Requests;
use App\Jobs\BlogIndexData;
use App\Post;
use App\Tag;
use Carbon\Carbon;
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($id, Request $request)
{

$post = Post::with('tags')->whereId($id)->firstOrFail();
$tag = $request->get('tag');

if ($tag) {
$tag = Tag::whereTag($tag)->firstOrFail();
}

return view($post->layout, compact('posts','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');

/**
* 拷贝任何需要的文件
*
* Do a 'gulp copyfiles' after bower updates
*/

gulp.task("copyfiles", function() {

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/font-awesome/less/**")
.pipe(gulp.dest("resources/assets/less/fontawesome"));

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

// 拷贝 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"));

});

/*
|--------------------------------------------------------------------------
| Elixir Asset Management
|--------------------------------------------------------------------------
|
| Elixir provides a clean, fluent API for defining some basic Gulp tasks
| for your Laravel application. By default, we are compiling the Sass
| file for our application, as well as publishing vendor resources.
|
*/


elixir(function(mix) {
// mix.sass('app.scss');
// mix.phpUnit();
// 合并脚本文件
// 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. 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.

<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

  • 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' => "HFBlog",
'title' => "My First Blog!",
'subtitle' => 'A clean blog written in Laravel 5.2',
'description' => 'This is my meta description',
'author' => '回风',
'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

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

<?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.

php artisan make:seeder TagTableSeeder

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 modified 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,
];
});

laravel 5.2 版本会遇到的问题

现在,我们可以看到美观的blog页面了,但是你会发现,如果你是 laravel 5.2 会遇到一个问题,点击文章的标签会报错: Illegal offset type in isset or empty

我去原作者的网站去看是否有同样的问题,找到了答案:
http://laravelcoding.com/blog/laravel-5-beauty-cleaning-up-the-blog#comment-2443202888
Gildas Niyigena 的版本也是 5.2 的,遇到了和我同样的问题,他找到了问题所在并且解决了问题,问题出在:
$layout 需要的是 string 类型,但是 static::whereTag($tag)->pluck('layout'); 实际上返回的是一个 array , 问题出在这里了,Gildas Niyigena 给出的解决办法是:

Tag.php 里:

$layout = static::whereTag($tag)->pluck('layout');

的下面添加一行提取 array 的内容为 string 的处理:

$layout = array_values($layout->toArray())[0];

当然这不是最好的办法,作者给出了回复,这个方法更好:
修改 Tag.php 里的 layout() 方法为:

public static function layout($tag, $default = 'blog.layouts.index')
{
$tagModel = static::whereTag($tag)->first();
return $tagModel ? $tagModel->layout : $default;
}

OK, 至此,问题解决了,目前没有什么问题了。