Найкращий спосіб передачі змінної PHP між партіями?


16

У мене є змінна в header.php, наприклад:

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

Як тільки я це роблю:

var_dump($page_extra_title);

Я завжди отримую NULL за межі header.php (var_dump працює належним чином лише у header.php). Я вставляв ту саму змінну всюди, де мені це потрібно (page.php, post.php, footer.php тощо), але це безумство і робить все майже неможливим для підтримки.

Мені цікаво, який найкращий спосіб пропустити змінну через усі файли моєї теми? Я думаю, що використання function.php разом із "get_post_meta" може бути не найкращою ідеєю? :)



Я думаю, що змінна знаходиться в тій самій області, також я хочу уникати використання GLOBAL з очевидних причин.
Wordpressor

Я вважаю, що коментар Ялоцина є місцем. Один скрипт PHP не знає, що інший існує, і він не може отримати доступ до його локальних змінних або їх значень.
jdm2112

1
заголовок і колонтитул включаються через функцію, тому обсяг у всіх файлах - це сфери дії цих функцій.
Міло,

4
Не стріляйте в месенджер :) Єдине, що я сказав - це справді проблема з масштабами. Є спосіб, так global, правда? Але це не можна з поважних причин. Крім того, вам також доведеться "дзвонити" global-змінними, використовуючи ключове слово, щоб зробити їх доступними. Залежно від випадку використання сесій може бути рішенням. Інакше - як уже згадувалося - я думаю, що функція чи клас, який виконує цю роботу для вас, - це шлях.
Миколай

Відповіді:


10

Основні розділені структури даних

Для передачі даних зазвичай використовується Модель (це "M" у "MVC"). Давайте розглянемо дуже простий інтерфейс для даних. Інтерфейси просто використовуються як "Рецепти" для наших будівельних блоків:

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

Зверху - те, що ми обходимо: загальний ідентифікатор та "мітка".

Відображення даних шляхом комбінування атомних частин

Далі нам потрібен перегляд, який узгоджує нашу модель та ... наш шаблон.

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

В основному, що говорить інтерфейс

"Ми можемо щось зробити, і модель є обов'язковою для цього завдання"

Нарешті нам потрібно здійснити вище і побудувати фактичний вигляд . Як бачите, конструктор розповідає, що обов’язковою для нашого перегляду є Шаблон і що ми можемо його надати. Для простої розробки ми навіть перевіряємо, чи файл шаблонів насправді присутній, щоб ми могли зробити життя інших розробників (і наше також) набагато простішим і відзначимо це.

На другому кроці функції візуалізації ми використовуємо закриття для побудови фактичної обгортки шаблону та bindTo()моделі до шаблону.

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

Розділення перегляду та візуалізації

Це означає, що ми можемо використовувати дуже простий шаблон на зразок наступного

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

щоб надати наш вміст. Збираючи шматки, ми отримаємо щось у наступних рядках (у нашому контролері, посереднику тощо):

namespace WeCodeMore\Package;

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

Що ми здобули?

Таким чином ми можемо

  1. Легко обмінюватися шаблонами без зміни структури даних
  2. Майте легко читати шаблони
  3. Уникайте глобальних масштабів
  4. Може блок-тест
  5. Можна обміняти модель / дані, не завдаючи шкоди іншим компонентам

Поєднання OOP PHP з WP API

Звичайно , це навряд чи можливо, використовуючи базову функціональність тематизації , як get_header(), get_footer()і т.д., НЕ так? Неправильно. Просто зателефонуйте своїм класам у будь-який шаблон або частину шаблону, який би ви хотіли. Візуалізуйте, перетворіть дані, робіть все, що завгодно. Якщо ви справді приємні, ви навіть просто додаєте власну купу спеціальних фільтрів і маєте переговорника, який повинен подбати про те, що отримує який контролер, на який маршрут / умовний шаблон завантажується.

Висновок?

Ви можете без проблем працювати з подібними речами в WP і все одно дотримуватися основного API та повторно використовувати код і дані, не викликаючи жодного глобального чи не заплутавшись та забруднюючи глобальний простір імен.


3
Виглядає здорово! Я детальніше розберуся на цю приємну відповідь!
marko

@kaiser майже через 3 роки, чи є якісь оновлення вашого мислення вище? Шаблонування ядра WP насправді не прогресувало в більш розвиненому напрямку, тому сторонні рішення все ще є річчю.
lkraav

1
@Ikraav Я, мабуть, не писав би це так сьогодні, але я все одно впевнений, що не використовувати окремий синтаксис для виведення вмісту змінних всередині тегів HTML - це шлях (і уникає зайвих накладних витрат). З іншого боку, сьогодні я рідко пишу фронтальні матеріали в PHP, але в JavaScript. І мені дуже подобається те, що VueJS та друзі подають до столу.
кайзер

11

