こんにちは、秦です。

自社サービスや受託開発にてphpのフレームワークである「Laravel」を使用して開発を行っておりますが、
その中で重宝したLaravelの開発Tipsをご紹介して参ります。

今回は、Bootstrap4と組み合わせた場合のView側(bladeファイル)の効率化についてご紹介します。

Laravelについてはこちら
 http://laravel.jp/

はじめに

バリデーション(入力チェック)時のエラーメッセージの表示は、
同じようなコードが並び冗長的になりがちです。

これに対し、カスタムディレクティブとコンポーネントで部品化し、冗長性を排除して行きます。

今回ご紹介する内容によって、以下のようにコードをスッキリさせることができます。

【変更前のコード】

<input type="text" id="email" class="form-control @if($errors->has('email')) is-invalid @endif" name="email" value="">
@if ($errors->has('email'))
<div class="invalid-feedback">
  @foreach ($errors->get('email') as $message)
  <p>{{ $message }}</p>
  @endforeach
</div>
@endif

【変更後のコード】

<input type="text" id="email" class="form-control @invalid('email')" name="email" value="">
@component('components.invalid_feedback', ['name' => 'email'])
@endcomponent

以下より詳細を解説して行きます。

尚、本記事で使用する技術の対象バージョンは以下を前提とします。

Laravel 5.5以上
php 7.1以上
Bootstrap 4

バリデーションエラー時のメッセージ表示

Bootstrapを使用して入力エラーのメッセージ表示を行いたい場合、
以下のような部品を作成するかと思います。

ご参考記事)
 Forms・Bootstrap
 バリデーション 5.5 Laravel

上記のイメージと参考記事より、Laravel+Bootstrapでは以下のような実装を行うことになります。

例)メールアドレスの入力とエラー表示の実装
<input type="text" id="email" class="form-control @if($errors->has('email')) is-invalid @endif" name="email" value="">
@if ($errors->has('email'))
<div class="invalid-feedback">
  @foreach ($errors->get('email') as $message)
  <p>{{ $message }}</p>
  @endforeach
</div>
@endif

「email」というフィールドにエラーがあった場合、エラーありの表示とエラーメッセージを表示する、
という内容の実装です。

さて、ログイン画面であれば2~3個ほどの入力項目で済みますが、
マスタデータや業務データについては多数の項目になることが通常ですよね。

その度に、各項目毎に判定をしてclass属性にis-invalidを付与して、
エラーの有無を判定して、該当するエラーメッセージを取り出して表示して…
といったコードを書いて行くとなるといかがでしょう。

例)複数の項目にエラー表示を行う実装
<!-- 会社ID -->
<input type="text" id="companyId" class="form-control @if($errors->has('companyId')) is-invalid @endif" name="companyId" value="">
@if ($errors->has('companyId'))
<div class="invalid-feedback">
  @foreach ($errors->get('companyId') as $message)
  <p>{{ $message }}</p>
  @endforeach
</div>
@endif
 
<!-- メールアドレス -->
<input type="text" id="email" class="form-control @if($errors->has('email')) is-invalid @endif" name="email" value="">
@if ($errors->has('email'))
<div class="invalid-feedback">
  @foreach ($errors->get('email') as $message)
  <p>{{ $message }}</p>
  @endforeach
</div>
@endif
 
<!-- パスワード -->
<input type="password" id="password" class="form-control @if($errors->has('password')) is-invalid @endif" name="password" value="">
@if ($errors->has('password'))
<div class="invalid-feedback">
  @foreach ($errors->get('password') as $message)
  <p>{{ $message }}</p>
  @endforeach
</div>
@endif

冗長的になり、コード量は増え、可読性が下がり、
その結果、メンテナンス性も損なわれることとなります。

局所的なデザイン変更などが発生することはあっても、
大多数の項目は統一されたデザインであることが求められると思います。

開発中やリリース後の運用フェーズにおいて、デザイン変更や仕様変更が入った場合、
全ての項目の全てのエラー表示箇所を修正することはとても現実的とは思えません。

コードの冗長性を排除する

さて、上記の通り、冗長的になるコードから冗長性を排除するには
どのような実装を行えば良いでしょうか。

一例として、「カスタムディレクティブ」と「コンポーネント」を組み合わせる方法を解説します。
カスタムディレクティブとコンポーネントについては、Laravelの公式ドキュメントを参照ください。

ご参考記事)
 Bladeテンプレート 5.5 Laravel

カスタムディレクティブの作成と適用

まずは、カスタムディレクティブにできる部分から考えて行きます。
各input要素のclass属性に着目してみてください。

<!-- class属性に判定と出力 -->
<input type="text" id="email" class="form-control @if($errors->has('email')) is-invalid @endif" name="email" value="">

$errorsに各name属性の値がある場合は、「is-invalid」というclassを追加する判定があります。
is-invalid自体はinputへのエラー表示と、下のinvalid-feedbackを表示するためのclass属性です。

将来的にBootstrapのバージョンアップを行った時、このclass属性の名称が変更されたら…
エラーがある場合に、is-invalidに加えて独自のclass属性を追加したくなったら…

