Laravelで簡単なページを作成するまで

# はじめに
今年はほぼ間違いなくPHPしかもLaravelを触ることになるので、簡単なサイトを作成することにしました。
環境構築から一通りやることがあるので、覚えておくためにまとめておきます。

# 前提
Windowsで作業してるので、MacやLinux使っている人には不要な作業も記載しています。
一通り記載するつもりですが、記載が漏れて不要な作業が書かれていたらごめんなさい。

## 環境
PHP 7.2
Laravel Framework 5.7.19
Postgres 11.1(psql 9.5.11)
Docker for Windows 2.0.0.0-win81 (29211)
nginx 1.15.8

# 今回作るもの
色々とやろうとしましたが、簡単なサイトなら何かの一覧表示すればいいやということで、データベースに登録した本一覧が画面に表示されるまでを作ることにしました。

# Laravelの環境構築
まずはLaravelを使用出来るようにする必要があります。
今回はlaradockを使用して楽させてもらいます。

http://laradock.io/

上で記載した環境は書いた当時(2019/01/03)のlaradockで使用されているバージョンそのままです。

## laradockのclone
Githubにありますので、cloneしてもってきます。
場所はどこでも大丈夫です。

```shell
git clone https://github.com/Laradock/laradock.git
```

## laradockの環境設定
Docker for Windows + Postgres用の設定変更をするのとコンテナ起動のためにフォルダ内に移動します。

```shell
cd laradock
```

そして設定ファイルのサンプルをベースに書き換えるために設定ファイルをコピーしておきます。

```shell
cp env-example .env
```

laradockのデフォルトはmysqlを使用する設定となっていてpostgresがインストールされないため、されるように以下の設定を`true`に変更します。

```shell
WORKSPACE_INSTALL_PG_CLIENT=true
```

次にdocker-compose.ymlも修正します。

修正の前にpostgres用のnamed volumeを作成しておきます。
postgresの永続化はwindowsだと上手くいかずnamed volumeを作成してそこを指定するというのが一般的です。

```docker-compose.yml
docker create volume --name postgres_db
```

named volumeを作成したらdocker-compose.ymlのpostgresのvoluesを作成したnamed volumeに変更します。
なんとなく必要のない`docker-entrypoint-initdb.d`の設定も書き換えていますが、気にしないでください。

```docker-compose.yml
postgres_db:/var/lib/postgresql/data
./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
```

後、なぜかelasticsearchのvolume設定が変でエラーになってしまうので、使用しませんが修正しておきます。

```docker-compose.yml
elasticsearch:/usr/share/elasticsearch/data
${DATA_PATH_HOST}/elasticsearch:/usr/share/elasticsearch/data
```

最後にvolumesの使用設定を一番下に追加して終わりです。

```docker-compose.yml
## volumes
volumes:
  postgres_db:
   external: true
```

次にlaradockでnginxを使用することになるので先回りして設定を変更しておきます。
`laradock/nginx/sites/default.conf`の`root`を今回作成するパスに変更します。
`app01`という名前で作成するつもりなので、下記のような設定にしていますが、自身の作成したい名前に合わせてフォルダ名は入力してください。

やる際はconfのバックアップを取得した方が無難です。

```shell
root /var/www/app01/public;
```

## コンテナ起動
必要なことは終わったのでコンテナを起動します。

```shell
docker-compose up -d nginx postgres
```

初回は色々と時間かかるので、待ってる間は適当に暇をつぶしてください。

## Laravelプロジェクトの生成
コンテナ起動後はコンテナ内に入ってLaravelプロジェクトを生成します。
laradockは`workspace`という全ての操作が可能なコンテナが用意されるので、ここから操作していきます。

```shell
docker-compose exec workspace bash
```

コンテナに入ったら`/var/www`配下にいますので、composerを使用してプロジェクトを生成します。
composerはデフォルトで入っているので、何もせず使用してOKです。

```shell
root@cdcd5245e1ef:/var/www# composer create-project laravel/laravel app01
```

## プロジェクトの生成確認
プロジェクトが生成された確認は`http://localhost/`にブラウザからアクセスすれば分かります。
nginxの設定次第なので、URLは適宜読み替えてください。

Laravelのデフォルト画面が出てくれば無事に生成することが出来ました。

# データベースの設定
プロジェクトの生成が終わると、cloneしたフォルダと同階層にapp01が出来ています(laradockのデフォルト設定)。
そこが実際に実装を行っていくプロジェクトです。

