ローダー

ローダーは、Latte がテンプレートのソースコードを取得するために使うしくみです。テンプレートはたいていディスク上のファイルですが、Latte の柔軟なローダーのしくみを使えば、事実上どこからでも読み込めますし、動的に生成することさえできます。

ローダーとは何か

テンプレートを扱うとき、ふつうはプロジェクトのディレクトリ構造にある .latte ファイルを思い浮かべます。これは Latte の既定の FileLoader が担当しています。しかし、テンプレート名('main.latte''components/card.latte' など)と実際のソースコードの結びつきは、必ずしもファイルパスへの直接の対応でなければならないわけではありません。

そこで登場するのがローダーです。ローダーは、テンプレート名(識別子となる文字列)を受け取り、そのソースコードを Latte に渡す役割を持つオブジェクトです。Latte はこの仕事を完全に設定されたローダーに任せます。これは $latte->render('main.latte') で要求された最初のテンプレートだけでなく、{include ...}{layout ...}{embed ...}{import ...} といったタグでその中から参照されるすべてのテンプレートにも当てはまります。

なぜカスタムローダーを使うのでしょうか。

  • 別の場所からの読み込み: データベース、キャッシュ(Redis や Memcached など)、バージョン管理システム(特定のコミットをもとにした Git など)に置かれたテンプレートを取得したり、動的に生成したりする場合。
  • 独自の命名規則の実装: テンプレートに短い別名を使いたい場合や、特定の探索ロジック(まずテーマのディレクトリを見て、なければ既定のディレクトリにフォールバックするなど)を実装したい場合。
  • セキュリティやアクセス制御の追加: カスタムローダーは、特定のテンプレートを読み込む前にユーザーの権限を確認できます。
  • 前処理: 一般にはおすすめしませんが(コンパイラパスのほうが適しています)、ローダーは理論上、テンプレートの内容を Latte に渡す前に前処理できます

Latte\Engine インスタンスのローダーは setLoader() メソッドで設定します。

$latte = new Latte\Engine;

// '/path/to/templates' 内のファイルに既定の FileLoader を使う
$loader = new Latte\Loaders\FileLoader('/path/to/templates');
$latte->setLoader($loader);

ローダーは Latte\Loader インターフェースを実装しなければなりません。

組み込みのローダー

Latte にはいくつかの標準ローダーが用意されています。

FileLoader

これは、ほかに指定がない場合に Latte\Engine クラスが使う既定のローダーです。ファイルシステムから直接テンプレートを読み込みます。

アクセスを制限するために、ルートディレクトリを設定することもできます。

use Latte\Loaders\FileLoader;

// 以下では /var/www/html/templates ディレクトリのテンプレートだけを読み込めます
$loader = new FileLoader('/var/www/html/templates');
$latte->setLoader($loader);

// $latte->render('../../../etc/passwd'); // これは例外を投げます

// /var/www/html/templates/pages/contact.latte にあるテンプレートのレンダリング
$latte->render('pages/contact.latte');

{include}{layout} のようなタグを使うとき、絶対パスが指定されていなければ、テンプレート名は現在のテンプレートからの相対で解決されます。ただしルートディレクトリが設定されている場合、すべての名前が現在のテンプレートからの相対で解決されます。

StringLoader

このローダーは、キーがテンプレート名(識別子)、値がテンプレートのソースコード文字列である連想配列から内容を取得します。テストや、テンプレートを PHP コードの中に直接置くような小さなアプリケーションで特に便利です。

use Latte\Loaders\StringLoader;

$loader = new StringLoader([
	'main.latte' => 'Hello {$name}, include is below:{include helper.latte}',
	'helper.latte' => '{var $x = 10}Included content: {$x}',
	// 必要に応じてテンプレートを追加します
]);

$latte->setLoader($loader);

$latte->render('main.latte', ['name' => 'World']);
// 出力: Hello World, include is below:Included content: 10

