Sistema básico de templates em PHP

Como em qualquer site dinâmico, existe o desenho e a disposição de todos os elementos que o compõem e o conteúdo que irá preencher o site com algo de útil para os utilizadores que o visitam. Mas algo que muitos programadores se esquecem quando desenvolvem um site dinâmico, é separação do código entre ambos. Com este artigo, pretendo mostrar como podemos desenvolver uma pequena classe que nos irá permitir separar de forma simples e básica o conteúdo de todos os outros elementos do site. Desenvolvendo assim, o código torna-se muito mais atraente e bem estruturado. Tudo isto recorrendo à linguagem de programação PHP.

Não faz parte deste artigo explicar detalhadamente cada linha e/ou acção apresentada no código, mas sim mostrar ao leitor como construir um simples e básico sistema de templates para os seus sites dinâmicos. Dito isto, espera-se que o leitor já possua conhecimentos básicos/moderados de PHP, nomeadamente em classes. No entanto, grande parte do código encontra-se comentado; apesar de ser em inglês, penso que poderá servir de ajuda a alguém.

Uma pequena nota antes de começarmos. Este tutorial é baseado num que vi há muitos anos atrás e, devido a esse distanciamento temporal, não posso precisar o seu autor nem facultar link para o artigo original. No entanto, aquilo que permanece do original é pouco mais que a ideia geral do artigo, já que o código sofreu bastantes modificações ao longo dos anos derivadas de todo o uso  que lhe dei para satisfazer as minhas necessidades que, penso, irão satisfazer também as vossas. De qualquer forma, o principio deste Sistema Básico de Templates mantém-se o mesmo e os créditos vão para o seu autor original que lamento não poder precisar.

Vamos então começar…

Estrutura da class

O nosso Sistema Básico de Templates (nome que decidi atribuir a esta classe) vai ser composto por apenas três funções e três variáveis comuns por toda a classe. Mais à frente irei explicar a utilidade de cada uma das funções e de cada uma das variáveis; para já, deixo o seguinte código que irá representar o “esqueleto” da nossa classe:

class BasicTemplateSystem {
   var $template_html = array();
   var $template_vars = array();
   var $template_path;

   function TemplateLoad($template) {

   }
   
   function TemplateDefine($references) {

   }

   function TemplateExport($template,
                           $getonly= false) {

   }
}

Começando pelo mais fácil, a variável $template_path irá apenas conter a localização no sistema operativo onde residem os ficheiros que fazem parte do nosso template. Certifique-se que o caminho aponta correctamente para a localização dos ficheiros ou o Sistema Básico de Templates não irá funcionar correctamente. Seguidamente, temos as variáveis $template_html e $template_vars. A primeira vai armazenar, sem quaisquer alterações, todo o código HTML que a função TemplateLoad() irá posteriormente obter dos mais diversos ficheiros. Já a segunda, irá armazenar as referências por nós definidas e o conteúdo pelo qual essas referências devem ser substituidas no código HTML armazenado na primeira variável.

Passando agora para as funções, a TemplateLoad(), como o próprio nome indica, serve para carregar os nossos templates. Esta função recebe apenas um parâmetro que indica o nome de cada ficheiro a carregar, bem como o nome pelo qual desejamos chamar o template. A função está construída de forma a que seja indiferente a passagem de apenas um ou de vários templates a carregar.

No caso da função TemplateDefine(), o nome já não significa tanto como na função anterior, mas isso não quer dizer nada porque é uma função ainda mais simples. A finalidade desta função é definir várias referências que irão existir nos nossos templates, bem como o conteúdo que irá substituir essas referências. Mas antes que estas referências sejam definidas, a função irá limpar todas as referências anteriormente criadas; as consequências desta acção irão ser explicadas posteriormente.

Por último, a função TemplateExport(), serve para processar todos os dados recolhidos nas funções anteriores. Ou seja, usando as referências lidas com o uso da função TemplateDefine(), procura por essas mesmas referências no código HTML carregado na função TemplateLoad() e substitui-as pelo conteúdo também obtido através da função TemplateDefine(). O código HTML processado com novo conteúdo irá ser devolvido pela função TemplateExport() após a substituição das referências pelo respectivo conteúdo.

