Bester Weg, vorbei PHP-Variable zwischen partials?

16

Ich habe eine Variable in header.php, wie zum Beispiel:

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

Sobald ich tun:

var_dump($page_extra_title);

Ich komme immer NULLaußerhalb von header.php (var_dump funktioniert nur in header.php richtig). Ich habe dieselbe Variable überall dort eingefügt, wo ich sie benötige (page.php, post.php, footer.php usw.), aber das ist Wahnsinn und macht es fast unmöglich, alles zu pflegen.

Ich frage mich, wie ich eine Variable am besten durch alle Dateien in meinem Design leiten kann. Ich vermute, dass die Verwendung von functions.php zusammen mit "get_post_meta" nicht die beste Idee ist. :)

Wordpressor
quelle
Ich denke, die Variable liegt im selben Bereich, und ich möchte aus offensichtlichen Gründen vermeiden, GLOBAL zu verwenden.
Wordpressor
Ich glaube, Ialocins Kommentar ist genau richtig. Ein PHP-Skript weiß nicht, dass das andere existiert, und kann nicht auf die lokalen Variablen oder deren Werte zugreifen.
jdm2112
1
Kopf- und Fußzeile werden über eine Funktion eingebunden, sodass der Umfang aller in diesen Dateien enthaltenen Informationen dem Funktionsumfang entspricht.
Milo
4
Schieße nicht auf den Boten :) Das Einzige, was ich gesagt habe, ist, dass es sich tatsächlich um ein Zielfernrohrproblem handelt. Es gibt einen Weg global, oder? Aber es kommt aus guten Gründen nicht in Frage. Außerdem müssen Sie auch globalVariablen "aufrufen" , indem Sie das Schlüsselwort verwenden, um sie verfügbar zu machen. Je nach Anwendungsfall Sitzungen könnte eine Lösung sein. Ansonsten - wie bereits erwähnt - denke ich, dass eine Funktion oder eine Klasse, die den Job für Sie erledigt, der richtige Weg ist.
Nicolai

Antworten:

10

Grundlegende getrennte Datenstrukturen

Um Daten weiterzugeben, verwenden Sie normalerweise ein Modell (das ist das "M" in "MVC"). Schauen wir uns eine sehr einfache Schnittstelle für Daten an. Schnittstellen werden nur als "Rezepte" für unsere Bausteine ​​verwendet:

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

Oben ist, was wir herumreichen: Eine gemeinsame ID und ein "Label".

Anzeigen von Daten durch Kombinieren von Atomteilen

Als nächstes benötigen wir eine Ansicht , die zwischen unserem Modell und ... unserer Vorlage verhandelt.

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

Grundsätzlich sagt diese Schnittstelle

"Wir können etwas rendern und ein Modell ist für diese Aufgabe obligatorisch."

Schließlich müssen wir oben implementieren und die tatsächliche Ansicht erstellen . Wie Sie sehen, gibt der Konstruktor an, dass für unsere Ansicht eine Vorlage obligatorisch ist und dass wir sie rendern können. Um die Entwicklung zu vereinfachen, prüfen wir sogar, ob die Vorlagendatei tatsächlich vorhanden ist, damit wir anderen Entwicklern das Leben (und auch uns) erheblich erleichtern und dies beachten können.

In einem zweiten Schritt der Render-Funktion verwenden wir ein Closure , um den eigentlichen Template-Wrapper und bindTo()das Model für das Template zu erstellen .

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

Ansicht und Rendering trennen

Dies bedeutet, dass wir eine sehr einfache Vorlage wie die folgende verwenden können

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

um unsere Inhalte zu rendern. Wenn wir die Teile zusammenfügen, erhalten wir etwas in den folgenden Zeilen (in unserem Controller, Mediator usw.):

namespace WeCodeMore\Package;

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

Was haben wir gewonnen?

Auf diese Weise können wir

  1. Tauschen Sie Vorlagen einfach aus, ohne die Datenstruktur zu ändern
  2. Habe leicht zu lesende Tempaltes
  3. Vermeiden Sie globalen Geltungsbereich
  4. Kann Unit-Test
  5. Kann das Modell / die Daten austauschen, ohne andere Komponenten zu beschädigen

Kombination von OOP PHP mit der WP API

Natürlich ist dies kaum möglich , wie grundlegende Theming - Funktionalität get_header(), get_footer()etc., nicht wahr? Falsch. Rufen Sie einfach Ihre Klassen in der gewünschten Vorlage oder im gewünschten Vorlagenteil auf. Rendern Sie es, transformieren Sie die Daten und machen Sie, was Sie wollen. Wenn Sie wirklich nett sind, fügen Sie einfach Ihre eigenen benutzerdefinierten Filter hinzu und lassen Sie sich von einem Unterhändler anzeigen, was von welchem ​​Controller auf welcher Route bzw. mit welcher bedingten Vorlage gerendert wird.

