Le meilleur moyen de passer une variable PHP entre des partiels?

16

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 NULLsors toujours 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 presque impossible à maintenir.

Je me demande quelle est la meilleure façon de passer une variable dans tous les fichiers de mon thème? Je suppose que l'utilisation de functions.php avec "get_post_meta" pourrait ne pas être la meilleure idée? :)

Wordpressor
la source
Je pense que la variable est dans la même portée, aussi je veux éviter d'utiliser GLOBAL pour des raisons évidentes.
Wordpressor
Je pense que le commentaire d'ialocin est tout à fait juste. Un script PHP ne sait pas que l'autre existe et ne peut pas accéder à ses variables locales ou à leurs valeurs.
jdm2112
1
l'en-tête et le pied de page sont inclus via une fonction, donc l'étendue de tout dans ces fichiers est l'étendue de cette fonction.
Milo
4
Ne tirez pas sur le messager :) La seule chose que j'ai dit, c'est en effet un problème de portée. Il y a un moyen, n'est global-ce pas? Mais il est hors de question pour de bonnes raisons. De plus, vous devez aussi "appeler" les globalvariables, en utilisant le mot-clé pour les rendre disponibles. Selon le cas d'utilisation, les sessions peuvent être une solution. Sinon - comme mentionné - je pense qu'une fonction ou une classe pour faire le travail pour vous est la voie à suivre.
Nicolai

Réponses:

10

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

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

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

Ci-dessus, ce que nous transmettons: un identifiant commun et un "label".

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

Ensuite, nous avons besoin d'une vue 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 );
}

Fondamentalement, cette interface dit

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

Enfin, nous devons implémenter ci-dessus et construire la vue réelle . Comme vous pouvez le voir, le constructeur dit que la chose obligatoire pour notre vue est un modèle et que nous pouvons le rendre. Pour faciliter le développement, nous vérifions même si le fichier de modèle est réellement présent afin que nous puissions faciliter la vie des autres développeurs (et la nôtre également) beaucoup plus et notons cela.

Dans une deuxième étape de la fonction de rendu, nous utilisons une fermeture pour créer le wrapper de modèle réel et bindTo()le modèle dans 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éparation de la vue et du rendu

Cela signifie que nous pouvons utiliser un modèle très simple comme le suivant

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

pour rendre notre contenu. En rassemblant les pièces, nous obtiendrions quelque chose autour des lignes suivantes (dans notre contrôleur, 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 modifier la structure des données
  2. Ayez des tempaltes faciles à lire
  3. Évitez la portée mondiale
  4. Test unitaire
  5. Peut échanger le modèle / les données sans nuire aux autres composants

Combiner OOP PHP avec l'API WP

Bien sûr , cela est à peine possible en utilisant la fonctionnalité de thématisation de base comme get_header(), get_footer(), etc., non? 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 sympa, vous n'avez qu'à ajouter votre propre groupe de filtres personnalisés et avoir un négociateur pour prendre soin de ce qui est rendu par quel contrôleur sur quelle route / chargement de modèle conditionnel.

Conclusion?

Vous pouvez travailler avec des choses comme ci-dessus dans WP sans problème et vous en tenir à l'API de base et réutiliser le code et les données sans appeler un seul global ou gâcher et polluer l'espace de nom global.

kaiser
la source
3
Ça a l'air super! Je vais approfondir cela, belle réponse!
marko
@kaiser presque 3 ans plus tard, y a-t-il des mises à jour de votre réflexion ci-dessus? Les modèles de base WP n'ont pas vraiment progressé dans une direction plus avancée, donc les solutions tierces sont toujours une chose.
lkraav
1
@Ikraav Je ne l'écrirais probablement pas comme ça de nos jours, mais je suis toujours certain que ne pas utiliser une syntaxe distincte pour sortir le contenu des variables à l'intérieur des balises HTML est la voie à suivre (et évite un surcoût inutile). D'un autre côté, j'écris rarement des choses frontales en PHP de nos jours, mais en JavaScript. Et j'aime vraiment ce que VueJS et ses amis apportent à la table.
kaiser
11

Il s'agit d'une approche alternative à la réponse @kaiser , que j'ai trouvée assez bien (+1 de moi) mais nécessite un travail supplémentaire pour être utilisé avec les fonctions de base de WP et elle est en soi peu intégrée à la hiérarchie des modèles.

L'approche que je veux partager est basée sur une seule classe (c'est une version allégée de quelque chose sur laquelle je travaille) qui prend en charge le rendu des données pour les modèles.

Il a quelques fonctionnalités (IMO) intéressantes:

  • les modèles sont des fichiers de modèles WordPress standard (single.php, page.php), ils obtiennent un peu plus de puissance
  • les modèles existants fonctionnent, vous pouvez donc intégrer le modèle à partir de thèmes existants sans effort
  • contrairement à l' approche @kaiser , dans les modèles, vous accédez aux variables à l'aide d'un $thismot-clé: cela vous donne la possibilité d'éviter les notifications en production en cas de variables non définies

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

(Disponible sous forme de Gist ici.)

Comment utiliser

La seule chose nécessaire est d'appeler la Engine::init()méthode, probablement en 'template_redirect'décrochant. Cela peut être fait par thème functions.phpou à 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 prévu. Mais maintenant, vous avez la possibilité d'accéder aux données du modèle personnalisé.

