Как устанавливать и использовать глобальные переменные? Или почему лучше их не использовать
ОБНОВЛЕНИЕ: Мой исходный вопрос был решен, но это превратилось в полезное обсуждение о том, почему не стоит использовать глобальные переменные, поэтому я обновляю вопрос, чтобы отразить это. Решением было <?php global $category_link_prop; echo esc_url( $category_link_prop ); ?>
, как предложил @TomJNowell.
ОБНОВЛЕНИЕ 2: Теперь у меня все работает именно так, как я хотел. Но я все еще использую глобальную область видимости и был бы рад найти лучший способ.
Я пытаюсь настроить множество глобальных переменных для постоянных ссылок на категории, которые будут использоваться в различных местах моей темы. Основная причина этого - использование как в основной навигации, так и в серии поднавигаций, которые выбираются в зависимости от того, к какой категории относится текущая запись. Это не тема, которую я буду выпускать для использования другими, она создана для одной конкретной цели.
Вот как я их сейчас создаю (я вставил только несколько переменных).
function set_global_nav_var()
{
// предложение
global $prop;
// Получаем ID категории
$category_id_prop = get_cat_ID( 'proposal' );
// Получаем URL этой категории
$category_link_prop = get_category_link( $category_id_prop );
$prop = '<a href="' .esc_url( $category_link_prop ). '" title="Предложение">Предложение</a>';
// Кальвинбол
global $cb;
// Получаем ID категории
$category_id_cb = get_cat_ID( 'calvinball' );
// Получаем URL этой категории
$category_link_cb = get_category_link( $category_id_cb );
$cb = '<a href="' .esc_url( $category_link_cb). '" title="Кальвинбол">Кальвинбол</a>';
}
add_action( 'init', 'set_global_nav_var' );
Теперь я могу использовать <?php global $prop; echo $prop; ?>
в 4 местах, где это нужно, и получить всю ссылку для кода. Когда это изменится, мне нужно будет изменить только в одном месте. Я открыт для альтернатив, не связанных с глобальной областью видимости.

