テンプレート

Nette は Latteというテンプレートシステムを使います。Latte を使うのは、PHP で最も安全なテンプレートシステムであり、同時に最も直感的なシステムだからです。新しく覚えることは多くありません。PHP の知識といくつかのタグで十分です。

ページはたいてい、レイアウトのテンプレートと個別のアクションのテンプレートから組み立てられます。レイアウトのテンプレートはたとえば次のような形です。{block} のブロックと {include} タグに注目してください。

<!DOCTYPE html>
<html>
<head>
	<title>{block title}My App{/block}</title>
</head>
<body>
	<header>...</header>
	{include content}
	<footer>...</footer>
</body>
</html>

そしてこれがアクションのテンプレートです。

{block title}Homepage{/block}

{block content}
<h1>Homepage</h1>
...
{/block}

ここでは content ブロックを定義していて、レイアウトの {include content} の場所に挿入されます。あわせて title ブロックを定義し直していて、レイアウトの {block title} を上書きします。結果を思い浮かべてみてください。

テンプレートの探索

プレゼンターでは、どのテンプレートを描くべきかを指定する必要はありません。フレームワークが自動的にパスを導くので、書く手間が省けます。

プレゼンターごとにディレクトリを持つ構成を使っているなら、そのディレクトリにアクション(つまりビュー)の名前でテンプレートを置くだけです。たとえば default アクションには default.latte テンプレートを使います。

app/
└── Presentation/
    └── Home/
        ├── HomePresenter.php
        └── default.latte

プレゼンターをひとつのディレクトリにまとめ、テンプレートを templates フォルダに置く構成を使っているなら、<Presenter>.<view>.latte<Presenter>/<view>.latte のファイルに保存します。

app/
└── Presenters/
    ├── HomePresenter.php
    └── templates/
        ├── Home/
        │   └── default.latte   ← 1 つめの形
        └── Home.default.latte  ← 2 つめの形

templates ディレクトリは 1 階層上、つまりプレゼンターのクラスがあるディレクトリと同じ階層に置くこともできます。

テンプレートが見つからない場合、プレゼンターは 404 – ページが見つかりませんで応えます。

ビューは $this->setView('otherView') で変えられます。$this->template->setFile('/path/to/template.latte') でテンプレートのファイルを直接指定することもできます。

テンプレートを探すファイルは、可能なファイル名の配列を返す formatTemplateFiles()メソッドを上書きすれば変えられます。

レイアウトのテンプレートの探索

Nette はレイアウトのファイルも自動的に探します。

プレゼンターごとにディレクトリを持つ構成を使っているなら、そのプレゼンター専用のレイアウトはプレゼンターのフォルダに、複数のプレゼンターで共通のレイアウトは 1 階層上に置きます。

app/
└── Presentation/
    ├── @layout.latte           ← 共通のレイアウト
    └── Home/
        ├── @layout.latte       ← Home プレゼンター専用
        ├── HomePresenter.php
        └── default.latte

プレゼンターをひとつのディレクトリにまとめ、テンプレートを templates フォルダに置く構成なら、レイアウトは次の場所で探されます。

app/
└── Presenters/
    ├── HomePresenter.php
    └── templates/
        ├── @layout.latte       ← 共通のレイアウト
        ├── Home/
        │   └── @layout.latte   ← Home 専用、1 つめの形
        └── Home.@layout.latte  ← Home 専用、2 つめの形

プレゼンターがモジュールの中にある場合は、モジュールの入れ子に応じてディレクトリの階層をさらに上へ探していきます。

レイアウトの名前は $this->setLayout('layoutAdmin') で変えられ、その場合は @layoutAdmin.latte ファイルが期待されます。$this->setLayout('/path/to/template.latte') でレイアウトのテンプレートファイルを直接指定することもできます。

$this->setLayout(false) を使うか、テンプレートの中で {layout none} タグを使うと、レイアウトの探索を無効にできます。

レイアウトのテンプレートを探すファイルは、可能なファイル名の配列を返す formatLayoutTemplateFiles()メソッドを上書きすれば変えられます。

テンプレートの変数

変数は $this->template に書き込むことでテンプレートに渡します。テンプレートの中ではローカル変数として使えるようになります。

$this->template->article = $this->articles->getById($id);

プロパティの値を変数として自動的にテンプレートへ渡すには、#[TemplateVariable] アトリビュートを付け、public にします。

use Nette\Application\Attributes\TemplateVariable;

class ArticlePresenter extends Nette\Application\UI\Presenter
{
	#[TemplateVariable]
	public string $siteName = 'My blog';
}

同じ名前の変数をテンプレートに渡した場合、#[TemplateVariable] はそれを上書きしません。

既定の変数