Données de modèle personnalisé

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 dymamique {$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 single.phpmodèle.

Les rappels attachés à ces hooks doivent renvoyer un tableau , où les clés sont les noms des 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 puis, à l'intérieur du modèle, vous pouvez simplement utiliser:

<?= $this->extra_title ?>

Mode débogage

Lorsque les deux constantes WP_DEBUGet WP_DEBUG_DISPLAYsont 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 produira une chaîne vide.

Modèles de données

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

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

Ci-dessous, il y a juste un exemple, mais vous êtes libre de le faire à votre manière.

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

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

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

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

dans la <head>section de la page.

Partiels

WordPress a des fonctions comme 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 lors de l'utilisation de la Engineclasse.

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é à l'aide $this.

Pour cette raison, la Engineclasse a une méthode partial()qui permet de charger un partiel (de manière entièrement compatible avec le thème enfant) et de pouvoir utiliser en partie les données du modèle personnalisé.

L'utilisation est assez simple.

En supposant qu'il existe un fichier nommé partials/content.phpdans le dossier thème (ou thème enfant), il peut être inclus en utilisant:

<?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 Engine::partial()méthode permet de transmettre des données spécifiques à des partiels, en passant simplement un tableau de données comme deuxième argument.

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

Par défaut, les partiels ont accès aux données disponibles dans le thème parent et aux données explicites transmises.

Si une variable passée explicitement à partial a le même nom qu'une variable de thème parent, alors la variable explicitement passée gagne.

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, passez simplement truele troisième argument à partial():

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

Conclusion

Même si elle est assez simple, la Engineclasse est assez complète, mais peut certainement être encore 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 sans problème avec du code existant et tiers.

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

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

  1. Échangez facilement des modèles sans modifier la structure des données
  2. Ayez des tempaltes faciles à lire
  3. Évitez la portée mondiale
  4. Test unitaire
  5. Peut échanger le modèle / les données sans nuire aux autres composants

sont également valables pour ma classe.

gmazzap
la source
1
Hehe. Bien joué, mec :) +1
kaiser
@gmazzap presque 3 ans plus tard, y a-t-il des mises à jour de votre réflexion ci-dessus? Les modèles de base WP n'ont pas vraiment progressé dans une direction plus avancée, donc les solutions tierces sont toujours une chose.
lkraav
1
Je ne travaille pas beaucoup de thèmes de nos jours. Dernièrement, mon chemin à parcourir consistait à combiner github.com/Brain-WP/Context + github.com/Brain-WP/Hierarchy pour créer des données et passer à des modèles. Pour le moteur de template lui-même, j'ai utilisé différentes approches, Foil (bien sûr), Moustache, mais aussi Twig (uniquement lorsque j'avais le contrôle sur l'ensemble du site pour éviter l'enfer des dépendances) @lkraav
gmazzap
5

Réponse simple, ne passez pas de variables n'importe où car cela pue d'utiliser des variables globales, ce qui est mauvais.

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

Utilisez l'API wordpress pour obtenir des données qui sont stockées dans la base de données et n'essayez pas de déjouer et d'optimiser son utilisation car l'API fait plus que simplement récupérer des valeurs et elle active des filtres et des actions. En supprimant l'appel d'API, vous supprimez la capacité des autres développeurs à modifier le comportement de votre code sans le modifier.

Mark Kaplun
la source
2

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

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

D'un autre côté, si vous étendez / ajustez simplement un thème existant et n'avez besoin que de passer une ou quelques variables, je pense que vous devriez vous en tenir global. Parce que header.phpest inclus dans une fonction, les variables que vous déclarez dans ce fichier sont utilisables uniquement dans ce fichier. Avec globalvous les rendez accessibles dans tout le projet WP:

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 );
redelschaap
la source
3
Je ne veux pas être impoli ou quoi que ce soit, mais c'est vraiment une mauvaise pratique de plonger dans la portée mondiale. Vous devez éviter d'ajouter complètement à la portée globale. Vous devez faire très attention aux conventions de dénomination ici et vous devez vous assurer que le nom de votre variable sera unique de telle manière que personne d'autre ne puisse reproduire un tel nom. L'approche @kaiser peut sembler exagérée pour vous, mais c'est de loin la meilleure et la plus sûre. Je ne peux pas vous dire comment y faire face, mais je vous conseille vraiment de rester en dehors de la portée mondiale :-)
Pieter Goosen
3
Bien sûr, vous devez faire attention à ne pas écraser les autres variables. Vous pouvez résoudre ce problème en utilisant un préfixe unique ou un tableau avec vos variables personnalisées, $wp_theme_vars_page_extra_titleou $wp_theme_vars['page_extra_title']par exemple. C'était juste une explication pour laquelle global fonctionnerait ici. OP a demandé un moyen de passer une variable dans tous les fichiers, en utilisant globalest un moyen de le faire.
redelschaap du
2
Non, les mondiaux ne sont pas un moyen de le faire. Il existe de bien meilleures façons de réaliser la même chose sans utiliser les globaux. Comme je l'ai déjà dit, et comme @kaiser l'a déclaré dans sa réponse, évitez la portée mondiale et restez en dehors. À titre d'exemple, prenez cette alternative très simple, enveloppez votre code dans une fonction et appelez la fonction si nécessaire. De cette façon, vous n'avez pas besoin de définir ou d'utiliser un global.
Pieter Goosen
3
Oui, ça l'est. Ce n'est peut-être pas le meilleur moyen, mais c'est certainement un moyen.
redelschaap
2
but it is really bad practice diving into the global scopeJ'aimerais que quelqu'un le dise aux développeurs principaux de WP. Je ne comprends vraiment pas l'intérêt d'utiliser des espaces de noms, l'abstraction de données, des modèles de conception, des tests unitaires et d'autres meilleures pratiques / techniques de programmation dans le code écrit pour Wordpress lorsque le noyau Wordpress est jonché de mauvaises pratiques de codage comme les variables glabales (par exemple, les widgets code).
Ejaz
1

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));
pbd
la source