# Laravel

# 準備

# セットアップ手順

  • Composer をインストール
  • C:/Users/Shota/AppData/Roaming/Composer/vendor/binにパスを通す
  • composer global require "laravel/installer"

# 基本

laravel new ${myProjectName}
php artisan serve # テスト専用コマンドである。本番環境では使うな。

# 本番環境のセットアップ(XAMPP の例)

例えばhtdocsにmy-appというプロジェクトを放り込んだとすると、https://some.com/my-app/publicというアドレスでアプリケーションにアクセスできる。

/my-app/publicを/にマップしたい(エイリアスを作りたい)場合は、本書 P20 に記載の設定を Apache の httpd.conf に入れること。

# ファイル・フォルダ構成

# ルートファイル

name description
.env, .env.example DB の認証情報など、動作に関する設定ファイル
artisan php artisan ***のようにして使う
composer.json, composer.lock composer がつかう
phpunit.xml ユニットテストに関する設定ファイル
server.php サーバ本体
webpack.min.js webpack の設定ファイル

# ルートフォルダ

*マークはよく使うフォルダ。

name description
app * アプリケーションの本体
bootstrap 起動時の処理
config 設定
database * DB 関係
public そのまま公開するファイル群。パスを取得したいときはasset()ヘルパを使う
resources * テンプレートや、ビルドすべき JS/CSS などのリソース
routes * ルーティング情報
storage ログなどのファイルの保存場所
tests ユニットテスト関係
vendor Laravel 本体のプログラム

# app フォルダ

name description
Console コンソールプログラム?
Exceptions 例外処理
Http Web アプリケーションにアクセスしたときの処理。一番良く使う
Providers サービスプロバイダ アプリ開始時の処理などを記述する
User.php ユーザ認証に関するプログラム

# routes フォルダ

name description
api.php API の機能を特定のアドレスに割り当てたい時に使う
channels.php ブロードキャストチャンネルのためのルーティング?
console.php コンソールプログラムのためのルーティング?
web.php 一般的な Web ページとしてアクセスするときのルーティング。一番良く使う

# ルーティングとコントローラ

# ルーティング

ルーティングの設定はroutes/web.phpに記載する。

# 生の HTML を返す

Route::get('/hello', function() {
  return '<h1>Some HTML!</h1>';
});

// GET以外のメソッドの場合
Route::post();
Route::put();
Route::delete();

# テンプレートを指定する

(実際はview()は Response オブジェクトを返す)

// routes/web.php

Route::get('/', function() {
  return view('welcome');
}); // => `resources/views/welcome.blade.php`

Route::get('/hello', function() {
  return view('hello.index');
}); // => `resources/views/hello/index.blade.php`

# コントローラを指定する

Route::get('/hello', 'HelloController@index');
Route::get('/hello', 'HelloController'); // シングルアクションコントローラの場合

# params を受け取る

任意の param には?をつける。任意の項目にはデフォルト値をセットしておくと良い。

Route::get('/hello/{msg}/{id}', function($msg, $id){ // 順に渡されるので名前は違ってもOK
    return "<h1>Hello! $msg2 $id</h1>";
});

Route::get('/hello/{msg?}/{id?}', function($msg='a', $id='b'){
    return "<h1>Hello! $msg $id</h1>";
});

# ルートの一覧を表示する

php artisan route:list

# コントローラ

# MVC

User <-> Controller <-┬-> View  <-> Templates
                      │
                      └-> Model <-> Database

# コントローラの作成

# 空のコントローラを作成する
php artisan make:controller HelloController

# リソースフル(典型的なメソッドをあらかじめ持っている)なコントローラを作成する
php artisan make:controller HelloController --resource
// app/Http/Controllers/HelloController.php

namespace App\Http\Controllers; // 名前空間の指定

use Illuminate\Http\Request; // Requestオブジェクトを使えるよう設定

class HelloController extends Controller {}; // コントローラ本体

# アクションの追加

  • アクション=コントローラに用意される処理のこと
  • 名前を変えることで、複数設定できる
  • インスタンスメソッドとして実装する
  • アクションは、HTML もしくはリクエストオブジェクトを Return する
// controller

class HelloController extends Controller
{
    public function index() {
        return <<<EOF
<h1>ID: $id</h1>
<h1>Pass: $pass</h1>
EOF;
    }
}

コントローラをルーティング設定に記載する。

// routes/web.php

Route::get('/hello', 'HelloController@index');

# シングルアクションコントローラ

一つしかアクションを持たないクラスの場合、__invoke()メソッドを実装することで記載を簡略化できる。

// controller

class HelloController extends Controller
{
    public function __invoke() {
        return '<h1>something</h1>';
    }
}

ルーティング設定ではメソッド名を省略する。

// routes/web.php
Route::get('/hello', 'HelloController');

# リソースフル

  • Resourceful = RESTful に加え、追加・編集・削除用フォーム等、リソースを扱うために必要なすべての機能を備えていること
  • 下記の手順でリソースフルなコントローラを作成し、作成したコントローラをRoute::resourceに渡してやることで、アクションを個別に作成することなく、簡単にリソースフルなコントローラを使い始めることができる。
php artisan make:controller RestappController --resource
Route::resource('/rest', 'RestappController');
http method route action RESTful Resourceful
GET /route index() * *
GET /route/create create() *
POST /route store() * *
GET /route/1 show() * *
GET /route/1/edit edit() *
PUT/PATCH /route/1 update() * *
DELETE /route/1 destroy() * *

# フォームから PUT や DELETE を送るには

  • リソースフルなルートを設定した場合は、更新や削除の処理をPUTやDELETEといったメソッドで行う必要がある。
  • しかし、フォームから遅れるのはPOSTメソッドだけである。
  • このため、PUTやDELETEを使う際は、_methodという隠し属性にメソッドを記述しておく必要がある。
<form action="/posts/1234/delete" method="POST">
    <input type="hidden" name="_method" value="PUT" />
</form>

# アクションで params を受け取る

// routes/web.php
Route::get('/hello/{id?}/{pass?}', 'HelloController@index');
// controller
class HelloController extends Controller
{
    public function index($id='defualtId', $pass='defaultPass') { /* do something */}
}

# Request, Response の利用

use Illuminate\Http\Request;
use Illuminate\Http\Response;

class HelloController extends Controller
{
    public function index(Request $request, Response $response) {
        $html = <<<EOF
<h1>Request</h1>
<pre>$request</pre>
<h1>Response</h1>
<pre>$response</pre>
EOF;
        $response->setContent($html);
        return $response;
    }
}

主なメソッド

  • $request->url() クエリを含まない URL を返す
  • $request->fullUrl() クエリを含む URL を返す
  • $request->path() ドメインより下のパス部分だけを返す
  • $request->QUERY_NAME クエリを取得する(クエリ名が id なら、$request->id)
  • $response->status() ステータスコードを返す
  • $response->content() コンテンツを取得
  • $response->setContent() コンテンツを設定

