この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:8
ページ更新者:guest
更新日時:2018-03-25 16:34:35

タイトル: クイックスタート & チュートリアル(中級者向け)

当記事ではLaravelを使用してWebアプリケーションを作成します。

アプリの作成を通してLaravelの以下の項目を説明します。

・データベース
・Eloqunetモデル
・Eloquentリレーション
・ルーティング
・認証
・コントローラー
・ビュー
・バリデーション
・依存注入
・認可
・Bladeテンプレート

※Laravelの導入が済んでいない場合はこちらを参照。

※XAMPPを使用するのでXAMPPの導入が済んでいない場合はこちらを参照。

 

 

 

インストール

quickstart」という名前のプロジェクトを新規に作成しましょう。

composer create-project laravel/laravel quickstart --prefer-dist

 

データベースの準備

■データベースの作成

XAMPPのControl PanelからphpMyAdminを起動しましょう。

 

phpMyAdminを開いたら「New」ボタンを押して「test」データーベースを作成しましょう。

 

■ユーザーの作成

「test」データベースを選択した状態で以下のコマンドを実行してデーターベースユーザーを作成しましょう。

GRANT ALL PRIVILEGES 
ON test.* 
TO testUser@localhost 
IDENTIFIED BY 'testPass';

 

■データーベースの接続設定

Laravel内にあるデータベースの接続設定を変更します。

ルートフォルダ直下の「.env」ファイルの以下の部分を変更しましょう。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=test
DB_USERNAME=testUser
DB_PASSWORD=testPass

 

■テーブルの作成

データベースのマイグレーション機能を用いてテーブルを作成してみましょう。

今回は「user」と「task」テーブルを使用します。

 

user」はLaravelインストール時にデフォルトで定義されています。

task」テーブルを作成しましょう。

 

ルートディレクトリに移動して以下のコマンドを実行します。

php artisan make:migration create_tasks_table --create=tasks

 

database\migrations」配下にファイルが新規作成されたことを確認できます。

ファイルの中身を見てみましょう。

public function up()
    {
        Schema::create('tasks', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
        });
    }

これはデフォルトの定義です。

このファイルを編集し、タスクの名前を保存するstringカラムと、tasksとusersテーブルを結びつけるuser_idカラムを追加しましょう。

以下の様にテーブル定義を変えます。

public function up()
    {
        Schema::create('tasks', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
        });
    }

 

tasksテーブルにカラムを追加してみましょう。

以下の例ではstring型のnameというカラムを追加しています。

public function up()
{
    Schema::create('tasks', function (Blueprint $table) {
        $table->increments('id');

        $table->integer('user_id')->unsigned()->index();
        $table->string('name');

        $table->timestamps();
    });
}

 

ファイルを書き換えただけではテーブルの構造は変化しません。

ファイルを保存して以下のコマンドを実行しましょう。

php artisan migrate

※デフォルトのテーブルでkey関連のエラーが発生した場合は「database\migrations」配下にある該当テーブルファイルの「->unique()」や「->index()」の記述を削除しましょう。

 

Eloqunetモデル

次にDBからデータの保存、取得をする際に利用するEloqunetというORMを作成しましょう。

※「ORM(O/R Mapping)」とは

O/Rは「Object/Relational」の略で、オブジェクトとリレーショナルデータベースのデータの間をマッピングする技術のこと。

 

user」と「task」のモデルを使用しますが、「user」はデフォルトで作成されています。

以下のコマンドを実行して「Task」というモデルを作成します。

php artisan make:model Task

モデルとなるクラスは、指定した名前でプロジェクトの「app」フォルダ内に作成されるので確認しましょう。

 

【Task.php】の中身

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    //
}

 

モデルに対して「複数代入」ができるようname属性を登録します。

以下の様に内容を書き変えておきましょう。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    /**
     * 複数代入を行う属性
     *
     * @var array
     */
    protected $fillable = ['name'];
}

 

Eloqunetリレーション

User」と「Task」のデータの紐づけを行います。

User」と「Task」は1対多の関係にします。

 

まず「User」の定義に以下の内容を追記します。(※黄色い部分)

<?php

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];
    

    /**
     * 特定ユーザーの全タスク取得
     */
    public function tasks()
    {
        return $this->hasMany(Task::class);
    }

}

 

まず「Task」の定義を以下のように書き換えます。

<?php

namespace App;

use App\User;
use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    /**
     * 複数代入する属性
     *
     * @var array
     */
    protected $fillable = ['name'];

    /**
     * タスク所有ユーザーの取得
     */
    public function user()
    {
        return $this->
belongsTo(User::class);
    }
}

 

ルーティング

ルーティングとはコントローラとURLを対応させる仕組みです。

※コントローラーについては後述。

 

