¿La mejor manera de pasar la variable PHP entre parciales?

16

Tengo una variable en header.php, como:

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

Una vez que lo haga:

var_dump($page_extra_title);

Siempre NULLsalgo de header.php (var_dump funciona correctamente solo en header.php). He estado pegando la misma variable en todas partes donde la necesito (page.php, post.php, footer.php, etc.), pero es una locura y hace que todo sea casi imposible de mantener.

Me pregunto cuál es la mejor manera de pasar una variable a través de todos los archivos de mi tema. Supongo que el uso de functions.php junto con "get_post_meta" podría no ser la mejor idea. :)

Wordpressor
fuente
Creo que la variable está en el mismo alcance, también quiero evitar usar GLOBAL por razones obvias.
Wordpressor
Creo que el comentario de ialocin es acertado. Un script PHP no sabe que el otro existe y no puede acceder a sus variables locales ni a sus valores.
jdm2112
1
el encabezado y el pie de página se incluyen a través de una función, por lo que el alcance de todo en esos archivos es el alcance de esas funciones.
Milo
44
No dispare al mensajero :) Lo único que dije es que, de hecho, es un problema de alcance. Hay una manera, es global, ¿verdad? Pero está fuera de discusión por buenas razones. Además, también tiene que "llamar" a las globalvariables, utilizando la palabra clave para que estén disponibles. Dependiendo del caso de uso, las sesiones pueden ser una solución. De lo contrario, como se mencionó, creo que una función o una clase para hacer el trabajo por usted es el camino a seguir.
Nicolai

Respuestas:

10

Estructuras básicas de datos separados

Para pasar datos, normalmente utiliza un Modelo (esa es la "M" en "MVC"). Veamos una interfaz muy simple para datos. Las interfaces solo se usan como "Recetas" para nuestros bloques de construcción:

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

Arriba es lo que pasamos: una identificación común y una "etiqueta".

Mostrar datos combinando piezas atómicas

A continuación, necesitamos una Vista que negocie entre nuestro Modelo y ... nuestra plantilla.

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

Básicamente esa interfaz dice

"Podemos renderizar algo y un Modelo es obligatorio para esa tarea"

Finalmente necesitamos implementar arriba y construir la Vista real . Como puede ver, el constructor le dice que lo obligatorio para nuestra vista es una Plantilla y que podemos renderizarla. En aras de un desarrollo fácil, incluso verificamos si el archivo de plantilla está realmente presente para que podamos hacer que la vida de otros desarrolladores (y la nuestra también) sea mucho más fácil y tenga en cuenta eso.

En un segundo paso en la función de representación, usamos un Cierre para construir el envoltorio de plantilla real y bindTo()el Modelo para la plantilla.

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
        );
    }
}

Separar la vista y renderizar

Esto significa que podemos usar una plantilla muy simple como la siguiente

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

para renderizar nuestro contenido. Al juntar las piezas obtendríamos algo alrededor de las siguientes líneas (en nuestro Controlador, Mediador, etc.):

namespace WeCodeMore\Package;

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

¿Qué ganamos?

De esta manera podemos

  1. Intercambie plantillas fácilmente sin cambiar la estructura de datos
  2. Tener plantillas fáciles de leer
  3. Evitar alcance global
  4. Prueba unitaria de latas
  5. Puede intercambiar el modelo / los datos sin dañar otros componentes

Combinando OOP PHP con la API de WP

Por supuesto esto no es posible mediante el uso de la funcionalidad básica tematización como get_header(), get_footer(), etc., ¿verdad? Incorrecto. Simplemente llame a sus clases en cualquier plantilla o parte de la plantilla que desee. Renderízalo, transforma los datos, haz lo que quieras. Si eres realmente amable, incluso solo agregas tu propio grupo de filtros personalizados y tienes un negociador para que se encargue de lo que representa qué controlador en qué ruta / carga de plantilla condicional.

¿Conclusión?

Puede trabajar con cosas como las anteriores en WP sin problemas y aún así atenerse a la API básica y reutilizar el código y los datos sin llamar a un solo global o desordenar y contaminar el espacio de nombre global.

emperador
fuente
3
¡Se ve muy bien! Voy a ver más en esto, buena respuesta!
marko
@kaiser casi 3 años después, ¿hay alguna actualización en lo que piensas arriba? Las plantillas centrales de WP realmente no han progresado en una dirección más avanzada, por lo que las soluciones de terceros siguen siendo una cosa.
lkraav
1
@Ikraav Probablemente no lo escribiría así hoy en día, pero todavía estoy seguro de que no usar una sintaxis separada para generar el contenido de las variables dentro de las etiquetas HTML es el camino a seguir (y evita una sobrecarga innecesaria). Por otro lado, rara vez escribo cosas frontend en PHP en estos días, pero en JavaScript. Y realmente me gusta lo que VueJS y sus amigos están trayendo a la mesa.
kaiser
11