Fazit?

Sie können problemlos mit den oben genannten Inhalten in WP arbeiten und sich dennoch an die grundlegende API halten und Code und Daten wiederverwenden, ohne ein einzelnes Global aufzurufen oder den globalen Namensraum zu beschädigen und zu verschmutzen.

Kaiser
quelle
3
Sieht großartig aus! Ich schaue mir das genauer an, nette Antwort!
Marko
@kaiser fast 3 Jahre später, gibt es irgendwelche Aktualisierungen Ihrer obigen Überlegungen? Das WP-Core-Templating hat sich nicht wirklich weiterentwickelt, daher sind Lösungen von Drittanbietern immer noch eine Sache.
lkraav
1
@Ikraav Ich würde es heutzutage wahrscheinlich nicht mehr so ​​schreiben, aber ich bin mir immer noch sicher, dass die Verwendung einer separaten Syntax zur Ausgabe des Inhalts von Variablen in HTML-Tags der richtige Weg ist (und unnötigen Overhead vermeidet). Andererseits schreibe ich heutzutage selten Frontend-Inhalte in PHP, sondern in JavaScript. Und ich mag wirklich, was VueJS und Freunde an den Tisch bringen.
Kaiser
11

Dies ist ein alternativer Ansatz zu @kaiser answer, den ich als ziemlich gut befunden habe (+1 von mir), der jedoch zusätzliche Arbeit erfordert, um mit den WP-Kernfunktionen verwendet zu werden, und der per se in die Vorlagenhierarchie integriert ist.

Der Ansatz, den ich teilen möchte, basiert auf einer einzelnen Klasse (es ist eine abgespeckte Version von etwas, an dem ich arbeite), die sich um das Rendern von Daten für Vorlagen kümmert.

Es hat einige (IMO) interessante Funktionen:

  • Vorlagen sind Standard-WordPress-Vorlagendateien (single.php, page.php), die etwas mehr Leistung bringen
  • vorhandene Vorlagen funktionieren einfach, sodass Sie ohne Aufwand Vorlagen aus vorhandenen Themen integrieren können
  • Im Gegensatz zum @ kaiser- Ansatz greifen Sie in Vorlagen mithilfe von $thisSchlüsselwörtern auf Variablen zu. Dadurch haben Sie die Möglichkeit, bei nicht definierten Variablen Benachrichtigungen in der Produktion zu vermeiden

Die EngineKlasse

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

(Hier als Gist erhältlich .)

Wie benutzt man

Sie müssen nur die Engine::init()Methode aufrufen , wahrscheinlich 'template_redirect'aufgelegt. Dies kann über ein Theme functions.phpoder über ein Plugin erfolgen.

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

Das ist alles.

Ihre vorhandenen Vorlagen funktionieren wie erwartet. Jetzt haben Sie jedoch die Möglichkeit, auf benutzerdefinierte Vorlagendaten zuzugreifen.

Benutzerdefinierte Vorlagendaten

Um benutzerdefinierte Daten an Vorlagen zu übergeben, gibt es zwei Filter:

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

Der erste wird für alle Vorlagen ausgelöst, der zweite ist vorlagenspezifisch. Der dynamische Teil {$type}ist der Basisname der Vorlagendatei ohne Dateierweiterung.

Mit dem Filter 'gm_template_data_single'können zB Daten an die single.phpVorlage übergeben werden.

Die an diese Hooks angehängten Rückrufe müssen ein Array zurückgeben , wobei die Schlüssel die Variablennamen sind.

Beispielsweise können Sie Metadaten wie folgt als Vorlagendaten übergeben:

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

Und dann können Sie innerhalb der Vorlage einfach Folgendes verwenden:

<?= $this->extra_title ?>

Debug-Modus

Wenn sowohl die Konstanten WP_DEBUGals WP_DEBUG_DISPLAYauch wahr sind, arbeitet die Klasse im Debug-Modus. Dies bedeutet, dass eine Ausnahme ausgelöst wird, wenn keine Variable definiert ist.

Wenn sich die Klasse nicht im Debug-Modus befindet (wahrscheinlich in der Produktion), wird beim Zugriff auf eine undefinierte Variable eine leere Zeichenfolge ausgegeben.

Datenmodelle

Eine schöne und verwaltbare Möglichkeit, Ihre Daten zu organisieren, ist die Verwendung von Modellklassen.

Dies können sehr einfache Klassen sein, die Daten mit den oben beschriebenen Filtern zurückgeben. Es gibt keine bestimmte Benutzeroberfläche, sie kann nach Ihren Wünschen organisiert werden.