Хотя я настоятельно не рекомендую этого делать, и это не ускорит работу, ваш способ использования неправильный.
WordPress уже кэширует эти вещи в объектном кэше/памяти, чтобы не приходилось получать их несколько раз в одном запросе, вам не нужно сохранять результат и повторно использовать, WP уже делает это "из коробки".
Скорее всего, ваш код работает медленнее из-за этой микрооптимизации, а не быстрее!
Как использовать глобальные переменные
Когда вы пытаетесь использовать глобальную переменную, вы должны сначала указать ключевое слово global
. Вы указали его здесь при определении значения, но вне этой области видимости его нужно снова объявить как глобальную переменную.
Например, в functions.php
:
function test() {
global $hello;
$hello = 'привет мир';
}
add_action( 'after_setup_theme', 'test' );
В single.php
это не сработает:
echo $hello;
Потому что $hello
не определена. Однако это сработает:
global $hello;
echo $hello;
Конечно, вам не стоит делать ни то, ни другое. WordPress уже пытается кэшировать эти вещи в объектном кэше.
Недостатки и опасности глобальных переменных
Вы не получите прироста скорости от этого (возможно, даже небольшое замедление), все, что вы получите, — это дополнительная сложность и необходимость писать множество ненужных объявлений global
.
Вы также столкнетесь с другими проблемами:
- код, для которого невозможно написать тесты
- код, который ведет себя по-разному при каждом запуске
- конфликты имен переменных из-за общего пространства имен
- случайные ошибки из-за забытого объявления
global
- полное отсутствие структуры хранения данных в вашем коде
- и многое другое
Что использовать вместо этого?
Лучше использовать структурированные данные, такие как объекты или внедрение зависимостей, или, в вашем случае, набор функций.
Вот 3 альтернативы:
Статические переменные
Статические переменные — это не хорошо, но считайте их немного менее злобными кузенами глобальных переменных. Статические переменные для глобальных — это как хлеб в грязи для цианида.
Например, вот способ сделать что-то подобное с помощью статических переменных:
function awful_function( $new_hello='' ) {
static $hello;
if ( !empty( $new_hello ) ) {
$hello = $new_hello;
}
return $hello;
}
awful_function( 'телефон' );
echo awful_function(); // выводит "телефон"
awful_function( 'банан');
echo awful_function(); // выводит "банан"
Обратите внимание, что есть другие причины использовать статические переменные, не связанные с кэшированием и производительностью, но у них есть свои недостатки. Трудно, если вообще возможно, правильно написать тесты для функции, использующей статическую переменную, и это усложняет отладку, так как нужно отслеживать значение этой переменной.
В хорошем коде чистая функция всегда делает одно и то же при одинаковых параметрах. Чистые функции предсказуемы и легко тестируемы. Функция со статической переменной никогда не может быть чистой функцией.
Синглтоны
Синглтоны — это объекты, которые создаются один раз, и может существовать только один экземпляр такого объекта. Они так же плохи, как глобальные переменные, просто с другим синтаксисом, имеют все те же проблемы, что и статические переменные, и создают видимость объектно-ориентированного программирования без его преимуществ. Избегайте их.
Дополнительное чтение:
WP_Cache — то, что вы пытались сделать, но WP уже делает это
Если вы действительно хотите сэкономить время, сохраняя данные для повторного использования, рассмотрите возможность использования системы WP_Cache
с wp_cache_get
и т.д., например:
$value = wp_cache_get( 'hello' );
if ( false === $value ) {
// не найдено, устанавливаем значение по умолчанию
wp_cache_set( 'hello', 'world' );
}
Теперь значение будет закэшировано WordPress на время запроса, появится в инструментах отладки, а если у вас есть объектный кэш, оно сохранится между запросами.
Значит ли это, что мне нужно использовать wp_cache_get
в своем коде?
Скорее всего, нет. WordPress уже делает это автоматически для записей/пользователей/мета/терминов/настроек и т.д., поэтому вам никогда не нужно хранить или получать данные записей таким образом. Просто используйте обычные API-функции, и все будет работать автоматически.
Примечание 1: Отмечу, что некоторые люди пытаются сохранять данные в глобальных переменных между запросами, не зная, что PHP так не работает. В отличие от Node-приложения, каждый запрос загружает свежую копию приложения, которая затем завершается по окончании запроса. По этой причине глобальные переменные, установленные в одном запросе, не сохраняются до следующего запроса.
Примечание 2: Судя по обновленному вопросу, ваши глобальные переменные не дают никакого прироста производительности. Вместо этого вам следует генерировать HTML по мере необходимости, и это будет работать так же быстро, а может быть, даже немного быстрее. Это микрооптимизация.

Я знаю, что использовать глобальную область видимости немного странно, но большинство, если не все эти переменные будут использоваться на каждой странице. Я открыт для лучших идей. Я собираюсь отредактировать вопрос, чтобы сделать мое намерение немного понятнее. Кстати, это прекрасно работает, когда я делаю <?php global $category_link_prop; echo esc_url( $category_link_prop ); ?>
, как вы предложили. Спасибо!

Ах, если мое решение работает, не могли бы вы отметить его как принятое? Ваши глобальные переменные работают так же быстро, как и исходный вызов, возможно, вам стоит попробовать использовать функции, чтобы не приходилось набирать 2 строки, еще лучше — синглтон, еще лучше — сделать все это динамическим и в части шаблона, подключаемой через get_template_part

Отмечено как принятое, так как сейчас я делаю именно так, хотя, возможно, я воспользуюсь одной из стратегий, которые предлагает @MarkKaplun ниже. Использование get_template_part() — интересная идея, но я не уверен, что хочу иметь папку, полную таких коротких файлов...

о нет нет, вам не нужен отдельный файл для каждой категории, вам нужен всего один, который будет получать название текущей категории и использовать его. Вам не придется жестко прописывать ничего, представьте какие сложности возникнут если все жестко прописать

Я поместил код в свой child-functions.php, который активен. Но я не могу получить доступ к переменной в php-подключаемом файле, который я вызываю из "обычного" поста, сгенерированного базой данных. Подскажите, что я делаю не так? (Я определяю её как global, конечно же.)

Вам необходимо объявлять global
каждый раз, когда вы используете её. Это не "один раз объявил - работает везде", вам нужно делать это каждый, абсолютно каждый раз, без исключений любого рода. Но, как я сказал в своем вопросе, глобальные переменные - это плохая практика, проблематичная, и не решение, которое вы ищете для своей проблемы. Даже такое зло как синглтоны было бы лучшим решением