Este es un enfoque alternativo para @kaiser respuesta de , que encontré bastante bien (+1 de mi parte) pero requiere un trabajo adicional para ser utilizado con las funciones principales de WP y está per se bajo integrado con la jerarquía de plantillas.

El enfoque que quiero compartir se basa en una sola clase (es una versión simplificada de algo en lo que estoy trabajando) que se encarga de procesar los datos para las plantillas.

Tiene algunas características interesantes (IMO):

  • las plantillas son archivos de plantilla estándar de WordPress (single.php, page.php) obtienen un poco más de potencia
  • Las plantillas existentes simplemente funcionan, por lo que puede integrar plantillas de temas existentes sin esfuerzo
  • a diferencia del enfoque @kaiser , en las plantillas se accede a las variables usando $thispalabras clave: esto le brinda la posibilidad de evitar avisos en la producción en caso de variables indefinidas

La Engineclase

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()
     );
  }
}

(Disponible como Gist aquí).

Cómo utilizar

Lo único que se necesita es llamar al Engine::init()método, probablemente en el 'template_redirect'gancho. Eso se puede hacer en tema functions.phpo desde un complemento.

require_once '/path/to/the/file/Engine.php';
add_action('template_redirect', array('GM\Template\Engine', 'init'), 99);

Eso es todo.

Sus plantillas existentes funcionarán como expirado. Pero ahora tiene la posibilidad de acceder a datos de plantilla personalizados.

Datos de plantilla personalizados

Para pasar datos personalizados a las plantillas hay dos filtros:

  • 'gm_template_data'
  • 'gm_template_data_{$type}'

El primero se activa para todas las plantillas, el segundo es específico de la plantilla, de hecho, la parte dinámica {$type}es el nombre base del archivo de plantilla sin extensión de archivo.

Por ejemplo, el filtro 'gm_template_data_single'se puede usar para pasar datos a la single.phpplantilla.

Las devoluciones de llamada adjuntas a estos enlaces tienen que devolver una matriz , donde las claves son los nombres de las variables.

Por ejemplo, puede pasar metadatos como datos de plantilla como:

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;
};

Y luego, dentro de la plantilla puedes usar:

<?= $this->extra_title ?>

Modo de depuración

Cuando ambas constantes WP_DEBUGy WP_DEBUG_DISPLAYson verdaderas, la clase funciona en modo de depuración. Significa que si una variable no está definida, se lanza una excepción.

Cuando la clase no está en modo de depuración (probablemente en producción) acceder a una variable indefinida generará una cadena vacía.

Modelos de datos

Una forma agradable y sostenible de organizar sus datos es usar clases de modelo.

Pueden ser clases muy simples, que devuelven datos utilizando los mismos filtros descritos anteriormente. No hay una interfaz en particular a seguir, se pueden organizar según sus preferencias.

A continuación, solo hay un ejemplo, pero puede hacerlo a su manera.

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);

El __invoke()método (que se ejecuta cuando una clase se usa como una devolución de llamada) devuelve una cadena que se usará para la <title>etiqueta de la plantilla.

Gracias al hecho de que el segundo argumento pasado 'gm_template_data'es el nombre de la plantilla, el método devuelve un título personalizado para la página de inicio.

Tener el código anterior, entonces es posible usar algo como

 <title><?= $this->seo_title ?></title>

en la <head>sección de la página.

Parciales

WordPress tiene funciones como get_header()o get_template_part()que se pueden usar para cargar parciales en la plantilla principal.

Estas funciones, al igual que todas las demás funciones de WordPress, se pueden usar en plantillas cuando se usa la Engineclase.

El único problema es que dentro de los parciales cargados usando las funciones centrales de WordPress no es posible usar la función avanzada de obtener datos de plantillas personalizadas $this.

Por esta razón, la Engineclase tiene un método partial()que permite cargar un parcial (de una manera totalmente compatible con temas secundarios) y aún así poder usar en parciales los datos de la plantilla personalizada.

El uso es bastante simple.

Suponiendo que hay un archivo llamado partials/content.phpdentro de la carpeta del tema (o tema secundario), se puede incluir usando:

<?php $this->partial('partials/content') ?>

Dentro de ese parcial, será posible acceder a todos los datos del tema principal de la misma manera.

A diferencia de las funciones de WordPress, el Engine::partial()método permite pasar datos específicos a parciales, simplemente pasando una matriz de datos como segundo argumento.

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

De forma predeterminada, los parciales tienen acceso a los datos disponibles en el tema principal y a la explicilidad de datos aprobada.

Si alguna variable pasada explícitamente a parcial tiene el mismo nombre de una variable de tema principal, entonces la variable pasada explícitamente gana.

Sin embargo, también es posible incluir un parcial en modo aislado , es decir, el parcial no tiene acceso a los datos del tema principal. Para hacer eso, simplemente pase truecomo tercer argumento a partial():

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

Conclusión

Incluso si es bastante simple, la Engineclase es bastante completa, pero seguramente se puede mejorar aún más. Por ejemplo, no hay forma de verificar si una variable está definida o no.

