Il modo migliore per passare la variabile PHP tra i parziali?


16

Ho una variabile in header.php, come ad esempio:

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

Una volta che faccio:

var_dump($page_extra_title);

Vado sempre NULLfuori da header.php (var_dump funziona correttamente solo in header.php). Ho incollato la stessa variabile ovunque ne abbia bisogno (page.php, post.php, footer.php ecc.), Ma è una follia e rende quasi impossibile mantenere tutto.

Mi chiedo qual è il modo migliore di passare una variabile attraverso tutti i file nel mio tema? Immagino che usare funzioni.php insieme a "get_post_meta" potrebbe non essere la migliore idea? :)



Penso che la variabile sia nello stesso ambito, inoltre voglio evitare di usare GLOBAL per ovvie ragioni.
Wordpressor

Credo che il commento di ialocin sia perfetto. Uno script PHP non sa che l'altro esiste e non può accedere alle sue variabili locali o ai loro valori.
jdm2112,

1
intestazione e piè di pagina sono inclusi tramite una funzione, quindi l'ambito di tutto in quei file è l'ambito di quella funzione.
Milo,

4
Non sparare al messaggero :) L'unica cosa che ho detto è che è davvero un problema di portata. C'è un modo global, vero? Ma è fuori discussione per buoni motivi. Inoltre, devi "chiamare" globalanche le variabili modificabili, usando la parola chiave per renderle disponibili. A seconda del caso d'uso, le sessioni potrebbero essere una soluzione. Altrimenti - come detto - penso che una funzione o una classe per fare il lavoro per te sia la strada da percorrere.
Nicolai,

Risposte:


10

Strutture di dati separate di base

Per passare i dati, normalmente si utilizza un modello (che è la "M" in "MVC"). Diamo un'occhiata a un'interfaccia molto semplice per i dati. Le interfacce sono usate solo come "ricette" per i nostri blocchi:

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

Sopra è ciò che passiamo in giro: un ID comune e una "etichetta".

Visualizzazione dei dati combinando pezzi atomici

Successivamente abbiamo bisogno di alcune viste che negoziano tra il nostro modello e ... il nostro modello.

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

Fondamentalmente tale interfaccia dice

"Siamo in grado di eseguire il rendering di qualcosa e un modello è obbligatorio per tale attività"

Infine, dobbiamo implementare sopra e costruire la vista effettiva . Come puoi vedere, il costruttore dice che la cosa obbligatoria per la nostra vista è un modello e che possiamo renderlo. Per motivi di facile sviluppo, verifichiamo anche se il file modello è effettivamente presente in modo da poter rendere la vita di altri sviluppatori (e anche la nostra) molto più semplice e notare che.

In una seconda fase della funzione di rendering, utilizziamo una chiusura per creare il wrapper di modello effettivo e bindTo()il modello sul modello.

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

Separare la vista e il rendering

Ciò significa che possiamo usare un modello molto semplice come il seguente

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

per rendere i nostri contenuti. Mettendo insieme i pezzi otterremmo qualcosa attorno alle seguenti righe (nel nostro controller, mediatore, ecc.):

namespace WeCodeMore\Package;

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

Cosa abbiamo guadagnato?

In questo modo possiamo

  1. Scambia facilmente i modelli senza modificare la struttura dei dati
  2. Abbia leggere tempaltes
  3. Evitare l'ambito globale
  4. Can Unit-Test
  5. Può scambiare il Modello / i dati senza danneggiare altri componenti

Combinazione di OOP PHP con l'API WP

Naturalmente questo non è certo possibile utilizzando funzionalità di base come tematizzazione get_header(), get_footer()e così via, giusto? Sbagliato. Chiama le tue lezioni in qualsiasi modello o parte di modello desideri. Esegui il rendering, trasforma i dati, fai quello che vuoi. Se sei davvero gentile, aggiungi semplicemente il tuo gruppo di filtri personalizzati e hai un negoziatore per occuparti di ciò che viene visualizzato da quale controller su quale percorso / modello condizionale viene caricato.

Conclusione?

Puoi lavorare con cose come sopra in WP senza problemi e comunque attenersi all'API di base e riutilizzare codice e dati senza chiamare un singolo globale o rovinare e inquinare lo spazio dei nomi globale.


3
Sembra fantastico! Guarderò di più in questo, bella risposta!
marko,

@kaiser quasi 3 anni dopo, ci sono aggiornamenti al tuo pensiero sopra? Il modello di core WP non è davvero progredito in una direzione più avanzata, quindi le soluzioni di terze parti sono ancora una cosa.
lkraav il

1
@Ikraav Probabilmente non lo scriverei in questo modo al giorno d'oggi, ma sono ancora certo che non usare una sintassi separata per produrre il contenuto delle variabili all'interno dei tag HTML sia la strada da percorrere (ed eviti un sovraccarico inutile). D'altra parte, sto scrivendo raramente cose frontend in PHP in questi giorni, ma in JavaScript. E mi piace molto quello che VueJS e gli amici stanno portando al tavolo.
Kaiser

11