なお、Laravel では、下記のデータが全てリクエストオブジェクトにぶっこまれる(Express のように、req.params や req.query などはなく、いきなり req.propertyName の形で追加される)。

  • params
  • query string
  • form data (input の name 属性)
  • ミドルウェアから渡された値($request->merge(配列)など)

# ビューとテンプレート

# PHP テンプレートの利用

PHP テンプレートを使うには、view(フォルダ名.ファイル名)を呼ぶことで、テンプレートから Response インスタンスを作成する。

Route::get('/hello', function(){
    // resources/views/hello/index.phpというテンプレートを基にする
    // view()はResponseインスタンスを生成する
    return view('hello.index');
});

上記は、ルーティング設定にテンプレートを指定している。 ルーティング設定にコントローラを指定した場合は、次にようにする。

class HelloController extends Controller
{
    public function index() {
        return view('hello.index');
    }
}

テンプレートにデータを渡したいときは、view の第二引数に Array を渡す。

// controller
class HelloController extends Controller
{
    public function index() {
        // compact等を使って、渡すデータを一つづつ指定する方法
        $title = 'my title';
        $msg = 'my msg';
        return view('hello.index', compact('title', 'msg'));

        // 配列を使って、まるごとデータを渡す方法
        $data = [
            'msg1' => 'msg1です',
            'msg2' => 'msg2です',
        ];
        return view('hello.index2', $data);
    }
}
// template
<h1>{{ $title . $msg }}</h1>
<h1>{{ $msg1 . $msg2 }}</h1>

# Blade テンプレートの利用

blade テンプレートを利用するには、FILENAME.blade.phpの形式でテンプレートを作成する。 PHP テンプレートと Blade テンプレートが両方存在する場合は blade が優先される。

# Form を実装してみる

input 要素の name 属性が、そのまま$requestのプロパティとして取り出せるようになる。

// template
<form method="POST" action="/hello">
    @csrf // 外部サイトからのフォーム送信を防ぐため、認証データを挿入
    <input type="text" name="myname">
    <input type="submit">
</form>
// routing
Route::post('/hello', 'HelloController@post');
// controller
class HelloController extends Controller
{
    public function post(Request $request) {
        // $request->myname に入力した値が入っている
    }
}

# Blade の構文

# 値の表示

{{ 値など }}
{!! 値など !!} // HTMLエスケープしたくない場合

# 条件分岐

@if()
@elseif ()
@else
@endif

@unless()
@else
@endunless

@empty()
@else
@endempty

@isset()
@else
@endisset

# 繰り返し

@for (i=1; i<10; i++)
@endfor

@foreach ($array as $value)
@endforeach

// foreach-else
@forelse ($array as $value)
// 配列がある場合の処理
@empty
// 配列が空の場合の処理
@endforelse

@while ()
@endwhile

@break // 繰り返しを終了
@continue // 次のループへ

繰り返しの中では$loopという特殊なオブジェクトを使える。

  • $loop->index インデックス(0 スタート)
  • $loop->iteration 繰り返し数(1 スタート)
  • $loop->remaining 残り回数(このループを含まず)
  • $loop->count 繰り返しの元配列の要素数
  • $loop->first 最初のループかどうか
  • $loop->last 最後のループかどうか
  • $loop->depth 繰り返しのネスト数(1 スタート)
  • $loop->parent ネストしている場合に、親のループ変数を返す
@foreach ([1,2,3] as $value)
    {{$loop->index}}
@endforeach
// => 0,1,2

# php ディレクティブ

@phpを使うと、blade テンプレートの中に php を記載できる。 基本的にテンプレートの中にロジックを書くのはバッドプラクティスなので、 あくまでビューに関する利用にとどめること。

@php
    $counter = 0;
@endphp

@while($counter < 3);
    {{ $counter }}
    @php
        $counter += 1;
    @endphp
@endwhile

# レイアウトの作成

# ベースレイアウトと継承レイアウト

ベース側において、@yield(), @section() - @showを使って場所を用意しておき、 継承側で@section(), @section()-@endsectionを使うことで内容を埋めていく方法。

Nuxt.js の Layouts と同じような使い方をすればいいんだと思う。

ベースレイアウト

// resourses/views/layouts/helloapp.blade.php (layoutsというフォルダ名は変えてもいい)
<body>
    <h1>@yield('title')</h1>
    @yield('content')
    @section('menubar')
        <p>メニュー</p>
    @show
    @yield('footer')
</body>

継承レイアウト

// resourses/views/hello.blade.php
@extends('layouts.helloapp')

@section('title', 'My Title')

@section('content')
    <p>ここが本文です。</p>
@endsection

@section('menubar')
    @parent // ベースレイアウトの中身を継承したい時に使う
    メニュー項目など
@endsection

@section('footer')
    <p>copyright 2018</p>
@endsection

# コンポーネントの作成

  • ヘッダ、フッタなど、パーツごとに作成する方法
  • コンポーネントの作成方法は通常のテンプレートと全く同じ
// resources/views/components/message.blade.php (componentsというフォルダ名は変えてもいい)
<p>これはコンポーネントです</p>
<p>{{ $msg_title }}</p>
<p>{{ $msg_content }}</p>

# コンポーネントの利用その 1 (@component)

  • 値は@slot()で渡す
  • 親テンプレートの変数スコープは、コンポーネントに引き継がれない。
// resources/views/hello/index.blade.php

@component('components.message')

@slot('msg_title','タイトルやで')

@slot('msg_content')
コンテンツです
@endslot

@endcomponent

# コンポーネントの利用その 2(@include=サブビュー)

  • 値を渡すときは Array で渡す
  • 親テンプレートの変数スコープは、コンポーネントに引き継がれる。親テンプレートで利用できる変数は、何もせずに読み込んだコンポーネント内で利用できる。まさに、そこにテンプレートを継ぎ足したような挙動をするということ。
@include('components.message',[
    'msg_title' => 'タイトルです',
    'msg_content' => '本文です'
])

# コンポーネントの利用その 3 (@each=コレクションビュー)

  • 繰り返しデータをコンポーネントで表示する方法
  • @each(読み込むコンポーネント, 渡す変数, コンポーネント内にマップする変数名)
// テンプレート側 $dataは現実のアプリではコントローラ等から受け取ることになる
@php
$data = [
    ['name' =>'john', 'mail' => 'john@test.com'],
    ['name' => 'jack', 'mail' => 'jack@test.com']
]
@endphp

@each('components.item', $data, 'item')
// コンポーネント側 resources/views/components/item.blade.php
<li>{{ $item['name'] }} - {{ $item['mail'] }}</li>

# ビューコンポーザー

コントローラから、ビューに関するロジックを分離するために使う。 特定のビューを表示する際に必要となる処理を実行し、結果などの情報をテンプレートに渡す役割を持つ。

Client <-> Controller <-Rendering <-- View Template
                            ^
                            └----- View Composer

# サービスプロバイダの作成と登録

  • ビューコンポーザのセットアップは、サービスプロバイダという仕組みを使って行う。
  • サービスプロバイダを使うと、アプリケーション開始時に必要な処理を行うことができる。
  • まずは空のサービスプロバイダを登録する。
