web-dev-qa-db-fra.com

Meilleur moyen de passer PHP variable entre partiels?

J'ai une variable dans header.php, telle que:

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

Une fois que je fais:

var_dump($page_extra_title);

Je reçois toujours NULL en dehors de header.php (var_dump fonctionne correctement dans header.php uniquement). J'ai collé la même variable partout où j'en ai besoin (page.php, post.php, footer.php, etc.), mais c'est de la folie et rend tout impossible à maintenir.

Je me demande quel est le meilleur moyen de passer une variable à travers tous les fichiers de mon thème? J'imagine que l'utilisation de functions.php avec "get_post_meta" n'est peut-être pas la meilleure idée? :)

15
Wordpressor

Structures de données séparées de base

Pour faire circuler des données, vous utilisez normalement un Model (c'est le "M" dans "MVC"). Regardons une interface très simple pour les données. Les interfaces sont simplement utilisées comme "recettes" pour nos blocs de construction:

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

Ci-dessus, nous transmettons: un identifiant commun et une "étiquette".

Affichage de données en combinant des pièces atomiques

Ensuite, nous avons besoin de View qui négocie entre notre modèle et ... notre modèle.

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

En gros que Interface dit

"Nous pouvons rendre quelque chose et un modèle est obligatoire pour cette tâche"

Enfin, nous devons implémenter ce qui précède et construire le View . Comme vous pouvez le constater, le constructeur indique que pour nous, l'élément obligatoire est un Template et que nous pouvons le restituer. Par souci de facilité de développement, nous vérifions même si le fichier de modèle est réellement présent afin de rendre la vie des autres développeurs (et la nôtre également) beaucoup plus facile, et nous en prenons bonne note.

Dans une deuxième étape de la fonction de rendu, nous utilisons un Closure pour construire le wrapper de modèle actuel et bindTo() le Modèle pour le modèle.

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

Séparer la vue et le rendu

Cela signifie que nous pouvons utiliser un modèle très simple comme celui-ci.

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

rendre notre contenu. En assemblant les pièces, nous obtiendrions quelque chose autour des lignes suivantes (dans notre contrôleur, notre médiateur, etc.):

namespace WeCodeMore\Package;

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

Qu'avons-nous gagné?

De cette façon nous pouvons

  1. Échangez facilement des modèles sans changer la structure de données
  2. Avoir facile à lire les tempaltes
  3. Éviter la portée globale
  4. Test unitaire
  5. Peut échanger le modèle/les données sans nuire aux autres composants

Combinaison de OOP PHP avec l'API WP

Bien sûr, cela n’est guère possible en utilisant les fonctionnalités de base telles que get_header(), get_footer(), etc., d’accord? Faux. Appelez simplement vos classes dans le modèle ou la partie de modèle que vous souhaitez. Rendez-le, transformez les données, faites ce que vous voulez. Si vous êtes vraiment gentil, vous pouvez même ajouter votre propre groupe de filtres personnalisés et un négociateur pour prendre en charge ce qui est rendu par quel contrôleur par quel chemin/modèle conditionnel.

Conclusion?

Vous pouvez travailler avec les éléments comme ci-dessus dans WP sans problème, tout en restant fidèle à l'API de base et en réutilisant le code et les données sans appeler un seul global ou déranger et polluer l'espace de noms global.

9
kaiser

Il s’agit d’une approche alternative à @kaiser answer, que j’ai trouvé très bien (+1 de ma part) mais qui nécessite un travail supplémentaire pour pouvoir être utilisée avec les fonctions principales WP. hiérarchie des modèles.

L'approche que je souhaite partager repose sur une seule classe (version simplifiée de quelque chose sur laquelle je travaille) qui prend en charge le rendu des données pour les modèles.

Il a quelques (OMI) caractéristiques intéressantes:

  • les modèles sont des fichiers de modèle WordPress standard (single.php, page.php), ils reçoivent un peu plus de puissance
  • les modèles existants fonctionnent simplement, vous pouvez donc intégrer des modèles de thèmes existants sans effort
  • contrairement à @kaiser approche, dans les modèles, vous accédez aux variables avec le mot clé $this: vous avez ainsi la possibilité d’éviter les notifications en cours de production dans le cas de variables non définies.

La classe Engine

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 en tant que Gist ici.)

Comment utiliser

La seule chose dont vous avez besoin est d'appeler la méthode Engine::init(), probablement sur le hook 'template_redirect'. Cela peut être fait dans le thème functions.php ou à partir d'un plugin.

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

C'est tout.

Vos modèles existants fonctionneront comme expcted. Mais vous avez maintenant la possibilité d'accéder à des données de modèle personnalisées.

Données de modèle personnalisées

Pour transmettre des données personnalisées aux modèles, il existe deux filtres:

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

Le premier est déclenché pour tous les modèles, le second est spécifique au modèle. En fait, la partie dimymatique {$type} est le nom de base du fichier de modèle sans extension de fichier.

Par exemple. le filtre 'gm_template_data_single' peut être utilisé pour transmettre des données au modèle single.php.

Les callbacks attachés à ces hooks doivent renvoyer un tableau , où les clés sont les noms de variables.

Par exemple, vous pouvez transmettre des métadonnées en tant que données de modèle, comme ceci:

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

Et ensuite, dans le modèle, vous pouvez simplement utiliser:

<?= $this->extra_title ?>

Mode débogage

