PHP
laravel
docker
LaraDock

LaradockでLaravelのクイックスタートをやってみた

概要

仕事でPHP使っているくせにPHPフレームワーク触ったことないサーバサイドエンジニアが、勉強も兼ねてLaravelのクイックスタートをやってみた話。
ついでにDockerの勉強も兼ねてLaradockも使ってみる。

対象

  • PHPユーザ
  • PHPフレームワーク初心者
  • Docker初心者

目標

  • Laravelを使えるようになる
  • docker-composeでのdocker利用をなんとなく把握する
    • 基本的な動かし方程度
    • docker-compose.ymlとかは触れないので、別途勉強する必要あり

参考

環境構築

http://laradock.io/getting-started/
DockerでLaravelの開発環境を構築する
[PHP]ComposerのCLIで利用できるコマンドの説明 | 9ensanのLifeHack

クイックスタート

https://laravel.com/docs/5.2/quickstart-intermediate
https://readouble.com/laravel/5.5/ja/migrations.html
https://laravel.com/docs/5.5/eloquent
https://laravel.com/docs/5.5/eloquent-relationships
https://github.com/laravel/quickstart-intermediate
https://laravel.com/docs/5.2/routing#route-model-binding

環境

  • macOS High Sierra 10.13.2
  • Docker for Mac
    • Docker 17.12.0-ce-mac49
    • Docker-compose 1.18.0
  • Laradock
    • Laravel 5.5.32
    • mysql 8.0.3
    • nginx 1.13.8

Laradockでの環境構築

http://laradock.io/getting-started/
今回はA.2のPHPプロジェクトがない場合に則って進めていく。
そのため、rootのディレクトリ構成は以下のようになる

ディレクトリ構成
laravel          # root
├── laradock     # Laradockリポジトリ
└── quickstart   # プロジェクトディレクトリ

1. Laradockのclone

ディレクトリを作成し、そこにlaradockのリポジトリをクローンする。

Laradockのclone
# rootディレクトリ作成
$ mkdir laravel
$ cd laravel

# Laradockのクローン
$ git clone https://github.com/laradock/laradock.git
$ cd laradock

2. 設定修正

env-exampleから.envファイルを作成する。

設定修正
# 設定ファイルのコピー
$ cp env-example .env

作成した.envファイルの8行目あたりのAPPLICATIONをプロジェクトディレクトリのパスに変更する。

laravel/laradock/.env
APPLICATION=../quickstart/

この.envファイルで指定した環境変数をdocker-compose.ymlで使っている。

  • 備考
    • Sequel Proを使用したい場合はMYSQL_VERSION8.0以下にする必要あり
    • Sequel Proの最新バージョン1.1.2だとMySQL 8.0以上に対応していないため(1.2以降から対応予定とのこと)

3. コンテナ起動

コンテナを起動する。

コンテナ起動
# コンテナ起動
$ docker-compose up -d nginx mysql

この時、一緒にworkspacephp-fpmのコンテナも起動する。
大抵の構成ではこの2つは自動で起動するらしい。
他の構成で起動したい場合は下記URLかdocker-compose.ymlを参考。
http://laradock.io/introduction/#supported-software-images

docker-compose upについて

コンテナを作成し、開始するコマンド

docker-compose
docker-compose up [options] [--scale SERVICE=NUM...] [SERVICE...]
  • -dオプションはデタッチド・モードでバックグラウンドでコンテナを実行し、新しいコンテナ名を表示

  • [SERVICE...]を指定するとそのコンテナのみ起動する

    • ただし、依存関係のコンテナは起動するため、上記ではworkspacephp-fpmのコンテナも起動した (nginxphp-fpmと依存関係にあり、php-fpmworkspaceと依存関係にあるため)

4. プロジェクト作成

まず、下記コマンドでworkspaceコンテナに入り、プロジェクトを作成する。
(Getting Started - LaraDockに記載がなかったので、DockerでLaravelの開発環境を構築するを参考にした。)

プロジェクト作成
# workspaceコンテナに入る
$ docker-compose exec workspace bash
or
$ docker exec -it laradock_workspace_1 /bin/bash