Questo è un approccio alternativo a @kaiser risposta di , che ho trovato abbastanza bene (+1 da parte mia) ma richiede un lavoro aggiuntivo da utilizzare con le funzioni WP di base ed è di per sé basso integrato con la gerarchia dei modelli.

L'approccio che voglio condividere si basa su una singola classe (è una versione ridotta da qualcosa su cui sto lavorando) che si occupa dei dati di rendering per i modelli.

Ha alcune caratteristiche interessanti (IMO):

  • i modelli sono file standard di WordPress (single.php, page.php) e ottengono un po 'più di potenza
  • i modelli esistenti funzionano, quindi puoi integrare modelli da temi esistenti senza alcuno sforzo
  • a differenza di dell'approccio @kaiser , nei template accedi alle variabili usando la $thisparola chiave: questo ti dà la possibilità di evitare avvisi in produzione in caso di variabili non definite

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

(Disponibile come Gist qui.)

Come usare

L'unica cosa necessaria è chiamare il Engine::init()metodo, probabilmente 'template_redirect'agganciato. Questo può essere fatto in tema functions.phpo da un plugin.

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

È tutto.

I tuoi modelli esistenti funzioneranno come previsto. Ma ora hai la possibilità di accedere ai dati del modello personalizzato.

Dati modello personalizzati

Per passare i dati personalizzati ai modelli ci sono due filtri:

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

Il primo viene attivato per tutti i modelli, il secondo è specifico del modello, infatti la parte dinamica {$type}è il nome di base del file modello senza estensione.

Ad esempio, il filtro 'gm_template_data_single'può essere utilizzato per passare dati alsingle.php modello.

I callback associati a questi hook devono restituire un array , in cui le chiavi sono i nomi delle variabili.

Ad esempio, puoi trasmettere metadati come piace ai dati dei modelli:

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 poi, all'interno del modello puoi semplicemente usare:

<?= $this->extra_title ?>

Modalità di debug

Quando entrambe le costanti WP_DEBUGe WP_DEBUG_DISPLAYsono vere, la classe funziona in modalità debug. Significa che se non viene definita una variabile viene generata un'eccezione.

Quando la classe non è in modalità debug (probabilmente in produzione), l'accesso a una variabile non definita genererà una stringa vuota.

Modelli di dati

Un modo piacevole e manutenibile per organizzare i tuoi dati è usare le classi del modello.

Possono essere classi molto semplici, che restituiscono dati utilizzando gli stessi filtri sopra descritti. Non esiste un'interfaccia particolare da seguire, possono essere organizzati secondo le tue preferenze.

Di seguito, c'è solo un esempio, ma sei libero di fare a modo tuo.

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

Il __invoke()metodo (che viene eseguito quando una classe viene utilizzata come un callback) restituisce una stringa da utilizzare per il <title>tag del modello.

Grazie al fatto che il secondo argomento passato 'gm_template_data'è il nome del modello, il metodo restituisce un titolo personalizzato per la home page.

Avendo il codice sopra, è quindi possibile usare qualcosa di simile

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

nella <head>sezione della pagina.

parziali

WordPress ha funzioni simili get_header()o get_template_part()che possono essere utilizzate per caricare i parziali nel modello principale.

Queste funzioni, proprio come tutte le altre funzioni di WordPress, possono essere utilizzate nei modelli quando si utilizza la Engineclasse.

L'unico problema è che all'interno dei parziali caricati utilizzando le funzioni principali di WordPress non è possibile utilizzare la funzionalità avanzata di ottenere dati modello personalizzati utilizzando $this.

Per questo motivo, la Engineclasse ha un metodo partial()che consente di caricare un parziale (in modo completamente compatibile con temi secondari) e di poter comunque utilizzare in parziali i dati del modello personalizzato.

L'utilizzo è piuttosto semplice.

Supponendo che ci sia un file chiamato partials/content.phpall'interno della cartella del tema (o tema figlio), può essere incluso usando:

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

All'interno di quel parziale sarà possibile accedere a tutti i dati del tema principale allo stesso modo.

A differenza delle funzioni di WordPress, il Engine::partial()metodo consente di trasmettere dati specifici ai parziali, semplicemente passando una matrice di dati come secondo argomento.

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

Per impostazione predefinita, i parziali hanno accesso ai dati disponibili nel tema principale e alla spiegazione dei dati trasmessa.

Se una variabile passata in modo esplicito al parziale ha lo stesso nome di una variabile del tema principale, allora vince la variabile passata in modo esplicito.

Tuttavia, è anche possibile includere un parziale in modalità isolata , ovvero il parziale non ha accesso ai dati del tema principale. Per fare ciò, basta passare truecome terzo argomento a partial():

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

Conclusione

Anche se piuttosto semplice, la Engineclasse è piuttosto completa, ma sicuramente può essere ulteriormente migliorata. Ad esempio, non è possibile verificare se una variabile è definita o meno.

Grazie alla sua compatibilità al 100% con le funzionalità di WordPress e la gerarchia dei modelli, puoi integrarlo senza problemi con codice esistente e di terze parti.

