hysryt / wiki

https://hysryt.github.io/wiki/
0 stars 0 forks source link

Laravel #10

Open hysryt opened 6 years ago

hysryt commented 6 years ago

Laravelインストーラのインストール

Composerでインストールする

$ composer global require "laravel/installer"

globalを指定しているので~/.composer/vendor配下にインストールされる バイナリファイルは~/.composer/vendor/bin配下にインストールされるため必要に応じてパスを通す

$ echo "export PATH=~/.composer/vendor/bin:$PATH" >> ~/.bash_profile
$ source ~/.bash_profile
$ laravel -V
Laravel Installer 3.0.1

Laravelインストーラを使うと簡単にLaravelプロジェクトを作成できる

hysryt commented 6 years ago

Laravelプロジェクトの雛形を作成

PHP7.2以上必須

$ laravel new laraveltest

laraveltestは任意のプロジェクト名 大量のライブラリがインストールされる

Laravelプロジェクトを開発サーバで動かす

$ cd laraveltest
$ php artisan serve
Laravel development server started: <http://127.0.0.1:8000>

プロジェクトフォルダへ移動する php artisan serveで、PHPに付属する簡易サーバ上で動かすことができる http://127.0.0.1:8000/ にアクセスすると実際に動いていることがわかる

hysryt commented 6 years ago

ルーティング設定

ルーディングの設定はプロジェクトフォルダ/routes/web.phpに記述する

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

getメソッドでルーティングの設定ができる 第一引数はリクエスト時のパス 第二引数は関数またはコントローラクラスの名前を入れる

Route::get('/', 'TestController@index');

コントローラクラスを指定する場合はコントローラクラス名@アクションメソッド名の形式で記述する @アクションメソッド名を省略した場合、__invokeメソッドが使われる アクションが一つしかない場合はこの方法でもいい

Route::get('/', 'TestController@index');
Route::get('/search', 'TestController@search');
Route::get('/form', 'TestController@form');

複数ルーティングを指定する場合はgetメソッドを複数記述する

クラスメソッドを使った仕組みについては以下を参照 http://www.1x1.jp/blog/2014/03/laravel-facade-class.html

hysryt commented 6 years ago

コントローラクラスの作成

artisanコマンドでコントローラクラスの雛形を作成できる

$ php artisan make:controller コントローラクラス名
$ php artisan make:controller TestController
$ ls app/Http/Controllers/
Auth               Controller.php     TestController.php

作成したコントローラクラスはプロジェクトフォルダ/app/Http/Controllers/配下に置かれる

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TestController extends Controller
{
    //
}
hysryt commented 6 years ago

コントローラへルーティング

app/Http/Controllers/TestController.php
<?php

namespace App\Http\Controllers;

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

class TestController extends Controller
{
    public function index(Request $request, Response $response) {
        $response->setContent('<h1>aiueo</h1>');
        return $response;
    }
}

TestControllerコントローラにindexアクションを定義 アクションは引数にRequestResponseを取れる 返り値としてResponseのインスタンスを返す ResponseインスタンスはsetContentメソッドでHTMLを設定できる

routes/web.php
<?php

Route::get('/', 'TestController@index');

パス/へのアクセスはTestControllerコントローラのindexアクションを実行する

hysryt commented 6 years ago

Bladeテンプレート

LaravelではBladeというテンプレートエンジンを使用する テンプレートファイルはプロジェクトフォルダ/resources/views/配下に作成する 一般的にはさらにコントローラ名のディレクトリを作成し、その中に作成する ファイル名はファイル名.blade.phpとする

$ mkdir resources/views/test
$ vi resources/views/test/index.blade.php

testディレクトリを作成し、その中にテンプレートファイルindex.blade.phpを作成

<html>
  <head></head>
  <body>
    {{$message}}
  </body>
</html>

{{$message}}のように{{$変数名}}とすることでそこに変数を出力できる テンプレートファイルを使用するにはview関数を使用する

routes/web.php
<?php

Route::get('/', function() {
    $data = [
        "message" => "こんにちは"
    ];
    return view("test.index", $data);
});

view関数の第一引数にテンプレートファイルを指定する 指定する形式はフォルダ名.ファイル名 第二引数にテンプレートで使用する変数を格納した連想配列を指定する 連想配列のキーがそのまま変数名となってテンプレートファイルに埋め込まれる view関数はResponseインスタンスを返す

Route::getの第二引数でコントローラを指定した場合は、アクションメソッド内で同じように記述する

{{$message}}にこんにちはが出力され以下のHTMLがブラウザで表示される

<html>
  <head></head>
  <body>
    こんにちは
  </body>
</html>
hysryt commented 6 years ago

タイムゾーン・ロケールの設定

プロジェクトフォルダ/config/app.phpを以下のように修正する

hysryt commented 6 years ago

Bladeテンプレート | 制御構文

ディレクティブを使うことでBladeテンプレート内で制御構文を記述できる

resources/views/test/index.blade.php
<html>
  <head></head>
  <body>
@if ($hour < 12)
    午前
@else
    午後
@endif
  </body>
</html>

@if@else@endifディレクティブを使って条件分岐を行なっている

routes/web.php
<?php

Route::get('/', function() {
    $data = [
        "hour" => (new DateTime())->format("G")  // 時刻
    ];
    return view("test.index", $data);
});
ディレクティブ 説明 ディレクティブ 説明
@ifbr>`@elseif`<br@else
@endif
条件分岐 @forbr>`@endfor`<br@while
@endwhile
繰り返し
@break
@continue
繰り返し制御 他多数
hysryt commented 6 years ago

Bladeテンプレート | レイアウト

使用するディレクティブ:@yield @extends @section @endsection

別のテンプレートを基にテンプレートを作成することができる 別のテンプレートを基に作成することを継承と呼ぶ

resources/views/test/layout.blade.php
<html>
  <head></head>
  <body>
    <header>
      <h1>ヘッダー部分</h1>
    </header>

@yield('content')

    <footer>
      フッター部分
    </footer>
  </body>
</html>
resources/views/test/index.blade.php
@extends('test.layout')

@section('content')
<div>
  コンテンツ部分
</div>
@endsection

index.blade.phplayout.blade.phpを継承している 継承には@extendsディレクティブを使用する index.blade.phpに記述した@section('content')@endsectionに記述した内容が、 layout.blade.php@yield('content')に埋め込まれる 継承することで差分のみの記述ですむ

結果、以下のHTMLがブラウザで表示される

<html>
  <head></head>
  <body>
    <header>
      <h1>ヘッダー部分</h1>
    </header>

<div>
  コンテンツ部分
</div>

    <footer>
      フッター部分
    </footer>
  </body>
</html>
hysryt commented 6 years ago