php artisan make:provider HelloServiceProvider
// app/Providers/HelloServiceProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class HelloServiceProvider extends ServiceProvider
{
    public function boot(){
      // アプリケーションの起動時に行われる処理
    }
}
// config/app.php
'providers' => [
    // 下記の行を追加する
    App\Providers\HelloServiceProvider::class,
],

# 無名関数を使用してセットアップ

// app/Providers/HelloServiceProvider.php
namespace App\Providers;

use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;

class HelloServiceProvider extends ServiceProvider
{
    public function boot()
    {
        View::composer('hello.index', function($view){
            $view->with('value_from_composer', 'this message is from view composer!');
        });
    }
}

// resources/views/hello/index.blade.php
{{ $value_from_composer }} // => 'this message is from view composer!'
  • サービスプロバイダ内でView::composer()を使って、ビューと対応する処理(無名関数 or クラス)を記載することでセットアップする。
  • 上記の例では、resources/views/hello/index.blade.phpが呼ばれた際に、無名関数内に記載した処理を行うように設定している。
  • $viewは View クラスのインスタンスで、これを使ってビューを操作する。上記例では、$view->with()を使ってビューに変数を追加している。

# クラスを使用してセットアップ

先述の無名関数の部分をクラスとして実装する方法。 ビューコンポーザクラスは、何も継承していないただのクラスである。 composeというメソッドを実装してさえいれば OK。

// app/Http/Composers/HelloComposer.php  Composerフォルダの名前は変えてもOK
namespace App\Http\Composers; // これを忘れると他のファイルから参照できない

use Illuminate\View\View;

class HelloComposer
{
    public function compose(View $view)
    {
        $view->with('value_from_composer',  'this message is from view composer!');
    }
}

クラスを作成したら、サービスプロバイダにおけるビューへの処理の割当を、クラスを使ったものに書き換える。

// app/Providers/HelloServiceProvider.php
class HelloServiceProvider extends ServiceProvider
{
    // アプリケーションの起動時に行われる処理
    public function boot()
    {
        View::composer('hello.index', 'App\Http\Composers\HelloComposer');
    }
}

# リクエスト・レスポンスの補完

# ミドルウェア

  • コントローラの前や後に割り込み、処理を行う
  • ミドルウェアは、ルーティング情報を記載する際に指定できる

# 雛形の作成

php artisan make:middleware HelloMiddleware
// app/Http/Middleware/HelloMiddleware.php
namespace App\Http\Middleware;

use Closure; // 無名クラスを表すクラス?

class HelloMiddleware
{
    public function handle($request, Closure $next)
    {
        // $requestを使って、リクエストに割り込む処理をここに書く
        $response = $next($request);
        // $responseを使って、レスポンスに割り込む処理をここに書く
        return $response;
    }
}
// ルーティング設定
Route::get('/hello', 'HelloController@index')
    ->middleware(HelloMiddleware::class)
    ->middleware(SomeOtherMiddleware::class);
  • ユーザからのリクエストがあると、最初のミドルウェアのhandle()が実行される。
  • handle()の中の$nextは下記のいずれかを呼ぶ。なお、下記はどちらもレスポンスオブジェクトを返す。
    • 次のミドルウェアのhandle()メソッド
    • 次のミドルウェアが存在しない場合はコントローラのアクション
  • $nextが次々に呼ばれていくので、再帰的に処理が実行されていく
    • 最初のミドルウェアのリクエストに関する処理
    • n 番目のミドルウェアのリクエストに関する処理
    • コントローラのアクション
    • n 番目のミドルウェアのレスポンスに関する処理
    • 1 番目のミドルウェアのレスポンスに関する処理

# リクエストを修正する

リクエストオブジェクトを操作することで、リクエスト内容に手を加えることができる。例えば下記では、 $request->merge(配列)を使うことで、リクエストオブジェクトにプロパティを追加し、ミドルウェアからコントローラにデータを渡している。

// ミドルウェア
class HelloMiddleware
{
    public function handle($request, Closure $next)
    {
        $additionalData = [
            ['name'=>'taro', 'mail'=>'taro@dot.com'],
            ['name'=>'hanako', 'mail'=>'hanako@dot.com'],
        ];
        $request->merge(['additionalData' => $additionalData]);
        return $next($request);
    }
}
// コントローラ
class HelloController extends Controller
{
    public function index(Request $request) {
        return view('hello.index', ['mydata'=>$request->additionalData]);
    }
}

# レスポンスを修正する

レスポンスオブジェクトを操作することで、コントローラの作成したレスポンス内容に手を加えることができる。

class HelloMiddleware
{
    public function handle($request, Closure $next)
    {
        $response = $next($request);
        $content = $response->content();
        $content = '<h1>some addition to the response</h1>'.$content;
        $response->setContent($content);
        return $response;
    }
}

# グローバルミドルウェア

ミドルウェアを、個別のコントローラではなく、アプリケーション全体に適用する方法

// app/Http/Kernel.php
protected $middleware = [
    \App\Http\Middleware\HelloMiddleware::class,
];

# ミドルウェアグループ

複数のミドルウェアをまとめて扱う方法

// app/Http/Kernel.php
protected $middlewareGroups = [
    'myMiddlewares' => [
        \App\Http\Middleware\HelloMiddleware1::class,
        \App\Http\Middleware\HelloMiddleware2::class,
    ],
];
// routes/web.php
Route::get('/hello', 'HelloController@index')
    ->middleware('myMiddlewares');

# バリデーション

バリデーションの設定方法の前に、バリデーション関係で共通する事項を記載しておく。

# エラーの表示

  • $error バリデーションに失敗した時に、エラーが格納されるオブジェクト
  • $error->all() すべてのエラーを配列にして受け取る
// Template
@if (count($errors) > 0)
    @foreach($errors->all() as $error)
        <p>{{ $error }}</p>
    @endforeach
@endif

# 特定項目のエラーを表示

  • $error->has(項目名) 特定項目にエラーがあるかどうかを確認する
  • $error->first(項目名) 特定項目の最初のエラーを文字列で取得
  • $error->get(項目名) 特定項目のすべてのエラーを配列で取得
@if($errors->has('email'))
    // 最初のエラーだけを取得
    <p>{{ $errors->first('email')}}</p>

    // 又は配列で取得
    @foreach($errors->get('email') as $error)
    <p>{{ $error }}</p>
    @endforeach
@endif

# 入力値の保持

バリデーション後も値を保持しておくには、old(項目名)を value 属性に設定する。 なお、バリデーションが成功した場合にはold()には何も値は入らない。

// Template
name:<input type="text" name="name" value="{{old('name')}}">
mail:<input type="text" name="mail" value="{{old('mail')}}">
age:<input type="text" name="age" value="{{old('age')}}">

# バリデーションルール

公式ドキュメントを参照

# バリデーション(コントローラの validate メソッドを使う方法)

# 基本的なセットアップ

// Template
<p>{{ $msg }}</p>
<form action="/hello" method="POST">
    name:<input type="text" name="name">
    mail:<input type="text" name="mail">
    age:<input type="text" name="age">
    <input type="submit" value="send">
