Nasz TemplateEngine jest połączeniem podejścia znanego nam z projektu phpiggy (polecam repozytorium autora) oraz podejścia polegającego na stosowaniu własnej składni, która jest zamieniana na PHP i poddawana działaniu eval.
Na chwilę obecną klasa wygląda tak:
<?php
class TemplateEngine
{
private array $globalTemplateData = [];
protected static $patterns = [
//(...)
];
public function __construct(private string $basePath)
{
}
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 renderTemplate(string $template, array $data = []){
extract($data, EXTR_SKIP);
extract($this->globalTemplateData, EXTR_SKIP);
$content = file_get_contents($this->resolve($template));
$content = static::searchAndReplace($content);
eval( '?>' . $content );
}
public function resolve(string $path)
{
return "{$this->basePath}/{$path}";
}
public function addGlobal(string $key, mixed $value)
{
$this->globalTemplateData[$key] = $value;
}
public static function searchAndReplace($source){
foreach(self::$patterns as $pattern ){
$source = preg_replace($pattern['pattern'], $pattern['replace'], $source);
}
return $source;
}
}
$template = new TemplateEngine(__DIR__);
$template->addGlobal('site_name', 'MyApp');
echo $template->renderTemplate('sth3.php', ['name' => 'John']);
Chcemy usprawnić działanie tego TemplateEngine i wyrzucić patterns do innego pliku. Poznamy ciekawą sztuczkę:
<?php
return [
[
"pattern" => '/\{\{\s*(\$.*?)\s*\}\}/',
"replace" => "<?php echo $1; ?>"
],
[
"pattern" => '/\[b\](.*?)\[\/b\]/s',
"replace" => "<b>$1</b>"
],
[
"pattern" => '/@if\((.*)\)/',
"replace" => "<?php if ($1) : ?>"
],
[
"pattern" => '/@else/',
"replace" => "<?php else : ?>"
],
[
"pattern" => '/@endif/s',
"replace" => "<?php endif; ?>"
],
[
"pattern" => '/@isset\((.*)\)/',
"replace" => "<?php if(isset($1)) : ?>"
],
[
"pattern" => '/@empty/',
"replace" => "<?php else : ?>"
],
];
Jak stare moduły w JavaScripcie. Top-level return w pliku PHP – kto powiedział, że nie można?
Teraz trzeba tego użyć:
class TemplateEngine
{
private array $globalTemplateData = [];
public function __construct(private string $basePath)
{
$this->patterns = include __DIR__ . "/templatepatterns.php";
}
//(...)
}
Ok, teraz patterns nie są statyczne, więc:
class TemplateEngine
{
//(...)
public function searchAndReplace($source){
foreach($this->patterns as $pattern ){
$source = preg_replace($pattern['pattern'], $pattern['replace'], $source);
}
return $source;
}
}
Metoda też być statyczna przestała, zatem musimy poprawić jej wywołanie ze static:: na $this->:
class TemplateEngine
{
//(...)
public function renderTemplate(string $template, array $data = []){
extract($data, EXTR_SKIP);
extract($this->globalTemplateData, EXTR_SKIP);
$content = file_get_contents($this->resolve($template));
$content = $this->searchAndReplace($content);
eval( '?>' . $content );
}
//(...)
}
Korzystanie z klasy bez zmian:
$template = new TemplateEngine(__DIR__);
$template->addGlobal('site_name', 'MyApp');
echo $template->renderTemplate('sth3.php', ['name' => 'John']);
Teraz nasza klasa jest mega „gibka”, możemy w dodatku to jeszcze podkręcić:
class TemplateEngine
{
private array $globalTemplateData = [];
public function __construct(private string $basePath, $templatePatterns)
{
$this->patterns = include $basePath . "/$templatePatterns";
}
//(...)
}
$template = new TemplateEngine(__DIR__, 'templatepatterns.php');
$template->addGlobal('site_name', 'MyApp');
echo $template->renderTemplate('sth3.php', ['name' => 'John']);
Tym sposobem możemy mieć różne enginy do różnych rzeczy (np. BBcodes, templatki w takiej czy innej notacji). Wszystko „loosely coupled” jak to się mówi.