Omawiamy prosty przykład klasy TemplateEngine, która wykorzystuje nie tylko mechanizm przekazywania kontekstu, ale też posiada własną składnię, która jest zamieniana na PHP i przez PHP ewaluowana.
Na początku przekazywanie kontekstu:
class Template {
private $vars = array();
public function assign($key, $value) {
$this->vars[$key] = $value;
}
}
Mamy tutaj do czynienia z kontekstem globalnym, dla wszystkich templates. Brakuje mi w tym przykładzie metody przekazywania kontekstu dla pojedynczych renderów:
class TemplateEngine
{
private array $globalTemplateData = [];
//(...)
public function render(string $template, array $data = [])
{
extract($data, EXTR_SKIP);
extract($this->globalTemplateData, EXTR_SKIP);
ob_start();
include $this->resolve($template);
$output = ob_get_contents();
ob_end_clean();
return $output;
}
public function resolve(string $path)
{
return "{$this->basePath}/{$path}";
}
public function addGlobal(string $key, mixed $value)
{
$this->globalTemplateData[$key] = $value;
}
}
Jak widać inny przykład powyżej zawiera zarówno dane globalne jak i lokalne. Są one wypakowywane za pomocą extract, zatem odnosimy się do nich po ich nazwie, nie po $this.
W tym przykładzie jednak nie mamy mechanizmu stosowania własnej składni, zamienianej do PHP i dopiero potem poddawanej ewaluacji.
Jak to osiągnąć? Po pierwsze odczytać zawartość pliku templatkowego:
class Template {
private $vars = array();
public function assign($key, $value) {
$this->vars[$key] = $value;
}
public function render($template_name) {
$path = $template_name . '.html';
if (file_exists($path)) {
$contents = file_get_contents($path);
//(...)
eval(' ?>' . $contents . '<?php ');
} else {
exit('<h1>Template error</h1>');
}
}
}
Kontent poddany jest działaniu eval, które samo sobie otwiera <?php, więc mu trzeba to zamknąć i dopiero ocenić kontent.
Teraz tylko musimy stworzyć mechanizm, który sprawa, że elementy własnej składni zamieniane będą na kody php oplecione we własne otwarcia i zamknięcia.
Dalej mamy fasadę do pracy ze zmiennymi vars:
class Template {
private $vars = array();
public function assign($key, $value) {
$this->vars[$key] = $value;
}
public function render($template_name) {
$path = $template_name . '.html';
if (file_exists($path)) {
$contents = file_get_contents($path);
foreach ($this->vars as $key => $value) {
$contents = preg_replace('/\[' . $key . '\]/', $value, $contents);
//(...)
}
eval(' ?>' . $contents . '<?php ');
} else {
exit('<h1>Template error</h1>');
}
}
}
Swoją drogą, autorowi coś się ewidentnie pomyliło, bo wrzucił do tej pętli zamienianie elementów składni templatkowej na składnię PHP:
//(...)
foreach ($this->vars as $key => $value) {
$contents = preg_replace('/\[' . $key . '\]/', $value, $contents);
$contents = preg_replace('/\<\!\-\- if (.*) \-\-\>/', '<?php if ($1) : ?>', $contents);
$contents = preg_replace('/\<\!\-\- else \-\-\>/', '<?php else : ?>', $contents);
$contents = preg_replace('/\<\!\-\- endif \-\-\>/', '<?php endif; ?>', $contents);
}
//(...)
Problem polega na tym, że vars może być puste zaś pętla przechodząca przez vars nie ma nic do zamiany składni templatkowej na PHP.
Tutaj albo te podmianki powinny być poza pętlą, albo w ogóle powinna być tablica ze wzorami REGEX i podmiankami i po niech powinna przechodzić pętla.
Tym niemniej – kierunek dobry.