Blade | コンポーネント

使用するディレクティブ:@component @endcomponent @slot @endslot

テンプレートファイル間で再利用したい部品がある場合は、コンポーネントとして別ファイルに分離できる

resources/views/test/searchbox.blade.php
<div>
  <input type="text">
  <input type="button" value="検索">
</div>
resources/views/test/index.blade.php
<html>
  <head></head>
  <body>
@component('test.searchbox')
@endcomponent
    <div>
      アイウエオ
    </div>
@component('test.searchbox')
@endcomponent
  </body>
</html>

@component@endcomponentsearchbox.blade.phpの内容が埋め込まれ、以下のHTMLがブラウザに表示される

<html>
  <head></head>
  <body>
<div>
  <input type="text">
  <input type="button" value="検索">
</div>
    <div>
      アイウエオ
    </div>
<div>
  <input type="text">
  <input type="button" value="検索">
</div>
  </body>
</html>

コンポーネントに引数を渡すこともできる

resources/views/test/searchbox.blade.php
<div>
  <h1>{{$title}}</h1>
  <input type="text">
  <input type="button" value="検索">
</div>
resources/views/test/index.blade.php
<html>
  <head></head>
  <body>
@component('test.searchbox')
    @slot('title')
        検索窓1
    @endslot
@endcomponent
    <div>
      アイウエオ
    </div>
@component('test.searchbox')
    @slot('title')
        検索窓2
    @endslot
@endcomponent
  </body>
</html>

引数を渡すには@slotディレクティブを使用する 以下のHTMLがブラウザに表示される

<html>
  <head></head>
  <body>
<div>
  <h1>検索窓1</h1>
  <input type="text">
  <input type="button" value="検索">
</div>
    <div>
      アイウエオ
    </div>
<div>
  <h1>検索窓2</h1>
  <input type="text">
  <input type="button" value="検索">
</div>
  </body>
</html>
hysryt commented 6 years ago

ミドルウェア

ミドルウェアを使うことによってアクションの前後に処理を挟むことができる 特定のアクションのみに対して設定することもできるし、全てのアクションに対して設定することもできる


ミドルウェアの作成

ミドルウェアの雛形はartisanコマンドで作成できる

$ php artisan make:middleware TestMiddleware

プロジェクトフォルダ/app/Http/Middleware/配下に作成される

<?php

namespace App\Http\Middleware;

use Closure;

class TestMiddleware
{
    public function handle($request, Closure $next)
    {
        return $next($request);
    }
}

引数で渡されたnext関数の先でアクションの処理が実行される 前後に処理を挟みたい場合は以下のように記述する

<?php

namespace App\Http\Middleware;

use Closure;

class TestMiddleware
{
    public function handle($request, Closure $next)
    {
        // アクション前処理

        $response = $next($request);

        // アクション後処理

        return $response;
    }
}
hysryt commented 6 years ago

ミドルウェアの設定


特定のアクションのみに対して設定

特定のアクションに設定する場合は、ルート設定時に記述する

routes/web.php
use App\Http\Middleware\TestMiddleware;

Route::get('/', 'TestController@index')
       ->middleware(TestMiddleware::class);

ミドルウェアを複数設定することもできる

routes/web.php
use App\Http\Middleware\TestMiddleware;

Route::get('/', 'TestController@index')
       ->middleware(FirstMiddleware::class);
       ->middleware(SecondMiddleware::class);
       ->middleware(ThirdMiddleware::class);

全てのアクションに対して設定

プロジェクトフォルダ/app/Http/Kernel.php$middleware配列にミドルウェアを追加する

(略)
protected $middleware = [
    (略)
    \App\Http\Middleware\TestMiddleware::class,
];
(略)
hysryt commented 6 years ago

バリデーション

バリデーションの方法

バリデーションには以下の方法がある

hysryt commented 4 years ago

ミドルウェア

https://laravel.com/docs/6.x/middleware

ミドルウェアとは

クライアントとWebアプリの間に追加する仕組み。 全てのHTTPリクエストをフィルタリングすることができる。 ユーザーの認証などもミドルウェアで行う。 HTTPヘッダを書き換えたり、ログをとったりする機能などにも使われる。

クライアントからWebアプリへのリクエストに追加することもできるし、Webアプリからクライアントへのレスポンスに追加することもできる。両方に追加することもできる。

ミドルウェアの作成

php artisan make:middleware CheckAge

app/Http/Middleware ディレクトリ内に作成される。

ミドルウェアに依存関係が存在する場合は Service Container に記述する。

ミドルウェアの適用

全てのHTTPリクエストに適用

ミドルウェアを全てのHTTPリクエストに適用させる場合は app/Http/Kernel.php$middleware に追加する。

ルートに適用

ルートにミドルウェアを適用する場合はまず app/Http/Kernel.php$routeMiddleware にキーとクラスを追加する。

protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
    'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
    ...
];

そして追加したキーでルートにミドルウェアを適用する。 適用には middleware() を使用する。

Route::get('admin/profile', function () {
    //
})->middleware('auth');

ミドルウェアグループ

app/Http/Kernel.php$middlewareGroups でミドルウェアをグループ化できる。

protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
    ...
];

ミドルウェアグループも単独のミドルウェアと同じように midlleware() で適用できる。

Route::get('/', function () {
    //
})->middleware('web');

ミドルウェアの優先度

ミドルウェアの優先度は app/Http/Kernel.php$middlewarePriority で指定する。

protected $middlewarePriority = [
    \Illuminate\Session\Middleware\StartSession::class,
    \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    \App\Http\Middleware\Authenticate::class,
    \Illuminate\Session\Middleware\AuthenticateSession::class,
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
    \Illuminate\Auth\Middleware\Authorize::class,
];

ミドルウェアへのパラメータ渡し

ミドルウェアを適用する際に:,でパラメータを渡すことができる。 次の例では role ミドルウェアに editor というパラメータを渡している。

Route::put('post/{id}', function ($id) {
    //
})->middleware('role:editor');

渡したパラメータは handle() の第3引数以降で受け取ることができる。

public function handle($request, Closure $next, $role)
{
    if (! $request->user()->hasRole($role)) {
        // Redirect...
    }

    return $next($request);
}

ターミナブルミドルウェア

ターミナブルミドルウェアはブラウザにレスポンスを送った後に発火するミドルウェア。 FastCGI を使用している必要がある。

ターミナブルミドルウェアを作るにはミドルウェアクラスに terminate() を実装する。

public function terminate($request, $response)
{
    // Store the session data...
}
hysryt commented 4 years ago

ルーティング

https://laravel.com/docs/6.x/routing

基本的なルーティング

URLとクロージャを指定する。