そういった可能性を考慮して、こちらをカスタムディレクティブで共通化します。

カスタムディレクティブを適用したblade
<!-- class属性にカスタムディレクティブを適用 -->
<input type="text" id="email" class="form-control @invalid('email')" name="email" value="">

まずは@invalidというカスタムディレクティブを作成する想定で、bladeファイルを書き換えます。
可変になるのはname属性の値、この場合だと「email」になりますので、
ディレクティブの引数としてname属性の値が渡すように設計しました。

さて、次にカスタムディレクティブの中身について実装します。
標準で作成されているAppServiceProviderクラスのboot()に、以下のコードを加えます。

/app/Providers/AppServiceProvider.php
public function boot()
{
  // @invalidディレクティブ
  // param $name 対象となるname属性の値('xxx')
  Blade::directive('invalid', function ($name) {
    return '<?php
      if ($errors->has('.$name.')) {
        echo "is-invalid"; // エラーがある場合は「is-invalid」を出力
      } else {
        echo ""; // エラーがない場合は何も出力しない
      }
    ?>';
  });
}

$errorsの判定を行い、エラーがある場合はis-invalidを出力します。

カスタムディレクティブを作成したら、以下のコマンドを実行します。
このコマンドを実行しないと、AppServiceProviderへの変更が反映されませんのでご注意ください。

$ php artisan view:clear

 

コンポーネントの作成と適用

次に、コンポーネントにできる部分について考えてみましょう。
input属性の下に記述されているinvalid-feedbackの部分に着目してみてください。

@if ($errors->has('email'))
<div class="invalid-feedback">
  @foreach ($errors->get('email') as $message)
  <p>{{ $message }}</p>
  @endforeach
</div>
@endif

こちらもname属性の値が変化するだけで、他のコードは共通化できそうですね。

もちろん、カスタムディレクティブを利用する方法もあるかと思いますが、
カスタムディレクティブは文字列でphpのコードを記述するため、
判定やループが入ったこの部分については不向きであると考えました。

そこで、コンポーネントを使って置き換えてみたいと思います。
新規にinvalid_feedback.blade.phpを作成します。

/resources/views/components/invalid_feedback.blade.php
@if ($errors->has($name))
<div class="invalid-feedback">
  @foreach ($errors->get($name) as $message)
    <p>{{ $message }}</p>
  @endforeach
</div>
@endif

可変となるname属性の値を$nameとして変数化しました。
では元のbladeをこのコンポーネントに置き換えてみます。

@component('components.invalid_feedback', ['name' => 'email'])
@endcomponent

コンポーネント名、引数としてname属性の値、この場合は「email」を渡します。
$errorsは各bladeで使用できますので、渡す必要はありません。

整理されたコード

最終的にこうなりました。
変更の前後を比較してみましょう。

整理される前と整理された後のコード
<!-- 会社ID -->
<input type="text" id="companyId" class="form-control @if($errors->has('companyId')) is-invalid @endif" name="companyId" value="">
@if ($errors->has('companyId'))
<div class="invalid-feedback">
  @foreach ($errors->get('companyId') as $message)
  <p>{{ $message }}</p>
  @endforeach
</div>
@endif
 
<!-- メールアドレス -->
<input type="text" id="email" class="form-control @if($errors->has('email')) is-invalid @endif" name="email" value="">
@if ($errors->has('email'))
<div class="invalid-feedback">
  @foreach ($errors->get('email') as $message)
  <p>{{ $message }}</p>
  @endforeach
</div>
@endif
 
<!-- パスワード -->
<input type="password" id="password" class="form-control @if($errors->has('password')) is-invalid @endif" name="password" value="">
@if ($errors->has('password'))
<div class="invalid-feedback">
  @foreach ($errors->get('password') as $message)
  <p>{{ $message }}</p>
  @endforeach
</div>
@endif

<!-- 会社ID -->
<input type="text" id="companyId" class="form-control @invalid('companyId')" name="companyId" value="">
@component('components.invalid_feedback', ['name' => 'companyId'])
@endcomponent
 
<!-- メールアドレス -->
<input type="text" id="email" class="form-control @invalid('email')" name="email" value="">
@component('components.invalid_feedback', ['name' => 'email'])
@endcomponent
 
<!-- パスワード -->
<input type="text" id="password" class="form-control @invalid('password')" name="password" value="">
@component('components.invalid_feedback', ['name' => 'password'])
@endcomponent

いかがでしょう。だいぶスッキリしましたよね。
もうひと工夫できるとすれば、name属性の値を変数化することかと考えます。

おわりに

Laravelには、とても便利な機能が備わっています。
更にバージョンが上がるに連れて、こういった便利な機能も増えています。
冗長性を排除して、可読性・保守性の高いコードを書くための一助になれば幸いです。

最後までお付き合い頂き、ありがとうございました。
次回もお楽しみに!

秦 祐二
エキスパートエンジニア 秦 祐二
社内にて受託開発を担当しています。社内コーディング規約やガイドラインの策定など、生産性向上や品質向上に努めて行く中で得た、業務で使えるノウハウをご紹介します。