Laravel で とても簡易なCMSを作ってみた

## 前提

- AWS EC2
- Amazon Linux2
- Apache
- mysql 5.7
- composer
- Laravel 7.15

## 参考にさせていただいた記事

    [Laravel5.4でシンプルなCMSを作るチュートリアル](https://www.webopixel.net/php/1259.html)

こちらのほぼコピペとなりますが、
今回は Laravel のバージョンがことなり、若干手順が違ったので自分用のメモとしてまとめてみました。

## インストール

Laravel をインストール(プロジェクトを作成)します。
※スペックにもよりますが、ちょこっと時間かかります。

```bash
$ composer create-project --prefer-dist laravel/laravel SimpleCMS
```

## 権限設定

Laravel プロジェクトの権限設定

```bash
$ cd SimpleCMS
$ chmod -R 777 ./bootstrap/cache
$ chmod -R 777 ./storage
```

## Apache の設定

設定ファイルの作成

```bash
$ sudo vi /etc/httpd/conf.d/SimpleCMS.conf
```

```SimpleCMS.conf
NameVirtualHost *:80
<VirtualHost *:80>
    #ドキュメントルート
    DocumentRoot /home/ec2-user/SimpleCMS/public

    #グローバルIPアドレス
    ServerName xxx.xxx.xxx.xxx

    #/home/ec2-user/SimpleCMS/publicの設定
    <Directory "/home/ec2-user/SimpleCMS/public">

        #.htaccessを利用可能にする
        AllowOverride All

        #アクセス許可
        Require all granted
    </Directory>
</VirtualHost>
```

```Apache再起動
$ sudo systemctl restrat httpd
```

## ここまでの確認 インストール ～ Apache の設定

ブラウザでグローバルアドレスを入力してインストール～設定までが上手くいっているか確認します。
こんな感じの画面が表示されれば OK

![確認.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/353569/1d839f6e-fe90-e93a-3856-516bf4c3d636.png)


## Laravel プロジェクトの初期設定

config の app.php は言語の設定だけ変更。

```./config/app.php
    /*
    |--------------------------------------------------------------------------
    | Application Timezone
    |--------------------------------------------------------------------------
    |
    | Here you may specify the default timezone for your application, which
    | will be used by the PHP date and date-time functions. We have gone
    | ahead and set this to a sensible default for you out of the box.
    |
    */
    // 'timezone' => 'UTC',
    'timezone' => 'Asia/Tokyo',

    /*
    |--------------------------------------------------------------------------
    | Application Locale Configuration
    |--------------------------------------------------------------------------
    |
    | The application locale determines the default locale that will be used
    | by the translation service provider. You are free to set this value
    | to any of the locales which will be supported by the application.
    |
    */
    // 'locale' => 'en',
    'locale' => 'ja',
```

DB 周りの設定を設定しておく

```./.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=SimpleCmsDB
DB_USERNAME=cmsuser
DB_PASSWORD=cms1234
```

## MySQL に DB を立ち上げる

さっき設定した「.env」に基づいて次のコマンドを実行

- DB の立ち上げ
- ユーザーの追加

```mysql
mysql> create database SimpleCmsDB;
mysql> grant all on SimpleCmsDB.* to 'cmsuser'@localhost identified by 'cms1234';
```

登録状況の確認

```mysql
mysql> select Host, User from mysql.user;
+-----------+---------------+
| Host      | User          |
+-----------+---------------+
| localhost | cmsuser       |
+-----------+---------------+
```

ログインできるかも確認しておきましょう。

## ユーザー認証関係

laravel/ui をインストール

```bash
$ composer require laravel/ui
```

Login 機能の実装＆テーブル作成

```bash
$ php artisan ui vue --auth
$ php artisan migrate
```

ブラウザから確認してみましょう。
右上にログインっぽい画面が増えてるはずです。

![ユーザー認証_確認.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/353569/950deb0c-209e-3365-bef9-5e9d74f10ba1.png)


DB の状態が気になるなら mysql にログインして確認してみよう。

```mysql
mysql > USE SimpleCmsDB;
mysql > SHOW TABLES;
```

## Post モデルの作成

「make:model」でモデルを作成。
「-m」オプションでマイグレーションファイルも一緒に作成してくれます。

```bash
$ php artisan make:model Post -m
```

作成したマイグレーションファイルに title,body を追記します。
XXXX には作成日のタイムスタンプが自動的に入ります。

```./database/migrations/XXXX_XX_XX_XXXXXX_create_posts_table.php
public function up()
{
    Schema::create('posts', function (Blueprint $table) {
        $table->increments('id');
        $table->string('title');
        $table->text('body');
        $table->timestamps();
    });
}
```

「migrate」で posts テーブルを作成します。
マイグレート。上で設定したテーブルを作ってくれます。

```bash
$ php artisan migrate
```

状態が気になるなら mysql にログインして確認してみよう。

```
mysql> USE SimpleCmsDB;
mysql> DESCRIBE posts
```

Post モデルに guarded を設定します。
「\$guarded」 とは ブラックリスト、複数代入時に代入を許可しない属性のことらしい、配列で設定する。

```./app/Post.php
class Post extends Model
{
    protected $guarded = ['id', 'created_at'];
}
```

## ダミーデータの作成

今回は二つの方法を紹介
どちらにせよ「シーダー(Seeder)」ってのを使ってダミーデータを作成します。
どっちでやってもいいです。

### シーダー(Seeder)に直接データを書く方法

まずシーダーを作成

```bash
$ php artisan make:seeder PostsTableSeeder
```

作成した「PostsTableSeeder」に直接書いていきます。
たぶん 10 件くらいあればいいでしょう…。

```./database/seeds/PostsTableSeeder.php
public function run()
{
    DB::table('posts')->insert([
        [
            'title' => '1番目の記事',
            'body' => '1番目の記事のテキストです。',
            'created_at' => '2017-05-02 14:28:19',
            'updated_at' => '2017-05-02 14:28:19'
        ],[
            'title' => '2番目の記事',
            'body' => '2番目の記事のテキストです。',
            'created_at' => '2017-05-03 14:28:19',
            'updated_at' => '2017-05-03 14:28:19'
        ]
        // ...
    ]);
}
```

データを書いたら「database/seeds/DatabaseSeeder.php」に登録して実行します。

```./database/seeds/DatabaseSeeder.php
public function run()
{
    $this->call(PostsTableSeeder::class);
}
```

db:seed コマンドでデータベースに投入します。

```bash
$ php artisan db:seed --class=PostsTableSeeder
```

確認してみるとこんな感じ。

```
mysql> Select * From posts;
+----+-------------------+-------------------------------------------+---------------------+---------------------+
| id | title             | body                                      | created_at          | updated_at          |
+----+-------------------+-------------------------------------------+---------------------+---------------------+
|  1 | 最初の記事         | 最初の記事のテキストです。                  | 2017-05-02 14:28:19 | 2017-05-02 14:28:19 |
|  2 | 2番目の記事        | 2番目の記事のテキストです。                 | 2017-05-03 14:28:19 | 2017-05-03 14:28:19 |
|  3 | 3番目の記事        | 3番目の記事のテキストです。                 | 2017-05-03 14:28:19 | 2017-05-03 14:28:19 |
|  4 | 4番目の記事        | 4番目の記事のテキストです。                 | 2017-05-03 14:28:19 | 2017-05-03 14:28:19 |
|  5 | 5番目の記事        | 5番目の記事のテキストです。                 | 2017-05-03 14:28:19 | 2017-05-03 14:28:19 |
|  6 | 6番目の記事        | 6番目の記事のテキストです。                 | 2017-05-03 14:28:19 | 2017-05-03 14:28:19 |
|  7 | 7番目の記事        | 7番目の記事のテキストです。                 | 2017-05-03 14:28:19 | 2017-05-03 14:28:19 |
|  8 | 8番目の記事        | 8番目の記事のテキストです。                 | 2017-05-03 14:28:19 | 2017-05-03 14:28:19 |
|  9 | 9番目の記事        | 9番目の記事のテキストです。                 | 2017-05-03 14:28:19 | 2017-05-03 14:28:19 |
| 10 | 10番目の記事       | 10番目の記事のテキストです。                | 2017-05-03 14:28:19 | 2017-05-03 14:28:19 |
+----+-------------------+-------------------------------------------+---------------------+---------------------+
```

### モデルファクトリ(ModelFactory)で自動的に作成する。

まずファクトリを作ります。

```bash
$ php artisan make:factory PostFactory --model=Post
```

作成したファクトリにどんなテストデータがいいか書き込んでいきましょう。

```./databse/factory/PostFactory.php
$factory->define(Post::class, function (Faker $faker) {
    return [
        'title' => $faker->sentence(rand(1, 2)),
        'body' => $faker->realText(128),
        'created_at' => $faker->dateTimeBetween('1day', '1year')->format('Y-m-d H:i'),
        'updated_at' => $faker->dateTimeBetween('1day', '1year')->format('Y-m-d H:i'),
    ];
});
```

ファクトリができたら今度はシーダーにダミーデータを何件作成するか書いていきます。

```./database/seeds/PostsTableSeeder.php
public function run()
{
    factory(App\Post::class, 10)->create();
}
```

DatabaseSeeder に実行登録（既にやってたら不要）。

```./database/seeds/DatabaseSeeder.php
public function run()
{
    $this->call(PostsTableSeeder::class);
}
```

db:seed コマンドでデータベースに投入します。

```
$ php artisan db:seed --class=PostsTableSeeder
```

テーブルをリフレッシュ（初期化）して投入したい場合はこちら。

```
php artisan migrate:fresh --seed
```

## 管理画面用 Posts コントローラーの作成

管理画面用のコントローラーを作成します。
「make:controller」コマンドでコントローラーの雛形を作成。

「SimpleCMS/app/Http/Controllers/」に「Admin/PostsController.php」が作成されます。

```./app/Http/Controllers/Admin/PostsController.php
namespace App\Http\Controllers\Admin;
 
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Post;
 
class PostsController extends Controller
{
    // バリデーションのルール
    public $validateRules = [
        'title' => 'required',
        'body' => 'max:500'
    ];
    public function index()
    {
        $posts = Post::orderBy('id', 'desc')->paginate(20);
        return view('admin.posts.index', compact('posts'));
    }
    public function create()
    {
        return view('admin.posts.create');
    }
    public function store(Request $request)
    {
        $this->validate($request, $this->validateRules);
        Post::create($request->all());
        \Session::flash('flash_message', '記事を作成しました。');
        return redirect('admin/posts');
    }
    public function show($id)
    {
        $post = Post::findOrFail($id);
        return view('admin.posts.show', compact('post'));
    }
    public function edit($id)
    {
        $post = Post::findOrFail($id);
        return view('admin.posts.edit', compact('post'));
    }
    public function update(Request $request, $id)
    {
        $this->validate($request, $this->validateRules);
 
        $post = Post::findOrFail($id);
        $post->update($request->all());
 
        \Session::flash('flash_message', '記事を更新しました。');
        return redirect('admin/posts');
    }
    public function destroy($id)
    {
        $post = Post::findOrFail($id);
        $post->delete($id);
        \Session::flash('flash_message', '記事を削除しました。');
        return redirect('admin/posts');
    }
}
```

## ビューの作成(準備)

### LaravelCollective Form ファサードをインストール

これ何？って思ったらこちらを見てください。
なんとなくイメージ湧くし、今後も役に立ちます。

[LaravelCollective Form ファサード チートシート](https://qiita.com/zushi_0905/items/a21212a205b755cff2db)

```インストール
$ composer require laravelcollective/html
```

```適用させるために設定追加
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
...
*/
'providers' => [
    ...
    // 末尾に追加
    Collective\Html\HtmlServiceProvider::class,
],
/*
|--------------------------------------------------------------------------
| Class Aliases
|--------------------------------------------------------------------------
...
*/
'aliases' => [
    ...
    // 末尾に追加
    'Form' => Collective\Html\FormFacade::class,
    'Html' => Collective\Html\HtmlFacade::class,
],
```

「管理画面用 Posts コントローラーの作成」で作成したコントローラーの中には、バリデーション設定も記述されていたので日本語で表示できるようにしてみましょう。
「resources/lang/ja/」に「validation.php」というファイルを作成し、下記のソースをコピペします。
※もちろんダウンロードして格納しても OK

[oppara/validation.php](https://gist.github.com/oppara/5160fcac9db0e3f8fbaf71da7ade10be)

attributes の日本語も設定します。

```resources/lang/ja/validation.php
'attributes' => [
    'title' => 'タイトル',
    'body' => '内容'
],
```

## 管理画面用 Posts ビューの作成

管理画面表示部分のビューを作成です。

最初にレイアウトファイルとして
「resources/views/layouts/」の「app.blade.php」をコピーして
「resources/views/layouts/admin.blade.php」を作成します。

## 一覧ページの作成

次に「resources/views/admin/posts/」フォルダを作成し「index.blade.php」を作成します。

```./resources/views/admin/posts/index.blade.php
@extends('layouts.admin')
@section('content')

<div class="container">
    <div class="row">
        <div class="col-md-12">
            <div class="panel panel-default">
                <div class="panel-heading">記事一覧</div>
                <div class="panel-body">
                    @if (Session::has('flash_message'))
                        <div class="alert alert-success">{{ Session::get('flash_message') }}</div>
                    @endif

                    <div class="mb10">
                        {!! link_to('admin/posts/create', '新規作成', ['class' => 'btn btn-primary']) !!}
                    </div>

                    <table class="table table-striped table-bordered table-hover">
                        <thead>
                        <tr>
                            <th>ID</th>
                            <th>タイトル</th>
                            <th>作成日</th>
                            <th>編集</th>
                        </tr>
                        </thead>
                        @foreach($posts as $post)
                            <tr>
                                <td>{{ $post->id }}</td>
                                <td>{{ $post->title }}</td>
                                <td>{{ $post->created_at->format('Y年m月d日') }}</td>
                                <td>
                                    {!! link_to_action('Admin\PostsController@show', '表示', [$post->id]) !!}
                                    {!! link_to_action('Admin\PostsController@edit', '編集', [$post->id]) !!}
                                    {!! Form::model($post,
                                    ['url' => [
                                        'admin/posts', $post->id],
                                        'method' => 'delete',
                                        'class' => 'delete-from'
                                    ]) !!}
                                        {!! Form::submit('削除', [
                                            'onclick' => "return confirm('本当に削除しますか?')",
                                            'class' => 'text-link'
                                            ]) !!}
                                    {!! Form::close() !!}
                                </td>
                            </tr>
                        @endforeach
                    </table>
                    {!! $posts->render() !!}
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
```

## 詳細ページの作成

同じく「resources/views/admin/posts/」に「show.blade.php」を作成します。

```./resources/views/admin/posts/show.blade.php
@extends('layouts.admin')
@section('content')

<div class="container">
    <div class="row">
        <div class="col-md-12">
            <div class="panel panel-default">
                <div class="panel-heading">記事詳細</div>
                <div class="panel-body">
                    <div>
                        <h1>{{ $post->title }}</h1>
                        <p>{{ $post->body }}</p>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
```

## 記事入力フォームの共通部作成

入力フォームは新規と編集ページ共通なので、それぞれの View から独立させて作成します
「resources/views/admin/posts/」に「fields.blade.php」を作成します。

```./resources/views/admin/posts/fields.blade.php
<div class="form-group">
    {!! Form::label('title', 'タイトル:', ['class' => 'col-sm-2 control-label']) !!}
    <div class="col-sm-10">
        {!! Form::text('title', null, ['class' => 'form-control']) !!}
    </div>
</div>

<div class="form-group">
    {!! Form::label('body', '内容:', ['class' => 'col-sm-2 control-label']) !!}
    <div class="col-sm-10">
        {!! Form::textarea('body', null, ['class' => 'form-control']) !!}
    </div>
</div>

<div class="form-group">
    <div class="col-sm-10 col-sm-offset-2">
        {!! Form::submit('保存', ['class' => 'btn btn-primary']) !!}
        {!! link_to('admin/posts', '一覧へ戻る', ['class' => 'btn btn-default']) !!}
    </div>
</div>
```

## 投稿フォームの作成

同じく「resources/views/admin/posts/」に「create.blade.php」を作成します。
25 行目あたりに先ほどの共通部をインクルードしています。

```./resources/views/admin/posts/create.blade.php
@extends('layouts.admin')
@section('content')

<div class="container">
    <div class="row">
        <div class="col-md-12">
            <div class="panel panel-default">
                <div class="panel-heading">新規作成</div>
                <div class="panel-body">

                    @if ($errors->any())
                        <div class="alert alert-danger">
                            <ul>
                                @foreach ($errors->all() as $error)
                                    <li>{{ $error }}</li>
                                @endforeach
                            </ul>
                        </div>
                    @endif

                    {!! Form::open(['url' => 'admin/posts',
                                'class' => 'form-horizontal',
                                'id' => 'post-input']) !!}

                    @include('admin.posts.fields')

                    {!! Form::close() !!}
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
```

## 編集フォームの作成

同じく「resources/views/admin/posts/」に「create.blade.php」を作成します。
投稿フォームと内容はほぼ変わりません。

```./resources/views/admin/posts/edit.blade.php
@extends('layouts.admin')
@section('content')

<div class="container">
    <div class="row">
        <div class="col-md-12">
            <div class="panel panel-default">
                <div class="panel-heading">編集</div>
                <div class="panel-body">

                    @if ($errors->any())
                        <div class="alert alert-danger">
                            <ul>
                                @foreach ($errors->all() as $error)
                                    <li>{{ $error }}</li>
                                @endforeach
                            </ul>
                        </div>
                    @endif

                    {!! Form::model($post,
                    ['url' => [
                        'admin/posts', $post->id],
                        'method' => 'PATCH',
                        'class' => 'form-horizontal',
                        'id' => 'post-input'

                    ]) !!}
                    @include('admin.posts.fields')

                    {!! Form::close() !!}
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
```

## ルーターの作成

ルーターの設定をしてアクセスできるようにします。

管理画面なので admin プレフィクスを付けます。
middleware に auth を指定してすべて認証が必要なようにします。

※ルーターって？：URL とコントローラーのアクションを対応付けするファイル

「./routes/web.php」を修正します。

```./routes/web.php
Route::group(['prefix' => 'admin', 'middleware' => 'auth'], function () {
    Route::resource('posts', 'Admin\PostsController');
});
```

「/admin/posts」 にアクセスして動作を確認してみてください。
作成・編集など一通りの動作ができるようになったので管理画面の方は完了です。

## フロント側の作成

フロントに表示されるページを作成します。
編集機能はいらないので、一覧・詳細ページを作成します。

「./app/Http/Controllers/PostsController.php」を作成しましょう。

```./app/Http/Controllers/PostsController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Post;

class PostsController extends Controller
{
    public function index()
    {
        $posts = Post::orderBy('id', 'desc')->paginate(20);
        return view('posts.index', compact('posts'));
    }
    public function show($id)
    {
        $post = Post::findOrFail($id);
        return view('posts.show', compact('post'));
    }
}

```

ルーターにも追記します。

```./routes/web.php
Route::resource('posts', 'PostsController', ['only' => [
    'index', 'show'
]]);
```

ビューもコントローラーと同じように
「/resources/views/posts/index.blade.php」と
「/resources/views/posts/show.blade.php」を作成します。

※Admin のものをコピペして改修してみてください

ブートストラップスキャフォールディングが正常にインストールされました。
「npm install && npm run dev」を実行して、新しい足場をコンパイルしてください。