app/Http/routes.php」のファイルにルーティングの定義をします。

※Laravel5.3以降は、「routes」フォルダ配下にある「web.php」がそれに該当します。

app/Http/routes.php」もしくは「routes/web.php」の内容を確認してみましょう。

<?php

Route::get('/', function () {
    return view('welcome');
});

/」にアクセスすると「welcome」ビューを表示するという意味です。

 

認証

Laravelでログイン認証システムを実装しましょう。

認証には「app/Http/Controllers/Auth/AuthController」を使用します。

※Laravel5.?以降は、「app/Http/Controllers/Auth/」フォルダ配下にあるファイル群がそれに該当します。

 

ユーザー登録/ログイン画面はコマンドで一括で作成することが出来ます。

以下のコマンドを実行しましょう。

php artisan make:auth

 

app/Http/routes.php」もしくは「routes/web.php」の内容を確認してみましょう。

下記の内容が追記されています。

※追記されていない、もしくは違う記述の場合は対応するLaravelのバージョンのドキュメントを参照してください。

Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');

 

認証画面を開いてみましょう。

ルートディレクトリ上で以下のコマンドを実行してサーバーを起動します。

php artisan serve

 

http://localhost:8000/login」にアクセスしてみましょう。

認証画面が開かれます。

 

コントローラー

app/Http/Controllers」に新しいコントローラーを作成しましょう。

今回作成するのはタスクコントローラーです。

以下のコマンドを実行します。

php artisan make:controller TaskController

TaskController」の中身は空です。

 

app/Http/routes.php」もしくは「routes/web.php」に「TaskController」を実行する記述をします。

とりあえず以下の実装をしておきましょう。

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

 

ルートの認証化

タスク関連のページはログインしていなければアクセス出来ない仕様にします。

app/Http/Controllers/TaskController」に以下の記述をしましょう。

<?php

namespace App\Http\Controllers;

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

class TaskController extends Controller
{
    /**
     * 新しいコントローラインスタンスの生成
     *
     * @return void
     */
    public function __construct()
    {
        $this->
middleware('auth');
    }
}

 

middleware('auth')」とすることで要認証と出来ます。

 

ビューについて

Laravelのビューは「resources/views」に保管します。

例えば「return view('welcome');」と定義したのは「resources/views/welcome.blade.php」を表示するということです。

↓が今回作るビューの完成形です。(※今回CSSはいじらないのであくまでイメージです)

 

レイアウトの作成

LaravelはBladeテンプレートを使用することでページ間の特徴を共有できます。

今回はレイアウトを「resources/views/layouts/app.blade.php」として定義します。

.blade.php」拡張子とすることでBladeテンプレートエンジンをするとフレームワークに指示を出します。

resources/views/layouts/app.blade.php」を確認しましょう。

<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
<head>
    ~~略~~
</head>
<body>
    <div id="app">
        ~~略~~
        <main class="py-4">
           
@yield('content')
        </main>
    </div>
</body>
</html>

 

@yield('content')」は「contentセクション」を持つ子ビューを挿入するという意味です。

 

子ビューの作成

それでは「contentセクション」を持つ子ビューを作成しましょう。

※先ほど作ったのはレイアウトで今回はビューです。

 

TaskController」のindexメソッドに対応する「resources/views/tasks/index.blade.php」を定義します。

※CSSは省きます

<!-- resources/views/tasks/index.blade.php -->

@extends('layouts.app')

@section('content')

    <!-- Bootstrapの定形コード… -->

    <div class="panel-body">
        <!-- バリデーションエラーの表示 -->
        @include('common.errors')

        <!-- 新タスクフォーム -->
        <form action="{{ url('task') }}" method="POST" class="form-horizontal">
            {{ csrf_field() }}

            <!-- タスク名 -->
            <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>

            <!-- タスク追加ボタン -->
            <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

@extends('layouts.app')」は先ほど作成した「resources/views/layouts/app.blade.php」レイアウトを使用するという意味です。

@section('content')」から「@endsection」の内容を、先ほどのレイアウトで定義した「@yield('content')」の部分に挿入します。

@include('common.errors')」は「resources/views/common/errors.blade.php」のテンプレートを使用するという意味です。

(このテンプレートは後ほど作成します。)

 

TaskController」に以下のindexメソッドを定義してこのビューを返すようにしましょう。

/**
 * ユーザーの全タスクをリスト表示
 *
 * @param  Request  $request
 * @return Response
 */
public function index(Request $request)
{
    return view('tasks.index');
}

 

バリデーション(入力値チェック)

子ビューの入力値をチェックする処理を実装します。

値の追加はPOSTメソッドで行います。新たに「TaskController@store」を追加します。