</form>
// Controller
class HelloController extends Controller
{
    public function index(Request $request) {
        return view('hello.index', ['msg'=>'フォームを入力:']);
    }

    public function post(Request $request) {
        $validate_rule = [
            'name' => 'required',
            'mail' => 'email',
            'age' => 'numeric|between:0,150',
        ];
        $this->validate($request, $validate_rule);
        return view('hello.index', ['msg'=>'正しく入力されています']);
    }
}
  • POST に対応するアクション(post())内で、$this->validate(リクエスト,ルール)を呼ぶことでバリデーションを行う。
  • バリデーションに失敗した場合は自動的に GET に対応するアクション(index())が呼ばれる。

# バリデーション(FormRequest を使う方法)

前述の方法はコントローラが直接バリデーション機能を呼び出しており、あまりスマートでない。Laravel には、FormRequest という Request クラスを継承したクラスがある。これを使うことにより、コントローラの前段で自動的にバリデーションを実行することができる。

php artisan make:request HelloRequest
// app/Http/Requests/HelloRequest.php

namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;

class HelloRequest extends FormRequest
{
    // このFormRequestを利用できるパスを限定している
    public function authorize()
    {
        if($this->path() == 'hello') return true;
        return false;
    }

    public function rules()
    {
        return [
            'name' => 'required',
            'mail' => 'email',
            'age' => 'numeric|between:0,150',
        ];
    }
}
// Controller
class HelloController extends Controller
{
    public function index(Request $request) {
        return view('hello.index', ['msg'=>'フォームを入力:']);
    }

    // RequestではなくHelloRequestにする
    public function post(HelloRequest $request) {
        return view('hello.index', ['msg'=>'正しく入力されています']);
    }
}

# メッセージのカスタマイズ

FormRequest クラスのmessages()メソッドをオーバーライドすることで、エラーメッセージをカスタマイズできる。

class HelloRequest extends FormRequest
{
    public function messages()
    {
        return [
            'name.required' => '名前を入力してください',
            'mail.email' => 'メールアドレスの形式が正しくありません',
            'age.numeric' => '年齢は整数で入力してください',
            'age.between' => '年齢が正しくありません',
        ];
    }
}

# バリデーション(バリデータオブジェクトを作成する方法)

下記のことを行いたい場合は、バリデータオブジェクトを作成する。

  • エラー時に GET ページにリダイレクトせず、別の処理を行いたい
  • フォームの値以外でバリデーションしたい場合
// Controller

use Validator;

class HelloController extends Controller
{
    public function post(Request $request) { // FormRequestではないので注意
        $rules = [
            'name' => 'required',
            'mail' => 'email',
            'age' => 'numeric|between:0,150',
        ];

        $messages = [
            'name.required' => '名前を入力してください',
            'mail.email' => 'メールアドレスの形式が正しくありません',
            'age.numeric' => '年齢は整数で入力してください',
            'age.between' => '年齢が正しくありません',
        ];

        // POSTデータを含む全てのデータ($request->all() = 連想配列)を渡している
        $validator = Validator::make($request->all(), $rules, $messages);
        if($validator->fails()) {
            return redirect('/hello')
                ->withErrors($validator)
                ->withInput();
        }
        return view('hello.index', ['msg'=>'正しく入力されています']);
    }
}
  • Validator::make(チェックしたい値の配列, ルールの配列[, メッセージの配列]) validator を作成する
  • $validator->fails() バリデーションが失敗かどうか
  • $validator->passes() バリデーションが成功かどうか
  • redirect(リダイレクト先のパス) リダイレクトする
  • ->withErrors($validator) エラー($errors)とともにリダイレクト
  • ->withInput() 入力内容(old())とともにリダイレクト
  • ->with(セッション値名前、セッション値) session 値とともにリダイレクト

# クエリにバリデータを適用する

バリデータオブジェクトを応用すれば、クエリを検証することも可能。

class HelloController extends Controller
{
    public function index(Request $request) {
        $validator = Validator::make($request->query(), [ // query(連想配列)を渡している
            'id' => 'required',
            'pass' => 'required',
        ]);
        /* do something */
    }
}

# バリデータに動的にルールを追加する

フォームへの入力内容などによって動的にルールを変更したい場合は、下記を使う。 無名関数の返値がfalse の場合に適用されるので注意。

$validator->sometimes(項目名, ルール, 適用するならfalseを返す無名関数)

class HelloController extends Controller
{
    public function post(Request $request) {
        $rules = [
            'name' => 'required',
            'mail' => 'email',
            'age' => 'numeric|between:0,150',
        ];

        $validator = Validator::make($request->all(), $rules);
        $validator->sometimes('age', 'min:0', function($input){
            return !is_int($input->age);
        });
        $validator->sometimes('age', 'max:150', function($input){
            return !is_int($input->age);
        });
        /* do something */
    }
}

# バリデーション(Validator クラス自体を上書きする方法)

オリジナルの検証ルールを作りたいときに使う方法。

1.オリジナルのバリデータークラスを作り、その中に検証ルールを作る

// app/Http/Validators/HelloValidator.php (Validatorsのフォルダ名は何でもOK)

namespace App\Http\Validators;

use Illuminate\Validation\Validator;

class HelloValidator extends Validator
{
    // validate*** の *** の部分がルール名になる(この場合'hello')
    public function validateHello($attribute, $value, $parameters)
    {
        return $value % 2 === 0;
    }
}

2.サービスプロバイダを使ってアプリ起動時にバリデータを上書きする

// サービスプロバイダ

use Illuminate\Validation\Validator;
use App\Http\Validators\HelloValidator;

class HelloServiceProvider extends ServiceProvider
{
    // アプリケーションの起動時に行われる処理
    public function boot()
    {
        $validator = $this->app['validator'];
        $validator->resolver(function(...$args){
            return new HelloValidator(...$args);
        });
    }
}

3.検証ルールを設定する

$rules = [
    'name' => 'required',
    'mail' => 'email',
    'age' => 'numeric|between:0,150|hello',
];

# オリジナルの検証ルール

オリジナルの検証ルールを作りたいが、Validator クラス自体を上書きするほどではないという場合は、 Validator::extend(ルール名, 無名関数)を使う。お手軽。

// サービスプロバイダ

use Validator;

class HelloServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Validator::extend('hello', function($attribute, $value, $parameters, $validator){
            return $value % 2 == 0;
        });
    }
}

# その他

# CSRF 対策を無効にする

Kernel.php=>$middlewareGroups=>webの中から下記の行を削除

\App\Http\Middleware\VerifyCsrfToken::class,

# CSRF 対策を一部のルートで無効にする

app\Http\Middleware\VerifyCsrfToken.phpに下記を追加する。

protected $except = [
    'hello', // somedomain.com/hello で無効化
    'hello/*', // somedomain.com/hello/ このパス以下のすべてのページで無効化
];

# クッキーの読み書き

  • $request->hasCookie(クッキー名) 指定したクッキーの有無を取得する
  • $request->cookie(クッキー名) 指定したクッキーを取得する
  • $response->cookie(クッキー名, 値, 保存期間) 指定したクッキーをセットする
