Laravel で掲示板作りたくなった(2)

[前回](http://qiita.com/localdisk/items/915ab90606fcdd469458)の続き。

インスパイヤ元: [インスパイヤされて掲示板を作りたくなった(2)](http://qiita.com/tadsan/items/adaade6b675ac344f373)

# テーブルを作ろう

テーブルについては [元ネタ](http://qiita.com/tadsan/items/adaade6b675ac344f373) を参照。以下のテーブルを作成しよう。

- boards
- threads
- posts

## いらないファイルを消そう

`databases/migration` の `2014_10_12_000000_create_users_table.php` と `2014_10_12_100000_create_password_resets_table.php` を消しましょう。これは、Laravel の認証に使用するテーブルのマイグレーションファイルですが、今回は必要ないと思います(多分)。

## boards テーブルを作ろう

```shell-session
$ php artisan make:migration create_boards_table --create=boards
```

とすると `boards` テーブルを作成するマイグレーションファイルが出来上がります。`--create` オプションが肝[^1]です。

[^1]: `--create` オプションをつけるとテーブルを作る `Schema::create` メソッドが定義されたマイグレーションファイルが生成されます。

生成されたマイグレーションファイルはこんな感じ。

```php:database/migration/2016_03_01_120922_create_boards_table.php
<?php

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

class CreateBoardsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('boards', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('boards');
    }
}
```

マイグレーションファイルには `up` メソッドと `down` メソッドが定義されていて `up` メソッドは `migrate` コマンド(後述) を実行した時に呼び出されるメソッドで `down` メソッドは `migrate:rollback` コマンドを実行した時に呼び出されます。このままで `migrate` コマンドを実行するとこんな感じの SQL が発行されます。

```shell-session
$ php artisan migrate --pretend
Migration table created successfully.
CreateBoardsTable: create table "boards" ("id" varchar not null, "name" varchar not null, "txt" varchar not null, primary key ("id"))
```

`boards` テーブルにオートインクリメントされる `id` という名前のプライマリキーと `datetime` 型の `created_at` と `updated_at` というカラムが作成されるのがわかりますね。

`id` カラムは `$table->increments('id')` 、 `created_at` と `updated_at` は `$table->timestamps()` に対応してます。今回はインスパイヤ元に従ってにいきます。そういったサンプルがあんまりないのでいいかな、と。

ではテーブルを定義していきましょう。まずは `boards` テーブル。`up` メソッドだけ抜粋。

```php:database/migration/2016_03_01_120922_create_boards_table.php
<?php
class CreateBoardsTable extends Migration
{
    public function up()
    {
        Schema::create('boards', function (Blueprint $table) {
            $table->string('id')->primary();
            $table->string('name');
            $table->string('txt');
        });
    }
}
```

次は `threads` テーブル。

コマンド叩いて…

```shell-session
$ php artisan make:migration create_threads_table --create=threads
```

マイグレーションファイル作って…こう書く。

```php:database/migration/2016_03_01_125931_create_threads_table.php
<?php
class CreateThreadsTable extends Migration
{
    public function up()
    {
        Schema::create('threads', function (Blueprint $table) {
            $table->integer('timestamp');
            $table->string('board_id');
            $table->string('title');
            $table->primary(['timestamp', 'board_id']);
        });
    }
}
```

複合主キーも定義できます。とはいえ、前述した `Eloquent` は複合主キーをサポートしていないのでクエリービルダーという `Eloquent` の基盤になっている仕組みを使うことになります。

最後に `posts` テーブル。

```shell-session
$ php artisan make:migration create_posts_table --create=posts
```

```php:database/migration/2016_03_01_130821_create_posts_table.php
<?php
class CreatePostsTable extends Migration
{
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->increments('id');
            // 元エントリは INT ってなっているけど、多分 string
            $table->string('board_id');
            $table->integer('thread_timestamp');
            $table->string('posted_at');
            $table->string('name');
            $table->string('email');
            $table->string('author_hash');
            $table->string('message');
            $table->string('ip_addr');

            $table->index(['board_id', 'thread_timestamp'], 'post_board_thread');
            $table->index('author_hash', 'post_author_hash');
        });
    }
}
```

こんな感じで index も定義できる。


SQL を確認しよう。`--pretend` オプション便利。

```shell-session
$ php artisan migrate --pretend
CreateBoardsTable: create table "boards" ("id" varchar not null, "name" varchar not null, "txt" varchar not null, primary key ("id"))
CreateThreadsTable: create table "threads" ("timestamp" integer not null, "board_id" varchar not null, "title" varchar not null, primary key ("timestamp", "board_id"))
CreatePostsTable: create table "posts" ("id" integer not null primary key autoincrement, "board_id" varchar not null, "thread_timestamp" integer not null, "posted_at" varchar not null, "name" varchar not null, "email" varchar not null, "author_hash" varchar not null, "message" varchar not null, "ip_addr" varchar not null)
CreatePostsTable: create index post_board_thread on "posts" ("board_id", "thread_timestamp")
CreatePostsTable: create index post_author_hash on "posts" ("author_hash")
```

うむ。よさげだ。では実行。

```shell-session
$ php artisan migrate
Migrated: 2016_03_01_120922_create_boards_table
Migrated: 2016_03_01_125931_create_threads_table
Migrated: 2016_03_01_130821_create_posts_table
```

できた。楽ちん。

# シードデータをつっこんでみる

`php artisan make:seeder {name}` でシードクラス作れるんですが、量も少ないので `database/seeds/DatabaseSeeder.php` に直接書いちゃいましょう。

```php:database/seeds/DatabaseSeeder.php
<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('boards')->insert([
            'id'   => 'gline',
            'name' => 'ガイドライン＠インスパイヤー',
            'txt'  => 'ここがガ板ですよ。。。',
        ]);
        DB::table('threads')->insert([
            'timestamp' => 123456789,
            'board_id'  => 'gline',
            'title'     => 'インスパイヤのガイドライン',
        ]);
        $now = \Carbon\Carbon::now();
        DB::table('posts')->insert([
            [
                'board_id'         => 'gline',
                'thread_timestamp' => 123456789,
                'posted_at'        => $now,
                'name'             => 'たっどさん',
                'email'            => '#いんすぱいやー',
                'author_hash'      => 'tW1nDri11',
                'message'          => 'てすとです…',
                'ip_addr'          => '127.0.0.1',
            ],
            [
                'board_id'         => 'gline',
                'thread_timestamp' => 123456789,
                'posted_at'        => $now,
                'name'             => 'たっどさん',
                'email'            => '#いんすぱいやー',
                'author_hash'      => 'tW1nDri11',
                'message'          => 'もういい加減ねるぽ',
                'ip_addr'          => '127.0.0.1',
            ],
        ]);
    }
}
```

一目瞭然な簡潔なコードですね。`posts` テーブルの `posted_at` のために `Carbon` という日時を扱う便利なライブラリを使ってるのと `posts` テーブルのインサートで2レコード一気にいれてます。ちなみにこう書くとバルクインサートになるので、覚えておくのがよいです。

```bash:シードデータの挿入
$ php artisan db:seed
```

# 画面を作ろう

Laravel には `Blade` というテンプレートエンジンがあってですね、これがなかなか便利です。PhpStorm つかうともっと便利[^2]です。

[^2]: PhpStorm の Laravel プラグインの `Blade` 構文のシンタックスハイライトとコード補完がめっちゃ便利。

## 共通点プレートを作ろう

Laravel の View は `resources/views` に作ります。`Blade` も `Twig` と同じようにテンプレートの継承ができるので、まずはテンプレートファイルから作りましょう。コマンドがないので手動でつくります。`Blade` を使う場合は `.blade.php` という拡張子(といっていいんだろうか)になります。


```php:resources/views/layouts/default.blade.php
<!doctype html>
<html lang="ja">
<head>
    <title>@yield('title', 'いんすぱいやーBBS')</title>
</head>
<body>
@yield('content')

@section('footer')
    <hr>
    <footer>
        <address>
            &copy; Copyright 2016 localdisk<br>
            <a href="https://github.com/localdisk/inspire-bbs">InspireBBS Laravel</a> is licenced under <a href="http://www.wtfpl.net/"><abbr title="Do What the Fuck You Want to Public License">WTFPL</abbr></a> and/or <a href="http://www.gnu.org/licenses/agpl-3.0.html"><abbr title="GNU Affero General Public License, Version 3">AGPLv3</abbr></a>
        </address>
    </footer>
@show
</body>
</html>
```

`@yield` と `@section` というディレクティブに注目。`Twig` と大体考え方は一緒だと思います。多分。詳しくは[Bladeテンプレート 5.1 Laravel](http://readouble.com/laravel/5/1/ja/blade.html)を参照してください（疲れた）

## 個別ページを作ろう

先ほど作った `default.blade.php` を継承した `index.blade.php` を作ります。

```php
@extends('layouts.default')

@section('title', 'いんすぱいやーBBSへようこそ')

@section('content')
    <h1>いんすぱいやーBBS</h1>

    <p>ヾ(〃＞＜)ﾉﾞ＜ {{ $greeting }}</p>
@stop
```

- `@extends('layouts.default')` と書くことで、`layouts/default.blade.php` を継承
- `@section` の `title` と `content` に定義を書く
- `{{ $greeting }}` で変数を出力

## タイムゾーンの定義

タイムゾーンを設定します。Laravel はデフォルトで `UTC` にタイムゾーンを設定しているので `Asia/Tokyo` に設定し直すには `config/app.php` を変更します。まぁ、実際のところは `UTC` のままで、表示の際に [Carbon](http://carbon.nesbot.com/docs/) 使ったり、[Moment Timezone ](http://momentjs.com/timezone/) あたりを使って `JST` に変換したりすると思います。

```php:config/app.php
<?php

return [
    'timezone' => 'Asia/Tokyo',
]
```

## 元気に挨拶をしつつ画面に表示しよう

疲れたので巻きに入ります。Laravel は `app/Http/routes.php` にルート定義をします。その中で書くもよし、コントローラーを作るもよし。今回は `routes.php` に直接書いちゃいましょう。

```php:app/Http/routes.php
<?php

Route::get('/', function () {
    $hour = Carbon\Carbon::now()->hour;

    $greeting = null;
    if (4 <= $hour && $hour < 10) {
        $greeting = "お早うございます";
    } elseif (10 <= $hour && $hour < 17) {
        $greeting = "こんにちは";
    } else {
        $greeting = "こんばんわ";
    }

    return view('index', compact('greeting'));
});
```

# まとめ

いろいろ説明を省略しすぎている感じがあるので[元記事](http://qiita.com/tadsan/items/adaade6b675ac344f373)を読んでからこっち読んでみてください。_(:3」∠)_

# 追記
続きを書いた。[Laravel で掲示板作りたくなった(3)](http://qiita.com/localdisk/items/d62327d064970635fcdd)