このフォームでは、nameフィールドの入力が必須で、内容が255文字以下であることを確認します。

バリデーションに失敗したら、ユーザを「/tasks」のURLへリダイレクトし、同時に以前の入力とエラーをsessionへフラッシュデータとして保存するようにします。

/**
 * 新タスク作成
 *
 * @param  Request  $request
 * @return Response
 */
public function store(Request $request)
{
    $this->validate($request, [
        'name' => 'required|max:255',
    ]);

    // タスクの作成処理…
}

これは「name」フィールドの入力値に何かしらの値が入力されていて、かつ255字以内であることをチェックしています。

バリデーションが失敗した場合のリダイレクト処理を記載していないことに気づくでしょう。

自動的に直前のページにリダイレクトする仕組みです。

かつ、sessionへフラッシュデータの保存も自動で行われます。

 

エラーの表示

次は$errors変数は子ビューの「@include('common.errors')」の部分で表示させるようにしましょう。

resources/views/common/errors.blade.php」を作成して以下の実装をしましょう。

<!-- resources/views/common/errors.blade.php -->

@if (count($errors) > 0)
    <!-- Form Error List -->
    <div class="alert alert-danger">
        <strong>入力値に誤りがあります。</strong>

        <br><br>

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

 

データベースへの値の追加

次は値をデータベースに追加する処理を記載します。「TaskController」に以下のstoreメソッドの続きを記載します。

/**
 * 新しいタスクの作成
 *
 * @param  Request  $request
 * @return Response
 */
public function store(Request $request)
{
    $this->validate($request, [
        'name' => 'required|max:255',
    ]);

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

    return redirect('/tasks');
}

今回使用するcreateメソッドは属性の配列を受け取り、データベースへ保存する前に、関連するモデルの外部キー値を自動的に設定します。

createメソッドは、$request->user()でアクセスできる現在の認証済みユーザのIDを指定したタスクのuser_idプロパティへ自動的に設定します。

 

データベースから値を取得

保存した値を取得する処理を実装します。

TaskController@index」の処理を以下の様に変更しましょう。

/**
 * ユーザーの全タスクをリスト表示
 *
 * @param  Request  $request
 * @return Response
 */
public function index(Request $request)
{
    $tasks = $request->user()->tasks()->get();

    return view('tasks.index', [
        'tasks' => $tasks,
    ]);
}

これでも動作しますが、全データアクセスに対して利用するTaskRepositoryをLaravelの依存注入能力を使い、「TaskController」に注入することにしましょう。

 

依存注入

Task」モデルとの全アクセスロジックを持つ、「TaskRepository」を定義します。

app/Repositories」ディレクトリを作成し、「TaskRepository」ファイルを作成しましょう。

以下の様に実装しましょう。

<?php

namespace App\Repositories;

use App\User;

class TaskRepository
{
    /**
     * 指定ユーザーの全タスク取得
     *
     * @param  User  $user
     * @return Collection
     */
    public function forUser(User $user)
    {
        return $user->tasks()
                    ->orderBy('created_at', 'asc')
                    ->get();
    }
}

 

リポジトリー注入

実際にリポジトリーを注入してみましょう。

TaskController 」を以下の内容に書き換えます。

<?php

namespace App\Http\Controllers;

use App\Task;
use App\Http\Requests;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Repositories\TaskRepository;

class TaskController extends Controller
{
    
    /**
     * タスクリポジトリーインスタンス
     *
     * @var TaskRepository
     */
    protected $tasks;


    /**
     * 新しいコントローラーインスタンスの生成
     *
     * @param  TaskRepository  $tasks
     * @return void
     */
    public function __construct(TaskRepository $tasks)
    {
        $this->middleware('auth');

        $this->tasks = $tasks;
    }
    

    /**
     * ユーザーの全タスクをリスト表示
     *
     * @param  Request  $request
     * @return Response
     */
    public function index(Request $request)
    {
        return view('tasks.index', [
            'tasks' => $this->tasks->forUser($request->user()),
        ]);
    }
    
    /**
     * 新しいタスクの作成
     *
     * @param  Request  $request
     * @return Response
     */
    public function store(Request $request)
    {
        $this->validate($request, [
            'name' => 'required|max:255',
        ]);

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

        return redirect('/tasks');
    }
    
}

 

ビューに値を表示

データベースから取得した値をビューに表示しましょう。

resources/views/tasks/index.blade.php」を以下の内容に書き換えましょう。

(※黄色が追加した部分です)

<!-- resources/views/tasks/index.blade.php -->

@extends('layouts.app')