Route::get('foo', function () {
    return 'Hello World';
});

ルーティング用ファイル

ルーティング情報は routes ディレクトリ内に保存する。これらは全てフレームワークによって自動的に読み込まれる。 routes/web.php には Web 用のルーティング情報を記述する。 ここに書かれたルートには web ミドルウェアが適用される。 routes/api.php は API 用のルーティング情報用のファイルであり、こちらには api ミドルウェアが適用される。

HTTPメソッド

GET 用のルートは Route::get()POST 用のルートは Route::post() で指定する。 複数のメソッドに指定したい場合は Route::match() を使用する。

Route::match(['get', 'post'], '/', function () {
    //
});

全てのメソッドに指定する場合は Route::any() が使える。

Route::any('/', function () {
    //
});

リダイレクト

リダイレクトを設定する場合は Route::redirect() を使用する。

Route::redirect('/here', '/there');
Route::redirect('/here', '/there', 301);  // ステータスコードの指定が可能
Route::permanentRedirect('/here', '/there');  // 上と同等

View ルート

直接 View を返す場合は Route::view() を使用する。

Route::view('/welcome', 'welcome');

必須ルートパラメータ

URLの一部をパラメータとして使用することが可能。

Route::get('user/{id}', function ($id) {
    return 'User '.$id;
});

// 複数も可
Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
    //
});

任意のルートパラメータ

ルートパラメータの任意版。 デフォルト値は必ず設定する必要がある。

Route::get('user/{name?}', function ($name = null) {
    return $name;
});

Route::get('user/{name?}', function ($name = 'John') {
    return $name;
});

ルートパラメータの制約

正規表現でルートパラメータに制約をつけることができる。 where() を使用する。

Route::get('user/{name}', function ($name) {
    //
})->where('name', '[A-Za-z]+');

Route::get('user/{id}', function ($id) {
    //
})->where('id', '[0-9]+');

Route::get('user/{id}/{name}', function ($id, $name) {
    //
})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);

名前付きルート

ルートに名前をつけることでURLの取得やリダイレクトの設定が簡単になる。 次はルートに profile という名前をつけた例。

Route::get('user/profile', function () {
    //
})->name('profile');
// URLの取得
$url = route('profile');

// リダイレクトの設定
return redirect()->route('profile');
hysryt commented 4 years ago

コントローラー

コントローラークラスでは関連するルートをグループ化でき、これによってプロジェクト全体の見通しをよくすることができる。 コントローラークラスは app/Http/Controllers ディレクトリに配置する。

基本

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\User;

class UserController extends Controller
{
    /**
     * Show the profile for the given user.
     *
     * @param  int  $id
     * @return View
     */
    public function show($id)
    {
        return view('user.profile', ['user' => User::findOrFail($id)]);
    }
}

コントローラークラスは Controller クラスを継承することが多いが、必須ではない。 Controller クラスには便利なメソッド(middleware, validate, dispatch など)がいくつかあり、継承すればそれらを使うことができる。

ルーティングの際以下のように設定することで、コントローラーへのルーティングが可能。 次は「URLが user/{id} に一致する場合 UserController クラスの show メソッドを実行する」例。

Route::get('user/{id}', 'UserController@show');

指定するコントローラー名は App\Http\Controllers 名前空間から検索される。 コントローラーが App\Http\Controllers\Photos\AdminController の場合は次のように設定する。

Route::get('foo', 'Photos\AdminController@method');

__invoke

コントローラーが一つのアクションしか行わない場合は、そのアクションを __invoke メソッドとして実装することでルーティング設定時の記述が簡潔になる。

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\User;

class ShowProfile extends Controller
{
    /**
     * Show the profile for the given user.
     *
     * @param  int  $id
     * @return View
     */
    public function __invoke($id)
    {
        return view('user.profile', ['user' => User::findOrFail($id)]);
    }
}

上記のように __invoke を実装した場合、次のようにクラス名の指定だけで良い。

Route::get('user/{id}', 'ShowProfile');

このコントローラクラスはCLIから作成することもできる。

php artisan make:controller ShowProfile --invokable

コントローラミドルウェア

ミドルウェアはルートに対して適用する方法の他に、コントローラのコンストラクタ内で適用する方法がある。

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

コントローラクラス内の特定のメソッドに対して適用することもできる。

public function __construct()
{
    $this->middleware('auth')->only('index');
}

middleware メソッドを使うには Controller クラスを継承する必要がある。

リソースコントローラ

リソース用に CRUD に対応したコントローラをリソースコントローラと呼ぶ。 リソースコントローラの雛形は次のコマンドで生成することができる。

php artisan make:controller PhotoController --resource

リソースコントローラへのルーティングは resource メソッドで適用できる。

Route::resource('photos', 'PhotoController');

これによって登録されるルートは以下の通り

メソッド URI アクション ルート名
GET /photos index photos.index
GET /photos/create create photos.create
POST /photos store photos.store
GET /photos/{photo} show photos.show
GET /photos/{photo}/edit edit photos.edit
PUT/PATCH /photos/{photo} update photos.update
DELETE /photos/{photo} destroy photos.destroy

storeshow などに渡す引数のタイプヒンティングとして --model を指定することができる。

php artisan make:controller PhotoController --resource --model=Photo

メソッドの指定

HTMLフォームでは PUT、PATCH、DELETE を送信できないため、代わりに _method フィールドでメソッドを指定する。 Bladeでは @method ディレクティブでフィールドできる。

<form action="/foo/bar" method="POST">
    @method('PUT')
</form>

部分的リソースルート

全てのアクションが必要ない場合は、必要とするアクションのみ指定することができる。

Route::resource('photos', 'PhotoController')->only([
    'index', 'show'
]);

APIリソースルート

apiResource メソッドを使うとAPIに不要な(HTMLフォームへのルート) createedit のアクションを除くことができる。

Route::apiResource('photos', 'PhotoController');

API用の(createeditを除いた)コントローラの雛形は次のコマンドで生成できる。

php artisan make:controller API/PhotoController --api

ネストリソース

写真に対するコメント、のようにあるリソースに対するリソースを指定することもできる。

Route::resource('photos.comments', 'PhotoCommentController');

URLは以下のようになる。

photos/{photos}/comments/{comments}

依存注入とコントローラー

Laravelのサービスコンテナは全てのコントローラを解決する。

ルートキャッシュ

アプリケーションがコントローラベースのルートのみを使用している場合はルートキャッシュを使用できる。 ルートキャッシュを使用することで、ルート登録にかかるコストが大幅に削減される。 次のコマンドでルートキャッシュを生成できる。

php artisan route:cache

新しくルートを追加した場合はこのコマンドを再度実行する必要がある。 そのため、デプロイ時に実行すれば良い。

