この記事はLaravel 5.7.19をもとに書かれています。
要約
- 親テンプレートの中では
@showを使う - 子の中では
@endsectionを使う
事の始まり
Laravelの公式ドキュメントの日本語訳としてよくお世話になるreadoubleですが、そのBladeテンプレートの項にこんな記述があります。
Tip!! 直前の例とは異なり、このsidebarセクションは@showの代わりに@endsectionで終わっています。@endsectionディレクティブはセクションを定義するだけに対し、@showは定義しつつ、そのセクションを即時にその場所に取り込みます。
なんとなく言っていることが分かるような分からないような…
曖昧なままコードを書くのは火傷の元、ということで今一度ちゃんとこのあたりの整理をしようと思いました。
実験
普通に書いてみる
まずはreadoubleにあるのと同じようなコードを書いてみます。
<html>
<head>
<title>Bladeテスト</title>
</head>
<body>
@section('content')
<p>親の内容</p>
@Show
</body>
</html>
@extends('layouts.app')
@section('content')
@parent
<p>子の内容</p>
@endsection
app.blade.phpでcontentというセクションを定義し、そこに「親の内容」という文を埋め込んでいます。
一方、child.blade.phpはapp.blade.phpを継承しており、やはりcontentというセクションに「子の内容」という文を埋め込んでいます。ただし、継承元であるapp.blade.phpにもcontentセクションが存在するため、その内容に追加する形になります。1
注目すべきなのはセクションの終わりを表すディレクティブです。app.blade.phpでは@showでセクションを閉じていますが、child.blade.phpでは@endsectionでセクションを閉じています。
このコードをコンパイルブラウザで表示すると次のようなHTMLが生成されます。
<html>
<head>
<title>Bladeテスト</title>
</head>
<body>
<p>親の内容</p>
<p>子の内容</p>
</body>
</html>
想定通りの出力がされました。
親でも@endsectionを使う
親テンプレートであるapp.blade.phpにおいて、@showの代わりに@endsectionを使ってみます。
<html>
<head>
<title>Bladeテスト</title>
</head>
<body>
@section('content')
<p>親の内容</p>
@endsection {{--@show->@endsection--}}
</body>
</html>
ページをブラウザで開くと、こんなHTMLが生成されています。
<html>
<head>
<title>Bladeテスト</title>
</head>
<body>
</body>
</html>
セクションの内容が丸々消えてしまいました!
子でも@endsectionを使う
最後に、親テンプレートであるchild.blade.phpにおいて、@endsectionの代わりに@showを使ってみます。
@extends('layouts.app')
@section('content')
@parent
<p>子の内容</p>
@show {{--@endsection -> @show--}}
ブラウザで見てみましょう。
<html>
<head>
</head>
<body>
<p>子の内容</p>
<title>Bladeテスト</title>
<p>親の内容</p>
<p>子の内容</p>
</body>
</html>
子の内容が2度表示されてしまいます。しかも変なところに<title>タグが表れてカオス。
実験結果からわかること
どうも@showがないとそもそもセクションの内容が埋め込まれないようです。これがreadoubleで言うところの「@showは定義しつつ、そのセクションを即時にその場所に取り込みます」ということなのでしょう。逆に、@showがあるとそこで埋め込みが即座に行われてしまうため、複数回@showが記述されるような場合はやはり埋め込みも複数回発生してしまいます。
コードを読む
実際のところ@showや@endsectionがどんな処理をするのか、Bladeテンプレートのコンパイル結果を見てみます。
今一度ソースコードを最初のまともに表示されていた状態に戻し、コンパイル結果を確認します。
<html>
<head>
<title>Bladeテスト</title>
</head>
<body>
<?php $__env->startSection('content'); ?>
<p>親の内容</p>
<?php echo $__env->yieldSection(); ?>
</body>
</html>
<?php $__env->startSection('content'); ?>
##parent-placeholder-040f06fd774092478d450774f5ba30c5da78acc8##
<p>子の内容</p>
<?php $__env->stopSection(); ?>
<?php echo $__env->make('layouts.app', \Illuminate\Support\Arr::except(get_defined_vars(), array('__data', '__path')))->render(); ?>
このように、@showは<?php echo $__env->yieldSection(); ?>に、@endsectionは<?php $__env->stopSection(); ?>に展開されます。前者はechoされるため表示が行われるが、後者はechoがないのでそのままでは表示されないということになります。
ちなみに、これらのメソッドの実態はIlluminate\View\Concerns\ManagesLayoutsクラスに記述されています。
public function yieldSection()
{
if (empty($this->sectionStack)) {
return '';
}
return $this->yieldContent($this->stopSection());
}
public function stopSection($overwrite = false)
{
if (empty($this->sectionStack)) {
throw new InvalidArgumentException('Cannot end a section without first starting one.');
}
$last = array_pop($this->sectionStack);
if ($overwrite) {
$this->sections[$last] = ob_get_clean();
} else {
$this->extendSection($last, ob_get_clean());
}
return $last;
}
yieldSection()は内部的にはstopSection()を呼び出すため、どちらもセクションを終わらせることがわかります。
おわり
Bladeテンプレートの表示処理は結構面白そうなので、もっといろいろ見てみたいですね。
参考
-
@parentディレクティブを使用していない場合、親の内容に追記するのではなく、親の内容に上書きします。 ↩