Gracias a su compatibilidad al 100% con las funciones de WordPress y la jerarquía de plantillas, puede integrarlo con el código existente y de terceros sin problemas.

Sin embargo, tenga en cuenta que solo se prueba parcialmente, por lo que es posible que haya problemas que aún no he descubierto.

Los cinco puntos bajo "¿Qué ganamos?" en @kaiser respuesta :

  1. Intercambie plantillas fácilmente sin cambiar la estructura de datos
  2. Tener plantillas fáciles de leer
  3. Evitar alcance global
  4. Prueba unitaria de latas
  5. Puede intercambiar el modelo / los datos sin dañar otros componentes

Todos son válidos para mi clase también.

gmazzap
fuente
1
Jeje. Bien hecho, amigo :) +1
kaiser
@gmazzap casi 3 años después, ¿hay alguna actualización a lo que piensas arriba? Las plantillas centrales de WP realmente no han progresado en una dirección más avanzada, por lo que las soluciones de terceros siguen siendo una cosa.
lkraav
1
No hago muchos temas de trabajo en estos días. Últimamente mi camino a seguir fue combinar github.com/Brain-WP/Context + github.com/Brain-WP/Hierarchy para crear datos y pasarlos a plantillas. Para el motor de plantillas en sí, utilicé diferentes enfoques, Foil (por supuesto), Moustache, pero también Twig (solo cuando tenía control en todo el sitio web para evitar el infierno de dependencia) @lkraav
gmazzap
5

Respuesta simple, no pase variables a ningún lado, ya que apesta a usar variables globales, lo cual es malo.

Según su ejemplo, parece que está tratando de hacer una optimización temprana, otro mal;)

Use la API de WordPress para obtener datos que se almacenan en la base de datos y no intente burlar y optimizar su uso, ya que la API hace más que solo recuperar valores y activa filtros y acciones. Al eliminar la llamada API, elimina la capacidad de otros desarrolladores de cambiar el comportamiento de su código sin modificarlo.

Mark Kaplun
fuente
2

Aunque la respuesta de Kaiser es técnicamente correcta, dudo que sea la mejor respuesta para usted.

Si está creando su propio tema, entonces creo que es la mejor manera de configurar algún tipo de marco utilizando clases (y tal vez espacios de nombres e interfaces también, aunque eso podría ser demasiado para un tema de WP).

Por otro lado, si solo está ampliando / ajustando un tema existente y solo necesita pasar una o algunas variables, creo que debe seguir global. Debido a que header.phpse incluye dentro de una función, las variables que declara en ese archivo solo se pueden usar en ese archivo. Con globalusted, haga que sean accesibles en todo el proyecto de WP:

En header.php:

global $page_extra_title;

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

En single.php(por ejemplo):

global $page_extra_title;

var_dump( $page_extra_title );
redelschaap
fuente
3
No quiero ser grosero ni nada, pero es realmente una mala práctica sumergirse en el alcance global. Debe evitar agregar al alcance global por completo. Debe tener mucho cuidado al nombrar las convenciones aquí y debe asegurarse de que el nombre de su variable sea único de tal manera que nadie más pueda reproducir dicho nombre. El enfoque de @kaiser puede parecerle excesivo, pero es, con mucho, el mejor y más seguro. No puedo decirte cómo abordar esto, pero realmente te aconsejo que te mantengas fuera del alcance global :-)
Pieter Goosen
3
Por supuesto, debe tener cuidado de no sobrescribir otras variables. Puede abordar eso utilizando un prefijo único o una matriz con sus variables personalizadas, $wp_theme_vars_page_extra_titleo $wp_theme_vars['page_extra_title']por ejemplo. Fue solo una explicación de por qué global funcionaría aquí. OP solicitó una forma de pasar una variable a través de todos los archivos, usando globales una forma de hacerlo.
redelschaap
2
No, los globales no son una forma de hacerlo. Hay formas mucho mejores de lograr lo mismo sin usar globals. Como dije antes, y como dijo @kaiser en su respuesta, evite el alcance global y manténgase alejado de él. Solo como ejemplo, tome esta alternativa muy fácil, ajuste su código en una función y llame a la función donde sea necesario. De esta manera, no necesita establecer o usar un global.
Pieter Goosen
3
Sí lo es. Puede que no sea la mejor manera, pero definitivamente es una forma.
redelschaap
2
but it is really bad practice diving into the global scopeMe gustaría que alguien les dijera eso a los desarrolladores principales de WP. Realmente no entiendo el punto de usar espacios de nombres, abstracción de datos, patrones de diseño, pruebas unitarias y otras mejores prácticas / técnicas de programación en código escrito para Wordpress cuando Wordpress core está plagado de malas prácticas de codificación como variables glabales (por ejemplo, los widgets código).
Ejaz
1

Una solución fácil es escribir una función para obtener el título adicional. Utilizo una variable estática para mantener las llamadas de la base de datos a una sola. Pon esto en tu 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;
}

Fuera de header.php, llame a la función para obtener el valor:

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