# プロジェクト作成
$ composer create-project laravel/laravel . --prefer-dist
$ exit

docker execについて

起動中のコンテナでコマンドを実行するコマンド

docker
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
  • -iオプションはインタラクティブモードでの起動
  • -tオプションは仮想端末の割り当て。コンソールっぽく使えるか否か? いまいちわからなかったが、少なくとも以下のような違いがあるっぽい
`-t`あり
$ docker exec -it laradock_workspace_1 /bin/bash
root@d59d9ed3d517:/var/www pwd
/var/www
root@d59d9ed3d517:/var/www exit
exit
`-t`なし
$ docker exec -i laradock_workspace_1 /bin/bash
pwd
/var/www
exit
  • CONTAINERはコマンドを実行するコンテナ
  • COMMANDは実行するコマンド
  • /bin/bashをインタラクティブモードで実行し仮想端末を割り当てることで、コンテナ内にアクセスしているような状態になる

docker-compose execについて

起動中のサービスでコマンドを実行するコマンド

docker-compose
docker-compose exec [options] [-e KEY=VAL...] SERVICE COMMAND [ARGS...]
  • docker execと似てるようで違うので注意
    • こっちはコンテナ名やコンテナIDではなくサービス名で指定する
    • デフォルトで仮想端末が割り当てられており-Tで無効にできるなど細かいオプションも違う

composer create-projectについて

composer
composer create-project [options] [--] [<package>] [<directory>] [<version>]

5. プロジェクト設定変更

プロジェクトの.envの9行目辺りにあるDB_HOSTmysqlに変更する。
また、DB_DATABASEDB_USERNAMEdefaultに変更する。

laravel/quickstart/.env
DB_HOST=mysql
DB_DATABASE=default
DB_USERNAME=default

6. ブラウザで確認

http://localhost/にアクセスし、下記のような画面が表示されるか確認する。
pic1.png

Laravelクイックスタート

今回は2種類あるチュートリアルうちのユーザ認証を実装するこっちをやる。
https://laravel.com/docs/5.2/quickstart-intermediate
ここから先のコマンドは全てworkspaceコンテナ上

1. データベースの準備

マイグレーション

usersテーブルとtasksテーブルを作成する。
マイグレーションの詳細に関しては以下を参照。
https://laravel.com/docs/5.2/quickstart-intermediate

マイグレーションファイル作成

usersのマイグレーションファイルはLaravelがデフォルトで用意しているため、tasksのマイグレーションファイルを作成する。
マイグレーションファイルはquickstart/databese/migrations下に配置される。

マイグレーションファイルの作成
$ php artisan make:migration create_tasks_table --create=tasks
php artisan make:migrationについて

マイグレーションファイルを作成するコマンド

migration
php artisan make:migration [options] [--] <name>
  • --createオプションは引数に渡した文字列のテーブルを作成するときのテンプレートを出力する
    • --tableオプションの場合は引数に渡した文字列のテーブルを更新するときのテンプレート

マイグレーションファイルの編集

カラムが定義されていないので、作成したマイグレーションファイルを編集する。
usersテーブルと関連づけるためのuser_idカラムを整数型で追加しindexを貼る。
タスク名を保存するnameカラムを文字列型で追加する。

create_tasks_table.php
<?php

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

class CreateTasksTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('tasks', function (Blueprint $table) {
            $table->increments('id');
            // 追記箇所
            $table->integer('user_id')->unsigned()->index();
            $table->string('name');
            // 追記終了
            $table->timestamps();
        });
    }

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

マイグレーション

マイグレーションを実行する。

マイグレーションの実行
php artisan migrate

Eloquent Models(ORM)の作成

usersテーブルとtasksテーブルのEloquentを作成する。
Eloquentの詳細に関しては以下を参照。
https://laravel.com/docs/5.5/eloquent

モデルの生成

例のごとくusersテーブルのモデルであるUserモデルはすでに作成されているため、tasksテーブルのモデルであるTaskモデルを作成する。
モデルファイルはquickstart/app下に配置される。

モデルの作成
php artisan make:model Task
php artisan make:modelについて

モデルを作成する

model
php artisan make:model [options] [--] <name>
  • マイグレーションファイルとかコントローラも一緒に作成することができる

