10
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Laravel5 チュートリアル ブログもどきを作る(7) フロントトップページ

Last updated at Posted at 2018-07-25

前回までは、ブログ記事投稿フォームなどの管理画面側を実装してきました。今回からは、主に投稿したブログ記事を表示する、フロント側の処理を実装していきます。まずは、ブログトップページの表示を実装します。

ルーティングの追加

ブログを表示するURL(ルーティング)を追加します。routes/web.php を開いて下記一行を追加します。

routes/web.php
Route::get('/', 'FrontBlogController@index')->name('front_index');

ブログトップページ、すなわち http://{ホスト名}/ と入力すれば、FrontBlogControllerindex メソッドを実行する、という設定にします。

コントローラーの作成

フロント側の処理を書いていくので、FrontBlogController という名前のコントローラークラスを作成します。Laravel ホームディレクトリに移動して、下記 artisan コマンドを打って、コントローラーを作成します。

php artisan make:controller FrontBlogController

そうすると、app/Http/Controllers/FrontBlogController.php ができるので、それを開いて下記のように編集します。

app/Http/Controllers/FrontBlogController.php
<?php

namespace App\Http\Controllers;

use App\Models\Article;

class FrontBlogController extends Controller
{
    /** @var Article */
    protected $article;

    // 1ページ当たりの表示件数
    const NUM_PER_PAGE = 10;

    function __construct(Article $article)
    {
        $this->article = $article;
    }

    /**
     * ブログトップページ
     *
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    function index()
    {
        // ブログ記事一覧を取得
        $list = $this->article->getArticleList(self::NUM_PER_PAGE);
        return view('front_blog.index', compact('list'));
    }
}

前回実装した、管理画面側の記事一覧ページを表示する処理とほぼ同じですね。

View テンプレートの作成

続いてブログトップページの View テンプレートを、継承を使って作成していきます。
まずは「親」テンプレートを作成します。resources/views/front_blog/app.blade.php ファイルを(front_blogディレクトリも含めて)作成して、下記のように編集します。

resources/views/front_blog/app.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>@yield('title')</title>
    <link rel="stylesheet" href="{{ asset('/css/bootstrap.min.css') }}">
    <link rel="stylesheet" href="{{ asset('/css/blog.css') }}">
</head>

<body>
@yield('body')
</body>
</html>

resources/views/admin_blog/app.blade.php と同じですね。

では、続いて「子」テンプレートを作成します。resources/views/front_blog/index.blade.php ファイルを作成して、下記のように編集します。

resources/views/front_blog/index.blade.php
@extends('front_blog.app')
@section('title', '私のブログ')

@section('body')
    <div class="container">
        <div class="row">
            <div class="col-md-10 col-md-offset-1">
                <h2><a href="{{ route('front_index') }}">私のブログ</a></h2>
            </div>

            <div class="col-md-8 col-md-offset-1">
                {{--forelse ディレクティブを使うと、データがあるときはループし、無いときは @empty 以下を実行する--}}
                @forelse($list as $article)
                <div class="panel panel-default">
                    <div class="panel-heading">
                        {{--post_date は日付ミューテタに設定してあるので、自動的に Carbon インスタンスにキャストされる--}}
                        <h3 class="panel-title">{{ $article->post_date->format('Y/m/d(D)') }} {{ $article->title }}</h3>
                    </div>
                    <div class="panel-body">
                        {{--nl2br 関数で改行文字を <br> に変換する。これをエスケープせずに表示させるため {!! !!} で囲む--}}
                        {{--ただし、このまま出力するととても危険なので e 関数で htmlspecialchars 関数を通しておく--}}
                        {!! nl2br(e($article->body)) !!}
                    </div>
                    <div class="panel-footer text-right">
                        {{--updated_at も日付ミューテタに設定してあるので Carbon インスタンスにキャストされる--}}
                        {{ $article->updated_at->format('Y/m/d H:i:s') }}
                    </div>
                </div>
                @empty
                    <p>記事がありません</p>
                @endforelse

                {{ $list->links() }}
            </div>
        </div>
    </div>
@endsection

ここまでできたら、http://{ホスト名}/ を開いてみて、ブログ記事が表示されるかどうか、試してみてください。

続いて、ブログの機能としてよく見られる、とある月に書かれたブログ記事をまとめて表示する、月別の表示(月別アーカイブ)を実装します。

月別アーカイブ用パラメータ

ルーティングはそのままで、下記のようにパラメータを付けることで、月別表示に対応します。

パラメータ名 意味 備考
year 対象年 対象年月を指定するときは必須
month 対象月 任意のパラメータとする

例えば、
http://{ホスト名}/?year=2018&month=7 なら2018年7月の記事を
http://{ホスト名}/?year=2017なら2017年の記事を表示するという意味です。
http://{ホスト名}/?month=7という month のみ指定するのはNGにしておきます。

リクエストクラスの作成

パラメータを扱うので、リクエストクラスを作成して、バリデーションの設定をします。Laravel ホームディレクトリで、下記 artisan コマンドを実行して、フロント用のリクエストクラス app/Http/Requests/FrontBlogRequest.php を作成します。

php artisan make:request FrontBlogRequest

作成されたファイルを開いて、下記のように編集します。

app/Http/Requests/FrontBlogRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class FrontBlogRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'year'  => 'integer',
            'month' => 'integer',
        ];
    }

    public function messages()
    {
        return [
            'year.integer'  => '年は整数にしてください',
            'month.integer' => '月は整数にしてください',
        ];
    }
}

対象年月パラメータが整数かどうかをチェックするだけの、シンプルなコードですね。

モデルの編集

続いて app/Models/Article.php を開いて getArticleList メソッドを下記のように編集し、getMonthList メソッドを追加します。

app/Models/Article.php
    /**
     * 記事リストを取得する
     *
     * @param  int   $num_per_page 1ページ当たりの表示件数
     * @param  array $condition    検索条件
     * @return Illuminate\Pagination\LengthAwarePaginator
     */
    public function getArticleList(int $num_per_page = 10, array $condition = [])
    {
    	// パラメータの取得
        $year  = array_get($condition, 'year');
        $month = array_get($condition, 'month');

        $query = $this->orderBy('article_id', 'desc');

        // 期間の指定
        if ($year) {
            if ($month) {
                // 月の指定がある場合はその月の1日を設定し、Carbonインスタンスを生成
                $start_date = Carbon::createFromDate($year, $month, 1);
                $end_date   = Carbon::createFromDate($year, $month, 1)->addMonth();     // 1ヶ月後
            } else {
                // 月の指定が無い場合は1月1日に設定し、Carbonインスタンスを生成
                $start_date = Carbon::createFromDate($year, 1, 1);
                $end_date   = Carbon::createFromDate($year, 1, 1)->addYear();           // 1年後
            }
            // Where句を追加
            $query->where('post_date', '>=', $start_date->format('Y-m-d'))
                  ->where('post_date', '<',  $end_date->format('Y-m-d'));
        }

        // paginate メソッドを使うと、ページネーションに必要な全件数やオフセットの指定などは全部やってくれる
        return $query->paginate($num_per_page);
    }