ルートキャッシュをクリアする場合は次のコマンドを使用する。

php artisan route:clear
hysryt commented 4 years ago

ビュー

HTMLを提供する役割を持つ。 アプリケーションからプレゼンテーションロジックを分離することで「関心の分離」を実現する。

ビューは resources/views ディレクトリに設置する。 以下にビューの例を示す。

<!-- View stored in resources/views/greeting.blade.php -->

<html>
    <body>
        <h1>Hello, {{ $name }}</h1>
    </body>
</html>

コードからビューを取得するには view メソッドを使用する。

Route::get('/', function () {
    return view('greeting', ['name' => 'James']);
});

resources/views 内にディレクトリがある場合、view では / ではなく . で指定する。

// resources/views/admin/profile.blade.php
return view('admin.profile', $data);

ビューが存在するかどうかの確認

View::exists を使用してビューか存在するかどうか確認できる。

use Illuminate\Support\Facades\View;

if (View::exists('emails.customer')) {
    //
}

first メソッド

first メソッドは複数のビューの中から存在するものを取得する機能を持つ。 次の場合、配列で指定したビューを順に見ていき、そのビューが存在すればそれを返す。

return view()->first(['custom.admin', 'admin'], $data);

データの受け渡し

view メソッドの第二引数に指定したデータがビューに渡される。 この時、データは連想配列(キーバリューペア)である必要がある。

return view('greetings', ['name' => 'Victoria']);

全てのビューでデータを使用する

サービスプロバイダの boot メソッドで View::share メソッドを使うことで全てのビューで使用するデータを指定できる。

ビューコンポーザー

ビューコンポーザーとはビューの描画時に呼び出される処理のことである。 ビューコンポーザーファイルの設置位置は定められていないためどこに設置しても良い。

ビューコンポーザーの登録はサービスプロバイダーで行う。

<?php

namespace App\Providers;

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

class ViewServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        // Using class based composers...
        View::composer(
            'profile', 'App\Http\View\Composers\ProfileComposer'
        );

        // Using Closure based composers...
        View::composer('dashboard', function ($view) {
            //
        });
    }
}

View::composer の第一引数にビュー名、第二引数にビューコンポーザー用クラス名またはクロージャを設定する。

ビューコンポーザーに新しくサービスプロバイダーを作った場合は config/app.phpproviders に追加する必要がある。

ビューコンポーザーをクラスで登録した場合、そのクラスの compose メソッドがビュー描画時に呼び出される。

<?php

namespace App\Http\View\Composers;

use App\Repositories\UserRepository;
use Illuminate\View\View;

class ProfileComposer
{
    /**
     * The user repository implementation.
     *
     * @var UserRepository
     */
    protected $users;

    /**
     * Create a new profile composer.
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        // Dependencies automatically resolved by service container...
        $this->users = $users;
    }

    /**
     * Bind data to the view.
     *
     * @param  View  $view
     * @return void
     */
    public function compose(View $view)
    {
        $view->with('count', $this->users->count());
    }
}

compose には表示するビューが引数として渡されるため、 with を用いて表示データを追加できる。

ビュークリエイター

View::creator でビューに対してビュークリエイターを設定できる。

View::creator('profile', 'App\Http\View\Creators\ProfileCreator');

ビュークリエイターはビューがインスタンス化されたとき即座に実行される。

hysryt commented 4 years ago

ファサード

https://laravel.com/docs/6.x/facades

概要

ファサードはクラスの静的メソッドのインターフェースを提供する。 Laravelでは多くのファサードを用意しており、Laravelのほぼ全ての機能にアクセスできるようになっている。 Laravelファサードは下層レイヤークラスの静的プロキシとして機能し、従来の静的メソッドよりもテスト容易性と柔軟性を維持しながら、簡潔で表現力豊かな構文を提供する。

Laravelのファサードは Illuminate\Support\Facades 名前空間で定義されている。

use Illuminate\Support\Facades\Cache;

Route::get('/cache', function () {
    return Cache::get('key');
});

ファサードを使うとき

ファサードには多くの利点がある。 長い名前のクラス名を覚える必要なく、簡潔な構文でLaravelの機能を使うことができる。 さらに、PHPの動的メソッドを独自に使用しているため、簡単にテストできる。

ただし、いくつか注意がある。 主な注意点は、クラススコープの肥大化である。 ファサードはインジェクションを必要とせず、簡単に使用できるため、クラスが肥大化する傾向にある。 依存注入する際はコンストラクタが大きくなっていることでクラスが肥大化していることに気づくことができる。 ファサードを使用する際はクラスの責任範囲について特別な注意を払う必要がある。

ファサード vs 依存性注入

依存性注入の一番の利点は、注入するクラスを容易に交換できることである。 モックやスタブを使うことができるため、テスト時に役立つ。

通常、クラスメソッドはモック化やスタブ化することはできない。 ただしファサードは内部でサービスコンテナーから解決されたオブジェクトへのメソッド呼び出しを行っているため、依存性注入と同じようにテストができる。

例えば次のルートの場合、

Route::get('/cache', function () {
    return Cache::get('key');
});

テストを次のようにすることで、Cache::get が期待通り呼び出されたことを確認できる。

use Illuminate\Support\Facades\Cache;

/**
 * A basic functional test example.
 *
 * @return void
 */
public function testBasicExample()
{
    Cache::shouldReceive('get')
         ->with('key')
         ->andReturn('value');

    $this->visit('/cache')
         ->see('value');
}

ファサード vs ヘルパー関数

Laravelにはビューの生成、イベントの発火、ジョブのディスパッチ、HTTPレスポンスの送信などを様々なヘルパー関数が存在する。 ヘルパー関数の多くはファサードと同じ機能を実行する。

以下の二つは同等と言える。

return View::make('profile');

return view('profile');

ヘルパー関数は内部でファサードを使用するため、全く同じと言っていい。

ファサードの仕組み

Laravelアプリケーションでは、ファサードはコンテナー内のオブジェクトにアクセスするための方法を提供するクラスである。これは Facade クラスが持つ機能あり、Laravel内のファサードは Illuminate\Support\Facades\Facade を継承している。

Facade 規定クラスは __callStatic マジックメソッドを利用して、コンテナ内のオブジェクトを遅延呼び出しする。

次の例ではLaravelのキャッシュシステムが呼び出されている。 Cacheクラスのクラスメソッドgetが呼び出されている。

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;

class UserController extends Controller
{
    /**
     * Show the profile for the given user.
     *
     * @param  int  $id
     * @return Response
     */
    public function showProfile($id)
    {
        $user = Cache::get('user:'.$id);

        return view('profile', ['user' => $user]);
    }
}