No entanto, e como é bem possível que determinado template não necessite de ter alguma referência a ser substituída por conteúdo, esta função possui 2 parâmetros. O primeiro diz-nos qual o template que desejamos processar e o segundo, caso seja Verdadeiro—pois o seu valor predefinido é Falso—indica que apenas queremos que o código HTML seja devolvido sem necessitarmos de procurar por referências a substituir por conteúdo.

Depois desta explicação básica das funcionalidades de cada função e de cada variável, segue-se a listagem de todo o código que compõe a classe, juntamente com alguns comentários ao mesmo:

<?php

class BasicTemplateSystem {
   var $template_html = array();
   var $template_vars = array();
   var $template_path;
   //---------------------------
   // Load template html files into variables
   //---------------------------
   function TemplateLoad($template) {
      // Goes through every template and loads it
      foreach ($template as $index=> $value) {
         // Just load the template or check if we should load it?
         if (is_array($value)) {
            foreach ($value as $file => $load) {
               // Should we load this template?
               if ($load) {
                  $this->template_html[$index] = file_get_contents($this->template_path.$file);
               }
            }
         } else {
            $this->template_html[$index] = file_get_contents($this->template_path.$value);
         }
      }
   }

   //------------------------------------
   // Define template content for some references
   //------------------------------------
   function TemplateDefine($references) {
       // Clears the current references and their content
       $this->template_vars = array();
       // Saves the content with it's reference associated
       foreach ($references as $reference => $content) {
          $this->template_vars['{'.$reference.'}'] = $content;
       }
   }

   //------------------------------------
   // Replace references in template with saved content
   //------------------------------------
   function TemplateExport($template,
                           $getonly = false) {
      // Should we parse the template references or just get the HTML?
      if (!$getonly) {
         // Split the HTML code into lines
         $html_code = $this->template_html[$template];
         $html_code = explode("\n",$html_code);
         $line_count = count($html_code);
         // Replace only the lines needed
         for ($i = 0; $i <$line_count; $i++) {
            foreach ($this->template_vars as $reference =>$content) {
               if(strstr($html_code[$i],$reference)) {
                  $html_code[$i] = str_replace($reference, $content, $html_code[$i]);
               }
            }
         }
         // Join the HTML code lines and return the final code
         $html_code = implode("\n",$html_code);
      } else {
         $html_code = $this->template_html[$template];
      }
      // Return our template HTML code
      return $html_code;
   }
}
?>

O próximo passo será copiar todo este código para um ficheiro template.php.

Conteúdo com referências

Presumindo que efectuou o passo anterior, gravando o código para o ficheiro template.php, peço agora que guarde o seguinte código HTML num ficheiro com o nome exemplo.html. Note que o código que se segue não é XHTML válido pela W3C:

<html>
   <head>
      <title>{webpage.title}</title>
   </head>
   <body bgcolor="{background.color}">
      <span style="color:{spantext.color}">Sistema Básico de Templates</span>
   </body>
</html>

Como podem facilmente reparar, esta simples página não possui um verdadeiro título, uma cor de fundo e uma cor de texto correctas. No seu lugar, usamos várias referências que identificam o que lá deveria estar. As referências podem ser qualquer tipo de texto, com ou sem pontos no meio das palavras como no meu exemplo, todas em letras minúsculas ou maiúsculas, tanto faz. Apenas existe uma regra que deve ser cumprida, cada referência tem de começar por { e acabar com }.

NOTA: É possível ainda repetir quantas vezes quiser a mesma referência, mas todas as ocorrências dessa referência irão ser substituídas pelo mesmo conteúdo, não sendo possível ter duas (ou mais) referências com o mesmo nome e substituí-las por conteúdos diferentes.

Utilização da class

Como em qualquer outra aplicação web, o primeira passo a fazer é incluir o código da classe. Seguidamente, e usando a cláusula New, criamos um novo objecto que irá englobar todas as variáveis e funções que a classe possui. Exemplo:

