Najlepszy sposób przekazywania zmiennej PHP między częściami?


16

Mam zmienną w header.php, taką jak:

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

Kiedy to zrobię:

var_dump($page_extra_title);

Zawsze NULLwychodzę poza head.php (var_dump działa poprawnie tylko w header.php). Wklejam tę samą zmienną wszędzie, gdzie jej potrzebuję (page.php, post.php, footer.php itp.), Ale jest to szaleństwo i sprawia, że ​​wszystko jest prawie niemożliwe do utrzymania.

Zastanawiam się, jaki jest najlepszy sposób przekazania zmiennej przez wszystkie pliki w moim motywie? Chyba użycie funkcji.php razem z „get_post_meta” może nie być najlepszym pomysłem? :)



Myślę, że zmienna ma ten sam zakres, a także chcę unikać używania GLOBAL z oczywistych powodów.
Wordpressor

Uważam, że komentarz ialocina jest trafny. Jeden skrypt PHP nie wie, że drugi istnieje i nie może uzyskać dostępu do jego lokalnych zmiennych ani ich wartości.
jdm2112

1
nagłówek i stopka są zawarte w funkcji, więc zakres wszystkiego w tych plikach jest zasięgiem tych funkcji.
Milo,

4
Nie strzelaj do posłańca :) Jedyne, co powiedziałem, to rzeczywiście problem z lunetą. Jest sposób global, prawda? Ale nie ma o tym dobrych powodów. Poza tym trzeba też „wywoływać” globalzmienne, używając słowa kluczowego, aby je udostępnić. W zależności od przypadku przypadki użycia mogą być rozwiązaniem. W przeciwnym razie - jak wspomniano - myślę, że funkcja lub klasa, która wykonuje dla ciebie pracę, jest właściwą drogą.
Nicolai

Odpowiedzi:


10

Podstawowe oddzielne struktury danych

Aby przekazywać dane, zwykle używasz modelu (to jest „M” w „MVC”). Spójrzmy na bardzo prosty interfejs danych. Interfejsy są po prostu używane jako „Przepisy” dla naszych bloków konstrukcyjnych:

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

Powyżej znajduje się to, co przekazujemy: wspólny identyfikator i „etykieta”.

Wyświetlanie danych poprzez łączenie atomów

Następnie potrzebujemy trochę widoku, który negocjuje między naszym modelem a ... naszym szablonem.

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

Zasadniczo mówi interfejs

„Możemy coś renderować, a model jest obowiązkowy dla tego zadania”

Wreszcie musimy wdrożyć powyższe i zbudować rzeczywisty Widok . Jak widać, konstruktor mówi, że obowiązkową cechą naszego widoku jest szablon i że możemy go renderować. W celu łatwego programowania sprawdzamy nawet, czy plik szablonu rzeczywiście jest obecny, dzięki czemu możemy ułatwić życie innym programistom (i naszym), i zauważmy to.

W drugim kroku w funkcji renderowania używamy zamknięcia do zbudowania faktycznego opakowania szablonu i bindTo()modelu do szablonu.

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

Rozdzielanie widoku i renderowania

Oznacza to, że możemy użyć bardzo prostego szablonu, takiego jak poniższy

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

do renderowania naszych treści. Łącząc elementy, uzyskamy coś wokół następujących linii (w naszym Kontrolerze, Mediatorze itp.):

namespace WeCodeMore\Package;

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

Co zyskaliśmy?

W ten sposób możemy

  1. Łatwo wymieniaj szablony bez zmiany struktury danych
  2. Czytelne tempaltes
  3. Unikaj globalnego zasięgu
  4. Może test jednostkowy
  5. Może wymieniać Model / dane bez szkody dla innych komponentów

Łączenie OOP PHP z WP API

Oczywiście jest to prawie niemożliwe stosując podstawową funkcjonalność Skórki jak get_header(), get_footer()itp, prawda? Źle. Po prostu zadzwoń do swoich klas w dowolnym szablonie lub części szablonu, którą chcesz. Renderuj, przekształć dane, rób co chcesz. Jeśli jesteś naprawdę miły, po prostu dodaj własny zestaw niestandardowych filtrów i poproś negocjatora, aby zadbał o to, co zostanie renderowane przez który kontroler na której trasie / szablon warunkowy zostanie załadowany.

