2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

BEAR.SundayAdvent Calendar 2024

Day 13

BEAR.Sundayとテンプレートエンジン

Last updated at Posted at 2024-12-13

はじめに

近年、BEAR.SundayのプロジェクトではテンプレートエンジンにQiqを採用しています。Qiqには2つの重要な特徴があります。

Qiqの特徴

1. 明示的なエスケープ

一つ目は、自動エスケープではなく明示的なエスケープを要求する点です:

<a href="{{a $link}}">  // HTML属性として安全にエスケープ
    {{h $var }}  // HTMLとして安全にエスケープ
</a>

一見すると、自動エスケープの方が安全で楽に見えるかもしれません。しかし、明示的なエスケープには重要な利点があります:

  • コンテキストの明確化:HTMLなのか、URLなのか、出力の文脈が一目瞭然
  • 適切なエスケープの強制:異なるコンテキストに応じた適切なエスケープ方法の選択
  • 意図の明示:どのような加工が行われているかがコードから明確

2. 独自の制御構文を持たない

もう一つの特徴は、独自の制御構文を持たず、PHPの構文をそのまま利用する点です:

// Twigの場合
{% if user.isAdmin %}
    {{ user.name }}
{% endif %}

// Qiqの場合
{{ if ($user->isAdmin): }}
    {{h $user->name }}
{{ endif }}

この選択は、「そもそもテンプレートエンジンにどこまでの制御構造が必要なのか?」という、より本質的な問いへとつながります。

テンプレートエンジンが抱えるよくある問題

Qiqのように優れたテンプレートエンジンであっても、テンプレートエンジンにはいくつかの課題があります。

1. コードの可視性

  • IDEや外部ツールによる静的解析が及ばない
  • コードの品質測定やリファクタリング支援が効かない
  • 型の追跡が途切れる「ブラックボックス」となってしまう
  • テンプレート内のロジックは単体テストが困難

2. 責務の境界の曖昧さ

  • フロントエンド・バックエンドの境界に位置するため、双方の関心事が混在しやすい
  • ビジネスロジックが紛れ込みやすい
  • 様々な判断ロジックの「最後の砦」となってしまう

制約による解決

これらの問題に対する解決策は、一見すると困難に思えますが、実はシンプルです。
テンプレートエンジンを本来の責務に限定するという「制約」を設けることです。

具体的には、以下の3つの責務に限定します:

  1. レイアウトの継承
  2. (コンテンツ)ブロックの定義と配置
  3. 安全な出力

テンプレートから制御構造を取り除く

テンプレート内のロジックで特に複雑になりがちなのは、foreachループとその中での条件分岐です。ループが入れ子になったり、複数の条件分岐が組み合わさったりすることで、可読性と保守性が急速に低下してしまいます。

これらの問題に対しては、ジェネレータを使用することで効果的に対処できます。

具体例を見てみましょう。
まずは、ジェネレータを使わないケースです。

{{ foreach ($items as $item): }}
    {{ if ($item->isAvailable()): }}
        {{ if ($item->getStock() > 0): }}
            {{ if ($item->isDiscounted()): }}
                <li class="sale">{{h $item->name }} - {{h $item->getDiscountPrice() }}</li>
            {{ else: }}
                <li>{{h $item->name }} - {{h $item->price }}</li>
            {{ endif }}
        {{ endif }}
    {{ endif }}
{{ endforeach }}

このように改善できます。

// プレゼンター側
class ProductPresenter
{
    public function getDisplayItems(): Generator
    {
        foreach ($this->items as $item) {
            if (!$item->isAvailable() || $item->getStock() <= 0) {
                continue;
            }
            
            yield [
                'name' => $item->getName(),
                'price' => $item->isDiscounted() 
                    ? $item->getDiscountPrice() 
                    : $item->getPrice(),
                'classes' => $item->isDiscounted() ? 'sale' : '',
            ];
        }
    }
}
// テンプレート側:シンプルで宣言的
{{ foreach ($displayItems as $item): }}
    <li {{a ['class' => $item['classes'] ]}}>
        {{h $item['name'] }} - {{h $item['price'] }}
    </li>
{{ endforeach }}

テンプレートから外部の値にアクセスしない

BEAR.Sundayでは、リソースの取得を機能の一つであるEmbedによって行います。

class Index extends ResourceObject
{
    #[Embed(rel: 'todos', src: 'app://self/todos{?status}')]
    public function onGet(string $status): static
    {
        return $this;
    }
}

テンプレートから、そのリソースを参照できるのですが、これを行わないようにします。

/qiq/template/Page/Index.qiq.php
// NG 参照
{{h $this->todos->title }}

// NG 別のテンプレートに渡す
{{ render ('/template/todos', ['todos' => $this->todos ]) }}

// OK 出力だけを行う
{{= $this->todos }}

リソース自身がテンプレートを持ち、その中で出力を行います。

qiq/tempalte/App/Todos.qiq.php
{{h $this->todos->title }}
{{ foreach ($this->todos->entries as $entry): }}
    {{h $entry->title }}
{{ endforeach }}

こうすることで、リソースそれぞれを文字列としてキャッシュすることが可能になります。

制約がもたらす恩恵

これらの制約を採用することで、以下のような恩恵が得られます。

1. テスタビリティの向上

  • ビジネスロジックをテンプレートから分離することで、ロジックの単体テストが容易になります
  • テンプレートは純粋に表示のみを担当するため、テストが単純化されます

2. キャッシュの最適化

  • テンプレートが外部にアクセスしないため、出力結果を文字列として安全にキャッシュできます

3. メンテナンス性の向上

  • 責務の境界が明確
  • コードの意図が理解しやすい
  • ビジネスロジックとプレゼンテーションの明確な分離

制約は福音

制約がなぜ必要なのかを理解することは難しいことですし、理解した上でそれを守り続けることも難しいです。

「そこまでの制約は必要ない」「ここにこう書いてしまえば悩まずにすむ」といった声も聞かれるでしょう。

しかし制約は、将来の複雑さから私たちを守る「福音」なのです。

一方、制約を緩めることは、短期的な利便性と引き換えに、長期的な保守性と信頼性を損なう「凶報」となりかねないのです。

まとめ

本記事では、BEAR.SundayプロジェクトにおけるテンプレートエンジンとしてQiqを採用する理由と、守るべき制約について詳しく解説しました。

適切な制約は私たちを混乱から守り、本来の目的である「テンプレートで何を表現したいのか」という本質に集中することを助けてくれます。結果として、長期的に見ると、より理解しやすく、保守がしやすく、そして安全なコードが自然と書けるようになります。

2
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?