Це альтернативний підхід до @kaiser відповіді , що я виявив досить добре (+1 від мене), але вимагає додаткової роботи, щоб використовувати основні функції WP, і вона сама по собі низька, інтегрована з ієрархією шаблонів.

Підхід, яким я хочу поділитися, ґрунтується на одному класі (це знята версія з чогось над чим я працюю), який піклується про рендеринг даних для шаблонів.

Він має деякі (IMO) цікаві особливості:

  • шаблони - це стандартні файли шаблонів WordPress (single.php, page.php), вони отримують трохи більше енергії
  • існуючі шаблони просто працюють, тому ви можете без особливих зусиль інтегрувати шаблон із існуючих тем
  • на відміну від @kaiser- підходу, у шаблонах ви отримуєте доступ до змінних за допомогою $thisключового слова: це дає можливість уникнути сповіщень у виробництві у випадку невизначених змінних

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

(Доступно як Gist тут.)

Як користуватись

Єдине, що потрібно - це викликати Engine::init()метод, ймовірно, на 'template_redirect'гачку. Це можна зробити в темі functions.phpабо з плагіна.

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

Це все.

Ваші наявні шаблони будуть працювати як очікувані. Але тепер у вас є можливість отримати доступ до даних спеціальних шаблонів.

Дані користувацьких шаблонів

Для передачі спеціальних даних у шаблони є два фільтри:

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

Перший запускається для всіх шаблонів, другий - специфічний для шаблону, насправді динамічна частина {$type}- це ім'я файлу шаблону без розширення файлу.

Наприклад, фільтр 'gm_template_data_single'може використовуватися для передачі даних доsingle.php шаблону.

Зворотні виклики, приєднані до цих гаків, повинні повертати масив , де ключі є іменами змінних.

Наприклад, ви можете передавати метадані у такий спосіб, як так:

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

І тоді всередині шаблону ви можете просто використовувати:

<?= $this->extra_title ?>

Режим налагодження

Коли і константи, WP_DEBUGі WP_DEBUG_DISPLAYістинні, клас працює в режимі налагодження. Це означає, що якщо змінна не визначена, викидається виняток.

Коли клас не знаходиться в режимі налагодження (можливо, у виробництві), доступ до невизначеної змінної виведе порожню рядок.

Моделі даних

Приємний та підтримуваний спосіб організації даних - це використання модельних класів.

Вони можуть бути дуже простими класами, які повертають дані за допомогою тих же фільтрів, що описані вище. Немає конкретного інтерфейсу, який слід керувати, їх можна організувати відповідно до ваших уподобань.

Нижче, є лише приклад, але ви вільні робити по-своєму.

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()Метод (який працює , коли клас використовується як зворотний виклик) повертає рядок , які будуть використовуватися для <title>тега шаблону.

Завдяки тому, що другий аргумент, який передається, 'gm_template_data'- це ім'я шаблону, метод повертає власний заголовок для домашньої сторінки.

Маючи код вище, тоді можна використовувати щось подібне

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

у <head>розділі сторінки.

Партії

WordPress має такі функції , як get_header()і get_template_part()що можна використовувати для завантаження партіалов в основний шаблон.

Ці функції, як і всі інші функції WordPress, можуть використовуватися в шаблонах при використанні Engineкласу.

Єдина проблема полягає в тому, що всередині частинок, завантажених за допомогою основних функцій WordPress, неможливо використовувати розширену функцію отримання даних користувальницького шаблону $this.

З цієї причини в Engineкласі є метод, partial()який дозволяє завантажити часткове (повністю сумісний з дочірньою темою спосіб) та все ще мати можливість використовувати в приватних даних користувальницькі шаблонні дані.

Використання досить просте.

Якщо припустимо, що у папці з partials/content.phpтемою (або дочірньою темою) є файл , його можна включити, використовуючи:

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

Всередині цього часткового можливо отримати доступ до всіх даних батьківських тем однаковим чином.

На відміну від функцій WordPress, Engine::partial()метод дозволяє передавати конкретні дані партизалам, просто передаючи масив даних як другий аргумент.

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

За замовчуванням партії мають доступ до даних, доступних у батьківській темі, та до переданих експлікацій даних.

Якщо якась змінна, явно передана частковій, має те саме ім'я батьківської тематичної змінної, то змінна, явно передана, перемагає.

Однак також можливо включити часткове в ізольованому режимі, тобто частковий не має доступу до даних батьківської теми. Для цього просто передайте trueтретій аргумент partial():

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

Висновок

Навіть якщо він досить простий, Engineклас досить повний, але, безумовно, його можна вдосконалити. Наприклад, немає можливості перевірити, чи визначена змінна чи ні.

Завдяки 100% сумісності з функціями WordPress та ієрархією шаблонів, ви можете інтегрувати його з існуючим та стороннім кодом без проблем.

Однак зауважте, що це лише частково перевірено, тому можливо, є проблеми, яких я ще не виявив.