Спасибо, я ценю ваш комментарий. Я объявил и использовал его, но где-то допустил ошибку. У меня просто не получилось. Я пробовал разные подходы. В итоге этот вариант сработал. Я согласен с вашим мнением о глобальных переменных, но иногда для личного сайта нужен быстрый фикс. Спасибо.

Не используйте глобальные переменные, вот и всё.
Почему не стоит использовать глобальные переменные
Потому что использование глобальных переменных усложняет поддержку программного обеспечения в долгосрочной перспективе.
- Глобальная переменная может быть объявлена где угодно в коде или вообще нигде, поэтому нет конкретного места, где можно интуитивно найти комментарий о её назначении
- При чтении кода обычно предполагается, что переменные локальны для функции, и не осознаётся, что изменение их значения в функции может повлиять на всю систему
- Если функции не обрабатывают ввод, они должны возвращать одно и то же значение/выходные данные при одинаковых параметрах. Использование глобальных переменных в функции вводит дополнительные параметры, которые не задокументированы в объявлении функции
- Глобальные переменные не имеют специфического конструктора инициализации, поэтому никогда нельзя быть уверенным, когда к ним можно обратиться, и не будет ошибки при попытке доступа до инициализации
- Кто-то другой (например, плагин) может использовать глобальные переменные с тем же именем, нарушая ваш код, или вы — его, в зависимости от порядка инициализации
В ядре WordPress глобальные переменные используются чрезмерно много. При попытке понять, как работают базовые функции и хуки, такие как the_content
, внезапно осознаёшь, что переменная $more
не локальная, а глобальная, и приходится искать по всем файлам ядра, чтобы понять, когда она устанавливается в true.
Так что же делать, если нужно избежать копирования-вставки нескольких строк кода вместо сохранения результата первого запуска в глобальной переменной? Есть несколько подходов: функциональный и ООП.
Функция-обёртка. Это просто макрос для сохранения копирования/вставки:
// вход: $id — идентификатор категории
// возвращает: значение foo2 для категории
function notaglobal($id) {
$a = foo1($id);
$b = foo2($a);
return $b;
}
Преимущества в том, что теперь есть документация о назначении бывшей глобальной переменной, а также очевидная точка для отладки, если возвращаемое значение не соответствует ожиданиям.
Если есть функция-обёртка, легко кэшировать результат при необходимости (делайте это только если обнаружите, что функция выполняется долго):
function notaglobal($id) {
static $cache;
if (!isset($cache)) {
$a = foo1($id);
$b = foo2($a);
$cache = $b;
}
return $cache;
}
Это даёт то же поведение, что и глобальная переменная, но с гарантированной инициализацией при каждом доступе.
Аналогичные шаблоны можно реализовать в ООП. Я считаю, что ООП обычно не добавляет ценности в плагинах и темах, но это отдельная тема:
class notaglobal {
var latestfoo2;
__constructor($id) {
$a = foo1($id);
$this->latestfoo2 = foo2($a)
}
}
$v = new notaglobal($cat_id);
echo $v->latestfoo2;
Это менее элегантный код, но если нужно предварительно вычислить несколько значений, которые всегда используются, такой подход возможен. По сути, это объект, содержащий все глобальные переменные в организованном виде. Чтобы избежать создания глобального экземпляра этого объекта (нужен только один экземпляр, иначе значения пересчитываются), можно использовать шаблон одиночки (singleton) (некоторые считают это плохой идеей, на ваше усмотрение).
Мне не нравится прямой доступ к атрибутам объекта, поэтому в моём коде будет ещё одна обёртка:
class notaglobal {
var latestfoo2;
__constructor() {}
foo2($id) {
if (!isset($this->latestfoo2)) {
$a = foo1($id);
$b = foo2($a);
$this->latestfoo2= $b;
}
return $this->latestfoo2;
}
}
$v = new notaglobal();
echo $v->foo2($cat_id);

Пожалуйста, не кричите. Не могли бы вы объяснить почему и предоставить какой-нибудь источник?

Я думаю, вы неправильно поняли ответ. Если бы он не пытался делать преждевременную оптимизацию, сохраняя значения в глобальных переменных, его код бы работал. Крикливость связана с тем, что следование базовым принципам разработки ПО — это то, что нельзя подчеркнуть слишком сильно. Люди, которые не понимают эти базовые принципы (доступные через любой поисковик), не должны распространять код в интернете.