ほかの名前付き文字列テンプレートを参照するインクルードや継承が不要で、文字列から単一のテンプレートを直接レンダリングしたいだけなら、配列なしの StringLoader を使い、render()renderToString() メソッドに文字列をそのまま渡せます。

$loader = new StringLoader;
$latte->setLoader($loader);

$templateString = 'Hello {$name}!';
$output = $latte->renderToString($templateString, ['name' => 'Alice']);
// $output には 'Hello Alice!' が入ります

カスタムローダーの作成

独自のローダー(データベース、キャッシュ、バージョン管理システムなど別の場所からテンプレートを読み込むもの)を作るには、Latte\Loader インターフェースを実装したクラスを作ります。

各メソッドが何をしなければならないのか見ていきましょう。

getContent (string $name)string

これはローダーの中心となるメソッドです。$name$latte->render() メソッドに渡された名前、または getReferredName() メソッドが返した名前)で識別されるテンプレートの完全なソースコードを取得して返すのが役目です。

テンプレートが見つからない、またはアクセスできない場合、このメソッドは Latte\TemplateNotFoundException を投げなければなりません

public function getContent(string $name): string
{
	// 例: 架空の内部ストレージからの読み込み
	$content = $this->storage->read($name);
	if ($content === null) {
		throw new Latte\TemplateNotFoundException("Template '$name' cannot be loaded.");
	}
	return $content;
}

getReferredName (string $name, string $referringName)string

このメソッドは、{include}{layout} などのタグで使われるテンプレート名の解決を担当します。たとえば main.latte の中で {include 'partial.latte'} に出会うと、Latte は $name = 'partial.latte'$referringName = 'main.latte' としてこのメソッドを呼びます。

このメソッドの仕事は、$referringName が与える文脈をもとに、$name を正規の識別子(絶対パスや一意なデータベースキーなど)に解決することです。その識別子がほかのローダーメソッドの呼び出しに使われます。

public function getReferredName(string $name, string $referringName): string
{
	return ...;
}

getUniqueId (string $name)string

Latte は性能向上のためにコンパイル済みテンプレートのキャッシュを使います。コンパイルされた各テンプレートファイルには、元のテンプレートの識別子から導かれる一意な名前が必要です。このメソッドは、テンプレート $name一意に識別する文字列を返します。

ファイルベースのテンプレートなら絶対パスがその役目を果たせます。データベース内のテンプレートなら、接頭辞とデータベース ID の組み合わせがよく使われます。

public function getUniqueId(string $name): string
{
	return ...;
}

例: シンプルなデータベースローダー

この例は、name(一意な識別子)、contentupdated_at の各カラムを持つ templates というデータベーステーブルに保存されたテンプレートを読み込むローダーの基本構造を示します。

use Latte;

class DatabaseLoader implements Latte\Loader
{
	public function __construct(
		private \PDO $db,
	) {
	}

	public function getContent(string $name): string
	{
		$stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?');
		$stmt->execute([$name]);
		$content = $stmt->fetchColumn();
		if ($content === false) {
			throw new Latte\TemplateNotFoundException("Template '$name' not found in database.");
		}
		return $content;
	}

	// この単純な例では、テンプレート名('homepage'、'article' など)が
	// 一意な ID であり、テンプレートどうしが相対的に参照し合わないと仮定しています。
	public function getReferredName(string $name, string $referringName): string
	{
		return $name;
	}

	public function getUniqueId(string $name): string
	{
		// ここでは接頭辞と名前そのもので一意になり、それで十分です
		return 'db_' . $name;
	}
}

// 使い方:
$pdo = new \PDO(/* connection details */);
$loader = new DatabaseLoader($pdo);
$latte->setLoader($loader);
$latte->render('homepage'); // DB から 'homepage' という名前のテンプレートを読み込みます

カスタムローダーを使うと、Latte のテンプレートがどこから来るのかを完全に制御でき、さまざまなストレージシステムやワークフローと統合できます。

バージョン: 3.x