П'ять балів у розділі "Що ми здобули?" у відповіді @kaiser :

  1. Легко обмінюватися шаблонами без зміни структури даних
  2. Майте легко читати шаблони
  3. Уникайте глобальних масштабів
  4. Може блок-тест
  5. Можна обміняти модель / дані, не завдаючи шкоди іншим компонентам

всі дійсні і для мого класу.


1
Хе-хе. Молодці, товариш :) +1
кайзер

@gmazzap майже через 3 роки, чи є якісь оновлення вашого мислення вище? Шаблонування ядра WP насправді не прогресувало в більш розвиненому напрямку, тому сторонні рішення все ще є річчю.
lkraav

1
Я не займаюся багатьма темами в ці дні. Останнім часом мій шлях поєднував github.com/Brain-WP/Context + github.com/Brain-WP/Hierarchy для збирання даних та переходу до шаблонів. Для самого механізму роботи з шаблонами я використовував різні підходи, Foil (звичайно), вуса, але також і Twig (лише тоді, коли я мав контроль над усією веб-системою, щоб уникнути пекла залежності) @lkraav
gmazzap

5

Проста відповідь, не передайте змінних ніде, оскільки це смердить від використання глобальних змінних, що є злом.

З вашого прикладу здається, що ви намагаєтеся зробити ранню оптимізацію, ще одне зло;)

Використовуйте Wordpress API, щоб отримати дані, що зберігаються в БД, і не намагайтеся перехитрити та оптимізувати його використання, оскільки API робить більше, ніж просто отримання значень, і він активує фільтри та дії. Видаляючи виклик API, ви видаляєте можливість інших розробників змінювати поведінку вашого коду, не змінюючи його.


2

Хоча відповідь кайзера технічно правильна, я сумніваюся, що це найкраща відповідь для вас.

Якщо ви створюєте власну тему, я вважаю, що це справді найкращий спосіб встановити якусь структуру за допомогою класів (а може бути, просторів імен та інтерфейсів теж, хоча це може бути занадто багато для теми WP).

З іншого боку, якщо ви просто розширюєте / коригуєте існуючу тему і вам потрібно пройти лише одну чи кілька змінних, я думаю, вам слід дотримуватися global. Оскільки header.phpвходить у функцію, змінні, які ви декларуєте у цьому файлі, можна використовувати лише у цьому файлі. Завдяки цьому globalви зробите їх доступними для всього проекту WP:

В header.php:

global $page_extra_title;

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

У single.php(наприклад):

global $page_extra_title;

var_dump( $page_extra_title );

3
Я не хочу бути грубим чи будь-яким іншим, але це дуже погана практика занурення у світову сферу. Вам слід уникати повного додавання до глобальної сфери. Ви повинні бути дуже обережними, щоб тут називати конвенції, і вам потрібно переконатися, що ім’я вашої змінної буде унікальним таким чином, щоб ніхто не міг відтворити таке ім'я. Підхід @kaiser може здатися вам за бортом, але він на сьогоднішній день найкращий і найбезпечніший. Я не можу сказати вам, як вирішити цю проблему, але я дійсно радимо вам залишатися поза світовою сферою :-)
Пітер Гусен

3
Звичайно, ви повинні бути обережними, щоб не перезаписати інші змінні. Ви можете вирішити цю проблему, використовуючи унікальний префікс або масив зі своїми користувацькими змінними, $wp_theme_vars_page_extra_titleабо, $wp_theme_vars['page_extra_title']наприклад. Це було лише поясненням, чому глобальний буде працювати тут. ОП запитав спосіб передачі змінної через усі файли, використовуючи globalспосіб це зробити.
redelschaap

2
Ні, глобалісти - це не спосіб це зробити. Є набагато кращі способи досягти того ж, не використовуючи глобалів. Як я вже говорив раніше, і як заявив @kaiser у своїй відповіді, уникайте глобального розмаху і не тримайтеся від нього. В якості прикладу візьміть цю дуже просту альтернативу, загорніть свій код у функцію та зателефонуйте до необхідної функції. Таким чином, вам не потрібно встановлювати або використовувати глобальний.
Пітер Гусен

3
Так. Це може бути не найкращий спосіб, але це, безумовно, спосіб.
redelschaap

2
but it is really bad practice diving into the global scopeЯ хотів би, щоб хтось сказав це розробникам ядер WP. Я дійсно не розумію сенсу використання просторів імен, абстрагування даних, моделей дизайну, тестування одиниць та інших найкращих практик / методів програмування в коді, написаному для Wordpress, коли ядро ​​Wordpress засмічене поганими методами кодування, такими як глабальні змінні (наприклад, віджети) код).
Ejaz

1

Просте рішення - написати функцію, щоб отримати додатковий заголовок. Я використовую статичну змінну, щоб підтримувати дзвінки бази даних лише одному. Помістіть це у своїх функціях.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;
}

Зовні header.php, викличте функцію, щоб отримати значення:

var_dump(get_extra_title($post->ID));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.