Melhor maneira de passar variável PHP entre parciais?

16

Eu tenho uma variável no header.php, como:

$page_extra_title = get_post_meta($this_page->ID, "_theme_extra_title", true);

Uma vez que eu faço:

var_dump($page_extra_title);

Eu sempre NULLsaio do header.php (o var_dump funciona corretamente apenas no header.php). Eu tenho colado a mesma variável em todos os lugares que eu preciso (page.php, post.php, footer.php etc.), mas é loucura e torna tudo quase impossível de manter.

Gostaria de saber qual é a melhor maneira de passar uma variável através de todos os arquivos no meu tema? Eu acho que usar o functions.php junto com "get_post_meta" pode não ser a melhor idéia? :)

Wordpressor
fonte
Eu acho que a variável está no mesmo escopo, também quero evitar o uso de GLOBAL por razões óbvias.
Wordpressor
Eu acredito que o comentário de ialocin está no local. Um script PHP não sabe que o outro existe e não pode acessar suas variáveis ​​locais ou seus valores.
Jdm2112
1
cabeçalho e rodapé são incluídos por meio de uma função; portanto, o escopo de tudo nesses arquivos é o escopo dessa função.
Milo
4
Não atire no messenger :) A única coisa que eu disse é que é realmente um problema de escopo. Existe um jeito global, certo? Mas está fora de questão por boas razões. Além de você ter que "chamar" globalvariáveis ​​também, usando a palavra-chave para torná-las disponíveis. Dependendo das sessões de casos de uso, pode ser uma solução. Caso contrário - como mencionado -, acho que uma função ou classe para fazer o trabalho para você é o caminho a percorrer.
Nicolai

Respostas:

10

Estruturas de dados separadas básicas

Para passar dados, você normalmente utiliza um Modelo (esse é o "M" em "MVC"). Vejamos uma interface muito simples para dados. As interfaces são usadas apenas como "Receitas" para nossos blocos de construção:

namespace WeCodeMore\Package\Models;
interface ArgsInterface
{
    public function getID();
    public function getLabel();
}

Acima é o que passamos: Um ID comum e um "Label".

Exibindo dados combinando peças atômicas

Em seguida, precisamos de uma visão que negocie entre nosso modelo e ... nosso modelo.

namespace WeCodeMore\Package;
interface PackageViewInterface
{
    /**
     * @param Models\ArgsInterface $args
     * @return int|void
     */
    public function render( Models\ArgsInterface $args );
}

Basicamente, a interface diz

"Podemos renderizar algo e um modelo é obrigatório para essa tarefa"

Finalmente, precisamos implementar acima e criar a visualização atual . Como você pode ver, o construtor diz que a coisa obrigatória para nossa visão é um modelo e que podemos renderizá-lo. Para facilitar o desenvolvimento, verificamos se o arquivo do modelo está realmente presente, para facilitar a vida de outros desenvolvedores (e também a nossa) muito mais e observamos isso.

Em uma segunda etapa na função render, usamos um Closure para criar o wrapper de modelo real e bindTo()o Model para o modelo.

namespace WeCodeMore\Package;

use WeCodeMore\Package\Models\ArgsInterface;

/** @noinspection PhpInconsistentReturnPointsInspection */
class PackageView implements PackageViewInterface
{
    /** @var string|\WP_Error */
    private $template;
    /**
     * @param string $template
     */
    public function __construct( $template )
    {
        $this->template = ! file_exists( $template )
            ? new \WP_Error( 'wcm-package', 'A package view needs a template' )
            : $template;
    }
    /**
     * @param Models\ArgsInterface $args
     * @return int|void
     */
    public function render( Models\ArgsInterface $args )
    {
        if ( is_wp_error( $this->template ) )
            return print $this->template->get_error_message();

        /** @var $callback \Closure */
        $callback = function( $template )
        {
            extract( get_object_vars( $this ) );
            require $template;
        };
        call_user_func(
            $callback->bindTo( $args ),
            $this->template
        );
    }
}

