HTMLテンプレートを扱う、"Twig Bundle"

[Symfony Component Advent Calendar 2022](https://qiita.com/advent-calendar/2022/symfony-component)の9日目の記事です。

## 最初に
[Symfony](https://symfony.com)はPHPのフレームワークのひとつです。しかし、公式サイトの説明文には

> Symfony is a set of PHP Components, a Web Application framework, a Philosophy, and a Community — all working together in harmony.
  （SymfonyはPHPコンポーネントのセットで、Webアプリケーションフレームワークで、哲学、そしてコミュニティです。それらがハーモニーを奏でながら動作しています。）

と書かれている通り、PHPコンポーネントのセットで、たくさんのコンポーネントを提供しており、それらを組み合わせてひとつのフレームワークとして動作しています。Symfonyのコンポーネントは、Symfony上だけで動作するのではなく、他のPHPフレームワークやアプリケーションでも動作している強力なものが揃っています。

今回はそれらの中から、役立ちそうなもの・お薦めしたいものを紹介していきたいと思います。

※記事内ではautoloadのインポートは省略します。


## HTMLテンプレートを扱う、"Twig Bundle"

[Twig Bundle](https://symfony.com/doc/current/templates.html)は、SymfonyでHTMLのテンプレートである[Twig](https://twig.symfony.com/)を扱えるようにするコンポーネントです。

### インストール

```shell
composer require symfony/twig-bundle
```

### Twigの基本

Twigの基本的な使い方は`{{ ... }}`, `{% ... %}`という文法を使ってアプリケーションから渡された値を出力したり、制御したりして使います。


```php:ItemController.php
public function index(ItemRepository $itemRepository): Response
{
    $items = $itemRepository->findAll();
    
    return $this->render('index.html.twig', [
        'name' => 'すみだ',
        'items' => $items,
    ]);
}
```

```twig:index.html.twig

<div>
こんにちは、{{ name }}さん。
</div>

<ul>
{% for item in items %}
<li>{{ item.name }} {{ item.price|number_format }}円</li>
{% endfor %}
</ul>
```

`{{ ... }}`で、アプリケーション（上記の例ではController）から渡されたキーに紐づく値やTwig関数の実行結果を出力します。Twig側は`$`が必要ありません。
`{% ... %}`で、Twig上で使えるTwigタグを実行します。上記の例では繰り返し処理を行う`{% for %}`, `{% endfor %}`を使って、商品リストを出力しています。
また、出力時に`|`移行にTwigフィルターを使って値を加工することもできます。

詳しくは[こちら](https://twig.symfony.com/doc/3.x/)

### Twigの特徴

Twigは他のフレームワークで利用しているテンプレートエンジンと同じく、HTMLのテンプレートを使って、HTMLを出力することができますが、他のものとはちょっと違う特徴があります。そのうちの一つで、個人的にとても気に入ってるのが**テンプレート内でPHPプログラムが動かせない**という点です。

例えばLaravelの[Blade](https://laravel.com/docs/9.x/blade)だと、

```php:index.blade.php
@php

$now = Carbon::now()->format('Y-m-d H:i:s');

@endphp

<span>{{ $now }}</span>
```

と、テンプレート内でPHPを実行することができます。ところが、Twigにはこのような記述をすることができません。公式ドキュメントにも冒頭で、

> You can't run PHP code inside Twig templates,

と記述してあります。

### 独自フィルター・関数を作る、"TwigExtension"

Twigには豊富なフィルターや関数がすでに用意されていますが、アプリケーション独自のフィルター・関数を作りたい時があります。そんな時は`TwigExtension`を作成することで、解決します。

```php:src/Twig/AppExtension.php

namespace App\Twig;

use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;

class AppExtension extends AbstractExtension
{
    public function __construct(private readonly float $taxRate)
    {
    }

    public function getFilters()
    {
        return [
            new TwigFilter('withTax', [$this, 'formatPrice']),
        ];
    }
    
    public function getFunctions()
    {
        return new TwigFunction('tax', [$this, 'getTax']);
    }

    public function formatPrice(int $price): string
    {
        $priceWithTax = floor($price + ($price * $this->taxRate));

        return number_format($priceWithTax);
    }
    
    public function getTax(int $price): int
    {
        return $price * $this->taxRate;
    }
}
```

`AbstractExtension`の子クラスを作り、`getFilters()`に`new TwigFilter('フィルター名', [$this, '実行するメソッド名'])`を指定することでフィルターを追加、'getFunctions()'に`new TwigFunction('関数名', [$this, '実行するメソッド名'])`を指定することで関数を追加できます。上記の例は消費税を含めた金額に変換するフィルターと、消費税額を返す関数を用意しました。
ここでも当然DIできるので、消費税率はDIしています。

使い方は、通常のTwigフィルター・関数と同様です。

```twig:index.html.twig

<div>
こんにちは、{{ name }}さん。
</div>

<ul>
{% for item in items %}
<li>{{ item.name }} {{ item.price|withTax }}円 （うち消費税{{ tax(item.price)|number_format }}円）</li>
{% endfor %}
</ul>
```

## まとめ

今回は`Twig Bundle`を紹介しました。TwigはViewの出力に専念できるので、とても好きなテンプレートエンジンです。
これもとてもおすすめなので、ぜひ使っていただけたらです。