この例では Cache ファサードをインポートしている。 Cache ファサードは Illuminate\Contracts\Cache\Factory インターフェースの実装クラスにアクセスするプロキシとして動作する。 このファサードへの呼び出しは全てLaravelのキャッシュサービスに渡される。

Illuminate\Support\Facades\Cache クラスに getクラスメソッドは存在しない。

class Cache extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor() { return 'cache'; }
}

代わりに getFacadeAccessor() が存在する。 このメソッドはサービスコンテナにバインドされている名前を返す役割を持つ。 Cache ファサードにクラスメソッドの呼び出しがあったとき、LaravelはgetFacadeAccessor()の戻り値にバインドされているオブジェクトに対して呼び出しを行う。

リアルタイムファサード

リアルタムファサードを使うことで、アプリケーション内のクラスをファサードのように扱うことが可能になる。

例えば Podcast クラスに publish メソッドがあり、このメソッドには Publisher インスタンスを渡す必要があるとする。

<?php

namespace App;

use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;

class Podcast extends Model
{
    /**
     * Publish the podcast.
     *
     * @param  Publisher  $publisher
     * @return void
     */
    public function publish(Publisher $publisher)
    {
        $this->update(['publishing' => now()]);

        $publisher->publish($this);
    }
}

Publisher をメソッドで渡すようにすることで、モック化が可能になり、テストが容易になる。 しかし、publish メソッドを呼ぶときに常に Publisher インスタンスを渡さなくてはならなくなる。 このような時、リアルタイムファサードを使うことでテストの容易性を保ったまま Publisher インスタンスを明示的に渡さなくても良い仕組みを作ることができる。

リアルタイムファサードを生成する場合、インポートするクラスの名前空間の先頭に Facades をつける

<?php

namespace App;

use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;

class Podcast extends Model
{
    /**
     * Publish the podcast.
     *
     * @return void
     */
    public function publish()
    {
        $this->update(['publishing' => now()]);

        Publisher::publish($this);
    }
}

リアルタイムファサードが使用されると、自動的にサービスコンテナで Facades プレフィックスが含まれるクラス(またはインターフェース)の実装が解決される。 テスト時にはLaravelのFacadeと同じようにテストが可能になる。

<?php

namespace Tests\Feature;

use App\Podcast;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class PodcastTest extends TestCase
{
    use RefreshDatabase;

    /**
     * A test example.
     *
     * @return void
     */
    public function test_podcast_can_be_published()
    {
        $podcast = factory(Podcast::class)->create();

        Publisher::shouldReceive('publish')->once()->with($podcast);

        $podcast->publish();
    }
}

ファサードクラスリファレンス

ファサードと実装クラスの関連

https://laravel.com/docs/6.x/facades#facade-class-reference

hysryt commented 4 years ago

サービスコンテナ

https://laravel.com/docs/6.x/container

概要

サービスコンテナは、クラスの依存関係の管理や依存性の注入を行うツールである。

簡単な例を見てみる。

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Repositories\UserRepository;
use App\User;

class UserController extends Controller
{
    /**
     * The user repository implementation.
     *
     * @var UserRepository
     */
    protected $users;

    /**
     * Create a new controller instance.
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
     * Show the profile for the given user.
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        $user = $this->users->find($id);

        return view('user.profile', ['user' => $user]);
    }
}

この例では UserContoroller はデータソースからユーザーを取得する必要があり、そのためユーザーを取得できるサービスを注入する必要がある。 このコンテキストでは UserRepository はデータベースからおそらく Eloquent を使用してユーザー情報を取得している。 UserRepository はコンストラクタによって外部から挿入されているため、他の実装やテスト用のモックなどと簡単に交換できる。

強力で大規模なアプリケーションを作るには、サービスコンテナの深い理解は不可欠となる。

バインディングの基礎

クラスがインターフェースに依存してない場合は、コンテナに明示的にバインドする必要はない。リフレクションを利用して自動的に生成される。

シンプルなバインディング

サービスプロバイダ内であればどこであっても $this->app プロパティでコンテナにアクセスできる。 bind メソッドにクラス名(またはインスターフェース名)とインスタンスを返すクロージャを渡すことでバインディングできる。

$this->app->bind('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});

クロージャの引数にはサービスコンテナが渡されるため、さらにその先の依存関係を解決できる。

シングルトンのバインディング

singleton メソッドでシングルトンとしてバインディングできる。

$this->app->singleton('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});

インスタンスのバインディング

instance メソッドでインスタンスをバインディングできる。

$api = new HelpSpot\API(new HttpClient);

$this->app->instance('HelpSpot\API', $api);

プリミティブデータのバインディング

$this->app->when('App\Http\Controllers\UserController')
          ->needs('$variableName')
          ->give($value);

実装に対するインターフェースのバインディング

サービスプロバイダの特徴はインターフェースを実装にバインディングできる点である。 例えば EventPusher インターフェースと RedisEventPusher クラスがあるとする。 次のようにすることでこの二つをバインディングできる。

$this->app->bind(
    'App\Contracts\EventPusher',
    'App\Services\RedisEventPusher'
);

これによって EventPusher を解決する場面で RedisEventPusher を注入する必要があることをコンテナに伝えることができる。

use App\Contracts\EventPusher;

/**
 * Create a new class instance.
 *
 * @param  EventPusher  $pusher
 * @return void
 */
public function __construct(EventPusher $pusher)
{
    $this->pusher = $pusher;
}

文脈的バインディング

同じインターフェースを実装するクラスが複数あることは多くある。 例えば2つのコントローラが Illuminate\Contracts\Filesystem\Filesystem を実装する別々のクラスに依存しているとする。

use App\Http\Controllers\PhotoController;
use App\Http\Controllers\UploadController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Facades\Storage;

$this->app->when(PhotoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('local');
          });

$this->app->when([VideoController::class, UploadController::class])
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('s3');
          });

タグ付け

場合によっては特定のカテゴリのバインディングを全て解決する必要がある。 例えば Report インターフェースを実装する複数のクラスを配列で返す場合。 bind でバインディングを行った後、tag でタグを割り当てることができる。

$this->app->bind('SpeedReport', function () {
    //
});

$this->app->bind('MemoryReport', function () {
    //
});

$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');

タグ付けしたものは tagged で取得できる。

$this->app->bind('ReportAggregator', function ($app) {
    return new ReportAggregator($app->tagged('reports'));
});

バインディングの拡張

extend メソッドで解決されたサービスを変更できる。

$this->app->extend(Service::class, function ($service, $app) {
    return new DecoratedService($service);
});

$service には解決されたサービスが渡される。

解決

makeメソッド

make メソッドを使ってインスタンスの取得ができる。