class HelloController extends Controller
{
    // クッキーの取得
    public function index(Request $request) {
        if($request->hasCookie('my-cookie-name'))
        {
            $msg = $request->cookie('my-cookie-name');
        }
        /* do something */
    }

    // クッキーの保存
    public function post(HelloRequest $request) {
        // 一度、Responseインスタンスを生成する必要がある
        $response = new Response(view('hello.index'));
        $response->cookie('my-cookie-name', $request->someValue, 100);
        return $response;
    }
}

# リダイレクト

  • redirect(パス) => RedirectResponse というオブジェクトを返す
  • redirect() => Redirector というオブジェクトを返す

RedirectResponse の使い方

  • ->withInput() フォームの値を付与したままリダイレクト
  • ->withErrors(<MessageProvider>) エラーメッセージを付与してリダイレクト
  • ->withCookie(cookieの配列) Cookie を付与してリダイレクト?

Redirector の使い方

  • ->route(ルート名, 渡すデータの配列) ルート名の部分は/helloなど
  • ->action(アクション, 渡すデータの配列) アクションの部分は'SomeController@index'など
  • ->view(ビュー名) ビューを指定してリダイレクト
  • ->json(テキスト) JSON データを返す
  • ->download(パス) ファイルをダウンロード
  • ->file(パス) ファイルを表示

# データベース

Laravel のデータベース操作にはいくつかの方法がある。

  • DB クラスを使う(SQL 直打ち)
  • DB クラスを使う(クエリビルダ)
  • Eloquent(Object-Relational Mapping)

# 準備

  • sqlite のインストール(system32 フォルダにダウンロードした dll をぶちこむ)
  • DB browser for sqlite のインストール

# DB との接続

接続の設定はconfig/database.phpで行われる。しかし、設定項目の多くはenv()ヘルパを使って環境変数から読み込まれ、環境変数がない場合だけデフォルト値(env()の第二引数)を指定する形式なっている。このため、実際の設定は.envファイル又は環境変数において行うほうがよい。

例えば、sqlite の場合の設定は下記の通り

DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
DB_DATABASE="C:\Users\SomeUser\Desktop\my-first-laravel\database\database.sqlite"
# DB_USERNAME=homestead
# DB_PASSWORD=secret

# DB クラス

# select

// コントローラ
use Illuminate\Support\Facades\DB;

class HelloController extends Controller
{
    public function index(Request $request) {
        $items = DB::select('select * from people');

        // or パラメータ結合を使う場合
        $params = ['id' => 1234];
        $items = DB::select('select * from people where id = :id', $params);
    }
}

# insert

  • /hello/addに GET でアクセスした場合は、フォームを表示
  • /hello/addに POST でアクセスした場合は、DB にデータを追加してリダイレクト
// ルーティング
Route::get('/hello/add', 'HelloController@showAddForm');
Route::post('/hello/add', 'HelloController@addNewData');
// コントローラ
class HelloController extends Controller
{
    public function showAddForm(Request $request) {
        return view('hello.add');
    }

    public function addNewData(Request $request) {
        $param = [
            'name' => $request->name,
            'mail' => $request->mail,
            'age' => $request->age,
        ];
        DB::insert('insert into people (name, mail, age) values (:name, :mail, :age)', $param);
        return redirect('/hello');
    }
}
<!-- テンプレート -->
<form action="/hello/edit" method="post">
  @csrf

  <div>name:<input type="text" name="name" /></div>
  <div>mail:<input type="text" name="mail" /></div>
  <div>age:<input type="text" name="age" /></div>

  <input type="submit" value="send" />
</form>

# update

  • /hello/edit に GET でアクセスした場合は、データを取得したのち、フォームを表示
  • /hello/update に POST でアクセスした場合は、データを更新してリダイレクト
// ルーティング
Route::get('/hello/edit', 'HelloController@showEditForm');
Route::post('/hello/update', 'HelloController@updateData');
  • old('mail', $form->mail)とすることで、バリデーション失敗時に値を保持しておくことができる。第二引数はデフォルト値であり、初回表示の際に使用される。
// コントローラ
class HelloController extends Controller
{
    public function showEditForm(Request $request) {
        $param = ['id' => $request->id];
        $item = DB::select('select * from people where id = :id', $param);
        return view('hello.edit', ['form' => $item[0]]);
    }

    public function updateData(Request $request) {
        $param = [
            'id' => $request->id,
            'name' => $request->name,
            'mail' => $request->mail,
            'age' => $request->age,
        ];
        DB::update('update people set name = :name, mail = :mail, age = :age where id = :id', $param);
        return redirect('/hello');
    }
}
<!-- テンプレート -->
<form action="/hello/edit" method="post">
  @csrf

  <!-- IDを保持しておく必要あり -->
  <input type="hidden" name="id" value="{{$form->id}}" />

  <div>
    name:<input
      type="text"
      name="name"
      value="{{ old('name', $form->name) }}"
    />
  </div>
  <div>
    mail:<input
      type="text"
      name="mail"
      value="{{ old('mail', $form->mail) }}"
    />
  </div>
  <div>
    age:<input type="text" name="age" value="{{ old('age', $form->age) }}" />
  </div>

  <input type="submit" value="send" />
</form>

# delete

  • /hello/delete に GET でアクセスした場合は、データを取得したのち、フォームを表示
  • /hello/delete に POST でアクセスした場合は、データを削除してリダイレクト
// ルーティング
Route::get('/hello/delete', 'HelloController@showDeleteForm');
Route::post('/hello/delete', 'HelloController@removeData');
// コントローラ
class HelloController extends Controller
{
    public function showDeleteForm(Request $request) {
        $param = ['id' => $request->id];
        $item = DB::select('select * from people where id = :id', $param);
        return view('hello.delete', ['form' => $item[0]]);
    }

    public function removeData(Request $request) {
        $param = [
            'id' => $request->id,
        ];
        DB::delete('delete from people where id = :id', $param);
        return redirect('/hello');
    }
}
<!-- テンプレート -->
<form action="/hello/edit" method="post">
  @csrf

  <!-- IDを保持しておく必要あり -->
  <input type="hidden" name="id" value="{{$form->id}}" />

  <div>name: {{$form->name}}</div>
  <div>mail: {{$form->mail}}</div>
  <div>age: {{$form->age}}</div>

  <input type="submit" value="send" />
</form>

# クエリビルダ

DB::table()は、Illuminate\Database\Query\Builderクラスを返す。 これを使うことでテーブルの操作を行える。

# select

// コントローラ

// 複数件を取得
$items=DB::table('people')->get();
$items=DB::table('people')->get(['id', 'name']); // カラムを指定する場合

// 最初の1件を取得
$items=DB::table('people')->first();

// 最初の3件を取得
$items=DB::table('people')->take(3);

// 条件を指定して取得
$items=DB::table('people')->where('id', $request->id)->get();
$items=DB::table('people')->where('id', $request->id)->first();
$items=DB::table('people')->where('id', '<=', $request->id)->get();
$items=DB::table('people')->where('name', 'like', '%John%')->get();