Unten sehen Sie nur ein Beispiel, aber Sie können es auf Ihre eigene Weise tun.

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

Die __invoke()Methode (die ausgeführt wird, wenn eine Klasse wie ein Rückruf verwendet wird) gibt eine Zeichenfolge zurück, die für das <title>Tag der Vorlage verwendet werden soll.

Dank der Tatsache, dass das zweite übergebene Argument 'gm_template_data'der Vorlagenname ist, gibt die Methode einen benutzerdefinierten Titel für die Homepage zurück.

Mit dem obigen Code ist es dann möglich, so etwas zu verwenden

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

im <head>Abschnitt der Seite.

Teilstücke

WordPress verfügt über Funktionen wie get_header()oder get_template_part()die zum Laden von Partials in die Hauptvorlage verwendet werden können.

Diese Funktionen können, genau wie alle anderen WordPress-Funktionen, in Vorlagen verwendet werden, wenn die EngineKlasse verwendet wird.

Das einzige Problem ist, dass in den mit den WordPress-Kernfunktionen geladenen Partials die erweiterte Funktion zum Abrufen benutzerdefinierter Vorlagendaten nicht verwendet werden kann $this.

Aus diesem Grund verfügt die EngineKlasse über eine Methode partial(), mit der ein Teil geladen werden kann (vollständig untergeordnetes Thema) und die benutzerdefinierten Vorlagendaten weiterhin in Teilbereichen verwendet werden können.

Die Verwendung ist ziemlich einfach.

Angenommen, es gibt eine Datei mit dem Namen partials/content.phpim Ordner theme (oder child theme), kann sie mit folgendem Befehl eingefügt werden:

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

Innerhalb dieses Teils ist der Zugriff auf alle übergeordneten Themendaten auf dieselbe Weise möglich.

Im Gegensatz zu WordPress-Funktionen können mit dieser Engine::partial()Methode bestimmte Daten an Partials übergeben werden, indem einfach ein Array von Daten als zweites Argument übergeben wird.

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

Standardmäßig haben Partials Zugriff auf Daten, die im übergeordneten Design verfügbar sind, und auf Daten, die explizit übergeben wurden.

Wenn eine explizit an partial übergebene Variable denselben Namen wie eine übergeordnete Designvariable hat, gewinnt die explizit übergebene Variable.

Es ist jedoch auch möglich, einen Teil im isolierten Modus einzuschließen, dh der Teil hat keinen Zugriff auf übergeordnete Themendaten. Übergeben Sie dazu einfach truedas dritte Argument an partial():

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

Fazit

Auch wenn es ziemlich einfach ist, ist der EngineUnterricht ziemlich vollständig, kann aber sicherlich weiter verbessert werden. ZB gibt es keine Möglichkeit zu überprüfen, ob eine Variable definiert ist oder nicht.

Dank seiner 100% igen Kompatibilität mit WordPress-Funktionen und Vorlagenhierarchie können Sie es problemlos in vorhandenen Code und Code von Drittanbietern integrieren.

Beachten Sie jedoch, dass dies nur teilweise getestet wurde. Möglicherweise gibt es Probleme, die ich noch nicht entdeckt habe.

Die fünf Punkte unter "Was haben wir gewonnen?" in @kaiser Antwort :

  1. Tauschen Sie Vorlagen einfach aus, ohne die Datenstruktur zu ändern
  2. Habe leicht zu lesende Tempaltes
  3. Vermeiden Sie globalen Geltungsbereich
  4. Kann Unit-Test
  5. Kann das Modell / die Daten austauschen, ohne andere Komponenten zu beschädigen

sind auch für meine Klasse gültig.

gmazzap
quelle
1
Hehe Gut gemacht, Kumpel :) +1
Kaiser
@gmazzap fast 3 Jahre später, gibt es irgendwelche Aktualisierungen Ihrer obigen Überlegungen? Das WP-Core-Templating hat sich nicht wirklich weiterentwickelt, daher sind Lösungen von Drittanbietern immer noch eine Sache.
lkraav
1
Ich arbeite heutzutage nicht mehr mit vielen Themen. In letzter Zeit kombinierte ich github.com/Brain-WP/Context + github.com/Brain-WP/Hierarchy , um Daten zu erstellen und an Vorlagen zu übergeben. Für die Template - Engine selbst, habe ich verschiedene Ansätze, Folie (natürlich), Schnurrbart, sondern auch Zweig (nur wenn hatte ich die Kontrolle über die ganze Webiste zu vermeiden Abhängigkeit Hölle) @lkraav
gmazzap
5

