Huymada icon

Template

Huymada | PRO | 03/13/26 03:49:18 PM UTC (Edited) | 0 ⭐ | 340 👁️ | Never ⏰ | []
PHP |

5.95 KB

|

None

|

0 👍

/

0 👎

<?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