Wniosek?

Możesz bezproblemowo pracować z takimi rzeczami jak powyżej w WP i nadal trzymać się podstawowego interfejsu API oraz ponownie wykorzystywać kod i dane bez wywoływania pojedynczego globalnego lub bałaganu i zanieczyszczania globalnej przestrzeni nazw.


3
Wygląda świetnie! Przyjrzę się temu, ładna odpowiedź!
marko

@kaiser prawie 3 lata później, czy są jakieś aktualizacje do twojego myślenia powyżej? Tworzenie szablonów WP nie rozwinęło się tak naprawdę w żadnym bardziej zaawansowanym kierunku, więc rozwiązania innych firm nadal są sprawą.
lkraav,

1
@Ikraav Prawdopodobnie nie napisałbym tego w dzisiejszych czasach, ale nadal jestem pewien, że niestosowanie osobnej składni do wyświetlania zawartości zmiennych wewnątrz tagów HTML jest dobrym rozwiązaniem (i pozwala uniknąć niepotrzebnego narzutu). Z drugiej strony obecnie rzadko piszę frontend w PHP, ale w JavaScript. I bardzo podoba mi się to, co przynoszą VueJS i przyjaciele.
kaiser

11

Jest to alternatywne podejście do odpowiedzi @kaiser , które uważam za całkiem w porządku (+1 ode mnie), ale wymaga dodatkowej pracy do użycia z podstawowymi funkcjami WP i samo w sobie jest zintegrowane z hierarchią szablonów.

Podejście, które chcę udostępnić, opiera się na jednej klasie (jest to wersja uproszczona z czegoś, nad czym pracuję), który zajmuje się renderowaniem danych dla szablonów.

Ma kilka interesujących funkcji (IMO):

  • Szablony to standardowe pliki szablonów WordPress (single.php, page.php), które zyskują nieco więcej mocy
  • istniejące szablony po prostu działają, dzięki czemu można bez problemu zintegrować szablony z istniejących motywów
  • w przeciwieństwie do podejścia @kaiser , w szablonach uzyskuje się dostęp do zmiennych za pomocą $thissłowa kluczowego: daje to możliwość uniknięcia powiadomień podczas produkcji w przypadku niezdefiniowanych zmiennych

EngineClass

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

(Dostępne tutaj jako Gist .)

Jak używać

Jedyne, czego potrzeba, to wywołanie Engine::init()metody, prawdopodobnie po 'template_redirect'podniesieniu słuchawki. Można to zrobić w temacie functions.phplub z wtyczki.

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

To wszystko.

Twoje istniejące szablony będą działać zgodnie z oczekiwaniami. Ale teraz masz możliwość dostępu do niestandardowych danych szablonu.

Niestandardowe dane szablonu

Do przekazywania niestandardowych danych do szablonów służą dwa filtry:

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

Pierwszy jest uruchamiany dla wszystkich szablonów, drugi jest specyficzny dla szablonu, w rzeczywistości część dymamiczna {$type}jest baząame pliku szablonu bez rozszerzenia pliku.

Np. Filtr 'gm_template_data_single'może służyć do przekazywania danych do single.phpszablonu.

Oddzwanianie dołączone do tych haków musi zwrócić tablicę , w której kluczami są nazwy zmiennych.

Na przykład możesz przekazywać metadane jako dane szablonu, więc:

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

A następnie w szablonie możesz po prostu użyć:

<?= $this->extra_title ?>

Tryb debugowania

Gdy obie stałe WP_DEBUGi WP_DEBUG_DISPLAYsą prawdziwe, klasa działa w trybie debugowania. Oznacza to, że jeśli zmienna nie jest zdefiniowana, generowany jest wyjątek.

Gdy klasa nie znajduje się w trybie debugowania (prawdopodobnie w środowisku produkcyjnym), dostęp do niezdefiniowanej zmiennej spowoduje wyświetlenie pustego ciągu.

Modele danych

Dobrym i łatwym w utrzymaniu sposobem organizacji danych jest użycie klas modeli.

Mogą to być bardzo proste klasy, które zwracają dane przy użyciu tych samych filtrów opisanych powyżej. Nie ma konkretnego interfejsu do naśladowania, mogą być zorganizowane zgodnie z twoimi preferencjami.