モデルの定義

コマンドで生成されたTaskモデルは中身が空なため、中身を定義する。

Task.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    // 追記箇所
    protected $fillable = ['name'];
    // 追記終了
}

Eloquent Relationshipsの作成

モデルの関連を定義する。
詳細については下記を参照。
https://laravel.com/docs/5.5/eloquent-relationships

Taskの関連を作成

UserモデルにTaskモデルとの関連を定義する。

User.php
<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    // Other Eloquent Properties...

    // 追記箇所
    public function tasks()
    {
        return $this->hasMany(Task::class);
    }
    // 追記終了
}

Userの関連を作成

TaskモデルにUserモデルとの関連を定義する。

Task.PHP
namespace App;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    protected $fillable = ['name'];

    // 追記箇所
    public function user()
    {
      return $this->belongsTo(User::class);
    }
    // 追記終了
}

2. ルーティング

認証機能

認証機能の実装

下記コマンドで認証機能を作成する。

認証の作成
php artisan make:auth

リダイレクト先の修正

ドキュメントと異なり、Laravel5.5ではAuthControllerRegisterController, LoginControllerPasswordControllerResetPasswordController, ForgetPasswordControllerに分かれている。
このうち、RegisterController, LoginController, ResetPasswordController$redirectToプロパティを'/tasks'に変更する。

リダイレクト先変更
protected $redirectTo = '/task';

また、RedirectIfAuthenticated.phpのリダイレクト先も変更する。

RedirectIfAuthenticated.php
return redirect('/tasks');

TaskController

TaskControllerの作成

TaskControllerを作成する。

TaskController作成
php artisan make:controller TaskController

ルートの追加

TaskControllerのルートを作成する。
quickstart/routes/web.phpに下記を追記してルートを作成する。

web.php
Route::get('/tasks', 'TaskController@index');
Route::post('/task', 'TaskController@store');
Route::delete('/task/{task}', 'TaskController@destroy');

認証の追加

TaskControllerをどのルートのアクセスでも認証機能が動作するようにする。

TaskController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TaskController extends Controller
{
    // 追記箇所
    public function __construct()
    {
        $this->middleware('auth');
    }
    // 追記終了
}

3. レイアウトとViewの構築

下記のようなページを目標にレイアウトとビューを作成していく。
完成図
ただし、Bootstrap部分は本稿では扱わないので、下記を参照。
https://github.com/laravel/quickstart-intermediate

レイアウトの定義

quickstart/resources/views/layouts/app.blade.phpを下記のように書き換える。

app.blade.php
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Laravel Quickstart - Intermediate</title>

        <!-- CSS And JavaScript -->
    </head>

    <body>
        <div class="container">
            <nav class="navbar navbar-default">
                <!-- Navbar Contents -->
            </nav>
        </div>

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

子Viewの定義

quickstart/resources/views/下にtasksディレクトリを作成し、TaskContorllerindexに対応するViewとなるindex.blade.phpを下記のように作成する。

index.blade.php
@extends('layouts.app')

@section('content')

    <!-- Bootstrap Boilerplate... -->

    <div class="panel-body">
        <!-- Display Validation Errors -->
        @include('common.errors')

        <!-- New Task Form -->
        <form action="{{ url('task') }}" method="POST" class="form-horizontal">
            {{ csrf_field() }}

            <!-- Task Name -->
            <div class="form-group">
                <label for="task-name" class="col-sm-3 control-label">Task</label>

                <div class="col-sm-6">
                    <input type="text" name="name" id="task-name" class="form-control">
                </div>
            </div>

            <!-- Add Task Button -->
            <div class="form-group">
                <div class="col-sm-offset-3 col-sm-6">
                    <button type="submit" class="btn btn-default">
                        <i class="fa fa-plus"></i> Add Task
                    </button>
                </div>
            </div>
        </form>
    </div>

    <!-- TODO: Current Tasks -->
@endsection

indexの追加

TaskControllerindexViewに対応する関数を下記のように追加する。

TaskController
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TaskController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
    }

    // 追記箇所
    public function index(Request $request)
    {
        return view('tasks.index');
    }
    // 追記終了
}

