太陽がまぶしかったから

C'etait a cause du soleil.

PHP 初心者による Laravel 入門 その2 入力フォームとテンプレート内制御

view メソッドのテンプレートで探索ディレクトリを指定

 上記から引き続いて、『PHPフレームワークLaravel入門』を参考に Laravel に入門してみる。前回まではブラウザからのリクエストを受け取り、それに応じて処理を切り分けるルーティング処理を実装した。

 view メソッドでは view('ディレクトリ名.ファイル名') 形式で呼び出し先の resources/views以下に探索ディレクトリを指定できる。機能ごとにディレクトリを分けるのが一般的だそう。テンプレートファイルには PHP の構文をそのまま入れることもできるが、地獄しか見えないので Controller 側で必要最低限の連想配列を生成して起動した方がよさそう。

Laravel でURLクエリストリングの取得と表示

 GET メソッドの末尾につける ?q1=x86&q2=z80 といったURLクエリストリングについては、 Request オブジェクトの query メソッドから取得できる。(参考:HTTPリクエスト 5.6 Laravel

<?php

namespace App\Http\Controllers;

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

class HelloController extends Controller
{
    public function __invoke(Request $req, Response $res, $target='world') {
        $param = [
            'target' => $target, 
            'url' => $req->url(), 
            'query' =>  json_encode($req->query()),
            'q1' =>  $req->query('q1', 'Empty'),
            'status' => $res->status()
        ];
        return view('hello.top', $param);
    }
}

 引数を指定しないと連想配列形式ですべてを取得。1つ目の引数で名前、2つ目の引数でデフォルト値を指定可能。isset 三項演算子などの定型文を書く必要がないのが楽。連想配列そのままはテンプレートに渡せないようなので、 JSON 文字列に変換している。

f:id:bulldra:20180502080416j:plain

 できた。

Laravel で入力フォーム

 POSTメソッドを呼び出す入力フォームをテンプレートに追加する。Laravel では {{ csrf_field() }} を入れておくことで、クロスサイトリクエストフォージェリと呼ばれる他サイトを経由した入力を防ぐためのトークンを発行することができる。

        <form method="POST" action="/hello">
            {{ csrf_field() }}
            <input type="text" name="target" value="" />
            <input type="submit" name="入力" />
        </form>

 ルーティングを設定して、GET と POST で呼び出す関数を切り分ける。

 
<?php

Route::get('/hello/{target?}', 'HelloController@doGet');
Route::post('/hello', 'HelloController@doPost');

 doPost() にフォームからの入力を受け取ってテンプレート用の連想配列に格納する処理を追加する。

<?php

 /* 中略 */

    public function doPost(Request $req, Response $res) {
        $target = $req->input('target') != null ? $req->input('target') : 'wolrd';
        $param = [
            'target' => $target,
            'url' => $req->url(), 
            'query' =>  json_encode($req->query()),
            'q1' =>  $req->query('q1', 'Empty'),
            'status' => $res->status()
        ];
        return view('hello.top', $param);
    }

 $req->input('target')<input name="target"〜 の入力内容を取得可能。 query() と同様に二つの目の引数でデフォルト値を設定可能であるが、「入力値が存在しない時」≠「空文字入力」であるため、期待通りには動かない。

Laravelのデフォルトグローバルミドルウェアスタックには、TrimStringsとConvertEmptyStringsToNullミドルウェアが含まれています。これらのミドルウェアは、App\Http\Kernelクラスにリストされています。これらのミドルウェアは自動的にリクエストの全入力フィールドをトリムし、それと同時に空の文字列フィールドをnullへ変換します。これにより、ルートやコントローラで、ノーマライズについて心配する必要が無くなります。

HTTPリクエスト 5.6 Laravel

  Laravel におけるデフォルト中間処理で入力フィールドのトリムと空文字列の null 化が自動的に行われるため、 null チェックのみが正解。ここで empty() 判定をしてしまうと数値の 0 が空文字列判定されて正しく動かない。PHPェ。

blade テンプレートにおける制御構文

 view 関数で呼び出す blade テンプレート内には @if などの判定制御ディレクティブを書くことができる。

        @if(strval($target) != null)
        <div class="flex-center">if target = {{ $target }}</div>
        @else
        <div class="flex-center">not if target = {{ $target }}</div>
        @endif

        @isset($target)
        <div class="flex-center">isset target = {{ $target }}</div>
        @else
        <div class="flex-center">not isset target = <{{ $target }}/div>
        @endisset

        @empty($target)
        <div class="flex-center">empty target = {{ $target }}</div>
        @else
        <div class="flex-center">not empty target = {{ $target }}</div>
        @endempty

 @empty は案の定で target=0 が空文字判定されてしまうので使いどころが難しい。@if ディレクティブは権限に応じた機能トグルのON/OFFをイージーに実装するのに使えるだろう。繰り返しディレクティブも可能。

        @forelse ($data as $d)  
        <p>{{ $d }}</p>
        @empty
        <p>なーんもない</p>
        @endforelse

 N件のデータを取得した場合に行列の要素を取り出して処理する用途が思い浮かぶ。 @for@while など一通りのディレクティブがあるが、 @forelse が便利。件数が0件だった場合の処理を @empty に書けるので、@if ($data->count() >= 1) 的な入れ子制御が不要となる。foreach 構文の引数の順番が JavaScript や Python などと逆で気持ち悪い。blade テンプレート内で PHP自体 を書けるディレクティブもある。

@php
 /* PHPスクリプト */
@endphpt

 制御処理自体はなるべくコントローラー側で吸収すべきであろうが、画面内の特定要素の出し分け制御や繰り返し表示はテンプレート側に書いた方が直感的。ブラウザサイドの JavaScript でこの手の制御をすると、ブラウザ毎の単体テストや事象再現が複雑になりがちなのでサーバーサイドで出力が確定できるのがシンプルでよい。