Einfache Antwort: Übergeben Sie keine Variablen, da es stinkt, als würden Sie globale Variablen verwenden, was böse ist.

Deinem Beispiel nach scheint es so, als würdest du versuchen, eine frühe Optimierung vorzunehmen, noch ein Übel;)

Verwenden Sie die WordPress-API, um Daten abzurufen, die in der Datenbank gespeichert sind, und versuchen Sie nicht, deren Verwendung zu überlisten und zu optimieren, da die API nicht nur Werte abruft und Filter und Aktionen aktiviert. Durch Entfernen des API-Aufrufs entfernen Sie die Fähigkeit anderer Entwickler, das Verhalten Ihres Codes zu ändern, ohne ihn zu ändern.

Mark Kaplun
quelle
2

Obwohl Kaisers Antwort technisch richtig ist, bezweifle ich, dass es die beste Antwort für Sie ist.

Wenn Sie ein eigenes Thema erstellen, ist dies meiner Meinung nach der beste Weg, ein Framework mithilfe von Klassen einzurichten (und möglicherweise auch Namespaces und Interfaces, obwohl dies für ein WP-Thema möglicherweise etwas zu viel ist).

Wenn Sie jedoch nur ein vorhandenes Thema erweitern / anpassen und nur eine oder mehrere Variablen übergeben müssen, sollten Sie sich meiner Meinung nach daran halten global. Da header.phpin einer Funktion enthalten ist, können die Variablen, die Sie in dieser Datei deklarieren, nur in dieser Datei verwendet werden. Mit globalmachen Sie sie im gesamten WP-Projekt zugänglich:

In header.php:

global $page_extra_title;

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

In single.php(zum Beispiel):

global $page_extra_title;

var_dump( $page_extra_title );
redelschaap
quelle
3
Ich möchte nicht unhöflich oder so sein, aber es ist wirklich eine schlechte Übung, in den globalen Bereich einzutauchen. Sie sollten es vermeiden, den globalen Bereich vollständig zu erweitern. Sie müssen hier besonders auf Namenskonventionen achten und sicherstellen, dass Ihr Variablenname so eindeutig ist, dass kein anderer Benutzer einen solchen Namen reproduzieren kann. Der @ kaiser-Ansatz scheint für Sie über Bord zu sein, ist aber bei weitem der beste und sicherste. Ich kann Ihnen nicht sagen, wie Sie das angehen sollen, aber ich rate Ihnen wirklich, sich aus dem globalen Rahmen herauszuhalten :-)
Pieter Goosen,
3
Natürlich müssen Sie darauf achten, dass Sie keine anderen Variablen überschreiben. Sie können dies beheben, indem Sie ein eindeutiges Präfix oder ein Array mit Ihren benutzerdefinierten Variablen verwenden, $wp_theme_vars_page_extra_titleoder $wp_theme_vars['page_extra_title']zum Beispiel. Es war nur eine Erklärung, warum global hier funktionieren würde. OP hat nach einer Möglichkeit gefragt, wie eine Variable durch alle Dateien geleitet werden kann global.
Redelschaap,
2
Nein, Globals ist kein Weg, das zu tun. Es gibt weitaus bessere Möglichkeiten, um dasselbe zu erreichen, ohne Globals zu verwenden. Vermeiden Sie, wie ich bereits sagte und wie @kaiser in seiner Antwort feststellte, den globalen Geltungsbereich und meiden Sie ihn. Nehmen Sie als Beispiel diese sehr einfache Alternative, binden Sie Ihren Code in eine Funktion ein und rufen Sie die Funktion bei Bedarf auf. Auf diese Weise müssen Sie kein globales festlegen oder verwenden.
Pieter Goosen
3
Ja ist es. Es ist vielleicht nicht der beste Weg, aber es ist definitiv ein Weg.
Redelschaap
2
but it is really bad practice diving into the global scopeIch wünschte, jemand hätte das den Entwicklern von WP Core erzählt. Ich verstehe den Sinn der Verwendung von Namespaces, Datenabstraktion, Entwurfsmustern, Komponententests und anderen bewährten Methoden / Techniken für die Programmierung in Code, der für Wordpress geschrieben wurde, nicht, wenn der Wordpress-Kern mit schlechten Codierungsmethoden wie glabalen Variablen (z. B. den Widgets) übersät ist Code).
Ejaz,
1

Eine einfache Lösung besteht darin, eine Funktion zu schreiben, um den zusätzlichen Titel zu erhalten. Ich verwende eine statische Variable, um nur einen Datenbankaufruf zu speichern. Gib das in deine functions.php ein.

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

Rufen Sie außerhalb von header.php die Funktion auf, um den Wert zu erhalten:

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