// 条件指定(AND)
$items=DB::table('people')->where()->where()->get();

// 条件指定(or)
$items=DB::table('people')->where()->orWhere()->get();

// 条件指定(条件を配列で指定)
$items=DB::table('people')->whereRaw('age >= ? and age <= ?', [10, 20])->get();

// 並べ替え
$items=DB::table('people')->orderBy('name', 'desc')->get();

// ページネーションなど
$items=DB::table('people')->offset($page * 10)->limit(10)->get();

# insert

$param = [
    'name' => $request->name,
    'mail' => $request->mail,
    'age' => $request->age,
];
DB::table('people')->insert($param);

# update

$param = [
    'id' => $request->id,
    'name' => $request->name,
    'mail' => $request->mail,
    'age' => $request->age,
];
$item = DB::table('people')->where('id', $request->id)->update($param);

# delete

DB::table('people')->where('id', $request->id)->delete();

# マイグレーション

マイグレーション= DB のバージョン管理機能のこと。雛形に基づいて DB を作成したり、削除したりする。

使用できる Column については公式ドキュメントを参照

# Eloquentのモデル作成時に、マイグレーションファイルを一緒に作成する方法
php artisan make:model Person -m

# マイグレーションを単体で作成する方法
php artisan make:migration create_people_table # people がテーブル名になる
// database/migration/****_create_people_table.php

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

class CreatePeopleTable extends Migration
{
    // テーブル生成の処理
    public function up()
    {
        Schema::create('people', function (Blueprint $table) {
            $table->increments('id'); // primary key
            $table->string('name');
            $table->string('mail');
            $table->integer('age');
            $table->timestamps(); // created_at, updated_at
        });
    }

    // テーブル削除の処理
    public function down()
    {
        Schema::dropIfExists('people');
    }
}
touch database/database.sqlite # 空のDBファイルを作成
php artisan migrate
php artisan migrate:fresh # すべてのテーブルをドロップして再作成

# あとからカラムを追加する

class AddUserIdToPosts extends Migration
{
    public function up() {
        Schema::table('posts', function (Blueprint $table) {
            // 列を追加する
            $table->integer('user_id');
        });
    }

    public function down() {
        Schema::table('posts', function (Blueprint $table) {
            // ロールバック時には列を削除する
            $table->dropColumn('user_id');
        });
    }
}

# シーディング

シーディング=シード(最初から用意しておくレコード)を作成する機能

php artisan make:seeder PeopleTableSeeder
// database/seeds/PeopleTableSeeder

use Illuminate\Support\Facades\DB;

class PeopleTableSeeder extends Seeder
{
    public function run()
    {
        $param = [
            'name' => 'taro',
            'mail' => 'taro@taro.com',
            'age' => 18,
        ];
        DB::table('people')->insert($param);
        /* 必要に応じて更にデータを追加する */
    }
}

シーディングで実行されるファイルはdatabase/seeds/DatabaseSeeder.phpなので、ここに作成したシーダーを追記しておく。

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $this->call(PeopleTableSeeder::class);
    }
}
php artisan db:seed

# Eloquent ORM

ORM = DB のデータを、クラスやオブジェクトの形式で扱えるようにするために、PHP と DB を橋渡しする仕組み

# セットアップ

php artisan make:controller PersonController # 複数形にすること

php artisan make:model Person # 単数形にすること
# or
php artisan make:model Person -m # -mをつけるとマイグレーションファイルも生成される
// ルーティング
Route::get('/person', 'PersonController@index');

# モデルクラス

モデルと DB を紐付けるための各種設定や、モデルを便利に扱うためのクラスの拡張をここで行う。

ORM と DB クラスとの相違点は、データが単なる配列ではなくPerson クラスのインスタンスである点である。このため、クラスを拡張すればインスタンスの振る舞いも拡張することができる。

// app/Person.php
class Person extends Model
{
    // テーブル名を手動設定する場合(default: モデル名の複数形)
    protected $table = 'people';

    // primary keyを手動設定する場合(default: id)
    public $primaryKey = 'id';

    // Auto Increment な項目など、値をサーバ側で
    // 設定するプロパティはここで明示しておくこと
    protected $guarded = ['id'];

    // モデルクラスのスタティックプロパティにバリデーションルールを
    // 持っておくと後々便利
    public static $rules = [
        'name' => 'required',
        'mail' => 'email',
        'age' => 'integer|min:0|max:150',
    ];

    // クラスの拡張
    public function getData() {
        return $this->id.':'.$this->message.'('.$this->url.')';
    }
}

# データの取得

# all()

$items = Person::all();

全件取得できる。Person::all()は、Illuminate\Database\Eloquent\Collectionクラスのインスタンスを返す。このインスタンスは配列と同じように扱うことができる。

# find()

$item = Person::find($request->id);

id フィールドが指定の値であるデータを 1 件だけ取得する。

なお、もし id フィールドの名前がidでない場合は、モデルクラスに$primaryKeyというプロパティを用意し、これにフィールド名を設定すること。

# where()

$items = Person::where('name', 'John')->get();
$items = Person::where('name', 'John')->first();

where()はIlluminate\Database\Eloquent\Builderクラスのインスタンスを返す。DB クラスのクエリビルダとは異なるものの、機能はほとんど同じ。

# その他

その他のメソッドは基本的にクエリビルダと同じ。

# スコープ

  • スコープ=予め設定しておいた検索条件の雛形。組み合わせて使うこともできる。
  • グローバルスコープとローカルスコープがある

# ローカルスコープ

scope****という名前で定義する。

// モデル
class Person extends Model
{
    public function scopeNameEqual($query, $str) {
        return $query->where('name', $str);
    }

    public function scopeAgeGreaterThan($query, $age) {
        return $query->where('age', '>', $age);
    }

    public function scopeAgeLessThan($query, $age) {
        return $query->where('age', '<', $age);
    }
}
  • 使うときはscopeはつけない。
  • 複数のスコープを組み合わせる場合は、チェーンして使う。
// コントローラ
$item = Person::nameEqual('taro')
    ->ageGreaterThan(10)
    ->ageLessThan(20)
    ->where('mail', 'taro@taro.com')
    ->first();

# グローバルスコープ(無名関数で指定)

特に指定しなくてもスコープを適用したい場合は、グローバルスコープを使う。 グローバルスコープは、モデルクラスのboot()という静的メソッド内部で定義する。

// モデル

use Illuminate\Database\Eloquent\Builder;

class Person extends Model
{
    protected static function boot(){
        parent::boot();

        static::addGlobalScope('age', function (Builder $builder){
            $builder->where('age', '>', 17);
        });
    }
}

# グローバルスコープ(スコープクラスで指定)

  • 前項をスコープクラスで行う方法。
  • Scope クラスは、apply()ファンクションをもつScopeインターフェースを実装する。
// app/Scopes/ScopePerson.php (Scopesのフォルダ名はなんでもOK)

namespace App\Scopes;

use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class ScopePerson implements Scope{
    public function apply(Builder $builder, Model $model) {
        $builder->where('age', '>', 10);
    }
}
// モデル
class Person extends Model
{
    protected static function boot(){
        parent::boot();
        static::addGlobalScope(new ScopePerson());
    }
}