Belowe, jest tylko przykład, ale możesz to zrobić na swój własny sposób.

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

__invoke()Metoda (to działa, gdy klasa jest używana jak callback) zwraca ciąg być stosowane do <title>tagu szablonu.

Dzięki temu, że drugim przekazywanym argumentem 'gm_template_data'jest nazwa szablonu, metoda zwraca niestandardowy tytuł strony głównej.

Mając powyższy kod, można wtedy użyć czegoś takiego

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

w <head>sekcji strony.

Częściowe

WordPress posiada funkcje podobne get_header()lub get_template_part()które mogą być wykorzystane do załadowania partials w głównym szablonie.

Te funkcje, podobnie jak wszystkie inne funkcje WordPress, mogą być używane w szablonach podczas korzystania z Engineklasy.

Jedyny problem polega na tym, że w częściach załadowanych przy użyciu podstawowych funkcji WordPress nie można użyć zaawansowanej funkcji uzyskiwania niestandardowych danych szablonu $this.

Z tego powodu Engineklasa ma metodę, partial()która pozwala na załadowanie częściowego (w sposób w pełni zgodny z motywem potomnym) i nadal jest w stanie używać częściowo niestandardowych danych szablonu.

Użycie jest dość proste.

Zakładając, że istnieje plik o nazwie partials/content.phpwewnątrz folderu motywu (lub motywu potomnego), można go dołączyć, używając:

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

Wewnątrz tej częściowej będzie można uzyskać dostęp do wszystkich danych motywu nadrzędnego w ten sam sposób.

W przeciwieństwie do funkcji WordPress, Engine::partial()metoda pozwala przekazywać określone dane do częściowych, po prostu przekazując tablicę danych jako drugi argument.

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

Domyślnie częściowe mają dostęp do danych dostępnych w motywie nadrzędnym i do przekazywanych danych wyjaśniających.

Jeśli jakaś zmienna jawnie przekazana do częściowej ma taką samą nazwę nadrzędnej zmiennej motywu, wówczas zmienna jawnie przekazana wygrywa.

Jednak możliwe jest również włączenie częściowego w trybie izolowanym , tj. Częściowy nie ma dostępu do danych motywu nadrzędnego. Aby to zrobić, wystarczy przekazać truetrzeci argument do partial():

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

Wniosek

Nawet jeśli dość prosta, Engineklasa jest dość kompletna, ale z pewnością można ją jeszcze ulepszyć. Np. Nie ma możliwości sprawdzenia, czy zmienna jest zdefiniowana, czy nie.

Dzięki 100% kompatybilności z funkcjami WordPress i hierarchią szablonów możesz bez problemu zintegrować go z istniejącym i zewnętrznym kodem.

Należy jednak pamiętać, że jest tylko częściowo testowany, więc możliwe są problemy, których jeszcze nie odkryłem.

Pięć punktów w części „Co zyskaliśmy?” w odpowiedzi @kaiser :

  1. Łatwo wymieniaj szablony bez zmiany struktury danych
  2. Czytelne tempaltes
  3. Unikaj globalnego zasięgu
  4. Może test jednostkowy
  5. Może wymieniać Model / dane bez szkody dla innych komponentów

są również ważne dla mojej klasy.


1
Hehe Dobra robota, kolego :) +1
kaiser

@gmazzap prawie 3 lata później, czy są jakieś aktualizacje do twojego myślenia powyżej? Tworzenie szablonów WP nie rozwinęło się tak naprawdę w żadnym bardziej zaawansowanym kierunku, więc rozwiązania innych firm nadal są sprawą.
lkraav,

1
Obecnie niewiele motywów działa. Ostatnio moją drogą było połączenie github.com/Brain-WP/Context + github.com/Brain-WP/Hierarchy do tworzenia danych i przekazywania szablonów. Do samego silnika szablonów użyłem różnych podejść, Foli (oczywiście), Wąsów, ale także Gałązki (tylko gdy miałem kontrolę nad całą stroną, aby uniknąć zależności piekła) @lkraav
gmazzap

5

Prosta odpowiedź, nigdzie nie przesyłaj zmiennych, ponieważ śmierdzi używaniem zmiennych globalnych, które są złe.