4. タスクの追加

タスクを追加する処理を実装していく。

妥当性チェック

妥当性チェック機能を実装する。

妥当性チェックの追加

TaskControllerにタスク追加処理を行うメソッドを追加し、下記のように妥当性チェックを記述する。

TaskController
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TaskController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
    }

    public function index(Request $request)
    {
        return view('tasks.index');
    }

    // 追記箇所
    public function store(Request $request)
    {
        $this->validate($request, [
            'name' => 'required|max:255',
        ]);

        // Create The Task...
    }
    // 追記終了
}

エラー表示を実装する

views下にcommonディレクトリを作成し、errors.blade.phpを下記のように作成する。

errors.blade.php
@if (count($errors) > 0)
    <!-- Form Error List -->
    <div class="alert alert-danger">
        <strong>Whoops! Something went wrong!</strong>

        <br><br>

        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif

タスクの作成

TaskController@storeにタスク追加処理を実装する。
TaskControllerを下記のように変更する。

TaskController
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TaskController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
    }

    public function index(Request $request)
    {
        return view('tasks.index');
    }

    public function store(Request $request)
    {
        $this->validate($request, [
            'name' => 'required|max:255',
        ]);

        // 追記箇所
        $request->user()->tasks()->create([
            'name' => $request->name,
        ]);

        return redirect('/tasks');
        // 追記終了
    }
}

$request->user()によって、認証されたユーザ情報へアクセスできる。
Eloquent Relationshipsの作成で作成したtasksメソッドからタスク情報へアクセスし、タスクの追加を行う。
この時、外部キーであるuser_idUseridが自動でセットされる。

5. 存在するタスクの表示

存在するタスクを表示する機能を実装します。

依存性の追加

リポジトリの作成

タスクを扱うTaskRepositoryを作成します。
app下にRepositoriesディレクトリを作成し、その中にTaskRepositoryクラスを下記の様に作成します。

TaskRepository
<?php

namespace App\Repositories;

use App\User;

class TaskRepository
{
    public function forUser(User $user)
    {
        return $user->tasks()
                    ->orderBy('created_at', 'asc')
                    ->get();
    }
}

リポジトリの導入

TaskControllerTaskRepositryに対応させます。
下記の様にTaskControllerを変更します。

  • App\Task, App\Repositories\TaskRepositoryのインポートを追加
  • TaskRepositryを保持する$tasksプロパティの追加
  • __constructに引数としてTaskRepositryを与え、$tasksへ代入する処理を追加
  • indexviewの第二引数でタスク情報を与える
TaskController
<?php

namespace App\Http\Controllers;

use App\Task;
use Illuminate\Http\Request;
use App\Repositories\TaskRepository;

class TaskController extends Controller
{
    protected $tasks;

    public function __construct(TaskRepository $tasks)
    {
        $this->middleware('auth');

        $this->tasks = $tasks;
    }

    public function index(Request $request)
    {
        return view('tasks.index', [
            'tasks' => $this->tasks->forUser($request->user()),
        ]);
    }

    public function store(Request $request)
    {
        $this->validate($request, [
            'name' => 'required|max:255',
        ]);

        $request->user()->tasks()->create([
            'name' => $request->name,
        ]);

        return redirect('/tasks');
    }
}

タスクの表示

index.blade.phpに存在しているタスクを表示する処理を追加する。
作成時に<!-- TODO: Current Tasks -->とした箇所に追加する。

index.blade.php
    <!-- Current Tasks -->
    // 追記箇所
    @if (count($tasks) > 0)
        <div class="panel panel-default">
            <div class="panel-heading">
                Current Tasks
            </div>

            <div class="panel-body">
                <table class="table table-striped task-table">

                    <!-- Table Headings -->
                    <thead>
                        <th>Task</th>
                        <th>&nbsp;</th>
                    </thead>

                    <!-- Table Body -->
                    <tbody>
                        @foreach ($tasks as $task)
                            <tr>
                                <!-- Task Name -->
                                <td class="table-text">
                                    <div>{{ $task->name }}</div>
                                </td>

                                <td>
                                    <!-- TODO: Delete Button -->
                                </td>
                            </tr>
                        @endforeach
                    </tbody>
                </table>
            </div>
        </div>
    @endif
    // 追記終了