# データの追加・更新・削除

以下、Create, Update, Delete のやり方を記載。なお、テンプレートのコードはDB クラスを使ったコードとほぼ同じなため記載省略。

# 追加

// ルーティング
Route::get('/person/add', 'PersonController@add');
Route::post('/person/add', 'PersonController@create');
class PersonController extends Controller
{
    public function add(Request $request) {
        return view('person.add');
    }

    public function create(Request $request) {
        // バリデートする。失敗したらGETのルーティングにリダイレクトされる。
        $this->validate($request, Person::$rules);

        // インスタンスを作成
        $person = new Person();

        // 【方法1】リクエストにぶら下がっている値をインスタンスにセットし、DBに保存
        $postData = $request->all();
        $person->fill($postData)->save();

        // 【方法2】リクエストのデータをインスタンスに一つずつセットして、DBに保存
        $person->name = $request->name;
        $person->mail = $request->mail;
        $person->age = $request->age;
        $person->save();

        return redirect('/hello');
    }
}

# 更新

// ルーティング
Route::get('/person/edit', 'PersonController@edit');
Route::post('/person/edit', 'PersonController@update');
// コントローラ
class PersonController extends Controller
{
    public function edit(Request $request) {
        $person = Person::find($request->id);
        return view('person.edit', ['form'=> $person]);
    }

    public function update(Request $request) {
        // バリデートする。失敗したらGETのルーティングにリダイレクトされる。
        $this->validate($request, Person::$rules);

        // インスタンスを作成
        $person = Person::find($request->id);

        // リクエストにぶら下がっている値をインスタンスにセットし、DBに保存
        $postData = $request->all();
        $person->fill($postData)->save();

        return redirect('/person');
    }
}

# 削除

// ルーティング
Route::get('/person/delete', 'PersonController@delete');
Route::post('/person/delete', 'PersonController@remove');
// コントローラ
class PersonController extends Controller
{
    public function delete(Request $request) {
        $person = Person::find($request->id);
        return view('person.delete', ['form'=> $person]);
    }

    public function remove(Request $request) {
        $person = Person::find($request->id)->delete();
        return redirect('/person');
    }
}

# リレーション

  • 主テーブル(キーを提供するテーブル。顧客マスタなど。)
  • 従テーブル(キーを利用するテーブル。顧客訪問履歴など。)

# hasOne

テーブルが 1:1 で結びつく関係。

例)person と board が 1:1で結びつく場合

class Person extends Model
{
    // 単数形
    public function board() {
        return $this->hasOne('App\Board');
    }
}
class Board extends Model
{
    protected $guarded = ['id'];

    public static $rules = [
        'person_id' => 'required', // 「テーブル名_id」というキーで自動的に結び付けられる
        'title' => 'required',
        'message' => 'required',
    ];
}
// personインスタンスからは、`board()`ではなく`board`のようにプロパティでアクセスできる。
// hasOneの場合は、オブジェクトが返る。
echo $person->board->message;

# hasMany

テーブルが 1:N で結びつく関係。

例)person と board が 1:N で結びつく場合

class Person extends Model
{
    // 複数形
    public function boards() {
        return $this->hasMany('App\Board');
    }
}
class Board extends Model
{
    protected $guarded = ['id'];

    public static $rules = [
        'person_id' => 'required', // 「テーブル名_id」というキーで自動的に結び付けられる
        'title' => 'required',
        'message' => 'required',
    ];
}
// personインスタンスからは、`board()`ではなく`board`のようにプロパティでアクセスできる。
// hasManyの場合は、オブジェクトではなくイテラブルが返る。
@foreach ($person->boards as $board)
  echo $board->message;
@endforeach

# belongsTo

従テーブルから主テーブルを取得する時に使う。hasOne や hasMany とは逆の方向。

例)board が person に所属する場合

class Board extends Model
{
    public static $rules = [
        'person_id' => 'required', // このキーを基にルックアップする
        'title' => 'required',
        'message' => 'required',
    ];

    public function person() {
        return $this->belongsTo('App\Person');
    }

    public function getData() {
        // $this->personで主テーブルにアクセスできる
        return $this->id.': '.$this->title.'('.$this->person->name.')';
    }
}

# リレーションの有無で検索する

例えば、person と board が 1:N で関連する場合、board(リレーション)を持つ person とそうでない person が生まれる。これを検索するための便利なメソッドとして、has()とdoesntHave()がある。

Person::has('boards')->get(); // => iterable
Person::doesntHave('boards')->get(); // => iterable

# with による Eager ローディング

前述の例は、実はあまり効率的でない。たとえば、Person::all()とした時、「Person を取得、その後 Person1 件ごとに、関連付けられた Board を取得」という動作になっている。(N+1 問題)

これを、「Person を取得、その後、関連する Board を 1 回で取得」という方法にするには、all()の替わりにwith()を使う。

Person::with('boards')->get();

# Tinker

モデルを使って DB を操作できる、REPL のようなもの。

php artisan tinker
App\Post::count()
App\Post::all()
App\Post::where('title','Post1')->get()
App\Post::find($id)->delete()

$post = new App\Post()
$post->title = 'Post1'
$post->body = 'Post1 body'
$post->save()

# フロントエンド

# セットアップ

下記コマンドを実行することで、resouces/js|cssのファイルがコンパイルされ、public/js|cssに配置される。

yarn
yarn dev # 1回きりのコンパイル
yarn watch # ファイルの変更を監視してコンパイル

# CSS

  • Laravel のプロジェクトには、resources/sassの中に標準で CSS が用意されている。
  • この CSS は Bootstrap を内包している。
  • Pagination のパーツなどはこの CSS ファイルを前提にでスタイリングされている。
<head>
  <link rel="stylesheet" href="{{ asset('css/app.css') }}" />
</head>

# その他

# RESTful API

# セットアップ

まず、--apiオプションにより、RESTful なコントローラを作成する。(逆に、Resourceful なコントローラを作るときは--resourceオプションを使う)

REST API では create()や edit()アクションは必要ないので作成されない。

php artisan make:controller RestappContoller --api

作成したコントローラをRoute::apiResources()に渡す。Route::resources()と異なり、create()や edit()のためのルートは必要ないので作成されない。

Route::apiResources('/rest', 'RestappController');

# データの取得

  • laravel では、アクション内で配列 | Eloquent の Collection | モデルのインスタンスを Return すると自動的に JSON に変換してクライアントに返してくれる。
  • よって、単に DB を検索して Return してやれば OK
class RestappController extends Controller
{
    public function index()
    {
        return Restdata::all();
    }

    public function show($id)
    {
        return Restdata::find($id);
    }
}

# データの追加

class RestappController extends Controller
{
    public function create()
    {
        return view('rest.create');
    }

    public function store(Request $request)
    {
        $restdata = new Restdata();
        $postData = $request->all();
        $restdata->fill($postData)->save();
        return redirect('/rest');
    }
}

# データの更新・削除

省略

