Как устанавливать и использовать глобальные переменные? Или почему лучше их не использовать

4 мар. 2013 г., 06:23:04
Просмотры: 275K
Голосов: 48

ОБНОВЛЕНИЕ: Мой исходный вопрос был решен, но это превратилось в полезное обсуждение о том, почему не стоит использовать глобальные переменные, поэтому я обновляю вопрос, чтобы отразить это. Решением было <?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 местах, где это нужно, и получить всю ссылку для кода. Когда это изменится, мне нужно будет изменить только в одном месте. Я открыт для альтернатив, не связанных с глобальной областью видимости.

11
Комментарии

Какую ссылку выводит это выражение echo esc_url( $category_link_prop ); ? Какой ожидаемый результат ссылки?

Vinod Dalvi Vinod Dalvi
4 мар. 2013 г. 06:57:39

Почему бы просто не использовать 'get_cat_ID( * )' везде, где вы планировали использовать глобальную переменную. Я сомневаюсь, что ваш способ даст какое-то преимущество в скорости. С точки зрения читаемости кода, 'get_cat_ID( * )' явно выигрывает.

Chris Strutton Chris Strutton
4 мар. 2013 г. 07:09:02

Можете переформулировать? Я прочитал ваш вопрос, но всё ещё не уверен, что именно вы хотите сделать и зачем. Мой общий совет - не использовать глобальные переменные и не засорять глобальную область видимости.

Tom J Nowell Tom J Nowell
4 мар. 2013 г. 12:59:08

@VinodDalvi Я надеялся получить ссылку на категорию с ярлыком 'proposal'.

JPollock JPollock
5 мар. 2013 г. 00:30:40

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

JPollock JPollock
5 мар. 2013 г. 00:31:59

@ChrisStrutton get_cat_id() не дает мне всего, что мне нужно (например, ссылку, заголовок и т.д.). Читаемость не является для меня большой проблемой, так как я буду единственным, кто это читает.

JPollock JPollock
5 мар. 2013 г. 01:08:26

это начинает походить на Проблему X/Y. возможно, вам стоит отступить назад и объяснить, какой именно результат вы хотите получить. я уверен, что существует гораздо более элегантное решение, чем создание кучи глобальных переменных, чтобы затем просто жестко прописывать ссылки на них в навигации в другом месте

Milo Milo
5 мар. 2013 г. 01:41:40

@Milo Отличное замечание. Моя реальная проблема в том, что в моей теме есть 4 разных меню (пока что). 1 стандартное верхнее меню и 3, которые показываются только при определенных условиях. Все они отображают разные комбинации примерно одних и тех же элементов в произвольном порядке, и будут меняться по мере развития проекта. Проблема в том, что если я буду жестко прописывать их, мне придется многократно дублировать один и тот же код, а затем вносить одни и те же изменения 4 раза при каждом изменении.

JPollock JPollock
5 мар. 2013 г. 01:49:36

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

Milo Milo
5 мар. 2013 г. 01:57:36

@Milo С этого я и начал, но теперь у меня есть одна функция для навигации в верхней панели; одна функция для боковой панели навигации, которая разрослась до 4 разных версий на основе условных операторов; и одна функция для поднавигации в шаблоне страницы, и их будет еще больше по мере развития проекта. Я не могу придумать разумный способ объединить все это в одну функцию.

JPollock JPollock
5 мар. 2013 г. 02:01:25

@Milo Также я хочу иметь возможность использовать их в качестве ссылок на различные части сайта в тексте сайта, либо в записях (если это работает), либо в шаблонах страниц.

JPollock JPollock
5 мар. 2013 г. 02:11:02
Показать остальные 6 комментариев
Все ответы на вопрос 4
7
56

Хотя я настоятельно не рекомендую этого делать, и это не ускорит работу, ваш способ использования неправильный.

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 по мере необходимости, и это будет работать так же быстро, а может быть, даже немного быстрее. Это микрооптимизация.

4 мар. 2013 г. 13:05:02
Комментарии

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

JPollock JPollock
5 мар. 2013 г. 00:34:04

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

Tom J Nowell Tom J Nowell
5 мар. 2013 г. 09:59:13

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

JPollock JPollock
6 мар. 2013 г. 10:12:12

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

Tom J Nowell Tom J Nowell
6 мар. 2013 г. 10:45:46

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

cvr cvr
3 июн. 2018 г. 14:37:02

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

Tom J Nowell Tom J Nowell
3 июн. 2018 г. 16:48:58

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

cvr cvr
3 июн. 2018 г. 17:20:09
Показать остальные 2 комментариев
13
26

Не используйте глобальные переменные, вот и всё.

Почему не стоит использовать глобальные переменные

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

  • Глобальная переменная может быть объявлена где угодно в коде или вообще нигде, поэтому нет конкретного места, где можно интуитивно найти комментарий о её назначении
  • При чтении кода обычно предполагается, что переменные локальны для функции, и не осознаётся, что изменение их значения в функции может повлиять на всю систему
  • Если функции не обрабатывают ввод, они должны возвращать одно и то же значение/выходные данные при одинаковых параметрах. Использование глобальных переменных в функции вводит дополнительные параметры, которые не задокументированы в объявлении функции
  • Глобальные переменные не имеют специфического конструктора инициализации, поэтому никогда нельзя быть уверенным, когда к ним можно обратиться, и не будет ошибки при попытке доступа до инициализации
  • Кто-то другой (например, плагин) может использовать глобальные переменные с тем же именем, нарушая ваш код, или вы — его, в зависимости от порядка инициализации

В ядре 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);
4 мар. 2013 г. 07:09:37
Комментарии

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

brasofilo brasofilo
4 мар. 2013 г. 08:04:23

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

Mark Kaplun Mark Kaplun
4 мар. 2013 г. 12:11:43

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

brasofilo brasofilo
4 мар. 2013 г. 12:37:46

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

Mark Kaplun Mark Kaplun
4 мар. 2013 г. 12:40:41

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

Mark Kaplun Mark Kaplun
4 мар. 2013 г. 12:43:47

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

Tom J Nowell Tom J Nowell
4 мар. 2013 г. 13:00:22

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

JPollock JPollock
5 мар. 2013 г. 00:53:16

@JPollock, отредактировал ответ.

Mark Kaplun Mark Kaplun
5 мар. 2013 г. 07:28:05

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

Mark Kaplun Mark Kaplun
5 мар. 2013 г. 08:27:33

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

JPollock JPollock
6 мар. 2013 г. 07:44:16

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

R Porter R Porter
2 апр. 2014 г. 13:35:14

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

klewis klewis
21 окт. 2016 г. 15:25:53

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

Joel M Joel M
18 февр. 2018 г. 06:15:10
Показать остальные 8 комментариев
0

Ваш вопрос связан с тем, как работает PHP.

Возьмем $wpdb в качестве примера.

$wpdb — это известная глобальная переменная.

Вы знаете, когда она объявляется и получает значения?

При каждой загрузке страницы, да, каждый раз, когда вы посещаете свой сайт на WordPress.

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

Хотя я не дизайнер тем, я могу сказать, что after_setup_theme — это одноразовый хук. Он срабатывает только при активации темы.

Если бы я был на вашем месте, я бы использовал init или другие хуки. Хотя нет, если бы я был на вашем месте, я бы вообще не использовал глобальные переменные...

Я не очень хорошо объясняю. Так что, если хотите углубиться в PHP, вам стоит взять книгу.

4 мар. 2013 г. 09:04:33
0

Вы всегда можете использовать шаблон 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
25 нояб. 2015 г. 10:35:03