include('template.php');
$BTS = New BasicTemplateSystem();

Para usarmos correctamente este nosso Sistema Básico de Templates, o primeiro passo a dar é carregar todos (ou grande parte) dos templates que o nosso código irá usar.

A função TemplateLoad() pode ser chamada quantas vezes for necessária para carregar um ou vários templates em várias partes do código consoante as nossas necessidades, sendo ainda possível carregar mais de um template na mesma instrução. Esta função recebe um vector como parâmetro que poderá ter um ou mais elementos; este vector possui ainda uma pequena particularidade, é um vector multidimensional. A sua chave irá identificar o template carregado para mais tarde ser processado e o valor associado à chave é o nome do ficheiro que será carregado. Exemplo:

$BTS->TemplateLoad(array('exp' => "exemplo.html"));

NOTA: Se usar a mesma chave para identificar vários templates, tenha em atenção que só o último template a ser carregado irá ser identificado por essa chave. Todos os outros templates carregados anteriormente irão ser destruídos.

A função TemplateLoad() possui ainda uma outra funcionalidade, a possibilidade de carregar ou não um template segundo o valor de um outro argumento. Se desejar usar esta funcionalidade para algum template terá de substituir o nome do template a carregar por um outro vector multidimensional com apenas um elemento, onde a chave deste novo vector deverá ser o nome do template a carregar e o valor correspondente—um valor booleano que irá indicar se o template deve ou não ser carregado. Exemplo:

$BTS->TemplateLoad(array(
   'abc' => array('abc.html', true),
   'exp' => "exemplo.html",
   'new' => array('new.html', false)
));

O código acima apenas vai carregar os templates abc.html e exemplo.html, ignorando o new.html, pois o valor booleano que indica se devemos ou não carregar o template é Falso. Este exemplo demonstra ainda como seria possível carregar mais de um template na mesma instrução.

Agora que temos os nossos templates carregados, está na altura de decidirmos quais as referências que queremos ver substituídas por determinado conteúdo. Seguindo uma sintaxe parecida com a anterior na função TemplateLoad(), a função TemplateDefine() é invocada da seguinte forma:

$BTS­->TemplateDefine(array(
 'webpage.title' => "Título da Minha Página",
 'background.color' => "#660000",
 'spantext.color' => "#FFFFFF")
);

Como se vê facilmente, esta função atribui determinado conteúdo—neste caso, apenas o título da página e duas cores, uma para o fundo e outro para o texto—às respectivas referências. Esta função não faz ainda qualquer tipo de substituição das referências  pelo conteúdo, apenas define que referências irão ser substituídas por qual conteúdo. O parâmetro passado para a função TemplateDefine() é também um vector multidimensional e continua a ser possível passar apenas um elemento ou vários.

Como em qualquer função existente nesta classe, é possível chamar a função TemplateDefine() mais que uma vez e com os elementos que quisermos. No entanto e, como foi dito na secção Estrutura da Class no início do artigo, esta acção tem consequências; isto significa que de cada vez que a função é invocada, as referências anteriores irão ser destruídas. Ou seja, as referências só serão realmente substituídas quando invocarmos a função TemplateExport() e se tivéssemos atribuído o nosso conteúdo às referências assim:

$BTS->TemplateDefine(array(
   'webpage.title' => "Título da Minha Página"
));
$BTS->TemplateDefine(array(
   'background.color' => "#660000",
   'spantext.color' => "#FFFFFF"
));

Quando a função TemplateExport() fosse invocada, apenas as referências {background.color} e {spantext.color} seriam substituídas pelo conteúdo a elas atribuído. A referência {webpage.title} e o seu conteúdo deixam de existir quando invocamos pela segunda vez a função TemplateDefine().

Durante o meu uso deste meu Sistema Básico de Templates, deparei-me com o facto de que muitas das referências que estavam definidas ao longo de todo o código e com conteúdo atribuído apenas foram utilizadas uma única vez no início do código e, dessa forma, estavam a desperdiçar recursos do sistema e a guardar conteúdo desnecessário, pois nunca mais o iria utilizar. Mas não se preocupem que esta medida apenas está aqui para vos ajudar a manter as vossas aplicações optimizadas e em nada irá dificultar o processo de utilização da classe.