Lorsque les constantes WP_DEBUG et WP_DEBUG_DISPLAY sont vraies, la classe fonctionne en mode débogage. Cela signifie que si une variable n'est pas définie, une exception est levée.

Lorsque la classe n'est pas en mode débogage (probablement en production), l'accès à une variable non définie générera une chaîne vide.

Modèles de données

Une façon agréable et maintenable d'organiser vos données consiste à utiliser des classes de modèle.

Il peut s’agir de classes très simples renvoyant des données à l’aide des filtres décrits ci-dessus. Il n’ya pas d’interface particulière à suivre, elles peuvent être organisées selon vos préférences.

Ci-dessous, vous trouverez un exemple, mais vous êtes libre de le faire à votre façon.

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

La méthode __invoke() (qui s'exécute lorsqu'une classe est utilisée comme un rappel) renvoie une chaîne à utiliser pour la balise <title> du modèle.

Grâce au fait que le deuxième argument passé par 'gm_template_data' est le nom du modèle, la méthode renvoie un titre personnalisé pour la page d'accueil.

Avoir le code ci-dessus, est alors possible d'utiliser quelque chose comme

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

dans la section <head> de la page.

Partials

WordPress possède des fonctions telles que get_header() ou get_template_part() qui peuvent être utilisées pour charger des partiels dans le modèle principal.

Ces fonctions, comme toutes les autres fonctions WordPress, peuvent être utilisées dans les modèles lorsque vous utilisez la classe Engine.

Le seul problème est qu’à l’intérieur des partiels chargés à l’aide des fonctions principales de WordPress, il n’est pas possible d’utiliser la fonctionnalité avancée d’obtenir des données de modèle personnalisées à l’aide de $this.

Pour cette raison, la classe Engine a une méthode partial() qui permet de charger un partiel (de manière totalement compatible avec le thème enfant) tout en pouvant utiliser les partiels des données de modèle personnalisé.

L'utilisation est assez simple.

En supposant qu'il existe un fichier nommé partials/content.php à l'intérieur du dossier thème (ou thème enfant), il peut être inclus à l'aide de:

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

À l'intérieur de ce partiel, il sera possible d'accéder à toutes les données du thème parent de la même manière.

Contrairement aux fonctions WordPress, la méthode Engine::partial() permet de transmettre des données spécifiques à des partiels, en passant simplement un tableau de données en tant que second argument.

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

Par défaut, les composants partiels ont accès aux données disponibles dans le thème parent et à l'explication des données transmises.

Si une variable explicitement transmise à partial porte le même nom qu'une variable de thème parent, la variable explicitement transmise l'emporte.

Cependant, il est également possible d'inclure un partiel en mode isolé, c'est-à-dire que le partiel n'a pas accès aux données du thème parent. Pour ce faire, il suffit de passer true en tant que troisième argument à partial():

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

Conclusion

Même si assez simple, la classe Engine est assez complète, mais peut certainement être améliorée. Par exemple. il n'y a aucun moyen de vérifier si une variable est définie ou non.

Grâce à sa compatibilité à 100% avec les fonctionnalités de WordPress et la hiérarchie des modèles, vous pouvez l'intégrer au code existant et tiers sans aucun problème.

Cependant, notez que ce n’est que partiellement testé, il est donc possible que des problèmes que je n’ai pas encore découverts.

Les cinq points sous "Qu'avons-nous gagné?" dans @kaiserréponse :

  1. Échangez facilement des modèles sans changer la structure de données
  2. Avoir facile à lire les tempaltes
  3. Éviter la portée globale
  4. Test unitaire
  5. Peut échanger le modèle/les données sans nuire aux autres composants

sont tous valables pour ma classe aussi.

11
gmazzap

Réponse simple, ne transmettez pas les variables n'importe où, cela pue d'utiliser des variables globales qui sont mauvaises.

D'après votre exemple, il semble que vous essayiez de faire une optimisation précoce, encore un autre mal;)

Utilisez l’API wordpress pour obtenir les données stockées dans la base de données et n’essayez pas de déjouer et d’optimiser son utilisation car l’API ne se contente pas de récupérer des valeurs et d’activer des filtres et des actions. En supprimant l'appel de l'API, vous empêchez les autres développeurs de modifier le comportement de votre code sans le modifier.

5
Mark Kaplun

Bien que la réponse de Kaiser soit techniquement juste, je doute que ce soit la meilleure solution pour vous.

Si vous créez votre propre thème, je pense que c’est le meilleur moyen de configurer un cadre utilisant des classes (et peut-être aussi des espaces de noms et des interfaces, bien que cela puisse être un peu trop pour un WP thème).

D'autre part, si vous développez/ajustez simplement un thème existant et que vous n'avez besoin que de passer une ou plusieurs variables, je pense que vous devriez vous en tenir à global. Étant donné que header.php est inclus dans une fonction, les variables que vous déclarez dans ce fichier ne peuvent être utilisées que dans ce fichier. Avec global, vous les rendez accessibles dans l’ensemble du WP projet:

Dans header.php:

global $page_extra_title;

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

Dans single.php (par exemple):

global $page_extra_title;

var_dump( $page_extra_title );
2
redelschaap

Une solution simple consiste à écrire une fonction pour obtenir le titre supplémentaire. J'utilise une variable statique pour garder les appels de base de données à un seul. Mettez ceci dans votre 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;
}

En dehors de header.php, appelez la fonction pour obtenir la valeur:

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