<?php
/**
* Gestor de Templates
* @version 2.0
*/
class Template {
/**
* Diretório base dos templates
*/
private string $dir;
/**
* Extensão dos arquivos de template
*/
private string $ext;
/**
* Cache em memória: ['nome' => 'conteúdo raw']
*/
private array $cache = [];
// -------------------------------------------------------------------------
/**
* @param string $dir Diretório dos templates (padrão: 'template/')
* @param string $ext Extensão dos arquivos (padrão: '.tpl')
*
* @throws RuntimeException se o diretório não existir
*/
public function __construct(string $dir = 'template', string $ext = '.tpl') {
if (!is_dir($dir)) {
throw new RuntimeException(
"Template: diretório não encontrado — \"{$dir}\""
);
}
$this->dir = rtrim($dir, '/\\');
$this->ext = '.' . ltrim($ext, '.');
}
// -------------------------------------------------------------------------
/**
* Renderiza um template e retorna o HTML como string
*
* @param string $name Nome do template (sem extensão)
* @param array $data Variáveis para substituir no template
* @param bool $minify Minifica o resultado
*
* @throws RuntimeException se o arquivo não existir
* @throws InvalidArgumentException se $data não for array
*
* @example
* $tpl = new Template();
* echo $tpl->render('home', ['titulo' => 'Olá Mundo']);
*/
public function render(string $name, array $data = [], bool $minify = false): string {
$raw = $this->load($name);
$output = $this->replace($raw, $data);
if ($minify) {
$output = $this->minify($output);
}
return $output;
}
/**
* Renderiza e envia direto para o output com o Content-Type correto
*
* @param string $name
* @param array $data
* @param bool $minify
* @param string $contentType Ex: 'text/html; charset=UTF-8'
*
* @example
* $tpl->output('home', ['titulo' => 'Olá'], minify: true);
* $tpl->output('style', [], contentType: 'text/css');
*/
public function output(
string $name,
array $data = [],
bool $minify = false,
string $contentType = 'text/html; charset=UTF-8'
): void {
if (headers_sent($file, $line)) {
throw new RuntimeException(
"Template: headers já enviados em {$file}:{$line}"
);
}
header('Content-Type: ' . $contentType);
echo $this->render($name, $data, $minify);
}
// -------------------------------------------------------------------------
// CACHE
// -------------------------------------------------------------------------
/**
* Pré-carrega um ou mais templates na memória
*
* @example
* $tpl->preload(['header', 'footer', 'home']);
*/
public function preload(array $names): void {
foreach ($names as $name) {
$this->load($name);
}
}
/**
* Limpa o cache em memória
*/
public function clearCache(?string $name = null): void {
if ($name === null) {
$this->cache = [];
} else {
unset($this->cache[$name]);
}
}
// -------------------------------------------------------------------------
// PRIVADOS
// -------------------------------------------------------------------------
/**
* Carrega o template do disco ou do cache em memória
*
* @throws RuntimeException se o arquivo não existir
*/
private function load(string $name): string {
if (isset($this->cache[$name])) {
return $this->cache[$name];
}
$path = $this->dir . DIRECTORY_SEPARATOR . $name . $this->ext;
if (!file_exists($path)) {
throw new RuntimeException(
"Template: arquivo não encontrado — \"{$name}{$this->ext}\""
);
}
$this->cache[$name] = file_get_contents($path);
return $this->cache[$name];
}
/**
* Substitui os placeholders {chave} pelos valores de $data
* Placeholders sem correspondência são mantidos no output
*/
private function replace(string $template, array $data): string {
foreach ($data as $key => $value) {
$template = str_replace('{' . $key . '}', (string) $value, $template);
}
return $template;
}
/**
* Minifica HTML, CSS ou JS de forma segura
*
* - Remove comentários HTML <!-- ... -->
* - Remove comentários CSS /* ... */
* - Colapsa espaços e quebras de linha entre tags HTML
* - NÃO toca em conteúdo dentro de <pre>, <script> ou <style>
* para evitar quebrar código
*/
private function minify(string $content): string {
// Remove comentários HTML (não remove condicionais <!--[if ...]>)
$content = preg_replace('/<!--(?!\[if).*?-->/s', '', $content);
// Remove comentários CSS/JS /* ... */
$content = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $content);
// Colapsa múltiplas quebras de linha e tabs em um único espaço
$content = preg_replace('/\s{2,}/u', ' ', $content);
// Remove espaços entre tags HTML
$content = preg_replace('/>\s+</u', '><', $content);
return trim($content);
}
}
USO
$tpl = new Template('template', '.tpl');
// Retorna string
$html = $tpl->render('home', ['titulo' => 'Olá Mundo']);
// Envia direto com header
$tpl->output('home', ['titulo' => 'Olá'], minify: true);
// CSS com Content-Type correto
$tpl->output('style', [], contentType: 'text/css');
Comments