# セッション

  • クライアント側にセッション ID を、サーバ側に ID に紐づくデータを保存することで、ユーザを識別する方法
  • サーバ側の保存手法には、ファイル利用(デフォルト。storage/framework/sessions)、メモリ利用、データベース利用などいくつかの方法がある。
// 保存
$request->session()->put('msg', $msg);

// 取得
$request->session()->get('msg');

// セッション情報とともにリダイレクト
redirect('/home')->with('somekey', 'somevalue');

# 保存先を DB にする

セッションの設定はconfig/session.phpで行われる。多くの設定は env ヘルパにより環境変数から読み込まれるため、.envを編集する。

// .env
SESSION_DRIVER=database

session 用のテーブルを作成する。手作業ではなく、マイグレーションを使用する。

php artisan session:table # マイグレーションファイルの作成
php artisan migrate # マイグレーションの実行

# リダイレクト時にセッションを使ってエラーメッセージを表示する

下記のような、メッセージ表示用コンポーネントを作成して、マスターレイアウトに include しておく。

// 'success'というセッション値があった場合
@if (session('success'))
    <div class="alert alert-success">
        {{ session('success')}}
    </div>
@endif

// 'error'というセッション値があった場合
@if (session('error'))
    <div class="alert alert-danger">
        {{ session('error')}}
    </div>
@endif

// ついでに、バリデーション失敗時のエラーも表示
@if (count($errors) > 0)
    @foreach ($errors->all() as $error)
        <div class="alert alert-danger">{{ $error }}</div>
    @endforeach
@endif

コントローラでリダイレクトする際にセッション値をセットする。

return redirect('/posts')
  ->with('success', 'Post Created');

# ページネーション

all()やwith()の替わりに、simplePaginate()を使うことで簡単にページネーションを実装できる。

//コントローラ

// DBクラスの場合
$items=DB::table('people')->simplePaginate(5);
$items=DB::table('people')->orderBy('age')->simplePaginate(5);
// モデルの場合
$items=Person::simplePaginate(4);
$items=Person::orderBy('age')->simplePaginate(4);

return view('hello.index', ['items'=>$items]);
// テンプレート

// 前後ページへのリンクを自動生成する
{{ $items->links() }}

// 前後ページへのリンクに対して追加のクエリを付与したい場合は、appends()を挟む
{{ $items->appends(['sort'=>$sort])->links() }} // => /some?sort=***&page=2

# ページ番号のリンクを表示する

simplePaginate()の替わりにpaginate()を使うと、自動生成されるリンクに、ページ番号も含まれる様になる。

# カスタムレイアウト

  • links()に引数を指定することで、ページネーションのレイアウトをカスタムできる。
  • 下記コマンドで雛形を生成できるので、適宜利用する
php artisan vendor:publish --tag=laravel-pagination

# ユーザ認証

# セットアップ

下記コマンドで関連するファイルが自動生成される。生成されたファイルをベースに移植を行うとお手軽である。

php artisan make:auth # 関連するRoute, View, Controllerを生成する
php artisan migrate # 認証に使うテーブルを作成

Auth::user() ユーザ情報の取得

// コントローラ
use Illuminate\Support\Facades\Auth;

class HelloController extends Controller {
  $user = Auth::user();
  return view('some.view', ['user' => $user]);
}

Auth::check() ユーザがログインしているかどうか

// テンプレート
@if (Auth::check())
    <p>{{$user->name}}</p>
    <p>{{$user->email}}</p>
@else
    <p>ログインしていません</p>
    <a href="/login">ログイン</a>|
    <a href="/register">登録</a>
@endif

# 既製のルーティング

  • /login ログイン
  • /register サインアップ
  • /home アカウント管理画面

# ログインの強制

authというミドルウェアを使うことで、特定のルートを閲覧する際にログインを矯正できる。ログインしていない場合は/loginにリダイレクトされる。

Route::get('/hello', 'HelloController@index')
    ->middleware('auth');

# ログインのマニュアル実装

if(Auth::attempt(['email'=>$mail, 'password'=>$pass])){
  /* on success */
} else {
  /* on fail */
}

# ユニットテスト

テストはTestCaseクラスを継承したクラスに対し、test****()というメソッドを実装することで行う。

# 設定ファイル

テストに関する設定はphpunit.xmlで行う。

<!-- テスト用のデータベースを指定 -->
<env name="DB_DATABASE" value="database\database_test.sqlite"/>

# ダミーレコードの用意

テスト用のダミーデータを作成するには、database/factoriesの中にある Model Factories を使う。

// database/factories/ModelFactory.php
$factory->define(App\Person::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->safeEmail,
        'age' => $faker->random_int(1,99),
    ];
});

# テストファイルの作成と実行

php artisan make:test HelloTest
// tests/Feature/HelloTest.php
class HelloTest extends TestCase
{
    public function testExample()
    {
        $this->assertTrue(true);

        $arr = [];
        $this->assertEmpty($arr);

        $msg = 'Hello';
        $this->assertEquals($msg, 'Hello');

        $number = random_int(0, 100);
        $this->assertLessThan(100, $number);
    }
}
vendor/bin/phpunit

# ルーティングや認証をテストする

class HelloTest extends TestCase
{
    use DatabaseMigrations;

    public function testExample()
    {
        // ルーティングのテスト
        $response = $this->get('/');
        $response->assertStatus(200);

        $response = $this->get('/hello');
        $response->assertStatus(302);

        $response = $this->get('/no_routes');
        $response->assertStatus(404);

        // 認証のテスト
        $user = factory(User::class)->create();
        $response = $this->actingAs($user)->get('/hello');
        $response->assertStatus(200);
    }
}

factory(クラス名)->create() ファクトリの設定を基にインスタンスを生成して DB に保存する

# データベースのテスト

class HelloTest extends TestCase
{
    // テスト開始前にマイグレーションを実行し、終了後にまっさらに戻す
    use DatabaseMigrations;

    public function testExample()
    {
        $dummyPerson = [
            'name' => 'XXX',
            'mail' => 'YYY@ZZZ.COM',
            'age' => 123,
        ];

        // ファクトリの設定を一部上書きしてインスタンスを生成しDBに保存
        factory(Person::class)->create($dummyPerson);

        // 指定した数のインスタンスをDBに保存
        factory(Person::class, 10)->create();

        $this->assertDatabaseHas('people', $dummyPerson);
    }
}

# 環境変数やコンフィグの取得

コントローラやテンプレート内で、環境変数等を取得する方法

// 環境変数の取得
env('APP_NAME');

// コンフィグの取得
config('app.name');

# WYSIWYG

WYSIWYG エディタを実装するには、ckeditorと、無害化のための Purifierを組み合わせる。

chkeditor の設定

<textarea name="sometextarea"></textarea>

<script src="https://cdn.ckeditor.com/4.11.1/standard/ckeditor.js"></script>
<script>
  CKEDITOR.replace('sometextarea');
</script>

Purifier の設定

composer require mews/purifier
php artisan vendor:publish --provider="Mews\Purifier\PurifierServiceProvider"

コントローラ

use Mews\Purifier\Facades\Purifier;
// .....
Purifier::clean($request->someInputValue);