プレゼンターとコンポーネントは、いくつかの役立つ変数をテンプレートに自動的に渡します。

  • $basePath はルートディレクトリへの絶対 URL パスです(たとえば /eshop
  • $baseUrl はルートディレクトリへの絶対 URL です(たとえば http://localhost/eshop
  • $userユーザーを表すオブジェクトです
  • $presenter は現在のプレゼンターです
  • $control は現在のコンポーネントまたはプレゼンターです
  • $flashesflashMessage() 関数で送られたメッセージの配列です

独自のテンプレートクラスを使っている場合、これらの変数はそのためのプロパティを作れば渡されます。

型安全なテンプレート

堅牢なアプリケーションを開発するときは、テンプレートがどの変数をどんな型で期待するかを明示的に定義しておくと役立ちます。PHP による型チェック、IDE の賢い補完が得られ、静的解析でエラーを捕まえられるようになります。

そうした一覧はどう定義するのでしょうか。テンプレートの変数を表すプロパティを持つクラスとして書くだけです。名前はプレゼンターに似せ、末尾に Template を付けます。

/**
 * @property-read ArticleTemplate $template
 */
class ArticlePresenter extends Nette\Application\UI\Presenter
{
}

class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template
{
	public Model\Article $article;
	public Nette\Security\User $user;

	// そしてほかの変数
}

これでプレゼンターの $this->template オブジェクトは ArticleTemplate クラスのインスタンスになります。書き込むとき、PHP が宣言された型を確認してくれます。

Nette はテンプレートのクラスを自動的に選びます。まず <Presenter><Action>Template という名前のクラス、たとえば edit アクションなら ArticleEditTemplate を探し、それがない場合にだけ <Presenter>Template に戻ります。

@property-read アノテーションは IDE と静的解析のためのもので、コード補完を可能にします。PhpStorm と $this⁠-⁠>⁠template のコード補完 をご覧ください。

テンプレートの中でも直接コード補完を使えます。PhpStorm 用の Latte プラグインをインストールし、テンプレートの先頭でテンプレートパラメータのクラス名を指定するだけです。詳しくは Latte: 型システムの章をご覧ください。

{templateType App\Presentation\Article\ArticleTemplate}
...

同じことがコンポーネントにも当てはまります。命名の決まりに従って、FifteenControl のようなコンポーネントには FifteenTemplate というパラメータのクラスを作るだけです。

別のパラメータのクラスを使う必要があるなら、createTemplate() メソッドを使います。

public function renderDefault(): void
{
	$template = $this->createTemplate(SpecialTemplate::class);
	$template->foo = 123;
	// ...
	$this->sendTemplate($template);
}

描画の前にテンプレートがどう仕上げられるかに手を入れたい場合、たとえばすべてのアクションで共通の変数を足したい場合は、プレゼンターの completeTemplate() メソッドを上書きできます。これはテンプレートが描かれる直前に呼ばれます。

protected function completeTemplate(Nette\Application\UI\Template $template): void
{
	parent::completeTemplate($template);
	$template->siteName = 'My blog';
}

リンクの作成

テンプレートでは、ほかのプレゼンターやアクションへのリンクを次のように作ります。

<a n:href="Product:show">商品の詳細</a>

n:href 属性は HTML の <a> タグにとても便利です。リンクをそれ以外の場所、たとえば文章の中に出したい場合は {link} を使います。

URL は次のとおりです: {link Home:default}

詳しくは URL リンクの作成の章をご覧ください。

カスタムフィルタ、タグなど

Latte のテンプレートシステムは、独自のフィルタ、関数、タグなどで拡張できます。手早いその場しのぎの解から、アプリケーション全体のためのアーキテクチャ上のパターンまで、3 つの方法があります。

プレゼンターのメソッドでその場しのぎに

最も手早いのは、プレゼンターやコンポーネントのコードで直接フィルタや関数を足す方法です。プレゼンターでは beforeRender()render<View>() メソッドがこれに向いています。

protected function beforeRender(): void
{
	// フィルタの追加
	$this->template->addFilter('money', fn($val) => '$' . number_format($val, 2));

	// 関数の追加
	$this->template->addFunction('isWeekend', fn($date) => $date->format('N') >= 6);
}

テンプレートでは次のようにします。

<p>価格: {$price|money}</p>

{if isWeekend($now)} ... {/if}

もっと複雑なロジックが必要なら、Latte\Engine オブジェクトを直接設定できます。

protected function beforeRender(): void
{
	$latte = $this->template->getLatte();
	$latte->setFeature(Latte\Feature::MigrationWarnings);
}

アトリビュートを使う

より優雅なのは、フィルタと関数をプレゼンターやコンポーネントのテンプレートパラメータのクラスのメソッドとして定義し、アトリビュートで印を付ける方法です。

class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template
{
	#[Latte\Attributes\TemplateFilter]
	public function money(float $val): string
	{
		return '$' . number_format($val, 2);
	}

	#[Latte\Attributes\TemplateFunction]
	public function isWeekend(DateTimeInterface $date): bool
	{
		return $date->format('N') >= 6;
	}
}

Latte はこれらのアトリビュートが付いたメソッドを自動的に見つけて登録します。テンプレートでのフィルタや関数の名前はメソッド名と同じです。これらのメソッドは public でなければなりません。

拡張を使って全体に

ここまでの方法は、特定のプレゼンターやコンポーネントでだけ必要なフィルタや関数に向いていて、アプリケーション全体には向きません。アプリケーション全体には拡張を作るのが最適です。このクラスが、プロジェクトのすべての Latte の拡張をひとつにまとめます。短い例を挙げます。

namespace App\Presentation\Accessory;

final class LatteExtension extends Latte\Extension
{
	public function __construct(
		private App\Model\Facade $facade,
		private Nette\Security\User $user,
		// ...
	) {
	}

	public function getFilters(): array
	{
		return [
			'timeAgoInWords' => $this->filterTimeAgoInWords(...),
			'money' => $this->filterMoney(...),
			// ...
		];
	}

	public function getFunctions(): array
	{
		return [
			'canEditArticle' =>
				fn($article) => $this->facade->canEditArticle($article, $this->user->getId()),
			// ...
		];
	}

	private function filterTimeAgoInWords(DateTimeInterface $time): string
	{
		// ...
	}

	// ...
}

拡張は設定で登録します。

latte:
	extensions:
		- App\Presentation\Accessory\LatteExtension

拡張にはいくつもの利点があります。依存性注入のサポート、アプリケーションのモデル層へのアクセス、そしてすべての拡張を一元的に管理できることです。独自のタグ、プロバイダ、コンパイラパスなどにも対応しています。

すべてのテンプレートの設定

すべてのテンプレートを作る TemplateFactory サービスには、コールバックの公開配列 $onCreate があります。テンプレートが作られるたびに呼ばれるので、アプリケーションのすべてのテンプレートに対するフィルタ、関数、変数を 1 か所から設定できます。各コールバックは、新しく作られたテンプレートを受け取ります。TemplateFactory サービスを注入してもらい、たとえばアプリケーションの起動時にコールバックを登録します。

$templateFactory->onCreate[] = function (Nette\Bridges\ApplicationLatte\Template $template): void {
	$template->addFilter('money', fn($val) => '$' . number_format($val, 2));
};

翻訳

多言語のアプリケーションを作っているなら、テンプレートのテキストを言語ごとに出し分ける必要が出てくるでしょう。そのために Nette Framework は翻訳のインターフェース Nette\Localization\Translatorを定義していて、translate() というメソッドをひとつだけ持ちます。メッセージ $message(ふつうは文字列です)とそのほかのパラメータを受け取ります。役目は翻訳された文字列を返すことです。Nette には既定の実装がありません。Componetteにある既製の解決策から、必要に合うものを選べます。トランスレーターの設定方法は、それぞれのドキュメントで説明されています。

テンプレートには、注入してもらったトランスレーターを setTranslator() メソッドで設定できます。

protected function beforeRender(): void
{
	// ...
	$this->template->setTranslator($translator);
}

あるいはトランスレーターを設定で指定することもできます。

latte:
	extensions:
		- Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)

そうすればトランスレーターを、たとえばフィルタ |translate として使えます。translate() メソッドに渡される追加のパラメータも書けます(foo, bar を参照)。

<a href="basket">{='カート'|translate}</a>
<span>{$item|translate}</span>
<span>{$item|translate, foo, bar}</span>

あるいはアンダースコアのタグとして。

<a href="basket">{_'カート'}</a>
<span>{_$item}</span>
<span>{_$item, foo, bar}</span>

テンプレートの一部を翻訳するには、ペアタグ {translate} があります(Latte 2.11 以降。以前は {_} タグが使われていました)。

<a href="order">{translate}注文{/translate}</a>
<a href="order">{translate foo, bar}注文{/translate}</a>

トランスレーターはふつう、テンプレートが描かれる実行時に呼ばれます。ただし Latte のバージョン 3 は、すべての静的なテキストをテンプレートのコンパイル時に翻訳できます。各文字列が一度だけ翻訳され、その結果がコンパイル済みの形に書き込まれるので、性能が上がります。この場合、キャッシュディレクトリには言語ごとに複数のコンパイル済みテンプレートができます。そのためには、第 2 パラメータに言語を指定するだけです。

protected function beforeRender(): void
{
	// ...
	$this->template->setTranslator($translator, $lang);
}

静的なテキストとは、たとえば {_'hello'}{translate}hello{/translate} のことです。{_$foo} のような静的でないテキストは、引き続き実行時に翻訳されます。

バージョン: 4.x