Z twojego przykładu wydaje się, że próbujesz dokonać wczesnej optymalizacji, jeszcze jednego zła;)

Użyj wordpress API, aby uzyskać dane przechowywane w DB i nie próbuj przechytrzyć i zoptymalizować jego użycie, ponieważ API robi więcej niż tylko pobieranie wartości i aktywuje filtry i akcje. Usuwając wywołanie API, usuwasz zdolność innych programistów do zmiany zachowania twojego kodu bez modyfikacji.


2

Chociaż odpowiedź Kaiser jest technicznie poprawna, wątpię, by była dla ciebie najlepszą odpowiedzią.

Jeśli tworzysz własny motyw, myślę, że to naprawdę najlepszy sposób na skonfigurowanie jakiegoś frameworka za pomocą klas (a może także przestrzeni nazw i interfejsów, chociaż może to być trochę za dużo dla motywu WP).

Z drugiej strony, jeśli tylko poszerzasz / dostosowujesz istniejący motyw i potrzebujesz tylko przekazać jedną lub kilka zmiennych, myślę, że powinieneś się trzymać global. Ponieważ header.phpjest zawarty w funkcji, zmienne zadeklarowane w tym pliku są użyteczne tylko w tym pliku. Po globaludostępnieniu ich w całym projekcie WP:

W header.php:

global $page_extra_title;

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

W single.php(na przykład):

global $page_extra_title;

var_dump( $page_extra_title );

3
Nie chcę być niegrzeczny ani nic, ale nurkowanie w skali globalnej to naprawdę zła praktyka. Należy unikać całkowitego dodawania do zakresu globalnego. Musisz bardzo uważać na konwencje nazewnictwa tutaj i musisz upewnić się, że nazwa zmiennej będzie unikalna w taki sposób, że nikt inny nie będzie w stanie odtworzyć takiej nazwy. Podejście @kaiser może wydawać Ci się przesadzone, ale jest zdecydowanie najlepsze i najbezpieczniejsze. Nie umiem powiedzieć, jak sobie z tym poradzić, ale naprawdę radzę, abyście pozostali poza zasięgiem globalnym :-)
Pieter Goosen 15.04.15

3
Oczywiście musisz uważać, aby nie zastąpić innych zmiennych. Można rozwiązać, że stosując unikalny prefiks lub tablicę z niestandardowych zmiennych, $wp_theme_vars_page_extra_titlelub $wp_theme_vars['page_extra_title']na przykład. To było tylko wyjaśnienie, dlaczego globalny miałby tutaj działać. OP poprosił o sposób przepuszczenia zmiennej przez wszystkie pliki, używając globaljest na to sposób.
redelschaap

2
Nie, globals nie jest na to sposobem. Istnieją znacznie lepsze sposoby na osiągnięcie tego samego bez użycia globałów. Jak powiedziałem wcześniej i jak stwierdził @kaiser w swojej odpowiedzi, unikaj globalnego zasięgu i trzymaj się od niego z daleka. Jako przykład weź tę bardzo łatwą alternatywę, zawiń swój kod w funkcję i wywołaj funkcję w razie potrzeby. W ten sposób nie musisz ustawiać ani używać globalnego.
Pieter Goosen

3
Tak to jest. To może nie być najlepszy sposób, ale zdecydowanie jest to sposób.
redelschaap

2
but it is really bad practice diving into the global scopeChciałbym, żeby ktoś powiedział to deweloperom WP. Naprawdę nie rozumiem sensu używania przestrzeni nazw, abstrakcji danych, wzorców projektowych, testów jednostkowych i innych najlepszych praktyk / technik programistycznych w kodzie napisanym dla Wordpress, gdy rdzeń Wordpress jest zaśmiecony złymi praktykami kodowania, takimi jak zmienne glabal (np. Widżety kod).
Ejaz

1

Łatwym rozwiązaniem jest napisanie funkcji, aby uzyskać dodatkowy tytuł. Używam zmiennej statycznej, aby zachować wywołania bazy danych tylko do jednego. Umieść to w swojej funkcji.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;
}

Poza header.php, wywołaj funkcję, aby uzyskać wartość:

var_dump(get_extra_title($post->ID));
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.