Já carregámos os templates necessários e já definimos as referências e o conteúdo pelo qual desejamos que essas referências sejam substituídas. Só nos resta fazer a substituição das referências pelo conteúdo e ver o resultado. É exactamente isso que a função TemplateExport() faz e esta é invocada da seguinte forma:

echo $BTS->TemplateExport('exp');

Esta função passa um argumento e devolve outro, daí o echo antes da invocação da função. O echo poderia ter sido facilmente substituído por uma variável seguido de um =, permitindo assim armazenar o nosso template com as referências substituídas pelo respectivo conteúdo para posteriormente imprimirmos o resultado no ecrã. O único parâmetro passado à função é a chave que usamos para identificar o template carregado na função TemplateLoad(). Ou seja, este parâmetro indica qual o template que vamos usar para procurar por uma ou várias ocorrências das últimas referências definidas e substituí-las pelo respectivo conteúdo. Após a substituição de tudo o que for encontrado, a função TemplateExport() devolve o template HTML processado.

Posto isto, o código final do nosso exemplo seria o seguinte:

<?php

include('template.php');

$BTS = New BasicTemplateSystem();

$BTS->TemplateLoad(array(
   'exp' =>"exemplo.html"
));

$BTS->TemplateDefine(array(
   'webpage.title' => "Título da Minha Página",
   'background.color' => "#660000",
   'spantext.color' => "#FFFFFF"
));

echo $BTS->TemplateExport('exp');
?>

Copiem o código e guardem num ficheiro, por exemplo, bts.php, juntamente com os ficheiros anteriores, template.php e exemplo.html, num servidor HTTP com suporte para PHP e executem-no para testar esta simples classe para gerir um Sistema Básico de Templates.

Exemplo mais avançado

Para finalizar este artigo, apresento-vos um exemplo ligeiramente mais avançado de como usar esta classe mas nada de muito complicado (porque estou sem ideias práticas):

index.html

<html>
   <head>
      <title>Exemplo + Avançado</title>
   </head>
   <body>
      <ul>{list.items}</ul>
   </body>
</html>

li.html

<li>{item.name}</li>

teste.php

<?php

include('template.php');

$BTS = New BasicTemplateSystem();

$BTS->TemplateLoad(array(
   'idx' => "idx.html",
   'li' => "li.html"
));

for($i = 0; $i < 5; $i++) {
   $BTS->TemplateDefine(array(
      'item.name' => "Numero".rand()
   ));
   if($i == 0)
      $items_list = "{$BTS->TemplateExport('li')}\n";
   elseif($i < 4)
       $items_list .="{$BTS->TemplateExport('li')}\n";
   else
      $items_list .= $BTS->TemplateExport('li');
}

$BTS->TemplateDefine(array(
   'list.items' => $items_list
));

echo $BTS->TemplateExport('idx');
?>

Guardem o conteúdo de cada um destes blocos de código num ficheiro individual utilizando o nome indicado por cima de cada bloco juntamente com o ficheiro template.php e executem o ficheiro teste.php para ver o resultado final.

Conclusão

Esta classe não pretende ser algo tão poderoso como o Smarty mas faz o trabalho para o qual foi designado de forma simples e eficaz. No entanto, com o uso desta, o código das vossas aplicações web vai ficar muito mais limpo e eficaz.  Apenas a vossa imaginação e capacidades na programação em PHP irá limitar o que podem fazer, além de terem sempre a possibilidade de modificar a classe e adaptá-la às vossas necessidades.

Como em qualquer programa, existem falhas e/ou inconvenientes que mais tarde ou mais cedo poderão descobrir, conforme o uso que derem à classe, que talvez venham (ou não) a ser resolvidos numa futura versão.

Desde já agradeço a vossa leitura até ao fim e, sinceramente, espero que esta classe vos seja tão útil como a mim foi, pois já não consigo fazer uma aplicação web sem ela.