Laravel Blade の {{ }} 記法について
Laravel Blade テンプレートをお使いのみなさん、いかがお過ごしでしょうか。
blade テンプレートを用いて文字列を展開するときに {{ }} を用いていると思います。
例えばコントローラーの中で下記のように書くと
public function index(Request $request){
$products = Product::all();
return view('product.index',[
'products' => $products,
]);
}
Bladeテンプレートの中では、下記のような表記方法でテンプレートを記述すると思います、
@foreach( $products as $product )
<div>{{$product->id}}</div>
<div>{{$product->name}}</div>
@endforeach
{{ }} で囲んだ値が html の中に展開されます。
この時、経験の差でこのような表記になるんだ。と考える方もいると思いますし、
<?php echo $product->id ?>
下記のようになるのだと考える方もいると思います。
<?php echo htmlspecialchars($product->id, ENT_QUOTES) ?>
もちろん {{ }} で囲まれた場合は、下記の htmlspecialchars で処理されます。
この htmlspecialchars は XSS 対策でも重要です。 展開文字列に & < >
等の文字が含まれた場合 & などに変換されます。
つまり、下記のような表記は htmlspecialchars を直接記載必要なく適切だと安心して使用できます。
@foreach( $products as $product )
<div>{{$product->id}}</div>
<div>{{$product->name}}</div>
@endforeach
ここまでくると気になるのが 変数に html タグを入れて そのまま表示したくないときです。
滅多にありませんが、たまにあります。たぶん。
<div>{!! $name !!}</div
このような表記方法をすればよいと laravel の blade のドキュメントにもあります。
そう、この表記をすれば エスケープ処理されず正しくでるのです。
どんな使い道があるか?ぱっと思いつくのは、decorationなどのヘルパ関数があり、
その中で適当に html 修飾されて出てくる等です。
<div>{!! decoration($name) !!}</div
そう、私もそう思っていました。
しかし、とあるときに不思議な気持ちにおいやられました。
ページネーションするときです。
public function index(Request $request){
$products = Product::paginate(15);//※1
return view('product.index',[
'products' => $products,
]);
}
@foreach( $products as $product )
<div>{{$product->id}}</div>
<div>{{$product->name}}</div>
@endforeach
{{ $products->links() }} {{-- //※2 --}}
はい ※1 のようにページネーションして結果を収集し、※2 のようにすると、なんとそこに
<a href="">prev</a>
<a href="">[1]</a>
<a href="">next</a>
のような表記が展開されるのです。
勘の言い方!「ちょったまったー」といいたくなると思います。
{{ }} で囲まれているのだから htmlspecialchars でエスケープ処理されて htmlタグは表示されないだろ!と
{{ }} の正体
じつは {{ }} は htmlspecialchars で囲んで表示するではなかったのです。
実装をみてみたいとおもいます。
{{ }} は 下記のメソッドの部分で展開の処理をしています。
※3 の部分は、echoFormat は e() で囲んで表示するのです。
つまり、 {{ $name }}
は e($name)で囲まれて出力されるのです。
protected function compileRegularEchos($value)
{
$pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->contentTags[0], $this->contentTags[1]);
$callback = function ($matches) {
$whitespace = empty($matches[3]) ? '' : $matches[3].$matches[3];
$wrapped = sprintf($this->echoFormat, $this->wrapInEchoHandler($matches[2]));//※3
return $matches[1] ? substr($matches[0], 1) : "<?php echo {$wrapped}; ?>{$whitespace}";
};
return preg_replace_callback($pattern, $callback, $value);
}
はい、やっぱり e() で htmlspecialchars でしょ!?
とおもったら、さぁ…
function e($value, $doubleEncode = true)
{
if ($value instanceof DeferringDisplayableValue) {
$value = $value->resolveDisplayableValue();
}
if ($value instanceof Htmlable) {
return $value->toHtml();
}
if ($value instanceof BackedEnum) {
$value = $value->value;
}
return htmlspecialchars($value ?? '', ENT_QUOTES, 'UTF-8', $doubleEncode);
}
e() は、確かに htmlspecialcharsをしていましたが、実は違ったのです。
そう、Htmlableの場合、return $value->toHtml();
つまり htmlspecialchars
せずに返していたのです。
{{ $products->links() }}が正しくhtmlを表示していたのはここだったのです。
htmlを出力したい場合 Htamlable なオブジェクトを {{}} に渡そう
もし、プロジェクトで {!! !!} を使わざるおえなく、困っていたらこの仕掛けを導入するのもありかもしれません。
いや、昔、 html をコントローラーから渡すことがあり、それを表示するために {!! !!} を使っていました。
XSS脆弱性判断のために {!! を全体的に走査していたので、 {!! で囲むものには特殊な命名規則でつけたものを渡そう。
そういう独自ルールで対処した時期があったのですが、こちら使えばいいですね。
{!! !!} を世界から追い出しましょう←暴論です。
詳細のソースはこちら