$api = $this->app->make('HelpSpot\API');

コンテナにアクセスできない場所では resolve ヘルパー関数を使用する。

$api = resolve('HelpSpot\API');

クラスの依存関係の一部がコンテナで解決できない場合は makeWith メソッドを使用する。

$api = $this->app->makeWith('HelpSpot\API', ['id' => 1]);

自動注入

タイプヒントによって自動注入が可能。

<?php

namespace App\Http\Controllers;

use App\Users\Repository as UserRepository;

class UserController extends Controller
{
    /**
     * The user repository instance.
     */
    protected $users;

    /**
     * Create a new controller instance.
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
     * Show the user with the given ID.
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        //
    }
}

コンテナーイベント

resolving メソッドでコンテナのイベントをフックできる。

$this->app->resolving(function ($object, $app) {
    // 解決が行われたときに常に呼び出される。
});

$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
    // HelpSpot\API が解決されたときに呼び出される。
});

PSR-11

PSR-11 に準拠している。 したがって Psr\Container\ContainerInterface のタイプヒントでコンテナを取得できる。

use Psr\Container\ContainerInterface;

Route::get('/', function (ContainerInterface $container) {
    $service = $container->get('Service');

    //
});
hysryt commented 4 years ago

サービスプロバイダ

概要

サービスプロバイダはアプリケーションの中心部分であり、Laravelコアを含む全てのサービスはサービスプロバイダを介してブートストラップされる。

ブートストラップとはサービスのバインドや、イベントリスナー、ミドルウェア、ルートの登録を意味する。

config/app.php$providers 配列があり、これはアプリケーションで読み込まれる全てのサービスプロバイダを指定している。 これらの多くは遅延プロバイダーであり、必要な場合のみ読み込まれる。

サービスプロバイダの書き方

サービスプロバイダは Illuminate\Support\ServiceProvider を継承し、 register メソッドと boot メソッドを持つ。 register メソッド内ではサービスコンテナへのバインディングのみ行うべきである。イベントリスナーやルートは register で登録すべきではない。

サービスプロバイダは make:provider コマンドで生成できる。

php artisan make:provider RiakServiceProvider

register メソッド

上述したように、 register メソッド内ではサービスコンテナへのバインディングのみ行うべきであり、イベントリスナーやルートの登録を行なってはならない。 まだ読み込んでいないサービスプロバイダのサービスを使用してしまう可能性があるからである。

基本的なサービスプロバイダを見てみよう。 サービスプロバイダーのメソッド内では $this->app でサービスコンテナにアクセスできる。

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Riak\Connection;

class RiakServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(Connection::class, function ($app) {
            return new Connection(config('riak'));
        });
    }
}

この例では register メソッドのみ定義しており、メソッド内では Riak\Connection の実装をサービスコンテナにバインディングしている。

$bindings プロパティと $singletons プロパティ

複数のサービスをバインディングする場合は $bindings プロパティと $singletons プロパティを使うと完結にすむ。 これらは自動的に読み込まれ、バインディングされる。

<?php

namespace App\Providers;

use App\Contracts\DowntimeNotifier;
use App\Contracts\ServerProvider;
use App\Services\DigitalOceanServerProvider;
use App\Services\PingdomDowntimeNotifier;
use App\Services\ServerToolsProvider;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * All of the container bindings that should be registered.
     *
     * @var array
     */
    public $bindings = [
        ServerProvider::class => DigitalOceanServerProvider::class,
    ];

    /**
     * All of the container singletons that should be registered.
     *
     * @var array
     */
    public $singletons = [
        DowntimeNotifier::class => PingdomDowntimeNotifier::class,
        ServerToolsProvider::class => ServerToolsProvider::class,
    ];
}

boot メソッド

ビューコンポーザの登録は boot メソッドで行う。 このメソッドは全てのサービスプロバイダが読み込まれた後に実行される。 つまり、他のサービス全てにアクセスできるということである。

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class ComposerServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        view()->composer('view', function () {
            //
        });
    }
}

boot メソッド依存性注入

タイプヒントで依存性を注入できる。

use Illuminate\Contracts\Routing\ResponseFactory;

public function boot(ResponseFactory $response)
{
    $response->macro('caps', function ($value) {
        //
    });
}

サービスプロバイダの登録

全てのサービスプロバイダは config/app.php 内で登録される。 このファイルの $providers にサービスプロバイダのクラスを列挙する。 初期値は Laravel コアのサービスプロバイダが指定されており、メーラー、キャッシュ、キューなどがブートストラップされる。

サービスプロバイダを登録する場合はこの配列に追加する。

'providers' => [
    // Other Service Providers

    App\Providers\ComposerServiceProvider::class,
],

遅延プロバイダ

もしサービスプロバイダがサービスコンテナへのバインディングのみ行う場合、必要とされるまで登録を遅らせる「遅延プロバイダ」とすることができる。 必要な時しか読み込まれないため、パフォーマンスがよくなる場合がある。

サービスプロバイダを遅延プロバイダとする場合、\Illuminate\Contracts\Support\DeferrableProvider インターフェースを実装し、provides メソッドを定義する。 provides メソッドの戻り値には register メソッドでバインディングしたサービスのバインドサキクラス名のリストを設定する。

<?php

namespace App\Providers;

use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
use Riak\Connection;

class RiakServiceProvider extends ServiceProvider implements DeferrableProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(Connection::class, function ($app) {
            return new Connection($app['config']['riak']);
        });
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return [Connection::class];
    }
}
hysryt commented 4 years ago

データベース

https://laravel.com/docs/6.x/database

概要

Laravel では生のSQL、Fluent Query Builder、Eloquent ORM のいずれかを使ってデータベースにアクセスする。 Laravel では次のデータベースをサポートしている。

設定

データベース設定は config/database.php に記述する。

URL での設定

DB設定は通常、 hostdatabaseusernamepassword など複数の情報によって構成される。 これらの情報にはそれぞれ環境変数が対応づけられており、本番サーバーではこれらの環境変数を管理する必要がある。

Heroku などのデータベースプロバイダではデータベースの接続情報を単一の文字列で表現する。 これをデータベースURLという。

driver://username:password@host:port/database?options

以下のような形になる。

mysql://root:password@127.0.0.1/forge?charset=UTF-8

Laravel でもデータベースURLをサポートする。 url (環境変数では DATABASE_URL)にデータベースURLを指定すればこちらが使用される。

Read & Write コネクション

まれに読み込み(SELECT)用のデータベースと書き込み(UPDATE、INSERT、DELETE)用のデータベースを分けたい場合がある。 そのような場合は以下のように設定を行う。