まずはデータベースの設定を行っていきます。
プロジェクトのrootフォルダに`.env`ファイルがありますので、開いてDBの設定を入れておきます。
ここで記載しているのはlaradockのデフォルト設定で作成した情報となりますので、違う設定でコンテナ作った人は読み替えてください。

注意点としては、`DB_HOST`はpostgresだと`postgres`にしないと駄目だということぐらいです。

```.env
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=default
DB_USERNAME=default
DB_PASSWORD=secret
```

もう一つ`/config/database.php`も同じ感じで修正します。
見れば分かると思うので割愛します。
`schema`についてはこちらのファイルでしか設定できないので、`public`が嫌な場合はコンテナ入って作って設定を変えてください。

今回はプロダクトじゃなくて自分の勉強用なので変更せずいきます。

## Laravel標準認証機能を使用する
今回は認証機能を使用する必要はないのですが、折角なので機能確認目的でデフォルトの認証機能について作成しておきます。
マイグレーションついでにデータベースの設定が正しいかもチェックしておきます。

```shell
root@cdcd5245e1ef:/var/www/app01# php artisan make:auth
root@cdcd5245e1ef:/var/www/app01# php argisan migrate
```

マイグレーションに成功したらこんな感じでユーザー系のテーブルが生成されます。

```shell
root@cdcd5245e1ef:/var/www/app01# psql -U default -h postgres
Password for user default:
psql (9.5.12, server 11.1)
WARNING: psql major version 9.5, server major version 11.
         Some psql features might not work.
Type "help" for help.

default=# \d
                List of relations
 Schema |       Name        |   Type   |  Owner
--------+-------------------+----------+---------
 public | migrations        | table    | default
 public | migrations_id_seq | sequence | default
 public | password_resets   | table    | default
 public | users             | table    | default
 public | users_id_seq      | sequence | default
 ```

# Index画面を変更する
一覧を表示するためIndex画面を流用して書き換えを行います。

デフォルトのRoutingは`routes/web.php`に書いているので、そこでIndexページのphpファイルが`
resources/views/welcome.blade.php
`であることが確認できます。
新しく作成するのも面倒なので、このままこれを使いまわしたいと思います。

デザインに関しては今回はある程度適当に作ってしまおうと思うので、デフォルトの`welcome.blade.php`を親にして`@yield('content')`にコンテンツを記述するように修正をします。
ついでにタイトルとかスタイルも修正しています。。

修正後は`views/layouts/index.blade.php`として保存します。

ちなみにhtmlとかcss周りはそれなりに表示出来ればいいやーぐらいで作成しています(今回の勉強したい主題ではないので)。

```index.blade.php
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Simple library</title>

        <!-- Fonts -->
        <link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet" type="text/css">

        <!-- Styles -->
        <style>
            html, body {
                background-color: #fff;
                color: #636b6f;
                font-family: 'Nunito', sans-serif;
                font-weight: 200;
                margin: 0;
            }

            .full-height {
                height: 100vh;
            }

            .flex-center {
                align-items: center;
                justify-content: center;
            }

            .position-ref {
                position: relative;
            }

            .top-right {
                position: absolute;
                right: 10px;
                top: 18px;
            }

            .title {
                font-size: 84px;
            }

            .content {
                text-align: center;
            }
            .m-b-md {
                margin-bottom: 30px;
            }

            @yield('style')
        </style>
    </head>
    <body>
        <div class="flex-center position-ref full-height">
            @if (Route::has('login'))
                <div class="top-right links">
                    @auth
                    @else
                        <a href="{{ route('login') }}">Login</a>

                        @if (Route::has('register'))
                            <a href="{{ route('register') }}">Register</a>
                        @endif
                    @endauth
                </div>
            @endif

            <div class="content">
                <div class="title m-b-md">Books</div>
                <div>
                    @yield('content')
                </div>
            </div>
        </div>
    </body>
</html>
```

中身も適当に作ります。
今回はページャーについては作成しないで、取れたもの全て表示するとします。
いったん仮で作成したもので表示確認だけ行って次の作業に移ります。

```welcome.blade.php
@extends('layouts.index')

@section('style')
ul li {
    list-style: none;
    float:left;
    display: table-cell;
}

.item-list {
    width : 20%;
}

.item-title {
    font-size : 14px;
}

.item-img {
    width : 150px;
    height : 150px;
}
@endsection

@section('content')
<div>
    <ul>
        <li class="item-list">
            <p class="item-title">タイトル</p>
            <img src="" class="item-img">
        </li>
    </ul>
</div>
@endsection
```