@section('content')

    <!-- Bootstrapの定形コード… -->

    <div class="panel-body">
        <!-- バリデーションエラーの表示 -->
        @include('common.errors')

        <!-- 新タスクフォーム -->
        <form action="{{ url('task') }}" method="POST" class="form-horizontal">
            {{ csrf_field() }}

            <!-- タスク名 -->
            <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>

            <!-- タスク追加ボタン -->
            <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>

    <!-- 現在のタスク -->
    @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">

                    <!-- テーブルヘッダ -->
                    <thead>
                        <th>Task</th>
                        <th>&nbsp;</th>
                    </thead>

                    <!-- テーブル本体 -->
                    <tbody>
                        @foreach ($tasks as $task)
                            <tr>
                                <!-- Task Name -->
                                <td class="table-text">
                                    <div>{{ $task->name }}</div>
                                </td>

                                <td>
                                    <!-- TODO: 削除ボタン -->
                                </td>
                            </tr>
                        @endforeach
                    </tbody>
                </table>
            </div>
        </div>
    @endif

@endsection

$tasks」が0件でない場合、foreach文で回して一件ずつ「name」を表示します。

 

データベースの値の削除

先ほど追加したコードの「<!-- TODO: 削除ボタン -->」の部分に、以下のコードを追加して削除ボタンを追加しましょう。

<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>削除
    </button>
</form>

 

削除ボタンをクリックすると、DELETEでリクエストします。

formタグ内ではPOSTを使用していますが、これはHTMLのフォームが許容しているのがGETとPOSTのみなので、とりあえず記載しているだけです。

実際はDELETEのリクエストとなります。

ボタンがクリックされると、DELETE 「/task」リクエストがアプリケーションに送信され、TaskController@destroyメソッドが起動されます。

 

認可

実装予定のdestroyメソッドを見てみましょう。

Route::delete('/task/{task}', 'TaskController@destroy');

 

public function destroy(Request $request, Task $task) { 

// 

}

ルート中の{task}変数が、コントローラメソッド中の$task変数定義と一致するため、

Laravelの暗黙的モデル結合により、対応するタスクモデルのインスタンスが自動的に依存注入されます。

 

このままだと「/tasks/{task}」のURLへランダムなタスクIDを渡すことで、悪意のあるリクエストを仕込むことが可能です。

Taskインスタンスが実際に認証済みユーザが所有していることを確認するため、Laravelの認可機能を使う必要があります。

 

ポリシー

認証ロジックを単純で小さなクラスへ系統立てるため、Laravelは「ポリシー」を使っています。

通常、各ポリシーはモデルに対応しています。

では、コマンドを使用して「TaskPolicy」を作成しましょう。「app/Policies/TaskPolicy.php」として生成されます。

php artisan make:policy TaskPolicy

 

次にdestroyメソッドをポリシーへ追加します。

このメソッドはUserインスタンスとTaskインスタンスを引数に取ります。

このメソッドはシンプルにユーザのIDがタスクのuser_idと一致するかを調べるだけです。

実際、全ポリシーメソッドはtrueかfalseを返す必要があります。

<?php

namespace App\Policies;

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

class TaskPolicy
{
    use HandlesAuthorization;

    /**
     * 指定されたユーザーが指定されたタスクを削除できるか決定
     *
     * @param  User  $user
     * @param  Task  $task
     * @return bool
     */
    public function destroy(User $user, Task $task)
    {
        return $user->id === $task->user_id;
    }
}

最後にTaskモデルをTaskPolicyと関連付ける必要があります。

app/Providers/AuthServiceProvider.php」ファイルの$policiesプロパティに一行加えることで可能です。

これによりTaskインスタンスに対するアクションを認可したい場合に毎回使われるべきポリシーをLaravelに指定しています。

/**
 * アプリケーションにマップするポリシー
 *
 * @var array
 */
protected $policies = [
    'App\Task' => 'App\Policies\TaskPolicy',
];

 

アクションの認可

これでポリシーが書き上がりましたので、destroyメソッドで使用しましょう。

Laravelの全コントローラーから、AuthorizesRequestトレイトにより提供されているauthorizeメソッドを呼び出せます。

TaskController」に以下の内容を追記しましょう。

/**
 * 指定タスクの削除

 *
 * @param  Request  $request
 * @param  Task  $task
 * @return Response
 */
public function destroy(Request $request, Task $task)
{
    $this->authorize('destroy', $task);

    // タスクの削除処理...
}

 

 

データベースの値削除

TaskController」のdestroyメソッドに以下の内容を追記しましょう。

/**
 * 指定タスクの削除
 *
 * @param  Request  $request
 * @param  Task  $task
 * @return Response
 */
public function destroy(Request $request, Task $task)
{
    $this->authorize('destroy', $task);

    $task->delete();

    return redirect('/tasks');
}

 

これで今回のWebアプリ作成は完了となります。

お疲れさまでした。