Separando a vista e a renderização

Isso significa que podemos usar um modelo muito simples como o seguinte

<!--suppress HtmlFormInputWithoutLabel -->
<p><?= $label ?></p>

para renderizar nosso conteúdo. Juntando as peças, obteríamos algo em torno das seguintes linhas (em nosso Controller, Mediador, etc.):

namespace WeCodeMore\Package;

$view = new PackageView( plugin_dir_path( __FILE__ ).'tmpl/label.tmpl.php' );
$view->render( new Models\Args );

O que ganhamos?

Desta forma, podemos

  1. Troque modelos facilmente sem alterar a estrutura de dados
  2. Tenha tempaltes fáceis de ler
  3. Evitar escopo global
  4. Teste de unidade de lata
  5. Pode trocar o modelo / os dados sem prejudicar outros componentes

Combinando OOP PHP com a API WP

Claro que isto é praticamente impossível usando a funcionalidade básica theming como get_header(), get_footer(), etc., certo? Errado. Basta ligar para suas aulas em qualquer modelo ou parte do modelo que você desejar. Renderize, transforme os dados, faça o que quiser. Se você é realmente legal, basta adicionar seu próprio grupo de filtros personalizados e ter um negociador para cuidar do que é renderizado por qual controlador em qual rota / modelo condicional é carregado.

Conclusão?

Você pode trabalhar com coisas como acima no WP sem nenhum problema e ainda manter a API básica e reutilizar códigos e dados sem chamar uma única global ou desarrumar e poluir o espaço de nome global.

kaiser
fonte
3
Parece ótimo! Eu vou olhar mais para isso, boa resposta!
Marko
@kaiser quase 3 anos depois, há alguma atualização em seu pensamento acima? A modelagem de núcleo do WP realmente não avançou em nenhuma direção mais avançada; portanto, as soluções de terceiros ainda são importantes.
precisa saber é o seguinte
1
@Ikraav Eu provavelmente não escreveria assim hoje em dia, mas ainda estou certo de que não usar uma sintaxe separada para gerar o conteúdo de variáveis ​​dentro de tags HTML é o caminho a seguir (e evita uma sobrecarga desnecessária). Por outro lado, raramente escrevo coisas de frontend em PHP atualmente, mas em JavaScript. E eu realmente gosto do que VueJS e amigos estão trazendo para a mesa.
Kaiser
11

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 $thispalavra-chave: isso permite evitar avisos de produção no caso de variáveis ​​indefinidas

A Engineclasse

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.phpou 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.phpmodelo.

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_DEBUGe WP_DEBUG_DISPLAYsã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 Engineclasse.

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 Engineclasse 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.phpdentro 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 truecomo terceiro argumento para partial():

<?php $this->partial('partials/content', array('greeting' => 'Welcome!'), true) ?>

Conclusão

Mesmo se bem simples, a Engineclasse é 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 :

  1. Troque modelos facilmente sem alterar a estrutura de dados
  2. Tenha tempaltes fáceis de ler
  3. Evitar escopo global
  4. Teste de unidade de lata
  5. Pode trocar o modelo / os dados sem prejudicar outros componentes

também são válidos para a minha turma.

gmazzap
fonte
1
Ele Ele. Bem feito, companheiro :) +1
kaiser
@gmazzap quase 3 anos depois, há alguma atualização em seu pensamento acima? A modelagem de núcleo do WP realmente não avançou em nenhuma direção mais avançada; portanto, as soluções de terceiros ainda são importantes.
precisa saber é o seguinte
1
Atualmente, não faço muitos temas trabalharem. Ultimamente, meu caminho a seguir foi combinar o github.com/Brain-WP/Context + github.com/Brain-WP/Hierarchy para criar dados e passar para os modelos. Para o próprio modelo de motor, eu usei diferentes abordagens, Foil (é claro), Bigode, mas também Twig (só quando eu tinha o controle sobre todo o webiste para evitar o inferno dependência) @lkraav
gmazzap
5

