Bladeテンプレートの@showと@endsectionを間違えないようにする

この記事は`Laravel 5.7.19`をもとに書かれています。

## 要約

- 親テンプレートの中では`@show`を使う
- 子の中では`@endsection`を使う

## 事の始まり

Laravelの公式ドキュメントの日本語訳としてよくお世話になる[readouble](https://readouble.com/laravel/5.7/ja/releases.html)ですが、そのBladeテンプレートの項にこんな記述があります。

> Tip!! 直前の例とは異なり、このsidebarセクションは@showの代わりに@endsectionで終わっています。@endsectionディレクティブはセクションを定義するだけに対し、@showは定義しつつ、そのセクションを即時にその場所に取り込みます。

なんとなく言っていることが分かるような分からないような…
曖昧なままコードを書くのは火傷の元、ということで今一度ちゃんとこのあたりの整理をしようと思いました。

## 実験
### 普通に書いてみる

まずはreadoubleにあるのと同じようなコードを書いてみます。

```php:layouts/app.blade.php
<html>
<head>
    <title>Bladeテスト</title>
</head>
<body>
@section('content')
    <p>親の内容</p>
@Show 
</body>
</html>
```

```php:child.blade.php
@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
<html>
<head>
    <title>Bladeテスト</title>
</head>
<body>
    <p>親の内容</p>
    <p>子の内容</p>
</body>
</html>
```

想定通りの出力がされました。

### 親でも`@endsection`を使う
親テンプレートである`app.blade.php`において、`@show`の代わりに`@endsection`を使ってみます。

```php:layouts/app.blade.php
<html>
<head>
    <title>Bladeテスト</title>
</head>
<body>
@section('content')
    <p>親の内容</p>
@endsection {{--@show->@endsection--}}
</body>
</html>
```

ページをブラウザで開くと、こんなHTMLが生成されています。

```html
<html>
<head>
    <title>Bladeテスト</title>
</head>
<body>

</body>
</html>
```

セクションの内容が丸々消えてしまいました!

### 子でも`@endsection`を使う

最後に、親テンプレートである`child.blade.php`において、`@endsection`の代わりに`@show`を使ってみます。

```php:child.blade.php
@extends('layouts.app')

@section('content')
	@parent

	<p>子の内容</p>
@show {{--@endsection -> @show--}}
```

ブラウザで見てみましょう。

```html
<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テンプレートのコンパイル結果を見てみます。
今一度ソースコードを最初のまともに表示されていた状態に戻し、コンパイル結果を確認します。

```php:layouts/app.blade.phpのコンパイル結果
<html>
<head>
    <title>Bladeテスト</title>
</head>
<body>
<?php $__env->startSection('content'); ?>
    <p>親の内容</p>
<?php echo $__env->yieldSection(); ?>
</body>
</html>
```
```php:child.blade.phpのコンパイル結果
<?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`クラスに記述されています。

```php:ManagesLayouts->yieldSection
   public function yieldSection()
    {
        if (empty($this->sectionStack)) {
            return '';
        }

        return $this->yieldContent($this->stopSection());
    }
```
```php:ManagesLayouts->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テンプレートの表示処理は結構面白そうなので、もっといろいろ見てみたいですね。

## 参考

- https://readouble.com/laravel/5.7/ja/blade.html

[^1]: `@parent`ディレクティブを使用していない場合、親の内容に追記するのではなく、親の内容に上書きします。