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.