Лучший способ передачи 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 и т. Д.), Но это безумие и делает все практически невозможным для обслуживания.

Мне интересно, как лучше всего передать переменную через все файлы в моей теме? Я думаю, что использование functions.php вместе с "get_post_meta" может быть не лучшей идеей? :)

Wordpressor
источник
Я думаю, что переменная находится в той же области, также я хочу избегать использования 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 );
}

В основном этот интерфейс говорит

«Мы можем что-то сделать, и модель является обязательной для этой задачи»

Наконец, нам нужно реализовать выше и построить фактический вид . Как видите, конструктор говорит, что обязательным элементом нашего представления является шаблон и что мы можем его отобразить. Для простоты разработки мы даже проверяем, присутствует ли файл шаблона на самом деле, чтобы мы могли сделать жизнь других разработчиков (и наших тоже) намного проще и отметим это.

На втором этапе функции рендеринга мы используем Closure, чтобы построить фактическую оболочку шаблона и 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. Может обмениваться моделью / данными без ущерба для других компонентов

Объединение ООП PHP с WP API

Конечно, это вряд ли возможно при использовании базовой функциональности тем, например get_header(), get_footer()и т. Д., Верно? Неправильно. Просто назовите свои классы в любом шаблоне или части шаблона, которую вы хотите. Визуализируйте его, преобразуйте данные, делайте что хотите. Если вы действительно хороши, вы даже просто добавляете свою собственную группу пользовательских фильтров и имеете некоторый посредник, чтобы позаботиться о том, что визуализируется каким контроллером, по какому маршруту / условному шаблону загружается.

Вывод?

Вы можете работать с такими вещами, как указано выше, в WP без проблем, и при этом придерживаться базового API и повторно использовать код и данные, не вызывая ни единого глобального, ни испортить и загрязнить глобальное пространство имен.

кайзер
источник
3
Выглядит отлично! Я посмотрю больше на это, хороший ответ!
Марко
@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>разделе страницы.

Partials

В WordPress есть функции, подобные get_header()или get_template_part()которые можно использовать для загрузки партиалов в основной шаблон.

Эти функции, как и все другие функции WordPress, могут использоваться в шаблонах при использовании Engine класса.

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

По этой причине в Engineклассе есть метод, partial()который позволяет загружать частичные (полностью совместимые с дочерними темами) и по-прежнему иметь возможность использовать частичные данные пользовательских шаблонов.

Использование довольно просто.

Предполагая, что файл находится в папке partials/content.phptheme (или child theme), его можно включить с помощью:

<?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. Может обмениваться моделью / данными без ущерба для других компонентов

все действительны для моего класса, а также.

Gmazzap
источник
1
Хехе. Молодец, приятель :) +1
кайзер
@gmazzap почти 3 года спустя, есть ли какие-либо обновления вашего мышления выше? Шаблонное ядро ​​WP на самом деле не продвинулось в более продвинутом направлении, поэтому сторонние решения все еще нужны.
lkraav
1
Я не делаю много тем работы в эти дни. В последнее время мой путь - объединение github.com/Brain-WP/Context + github.com/Brain-WP/Hierarchy для создания данных и передачи в шаблоны. Для самого движка шаблонов я использовал разные подходы: Foil (конечно), Mustache, но также Twig (только когда у меня был контроль над всем веб-
сайтом,
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 );
redelschaap
источник
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

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

За пределами header.php вызовите функцию, чтобы получить значение:

var_dump(get_extra_title($post->ID));
PBD
источник