tohokuaikiのチラシの裏

技術的ネタとか。

「Laravelリファレンス」 2-3 基本コンポーネント

環境設定

.envファイル

このファイルは、.gitignoreに含める。

APP_ENVで本番環境(production)、ユニットテスト環境(testing)、それ以外の開発環境(任意)を設定できる。

値は、env()ヘルパ関数で取得できる。配列は設定できない。

config/*.phpファイル

アプリケーションの設定値を記述。

値は、Config::get('foo.bar')で、config/foo.phpのbarキーを取得する。.を更につなげてハッシュの更に奥まで指定できる。

動作環境の設定

config/*php内で.envを使って設定する。

$GET/$POST/$COOKIE/$SERVER

Requestファサードで取得。

<?php
$foo = Request::input('foo'); // $_GET['foo']/$_POST['foo']
// デフォルト値付き
$foo = Request::input('foo', 'default value');
// 全取得
$vars = Request::all();
// foo barのみ取得
$foobar = Request::only(['foo', 'bar']);
// foo bar以外取得
$foobar = Request::except(['foo', 'bar']);
// ファイルインスタンス(UploadedFile)を取得
$file = Request::file('foo');
// cookie値を取得
$cookie_foo = Request::cookie('foo');
// $_SERVERを取得
$server = Request::server();
// getallheaders
$headers = Request::header('Accept-Language');

ブラウザに返却するResponseを組み立てるResponseインスタンス

ControllerやRouteの返却値にResponseインスタンスを返すといい感じにしてくれる。

<?php
// Responseの作り方
$response = Response::make('hello world');
$response = response('hello world'); // 上と同じ

// Viewにつながる渡し方
$response = Response::view(View::make('template.file'));
$response = Response::view(view('template.file')); // 上と同じ

// Jsonを出力
$response = Response::json(['foo' => 'bar']);

// ファイルダウンロード
$response = Response::download('file-path');

// Locationによるリダイレクト
$response = Response::redirectTo('/foo/bar');
$response = redirect(action('FooController@getIndex')); 

// Cookieを追加してリダイレクト
$response = Response::redirectTo('/foo/bar')->withCookie('foo', 'bar');
$response = Response::redirectTo('/foo/bar')->withCookie(cookie()->forever('foo', 'bar'));

ルーティング

routes/web.php にマッチするものがあればその時点で評価は終了し、次には回らない。

名前付きルーティング、便利。

ルーティング自体にパラメータをつけてController(Closure)でそれを取得したい場合、

<?php
Route::get('/home', [
  'param1' => 'foo/bar',
  function(){
    $actions = Route::current()->getAction();
    return $actions['param1']; // foo/barが表示される。 
  }
]);

パラメータをパスに含んでいる場合

<?php
// スラッシュによる区切り
Route::get('/foo/{id}/{param}', function($id, $param){ 

// {id}はある正規表現によって制約をつけることができる。ただし、これは{id}を使ったRoute設定の前におかないとダメ
Route::pattern('id', '^[a-z]+$'); // ^と$は省略可能

// このルートの{id}だけ制約をつけたい場合
Route::get('/foo/{id}/{param}', function($id, $param){ 
})->where(['id' => '0[a-z]+', 'param' => 'a[a-z]+']);

暗黙のController

この機能は5.3でなくなったので省略

RESTfulコントローラ

<?php
Route::resource('resource', 'ResourceController', [オプション]);

オプションには下記がある。 * names => メソッド名とルート名の対応の変更ができる。通常は、ルート名は{resource名}.{Controllerメソッド} となる。 * only => RESTメソッドの制約ができる * except => RESTメソッドの除外ができる

ルート名というのは何なんだろう?

・・・あぁ、わかった。RESTのルート名とは関係なくて単にLaravelでルートを識別するための名称のことか。Route::getとかだと単一のルーティングを指定するのに対して、RESTfulにルーティングを決めると複数のルート名をいっぺんに決めるので個々の設定をReoute::get()->name('foo')でできないってことね。URIを自在に変えられるのかと思った。

あと、RESTfulであってRESTを提供しているわけでは無い。

ミドルウェア

よくある感じのフィルタ機能。フィルタというのはLaravelでもその名称であるらしいがミドルウェアに変わっていった。

3段階の設定場所があり、それぞれに実行に入る前の処理(Before)と実行後の処理(After)が行える。登録の方法は、App/Http/Kernel.phpに記述する。

AfterとBeforeの指定の方法

こっちだとAfter

<?php
class AfterHoge
{
    public function handle($request, Closure $next)
    {
        $response = $next($request);
        // ここに処理


        
        return $response;
    }
}

これだとBefore

<?php
class BeforeHoge
{
    public function handle($request, Closure $next)
    {
        // ここに処理
        
        
        
        return $next($request);
    }
}
グローバルなミドルウェア
php artisan make:middleware Hogehoge

で雛形を作れる。これをKernel.phpに登録して完了。

Routerに仕込むミドルウェア
// get/post
<?php
Route::get('user/{id}',[
    'middleware' => ['hoge', 'hoge.b','hoge.a', ],
    function($userId){
    }
]);

// resourceは以下のようにはできないのでControllerに仕込む
Route::resource('user', 'UserController', [
    'middleware' => 'hoge'
]);
Controllerに仕込むミドルウェア

constructで実行させる。

<?php
class UserController extends Controller {
    public function __construct() {
        $this->middleware('hoge.b');
        $this->middleware('hoge.a');
    }

もちろん、これでもBefore/Afterの区別をつけて処理してくれる。

バリデーション

基本的なシンタックス

<?php
$inputs = ['name' => 'Tanaka']; // GET/POSTで与えられる配列
$rules = ['name' => ['required', 'min:4']];
$validator = Validator::make($inputs, $rules);
if ($validator->fail() === true) {
    abort(404);
}

という感じ。バリデーションルールは、「ルール名:パラメータ」という感じ。

バリデーションルールの拡張

簡単にできる。

<?php
use Validator;
Validator::extend(新ルール名, function($attr, $value, $params){
    return false ; // => エラー
});

$attrには、$inputsのキーが入る。上記の場合"name"が出てくる。

・・・・と、これ、どこに仕込ませればいいのだろう?と疑問に思って調べてみると

あたりが見つかった。extend使うよりクラスを作った方がいいよね・・・。こういうところ、この本読んでて思うことがしばしば・・・。なんか、こう、詰めが甘いというかサンプルコードがあって動くしコメント見てな的な・・・。

で、このextendは app/Providers/AppServiceProvider.php のbootメソッド内に書けば使える。

条件付きバリデーションルール

Validator::sometimeメソッドを使う。

<?php
$val = Validator::make($inputs, $rules);
$val->sometimes('追加したいINPUT名', [追加したいルール], function($入力値) use ($他の入力値) {
    // ここに判定条件。trueをリターンすると追加したいルールが適用される。
});
エラーメッセージの変更

Validator::makeの第三引数に配列として入れてやる。

[
'required' => ':attributeは必要です。'
]

この際、:attirbuteは自動的に「name」や「email」などの入力項目に変換される。

あるいは、resources/lang/ja/validation.php

<?php
    'custom' => [
        'name' => [
            'required' => ':attribute は入れなくても良いけど入れないと登録できません。',
            ],
    ],

をいうように指定する。また、このvalidation.phpのrequiredキーに書くとrequiredの際のエラーメッセージを一括で変更できる。

また、nameを「名前」にするのも、このvalidation.phpで指定している。

Controllerにおけるvalidateの簡略化

Validator::makeしてfailsをチェックして元のURLに戻るのは定型なので、Illuminate/Foundation/Validation/ValidatesRequests.php にvalidateメソッドがある。BaseControllerとなるapp/Http/Controllers/Controller.phpはこれをtraitしているのでvalidateメソッド一発で失敗した際に前の画面に戻るなどの処理も行ってくれる。

この「前のURL」を取る処理は、Illuminate/Routing/UrlGenerator->previous()であり、Refererがあればそれを、なければSessionから取り出す。てか、いまさら気づいたけどLaravelってセッションスタート前提なのか。

なので、アクションメソッドの範囲で

<?php
$this->validate($request, $rules, $custom_message);
// バリデーション通過後

とするだけで構わない。

フォームリクエストでこれらの設定をControllerからRequestクラスへ

$GET/$POSTはRequestクラスで受け取るのだけど、ControllerにFormの情報であるnameの羅列やValidationルールを書きたくない。そんな感じでロジックをRequestクラスに詰め込んでしまう。

$ ./artisan make:request NameFormRequest

でひな形作成。

<?php
    public function authorize()
    {
        // このフォームへのアクセス権限。とりあえずtrueにしておかないと403 Forbiddenが返る
        return true;
    }

    public function rules()
    {
//        $userId = $this->route()->parameters();
        $userId = $this->route('one');
        $user = User::findOrFail($userId);
        
        return [
            'name' => 'required|max:255',
            // 更新時、ユーザー自身のメールアドレスとの重複は許す
            'email' => 'required|email|max:255|unique:users,email,'.$user->id,
            'password' => 'min:6',
        ];
    }

    /**
     * カスタムエラーメッセージはここに入れる。
     *
     * @return array
     */
    public function messages()
    {
        return [
            'required' => ':attributeが足りません。',
            'name.required' => ':attributeを正しく入れてください。',
            ];
    }

    /**
     * 要素名はここに入れる。
     *
     * @return array
     */
    public function attributes()
    {
        return [
            'name' => 'お名前'
            ];
    }
    

ルーティングによるパラメータは、$this->route('one')で取得できる。2つ目はtwo... これは vendor/laravel/framework/src/Illuminate/Routing/ControllerInspector.php で設定されている模様。

このフォームクラスを使うには、Controllerのアクションメソッドで

<?php
    public function postEdit(NameFormRequest $request, $userId) {

としてしまえば自動的に$requestにはNameFormRequestクラスのインスタンスになる。