## Index用のControllerの作成
データベースからデータを取得しないと成り立たないので、Contollerを作成していきます。

### Controllerの生成
Contollerはコマンドから生成できるので、コンテナに入って生成します。

```shell
root@cdcd5245e1ef:/var/www/app01# php artisan make:controller IndexController
```

### Routingの変更
今まで直接viewを返していましたが、Controller経由で返却するよう`routes/web.php`のRoutingを変更します。

```web.php
Route::get('/', 'IndexController@index');
```

### Contollerの仮実装
まだ貸すための本が取得出来ない状態なので、いったん`welcome.blade.php`を返却するよう仮実装を行います。

```IndexController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
    public function index()
    {
        return view('welcome');
    }
}
```

## データを取得できるようにする

### migrate用ファイルの生成
まずはテーブルを生成するための実装を行っていきます。
migration用のファイルもコマンドから生成します。

```shell
php artisan make:migration create_books_table
```

### データベース定義の作成
作成したファイルに定義を追加していきます。
今回はタイトルと画像URLを保存するとします。

作成したphpファイルに定義を追加してコンテナからmigrateして完了です。

カラムは単純ですが一応not null制約とindexを付けてみます。

```2018_12_29_145459_create_books_table.php
    public function up()
    {
        Schema::create('books', function (Blueprint $table) {
            $table->increments('id');
            $table->text('title')->nullable(false);
            $table->text('img_url')->nullable(false);
            $table->timestamps();
            $table->index('title');
        });
    }
```

pgadmin上でnot null制約とindexがきちんと付与されているかだけチェックしておきます。


```text
-- Table: public.books

-- DROP TABLE public.books;

CREATE TABLE public.books
(
    id integer NOT NULL DEFAULT nextval('books_id_seq'::regclass),
    title text COLLATE pg_catalog."default" NOT NULL,
    img_url text COLLATE pg_catalog."default" NOT NULL,
    created_at timestamp(0) without time zone,
    updated_at timestamp(0) without time zone,
    CONSTRAINT books_pkey PRIMARY KEY (id)
)
WITH (
    OIDS = FALSE
)
TABLESPACE pg_default;

ALTER TABLE public.books
    OWNER to "default";

-- Index: books_title_index

-- DROP INDEX public.books_title_index;

CREATE INDEX books_title_index
    ON public.books USING btree
    (title COLLATE pg_catalog."default")
    TABLESPACE pg_default;
```

## Controllerからデータを全部取得する
テーブルに適当なデータを入れておいたとします。
(自分は確認用にアマゾンから適当な商品名と画像リンクを使わせていただきました)

そこからデータを取得をするべくControllerの修正を行います。

修正点は`use Illuminate\Support\Facades\DB;`を追加したのとviewへの変数受け渡しとして`['books' => DB::table('books')->get()]`を追加した2点です。

```IndexController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class IndexController extends Controller
{
    public function index()
    {
        return view('welcome', ['books' => DB::table('books')->get()]);
    }
}
```

## viewで取得したデータを表示できるようにする
viewの方も動的に出力できるよう修正します。

修正点は`@foreach`の追加とタイトル、URLの埋め込みをそれぞれしています。

```welcome.blade.php
@extends('layouts.index')

@section('style')
ul li {
    list-style: none;
    float:left;
    display: table-cell;
}

.item-list {
    width : 20%;
}

.item-title {
    font-size : 14px;
}

.item-img {
    width : 150px;
    height : 150px;
}
@endsection

@section('content')
<div>
    <ul>
        @foreach ($books as $book)
        <li class="item-list">
            <p class="item-title">{{ $book->title }}}</p>
            <img src="{{$book->img_url}}" class="item-img">
        </li>
        @endforeach
    </ul>
</div>
@endsection
```

# さいごに
Laradock導入からHTML除けば2時間くらいでここまで来ることが出来ています(HTMLに小1時間ほど悩んだ)。
PHPは全然触ったことないけど(現時点で3時間くらい)、書き方覚えればなんとかいけそうなぐらい簡単でした。

これから使うプロダクトも増えてくるでしょうし、そんなに難しくないので一度は触っておいた方が良いかもしれません。

# 参考にしたサイト
https://qiita.com/J_Shell/items/695a30fd38444d065ae5
https://gist.github.com/yano/64c07ee81db24985fe8f6091cace503d
https://readouble.com/laravel/5.7/ja/