Привет, Mark, извини, мой комментарий был таким же коротким, как и твой ответ, и мне следовало выразиться яснее: 1) На мой взгляд, выделение жирным достаточно, чтобы донести мысль. 2) Хотя иногда больше нечего добавить, я скептически отношусь к однострочным ответам: Допустимо ли публиковать однострочные ответы или их лучше оставлять как комментарии?

да, только после публикации я понял, что нужно было использовать жирный шрифт. исправлю этот аспект

IMO это ответ, люди, которые приходят сюда из гугла, должны видеть, что это плохая идея — даже думать об использовании глобальных переменных сразу.

Недостаточно просто сказать "не делай X", нужно объяснить почему, иначе выглядит, будто ты говоришь это просто так

@MarkKaplun Что бы вы сделали вместо этого, чтобы избежать необходимости писать одно и то же снова и снова, а затем вручную изменять каждую часть, если что-то изменится?

@TomJNowell, мне кажется забавным, что я был единственным, кто поставил отрицательный голос за сам вопрос, так как он явно выходил за рамки WASE. Я не видел смысла развивать тему, которую вообще не следовало здесь начинать.

@MarkKaplun Отлично. Ваше первое решение кажется наилучшим. Я поэкспериментирую с кэшированием, которое, вероятно, понадобится. Я не уверен, почему это выходит за рамки данного stackexchange? Вопрос касается PHP, но оказывается, что он напрямую связан с тем, как WordPress работает с глобальными переменными. Также этот случай специфичен для навигационных меню WordPress.

"В WordPress core катастрофически много использования глобальных переменных." Я бы сказал, что WordPress вообще не должен использовать глобальные переменные, но это только моё мнение.

@MarkKaplun спасибо за ваш функциональный подход. Если мы решим его использовать, не могли бы вы обновить пример, чтобы показать, как это должно выглядеть с запасным значением $ID, если по какой-то причине его не существует, он не установлен или не является положительным целым числом?

Ваш вопрос связан с тем, как работает PHP.
Возьмем $wpdb в качестве примера.
$wpdb — это известная глобальная переменная.
Вы знаете, когда она объявляется и получает значения?
При каждой загрузке страницы, да, каждый раз, когда вы посещаете свой сайт на WordPress.
Аналогично, вам нужно убедиться, что переменные, которые вы хотите сделать глобальными, объявляются и получают соответствующие значения при каждой загрузке страницы.
Хотя я не дизайнер тем, я могу сказать, что after_setup_theme
— это одноразовый хук. Он срабатывает только при активации темы.
Если бы я был на вашем месте, я бы использовал init
или другие хуки. Хотя нет, если бы я был на вашем месте, я бы вообще не использовал глобальные переменные...
Я не очень хорошо объясняю. Так что, если хотите углубиться в PHP, вам стоит взять книгу.

Вы всегда можете использовать шаблон Singleton через статические геттеры.
<ul>
<li><?php echo MyGlobals::get_nav_prop( 'proposal' )[ 'html' ]; ?></li>
<li><?php echo MyGlobals::get_nav_prop( 'calvinball', 'html' ); ?></li>
</ul>
<?php
if ( ! class_exists('MyGlobals') ):
class MyGlobals {
public $props;
public function __construct(){
$this->props = array (
'proposal' => array( 'title' => 'Предложение', 'text' => 'Предложение' ),
'calvinball' => array( 'title' => 'Кальвинбол', 'text' => 'Кальвинбол' ),
);
}
public function get_nav_prop ( $term, $prop = false )
{
$o = self::instance();
if ( ! isset( $o->props[$term] ) ) { return false; }
if ( ! isset( $o->props[$term][ 'html' ] ) ) {
$id = get_cat_ID( $term );
$link = esc_url ( get_category_link( $id ) );
$title = $o->props[$term]['title'];
$text = $o->props[$term]['text'];
$o->props[$term]['html'] = '<a href="'.$link.'" title="'.$title.'">'.$text.'</a>';
$o->props[$term]['link'] = $link;
$o->props[$term]['id'] = $id;
}
if($prop){ return isset($o->props[$term][$prop]) ? $o->props[$term][$prop] : null; }
return $o->props[$term];
}
// -------------------------------------
private static $_instance;
public static function instance(){
if(!isset(self::$_instance)) {
self::$_instance = new MyGlobals();
}
return self::$_instance;
}
}
endif; // end MyGlobals