    /**
     * 月別アーカイブの対象月のリストを取得
     *
     * @return Illuminate\Database\Eloquent\Collection Object
     */
    public function getMonthList()
    {
        // selectRaw メソッドを使うと、引数にSELECT文の中身を書いてそのまま実行できる
        // 返り値はコレクション(Illuminate\Database\Eloquent\Collection Object)
        // コレクションとは配列データを操作するための便利なラッパーで、多種多様なメソッドが用意されている
        $month_list = $this->selectRaw('substring(post_date, 1, 7) AS year_and_month')
            ->groupBy('year_and_month')
            ->orderBy('year_and_month', 'desc')
            ->get();

        foreach ($month_list as $value) {
            // YYYY-MM をハイフンで分解して、YYYY年MM月という表記を作る
            list($year, $month) = explode('-', $value->year_and_month);
            $value->year  = $year;
            $value->month = (int)$month;
            $value->year_month = sprintf("%04d年%02d月", $year, $month);
        }
        return $month_list;
    }

getArticleList メソッドの引数に、検索条件を格納した配列を追加して、その中にパラメータ year month が存在すれば、クエリビルダの where メソッドで検索条件を追加していきます。
getMonthList メソッドは、既存記事の post_date から、何年の何月に記事の投稿があるか、そのリストを取得するメソッドです、下記のSQLを実行することで、ブログ記事を post_date の年月でまとめています。

SELECT
    substring(post_date, 1, 7) AS year_and_month -- substring で YYYY-MM の部分だけ切り出し
FROM
    articles
WHERE
    deleted_at is NULL
GROUP BY
    year_and_month
ORDER BY
    year_and_month desc

それから、getMonthList メソッドに出てくる「コレクション」ですが、「コレクション」とは、配列データを操作するための多くの便利なメソッドが定義されているラッパークラスです。
Eloquent で取得したレコードが複数ある場合は、このコレクションインスタンスに、その結果(レコード)が詰められた状態で返ってきます。詳細は日本語ドキュメントを参照してください。

コントローラーの編集

それでは、パラメータに対応した記事を取得して View テンプレートに渡す処理を実装していきます。app/Http/Controllers/FrontBlogController.php を開いて index メソッドを下記のように編集します。

app/Http/Controllers/FrontBlogController.php
use App\Http\Requests\FrontBlogRequest;

...中略...