Tuttavia, tieni presente che è stato testato solo parzialmente, quindi è possibile che non ci siano ancora problemi rilevati.

I cinque punti in "Che cosa abbiamo guadagnato?" nella risposta di @kaiser :

  1. Scambia facilmente i modelli senza modificare la struttura dei dati
  2. Abbia leggere tempaltes
  3. Evitare l'ambito globale
  4. Can Unit-Test
  5. Può scambiare il Modello / i dati senza danneggiare altri componenti

sono tutti validi anche per la mia classe.


1
Hehe. Ben fatto, amico :) +1
kaiser

@gmazzap quasi 3 anni dopo, ci sono aggiornamenti sul tuo pensiero sopra? Il modello di core WP non è davvero progredito in una direzione più avanzata, quindi le soluzioni di terze parti sono ancora una cosa.
lkraav il

1
Non lavoro molti temi in questi giorni. Ultimamente la mia strada da percorrere era combinare github.com/Brain-WP/Context + github.com/Brain-WP/Hierarchy per creare dati e passare a modelli. Per lo stesso template engine, ho usato approcci diversi, Foil (ovviamente), Moustache, ma anche Twig (solo quando avevo il controllo su tutto il webiste per evitare l'inferno delle dipendenze) @lkraav
gmazzap

5

Risposta semplice, non passare variabili da nessuna parte perché puzza di usare variabili globali che sono cattive.

Dal tuo esempio sembra che tu stia provando a fare un'ottimizzazione precoce, ancora un altro male;)

Usa l'API wordpress per ottenere i dati archiviati nel DB e non cercare di superare in astuzia e ottimizzarne l'utilizzo poiché l'API fa di più quindi semplicemente recuperando valori e attiva filtri e azioni. Rimuovendo la chiamata API rimuovi la capacità di altri sviluppatori di cambiare il comportamento del tuo codice senza modificarlo.


2

Anche se la risposta di Kaiser è tecnicamente corretta, dubito che sia la risposta migliore per te.

Se stai creando il tuo tema, penso che sia davvero il modo migliore per impostare una sorta di framework usando le classi (e forse anche spazi dei nomi e interfacce, anche se potrebbe essere un po 'troppo per un tema WP).

D'altra parte, se stai solo estendendo / adattando un tema esistente e hai solo bisogno di passare una o poche variabili, penso che dovresti restare global. Poiché header.phpè incluso in una funzione, le variabili dichiarate in quel file sono utilizzabili solo in quel file. Con globalte li rendi accessibili nell'intero progetto WP:

In header.php:

global $page_extra_title;

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

In single.php(ad esempio):

global $page_extra_title;

var_dump( $page_extra_title );

3
Non voglio essere scortese o altro, ma è davvero una brutta pratica immergersi nell'ambito globale. Si dovrebbe evitare di aggiungere completamente all'ambito globale. Devi stare molto attento alle convenzioni di denominazione qui e devi assicurarti che il tuo nome di variabile sarà univoco in modo tale che nessun altro possa riprodurre un tale nome. L'approccio @kaiser potrebbe sembrare esagerato per te, ma è di gran lunga il migliore e il più sicuro. Non posso dirti come affrontarlo, ma ti consiglio davvero di rimanere fuori dall'ambito globale :-)
Pieter Goosen,

3
Ovviamente devi stare attento a non sovrascrivere altre variabili. Puoi affrontarlo usando un prefisso univoco o un array con le tue variabili personalizzate, $wp_theme_vars_page_extra_titleo $wp_theme_vars['page_extra_title']per esempio. Era solo una spiegazione del perché il globale avrebbe funzionato qui. OP ha chiesto un modo per passare una variabile attraverso tutti i file, usando globalè un modo per farlo.
redelschaap,

2
No, i globi non sono un modo per farlo. Ci sono modi molto migliori per ottenere lo stesso senza usare i globi. Come ho detto prima, e come ha affermato @kaiser nella sua risposta, evita l'ambito globale e mantienilo fuori. Ad esempio, prendi questa alternativa molto semplice, racchiudi il tuo codice in una funzione e chiama la funzione dove necessario. In questo modo, non è necessario impostare o utilizzare un globale.
Pieter Goosen,

3
Sì. Potrebbe non essere il modo migliore, ma è sicuramente un modo.
redelschaap,

2
but it is really bad practice diving into the global scopeVorrei che qualcuno lo avesse detto agli sviluppatori core di WP. Non capisco davvero il punto di usare spazi dei nomi, astrazione dei dati, modelli di progettazione, unit test e altre migliori pratiche / tecniche di programmazione nel codice scritto per Wordpress quando il core di Wordpress è disseminato di cattive pratiche di codifica come le variabili glabal (ad esempio, i widget codice).
Ejaz,

1

Una soluzione semplice è scrivere una funzione per ottenere il titolo aggiuntivo. Uso una variabile statica per mantenere le chiamate del database a una sola. Inseriscilo nelle tue funzioni.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;
}

All'esterno di header.php, chiama la funzione per ottenere il valore:

var_dump(get_extra_title($post->ID));
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.