'mysql' => [
    'read' => [
        'host' => [
            '192.168.1.1',
            '196.168.1.2',
        ],
    ],
    'write' => [
        'host' => [
            '196.168.1.3',
         ],
    ],
    'sticky'    => true,
    'driver'    => 'mysql',
    'database'  => 'database',
    'username'  => 'root',
    'password'  => '',
    'charset'   => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
    'prefix'    => '',
],

readwritesticky を追加した。 read および writehost を持つ。

sticky オプション

データベースの設定によっては読み込み用のデータベースと書き込み用のデータベースに不整合が発生する場合がある。 その場合、sticky オプションを有効化する。 sticky オプションが有効な場合、書き込み操作がされたデータベースに対してはその後の読み取りも書き込み用データベースに対して行われる。 これによって不整合が解消される。

複数のデータベースコネクションを使用する

コネクション設定が複数ある場合は DB::connection メソッドにコネクション名を渡す。

$users = DB::connection('foo')->select(...);

生のSQL

DB ファサードからクエリを実行できる。 DB ファサードには selectupdateinsertdeletestatement メソッドが用意されている。

SELECT クエリを実行する

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB;

class UserController extends Controller
{
    /**
     * Show a list of all of the application's users.
     *
     * @return Response
     */
    public function index()
    {
        $users = DB::select('select * from users where active = ?', [1]);

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

プレースホルダーを使ったSQL文を第一引数に設定し、パラメータを第二引数に設定する。 select メソッドは常に配列を返す。 配列の要素は stdClass オプジェクト。

foreach ($users as $user) {
    echo $user->name;
}

名前つきバインディング

$results = DB::select('select * from users where id = :id', ['id' => 1]);

INSERT 文を実行する

DB::insert('insert into users (id, name) values (?, ?)', [1, 'Dayle']);

UPDATE 文を実行する

$affected = DB::update('update users set votes = 100 where name = ?', ['John']);

DELETE 文を実行する

$deleted = DB::delete('delete from users');

それ以外の文を実行する。

DB::statement('drop table users');

クエリイベントの取得

DB::listen メソッドでクエリイベント発火時のイベントを取得できる。 ログをとるときに便利。

<?php

namespace App\Providers;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        DB::listen(function ($query) {
            // $query->sql
            // $query->bindings
            // $query->time
        });
    }
}

トランザクション

DB::transaction メソッドでトランザクションを作成できる。 コミットやロールバックは自動的に行われる。

DB::transaction(function () {
    DB::table('users')->update(['votes' => 1]);

    DB::table('posts')->delete();
});

デッドロックの回避

デッドロックが生じた際の再試行回数を第二引数で指定できる。 最終的に実行できなかった場合、例外が投げられる。

DB::transaction(function () {
    DB::table('users')->update(['votes' => 1]);

    DB::table('posts')->delete();
}, 5);

手動トランザクション

beginTransactioncommitrollBack が使用できる。

DB::beginTransaction();
DB::rollBack();
DB::commit();
hysryt commented 4 years ago

Gate と Policy

https://josephsilber.com/posts/2016/08/03/authorization-improvements-in-laravel-5-3#table-of-contents

認証の改善点を見てきましたが、Laravel 5.3の認証には何があるのかを見てみましょう。

注意: 以下で説明するいくつかの機能は、元々 5.2 のサイクルの途中で導入されたものです。しかし、これらの機能はすべて 5.3 のために微調整され、洗練されていますので、ここではそれらの機能を取り上げます。

Laravel認証入門

すでにLaravelでの権限付与に慣れている方は、すぐに読み飛ばしても構いません。

Laravelの認可は、主に2つのコンセプトを中心に構築されています。

Gateクラス。Gateは、誰がどのような能力を持っているか(つまり、誰が何をすることができるか)の正式な権限を持っています。 Gateを使ってユーザーの能力を登録し、後でそのユーザーが与えられたアクションを実行できるかどうかをGateでチェックします。

Policyクラス。Policyは、単一のモデルタイプの能力をチェックする責任があります。 システム内の各モデルクラスに対して認証を行いたい場合は、一致するPolicyクラスがあります。

これがどのように動作するかを確認するために、

php artisan make:policy TaskPolicy

を実行して、理論的なTaskモデル用のPolicyクラスを生成してみましょう。 作成したら、ユーザーが与えられたTaskを更新できるかどうかをチェックするシンプルな update メソッドを追加します。

namespace App\Policies;

use App\Task;
use App\User;

class TaskPolicy
{
    public function update(User $user, Task $task)
    {
        return $user->id === $task->user_id;
    }
}

次に、このPolicyをAuthServiceProviderのPolicyマッピングに追加して、Gateに登録します。

protected $policies = [
    \App\Task::class => \App\Policies\TaskPolicy::class,
];

裏では、GateにPolicyを登録しています。 これからは、Taskに対する権限チェックはすべてTaskPolicyにルーティングされます。

具体的には、与えられたTaskを更新するための簡単なRouteを作成してみましょう。

Route::put('tasks/{task}', function (App\Task $task) {
   abort_unless(Gate::allows('update', $task), 403);

   $task->update(request()->input());
});

ユーザーが与えられたタスクを更新できるかどうかをゲートでチェックします。 GateはPolicyの update メソッドを呼び出し、現在認証されているユーザーと与えられたTaskの両方を渡します。 (ユーザーがログインしていない場合、Gateは自動的にすべての能力の問い合わせを拒否します) 能力が付与されていない場合、403で中止します。