    /**
     * ブログトップページ
     *
     * @param FrontBlogRequest $request
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    function index(FrontBlogRequest $request)
    {
        // パラメータを取得
        $input = $request->input();

        // ブログ記事一覧を取得
        $list = $this->article->getArticleList(self::NUM_PER_PAGE, $input);
        // ページネーションリンクにクエリストリングを付け加える
        $list->appends($input);
        // 月別アーカイブの対象月リストを取得
        $month_list = $this->article->getMonthList();
        return view('front_blog.index', compact('list', 'month_list'));
    }

変更点は、作成した FrontBlogRequest クラスを、メソッドの引数に指定して、フォームリクエストバリデーションを設定していることと、パラメータを取得して、getArticleList メソッドに渡していること、ページネーションリンクにクエリストリングを付け加える処理、月別アーカイブの対象月リストを取得して、テンプレートに渡しているところですね。
ページネーションリンクについてですが、year=2018 というクエリストリングを追加する場合は、下記のように設定します。

$list->appends(['year' => 2018]);

$input にはパラメータが連想配列で入っていて、これがそのままクエリストリングになればよいので、そのまま引数として渡しています($inputpageパラメータが含まれていても、うまく調整してくれるようです)。※2018/07/28追記

View テンプレートの編集

さて、View テンプレートですが、レイアウトを2カラムにしたいと思います。左カラムはメインカラムとして、各ページのコンテンツを表示し、右カラムはどのページでも表示するサブカラムとして、月別アーカイブのリンクなどの情報を表示したいと思います。

まずは、親テンプレートから編集していきます。resources/views/front_blog/app.blade.php を開いて、下記のように編集します。

resources/views/front_blog/app.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>@yield('title')</title>
    <link rel="stylesheet" href="{{ asset('/css/bootstrap.min.css') }}">
    <link rel="stylesheet" href="{{ asset('/css/blog.css') }}">
</head>

<body>
<div class="container">
    <div class="row">
        <div class="col-md-10 col-md-offset-1">
            <h2><a href="{{ route('front_index') }}">私のブログ</a></h2>

            {{--何らかのエラー表示用--}}
            @if ($errors->any())
                <div class="alert alert-danger">
                    <ul>
                        @foreach ($errors->all() as $error)
                            <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
            @endif
        </div>

        {{--メインカラム--}}
        @yield('main')
        {{--右サブカラム--}}
        {{--@include ディレクティブで他のテンプレートを読み込むこともできる--}}
        @include('front_blog.right_column')
    </div>
</div>
</body>
</html>

<body> タグ内のメインカラム部分だけを、子テンプレートの main セクションに記述するような形にしています。右サブカラムの HTML を、親テンプレートに書くと、なんとなく気持ちが悪いので、別テンプレートに記述して、それを @include ディレクティブで読み込む形にします。

では、resources/views/front_blog/right_column.blade.php を新規に作成して、右サブカラムのHTMLを書いていきます。

resources/views/front_blog/right_column.blade.php
{{--右カラム--}}
<div class="col-md-2">
    <div class="panel panel-default">
        <div class="panel-heading">
            <h3 class="panel-title">月別アーカイブ</h3>
        </div>
        <div class="panel-body">
            <ul class="monthly_archive">
                @forelse($month_list as $value)
                    <li>
                        <a href="{{ route('front_index', ['year' => $value->year, 'month' => $value->month]) }}">
                            {{ $value->year_month }}
                        </a>
                    </li>
                @empty
                    <p>記事がありません</p>
                @endforelse
            </ul>
        </div>
    </div>
</div>

ここでは、月別アーカイブを表示するHTMLを記述しました。

最後に resources/views/front_blog/index.blade.php を開いて、下記のように編集します。

resources/views/front_blog/index.blade.php
@extends('front_blog.app')
@section('title', '私のブログ')

@section('main')
    <div class="col-md-8 col-md-offset-1">
        {{--forelse ディレクティブを使うと、データがあるときはループし、無いときは @empty 以下を実行する--}}
        @forelse($list as $article)
            <div class="panel panel-default">
                <div class="panel-heading">
                    {{--post_date はモデルクラスで $dates プロパティに指定してあるので、自動的に Carbon インスタンスにキャストされる--}}
                    <h3 class="panel-title">{{ $article->post_date->format('Y/m/d(D)') }} {{ $article->title }}</h3>
                </div>
                <div class="panel-body">
                    {{--nl2br 関数で改行文字を <br> に変換する。これをエスケープせずに表示させるため {!! !!} で囲む--}}
                    {{--ただし、このまま出力するととても危険なので e メソッドで htmlspecialchars 関数を通しておく--}}
                    {!! nl2br(e($article->body)) !!}
                </div>
                <div class="panel-footer text-right">
                    {{--updated_at も同様に自動的に Carbon インスタンスにキャストされる--}}
                    {{ $article->updated_at->format('Y/m/d H:i:s') }}
                </div>
            </div>
        @empty
            <p>記事がありません</p>
        @endforelse

        {{ $list->links() }}
    </div>
@endsection

元々、<body> タグの中身が全て書いてあったので、それをメインカラムの部分だけに削減した形ですね。

ここまでできたら、実際に画面を表示して、ブログが表示されること、月別表示がちゃんと機能することを確認してください。うまくいけば、下記のような画面になります。(1ページあたりの表示件数を3件にしたときの表示です)

front_blog_top.png

次回は、カテゴリーを追加します。

参考資料

Laravel 日本語ドキュメント

Carbon

プログラムソース(github)

10
10
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?