Esta é uma abordagem alternativa ao @kaiser resposta do , que achei muito boa (+1 de mim), mas requer trabalho adicional para ser usado com as principais funções do WP e, por si só, é pouco integrado à hierarquia de modelos.
A abordagem que quero compartilhar é baseada em uma única classe (é uma versão simplificada de algo em que estou trabalhando) que cuida da renderização de dados para modelos.
Possui alguns recursos interessantes (IMO):
- modelos são arquivos de modelo padrão do WordPress (single.php, page.php), eles têm um pouco mais de poder
- modelos existentes simplesmente funcionam, para que você possa integrar modelos de temas existentes sem nenhum esforço
- Ao contrário da abordagem @kaiser , nos modelos você acessa variáveis usando a
$this
palavra-chave: isso permite evitar avisos de produção no caso de variáveis indefinidas
A Engine
classe
namespace GM\Template;
class Engine
{
private $data;
private $template;
private $debug = false;
/**
* Bootstrap rendering process. Should be called on 'template_redirect'.
*/
public static function init()
{
add_filter('template_include', new static(), 99, 1);
}
/**
* Constructor. Sets debug properties.
*/
public function __construct()
{
$this->debug =
(! defined('WP_DEBUG') || WP_DEBUG)
&& (! defined('WP_DEBUG_DISPLAY') || WP_DEBUG_DISPLAY);
}
/**
* Render a template.
* Data is set via filters (for main template) or passed to method for partials.
* @param string $template template file path
* @param array $data template data
* @param bool $partial is the template a partial?
* @return mixed|void
*/
public function __invoke($template, array $data = array(), $partial = false)
{
if ($partial || $template) {
$this->data = $partial
? $data
: $this->provide(substr(basename($template), 0, -4));
require $template;
$partial or exit;
}
return $template;
}
/**
* Render a partial.
* Partial-specific data can be passed to method.
* @param string $template template file path
* @param array $data template data
* @param bool $isolated when true partial has no access on parent template context
*/
public function partial($partial, array $data = array(), $isolated = false)
{
do_action("get_template_part_{$partial}", $partial, null);
$file = locate_template("{$partial}.php");
if ($file) {
$class = __CLASS__;
$template = new $class();
$template_data = $isolated ? $data : array_merge($this->data, $data);
$template($file, $template_data, true);
} elseif ($this->debug) {
throw new \RuntimeException("{$partial} is not a valid partial.");
}
}
/**
* Used in templates to access data.
* @param string $name
* @return string
*/
public function __get($name)
{
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
if ($this->debug) {
throw new \RuntimeException("{$name} is undefined.");
}
return '';
}
/**
* Provide data to templates using two filters hooks:
* one generic and another query type specific.
* @param string $type Template file name (without extension, e.g. "single")
* @return array
*/
private function provide($type)
{
$generic = apply_filters('gm_template_data', array(), $type);
$specific = apply_filters("gm_template_data_{$type}", array());
return array_merge(
is_array($generic) ? $generic : array(),
is_array($specific) ? $specific : array()
);
}
}
(Disponível como Gist aqui.)
Como usar
A única coisa necessária é chamar o Engine::init()
método, provavelmente no 'template_redirect'
gancho. Isso pode ser feito no tema functions.php
ou em um plugin.
require_once '/path/to/the/file/Engine.php';
add_action('template_redirect', array('GM\Template\Engine', 'init'), 99);
Isso é tudo.
Seus modelos existentes funcionarão conforme o esperado. Mas agora você tem a possibilidade de acessar dados de modelo personalizados.
Dados do modelo personalizado
Para passar dados personalizados para modelos, existem dois filtros:
'gm_template_data'
'gm_template_data_{$type}'
O primeiro é acionado para todos os modelos, o segundo é específico do modelo; na verdade, a parte dinâmica {$type}
é o nome base do arquivo de modelo sem extensão de arquivo.
Por exemplo, o filtro 'gm_template_data_single'
pode ser usado para passar dados para o single.php
modelo.
Os retornos de chamada anexados a esses ganchos precisam retornar uma matriz , onde as chaves são os nomes das variáveis.
Por exemplo, você pode transmitir metadados como os dados do modelo gostam:
add_filter('gm_template_data', function($data) {
if (is_singular()) {
$id = get_queried_object_id();
$data['extra_title'] = get_post_meta($id, "_theme_extra_title", true);
}
return $data;
};
E então, dentro do modelo, você pode simplesmente usar:
<?= $this->extra_title ?>
Modo de depuração
Quando as constantes WP_DEBUG
e WP_DEBUG_DISPLAY
são verdadeiras, a classe funciona no modo de depuração. Isso significa que, se uma variável não for definida, uma exceção será lançada.
Quando a classe não está no modo de depuração (provavelmente em produção), acessar uma variável indefinida produzirá uma sequência vazia.
Modelos de dados
Uma maneira agradável e sustentável de organizar seus dados é usar classes de modelo.
Eles podem ser classes muito simples, que retornam dados usando os mesmos filtros descritos acima. Não existe uma interface específica a seguir, eles podem ser organizados de acordo com a sua preferência.
Abaixo, há apenas um exemplo, mas você é livre para fazer à sua maneira.
class SeoModel
{
public function __invoke(array $data, $type = '')
{
switch ($type) {
case 'front-page':
case 'home':
$data['seo_title'] = 'Welcome to my site';
break;
default:
$data['seo_title'] = wp_title(' - ', false, 'right');
break;
}
return $data;
}
}
add_filter('gm_template_data', new SeoModel(), 10, 2);
O __invoke()
método (que é executado quando uma classe é usada como um retorno de chamada) retorna uma string a ser usada para a <title>
tag do modelo.
Graças ao fato de o segundo argumento passado 'gm_template_data'
ser o nome do modelo, o método retorna um título personalizado para a página inicial.
Tendo o código acima, é possível usar algo como
<title><?= $this->seo_title ?></title>
na <head>
seção da página.
Parciais
O WordPress possui funções como get_header()
ou get_template_part()
que podem ser usadas para carregar parciais no modelo principal.
Essas funções, assim como todas as outras funções do WordPress, podem ser usadas em modelos ao usar a Engine
classe.
O único problema é que, dentro das parciais carregadas usando as principais funções do WordPress, não é possível usar o recurso avançado de obter dados de modelo personalizados $this
.
Por esse motivo, a Engine
classe possui um método partial()
que permite carregar um parcial (de maneira totalmente compatível com o tema filho) e ainda poder usar em parciais os dados do modelo personalizado.
O uso é bem simples.
Supondo que exista um arquivo nomeado partials/content.php
dentro da pasta theme (ou tema filho), ele pode ser incluído usando:
<?php $this->partial('partials/content') ?>
Dentro dessa parcial será possível acessar todos os dados do tema pai da mesma maneira.
Diferentemente das funções do WordPress, o Engine::partial()
método permite passar dados específicos para parciais, simplesmente passando uma matriz de dados como segundo argumento.
<?php $this->partial('partials/content', array('greeting' => 'Welcome!')) ?>
Por padrão, os parciais têm acesso aos dados disponíveis no tema pai e aos dados transmitidos explicitamente.
Se alguma variável passada explicitamente para parcial tiver o mesmo nome de uma variável de tema pai, a variável passada explicitamente vencerá.
No entanto, também é possível incluir uma parcial no modo isolado , ou seja, a parcial não tem acesso aos dados do tema pai. Para fazer isso, basta passar true
como terceiro argumento para partial()
:
<?php $this->partial('partials/content', array('greeting' => 'Welcome!'), true) ?>
Conclusão
Mesmo se bem simples, a Engine
classe é bastante completa, mas certamente pode ser melhorada ainda mais. Por exemplo, não há como verificar se uma variável está definida ou não.
Graças à sua compatibilidade 100% com os recursos do WordPress e a hierarquia de modelos, você pode integrá-lo ao código existente e de terceiros sem problemas.
No entanto, observe que é apenas parcialmente testado, portanto, é possível que haja problemas que ainda não descobri.
Os cinco pontos em "O que ganhamos?" na resposta @kaiser :
- Troque modelos facilmente sem alterar a estrutura de dados
- Tenha tempaltes fáceis de ler
- Evitar escopo global
- Teste de unidade de lata
- Pode trocar o modelo / os dados sem prejudicar outros componentes
também são válidos para a minha turma.