@endsection

6. タスクの削除

タスクの削除機能を実装していく。

削除ボタンの追加

上記で追記したタスクリストの<!-- TODO: Delete Button -->部を下記の様に書き換える。

index.blade.php
<tr>
    <!-- Task Name -->
    <td class="table-text">
        <div>{{ $task->name }}</div>
    </td>

    <!-- Delete Button -->
    <td>
        // 追記箇所
        <form action="{{ url('task/'.$task->id) }}" method="POST">
            {{ csrf_field() }}
            {{ method_field('DELETE') }}

            <button type="submit" id="delete-task-{{ $task->id }}" class="btn btn-danger">
                <i class="fa fa-btn fa-trash"></i>Delete
            </button>
        </form>
        // 追記終了
    </td>
</tr>

method_fieldについて

method_field('DELETE')は下記と等価。

method_field
<input type="hidden" name="_method" value="DELETE">

ルートモデルとの関連づけ

TaskControllerに下記のメソッドを追記します。

TaskController
<?php

namespace App\Http\Controllers;

use App\Task;
use Illuminate\Http\Request;
use App\Repositories\TaskRepository;

class TaskController extends Controller
{
    ...

    // 追記箇所
    public function destroy(Request $request, Task $task)
    {
        // 処理を記述する
    }
    // 追記終了
}

変数名とルートのURIの名前が一致した場合、Type Hintで与えた型のEloquentModelでIDに一致したものを渡します。
https://laravel.com/docs/5.2/routing#route-model-binding

承認

ポリシーの作成

まず、下記コマンドを実行し、認証ロジックを収容するTaskPolicyを作成します。

ポリシーの作成
php artisan make:policy TaskPolicy

ポリシーへの認証ロジックの追加

作成したapp/Policies/TaskPolicy.phpdestroyの認証ロジックを追加します。

/TaskPolicy.php
<?php

namespace App\Policies;

use App\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class TaskPolicy
{
    use HandlesAuthorization;

    /**
     * Create a new policy instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    // 追記個所
    public function destroy(User $user, Task $task)
    {
        return $user->id === $task->user_id;
    }
    // 追記終了
}

モデルとポリシーの関連付け

TaskモデルとTaskPolicyを関連づけるため、app/Providers/AuthServiceProvider.php$policiesに下記を追記する。

AuthServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        'App\Model' => 'App\Policies\ModelPolicy',
        // 追記個所
        'App\Task' => 'App\Policies\TaskPolicy',
        // 追記終了
    ];
    ...
}

アクションの承認の追加

TaskControllerdestoryメソッドに下記の様に承認処理を追加する。

TaskController.php
<?php
...

class TaskController extends Controller
{
    ...
    public function destroy(Request $request, Task $task)
    {
        // 追記個所
        $this->authorize('destroy', $task);
        // 追記終了

        // Delete The Task...
    }
}

タスクの削除

最後にTaskControllerdestroy メソッドへタスク削除処理を追加します。

TaskController.php
<?php
...

class TaskController extends Controller
{
    ...
    public function destroy(Request $request, Task $task)
    {
        $this->authorize('destroy', $task);

        // 追記個所
        $task->delete();

        return redirect('/tasks');
        // 追記終了
    }
}

7. 確認

http://localhost/ にアクセスし、ユーザ登録する。
ユーザ登録後にログイン画面へ遷移しログインすることで、タスクリストのページに遷移し、
タスクの追加や削除ができればOK。
Bootstrapを組み込んでいない場合は、LOGINやREGISTERが表示されない可能性があるので、下記を参照してBootStrapを組み込むこと推奨。
https://laravel.com/docs/5.2/quickstart-intermediate

まとめ

Laravelは初めてだったし、Dockerをちゃんと使うのも初めてだったけど、この記事を書きながら取り組んでも半日もかからないくらいで終わった。
実装の簡単さもそうだけど、いろいろな取り回しが簡単になるゆおに作られているのを実感し、改めてフレームワークの実力を思い知らされた。