(ところで、あのabort_unless関数が好きになりませんか?つまり、あれを見てください! まるで一文のように読めます。"GateがTaskの更新を許可しない限り、abortします。これが最高のLaravelです)。

コントローラで簡単にリクエストを承認

LaravelのコントローラはAuthorizesRequestsトレイトを使用して、任意のリクエストを簡単に認可します。 コントローラ内からauthorizeメソッドを呼び出すことができ、Gateで能力をチェックします。 もし能力が拒否された場合、リクエストは自動的に403レスポンスで中断されます。

これを実際に見るために、Routeをコントローラに移動させてみましょう。

use App\Task;

class TaskController
{
    public function update(Task $task)
    {
        $this->authorize('update', $task);

        $task->update(request()->input());
    }
}

前と同じように、GateはTaskPolicyの update メソッドを参照し、その結果を返します。

自動能力名

Laravelは実際にはもう一歩進んで、authorize メソッドを呼び出す際にアビリティ名を省略することができます。 アビリティ名を省略した場合、Laravelは自動的にコントローラのメソッドからアビリティ名を取得します。

サンプルコントローラで $this->authorize($task) をコールすると、 チェックのために TaskPolicy@update を参照することになります。なかなかいいですね!

Laravel 5.2では、自動能力名は常にそれを呼び出すコントローラメソッドの名前になります。 以下のコントローラを与えます。

use App\Task;

class TaskController
{
    public function show(Task $task)
    {
        $this->authorize($task);

        return view('tasks.show')->with('task', $task);
    }

    public function create()
    {
        $this->authorize(Task::class);

        return view('tasks.create');
    }
}

...コントローラのメソッド名と一致するメソッド名を持つPolicyを持つことになります。

use App\Task;
use App\User;

class TaskPolicy
{
    public function show(User $user, Task $task)
    {
        // check if the user can view the given $task
    }

    public function create(User $user)
    {
        // check if the user can create new tasks
    }
}

能力名とメソッド名を一致させる問題

2016年8月8日更新:この記事を公開した後、テイラーはオーソリティにいくつかの追加変更を加えました。これらの変更を反映して、以下を更新しました。

表面上は非常に直感的なように見えますが、よく見るとこの規約にはいくつかの亀裂があることがわかります。

この慣習がどこで崩れるのか、この2つのサンプルを考えてみましょう。

1.コントローラ以外のアビリティをチェックする コントローラのメソッド名は、能力をチェックするための最も直感的な方法とは限りません。 このテンプレートを見てみましょう。

@foreach ($tasks as $task)
    @if ($user->can('show', $task))
        <a href="{{ url('tasks', $task) }}">View task</a>
    @endif
@endforeach

うーん... これはちょっとおかしいですね。 user->can('show', $task) は何をチェックしているのでしょうか? ユーザがタスクを表示できるかどうかをチェックしているのでしょうか? それはどういう意味なのでしょうか? 表示できるかどうかを知りたいのです!

2.Policyメソッドの重複 新しいTaskを作成するためにフォームを表示するかどうかのチェックは、 ユーザーにそのTaskの保存を許可するかどうかを決定するのとまったく同じチェックです。 コントローラのメソッド名を勝手にマッピングすると、2つの同じメソッドを持つPolicyになってしまいます。

use App\User;

class TaskPolicy
{
    public function create(User $user)
    {
        // check if the user can create new tasks
    }

    public function store(User $user)
    {
        // check if the user can create new tasks
    }
}

この2つは全く同じチェックを実行しますが、コントローラ上の2つの異なるメソッドには両方が必要です。 editstoreにも同じ重複が適用されます。

能力名の正規化

Laravel 5.3では、自動能力名がアップグレードされました。 やみくもにメソッド名を使うのではなく、あなたが完了しようとしているアクションに基づいて、 Laravelは適切なアビリティ名を選びます。 アイテムを表示しようとしている場合は、ビューアビリティ名を使用します。 新しいレコードを作成しようとしている場合は、コントローラの store メソッドから呼び出しても、 作成アビリティ名を使用します。

Laravelは、メソッドとアビリティの間のシンプルなマッピングによってこれを行います。 これがソースからの完全なマップです。

[
    'show' => 'view',
    'create' => 'create',
    'store' => 'create',
    'edit' => 'update',
    'update' => 'update',
    'destroy' => 'delete',
]

これは、コントローラのコードを変更することなく自動的に行われます。 ビューで $user->can('view', $task) をチェックできるようになり、より直感的になりました。

今では、コントローラが能力名がコントローラのメソッドと一致するゲートを常に呼び出すとは限らないので、 Policyにはそれらのメソッドがないはずです。このコントローラがあるとします。

use App\Task;

class TaskController
{
    public function create()
    {
        $this->authorize(Task::class);

        return view('tasks.create');
    }

    public function store()
    {
        $this->authorize(Task::class);

        Task::create(request()->input());
    }
}

...createメソッドだけでポリシーを持つことになります。

use App\User;

class TaskPolicy
{
    public function create(User $user)
    {
        // check if the user can create tasks
    }
}

この作成メソッドは、コントローラ内の作成と保存の両方から呼び出されます。とてもすっきりしました。

Full policy generator

これらの変更を支援するために、Policy生成機能が大幅に強化されています。 ジェネレータにモデル名を渡すと、必要なPolicyメソッドを完全にスタブアウトして、すべてのデフォルトのリソースコントローラメソッドをカバーします。

php artisan make:policy TaskPolicy --model=Task

Laravelのソースコードにある生のスタブを見ることができ、生成されたメソッドについてより良いイメージが得られるはずです。

The can middleware

アビリティをインラインで認可するのはうまくいきますが、ルートレベルで認可する方が理にかなっていることもあります。 Laravelには、この目的のためのcanミドルウェアが付属しています。

access-dashboardという一般的な能力を定義してみましょう。

Gate::define('access-dashboard', function ($user) {
    return $user->isAdmin();
});

不正アクセスからRouteのグループ全体をガードするには、canミドルウェアでグループ化することができます。

Route::group(['middleware' => 'can:access-dashboard'], function () {
    // define all dashboard routes...
});

これで、このグループ内の任意のルートを実行する前に、Gateがアクセスダッシュボードの能力をチェックされます。

ミドルウェアに第二引数を渡すこともできます。 これはモデルのクラス名か、与えられたモデルのインスタンスをチェックするためのモデルのルートパラメータ名のどちらかです。

Route::get('tasks/{task}', [
   'uses' => 'TaskController@show',
   'middleware' => 'can:view,task',
]);

Route::post('tasks', [
   'uses' => 'TaskController@store',
   'middleware' => 'can:create,App\Task',
]);

ルートのパラメータ名を渡すと、ミドルウェアはルート自体からモデルインスタンスを引き出します。 ルートモデルのバインディングについてはLaravelのドキュメントを参照してください。

フルリソースコントローラの認可

認可システムのもう一つの端正な宝石は、 コントローラで利用できるようになった authorizeResource メソッドです。 これはあなたのコントローラを大幅にクリーンアップする可能性があります。 では、この魔法の authorizeResource は何をするのでしょうか?

要するに、さまざまなコントローラメソッドでの認証のためのすべての呼び出しを コンストラクタでの単一の呼び出しに置き換えます。

use App\Task;

class TaskController
{
    public function __construct()
    {
        $this->authorizeResource(Task::class);
    }

    // ... all resource methods ...
}

これだけでOK! 個々のメソッドで認証するための呼び出しをすべてスキップできるようになりました。 Laravelは、任意のアクションに対して正しいアビリティを自動的にチェックするようになりました。 実際にはかなり魔法のようです。

Bouncerでデータベース内のロールとパーミッションを管理します。

Laravelのビルトインゲートは、アプリ内のすべての認可ロジックをハードコーディングすることで動作します。 データベース経由でユーザーのロールや権限を管理したい場合は、Laravelのビルトイン認証システムと簡単に統合できるBouncerをチェックしてみてください。