Resposta simples, não passe variáveis ​​em lugar algum, pois fede ao uso de variáveis ​​globais que são más.

Do seu exemplo, parece que você está tentando fazer uma otimização precoce, mais um mal;)

Use a API do wordpress para obter dados armazenados no banco de dados e não tente enganar e otimizar seu uso, pois a API faz mais do que apenas recuperar valores e ativar filtros e ações. Ao remover a chamada da API, você remove a capacidade de outros desenvolvedores de alterar o comportamento do seu código sem modificá-lo.

Mark Kaplun
fonte
2

Embora a resposta do kaiser seja tecnicamente correta, duvido que seja a melhor resposta para você.

Se você está criando seu próprio tema, acho que é realmente a melhor maneira de configurar algum tipo de estrutura usando classes (e talvez espaços para nome e interfaces também, embora isso possa ser um pouco demais para um tema WP).

Por outro lado, se você está apenas estendendo / ajustando um tema existente e precisa passar apenas uma ou algumas variáveis, acho que você deve continuar global. Como header.phpestá incluído em uma função, as variáveis ​​que você declara nesse arquivo são utilizáveis ​​apenas nesse arquivo. Com globalvocê torná-los acessíveis em todo o projeto WP:

Em header.php:

global $page_extra_title;

$page_extra_title = get_post_meta($this_page->ID, "_theme_extra_title", true);

Na single.php(por exemplo):

global $page_extra_title;

var_dump( $page_extra_title );
redelschaap
fonte
3
Não quero ser rude nem nada, mas é realmente uma prática ruim mergulhar no escopo global. Você deve evitar adicionar completamente ao escopo global. Você deve ter muito cuidado com as convenções de nomenclatura aqui e precisa garantir que o nome da sua variável seja exclusivo de tal maneira que ninguém mais possa reproduzir esse nome. A abordagem do @kaiser pode parecer exagerada para você, mas é de longe a melhor e a mais segura. Eu não posso dizer-lhe como lidar com isso, mas eu realmente aconselhá-lo a ficar fora do escopo global :-)
Pieter Goosen
3
Claro que você precisa ter cuidado para não sobrescrever outras variáveis. Você pode resolver isso usando um prefixo exclusivo ou uma matriz com suas variáveis ​​personalizadas, $wp_theme_vars_page_extra_titleou $wp_theme_vars['page_extra_title']por exemplo. Era apenas uma explicação do porquê o global funcionaria aqui. O OP pediu uma maneira de passar uma variável por todos os arquivos, usando globalé uma maneira de fazer isso.
precisa saber é o seguinte
2
Não, globals não é uma maneira de fazer isso. Existem maneiras muito melhores de conseguir o mesmo sem usar globals. Como eu disse antes e como a @kaiser afirmou em sua resposta, evite o escopo global e fique fora dele. Apenas como exemplo, pegue essa alternativa muito fácil, envolva seu código em uma função e chame a função quando necessário. Dessa forma, você não precisa definir ou usar um global.
Pieter Goosen
3
Sim, ele é. Pode não ser o melhor caminho, mas é definitivamente um caminho.
precisa saber é o seguinte
2
but it is really bad practice diving into the global scopeEu gostaria que alguém dissesse isso aos desenvolvedores principais do WP. Realmente não entendo o uso de espaços para nome, abstração de dados, padrões de design, teste de unidade e outras práticas / técnicas de programação em código escrito para o Wordpress quando o núcleo do Wordpress está repleto de práticas de codificação ruins, como variáveis ​​glabal (por exemplo, os widgets código).
Ejaz
1

Uma solução fácil é escrever uma função para obter o título extra. Eu uso uma variável estática para manter as chamadas do banco de dados em apenas uma. Coloque isso em seu functions.php.

function get_extra_title($post_id) {
    static $title = null;
    if ($title === null) {
        $title = get_post_meta($post_id, "_theme_extra_title", true)
    }
    return $title;
}

Fora do header.php, chame a função para obter o valor:

var_dump(get_extra_title($post->ID));
pbd
fonte