Глава 10 · С нуля → практика
Область видимости: аргументы, global и состояние
Почему функция не видит соседнюю переменную, где оправдан global и как не смешать состояние разных задач.
Местные значения функции: подготовить подпись, сохранив исходную цену
В каталоге цена товара 150 рублей. Для оформления доставки нужно получить 200, сохранив исходную цену 150 для карточки. Функция получает значение через аргумент и работает со своим местным параметром. Одинаковое написание переменной внутри и снаружи не связывает их автоматически. Сначала изучены вызов, параметр и return.
- Снаружи $priceRubles = 150 — цена каталога.
- addDelivery($priceRubles, 50): местный $priceRubles получает 150.
- Внутри параметр увеличивается до 200.
- return отдаёт 200 в наружную $payable.
- Внешняя $priceRubles по-прежнему 150.
<?php
function addDelivery($priceRubles, $deliveryRubles)
{
$priceRubles = $priceRubles + $deliveryRubles;
return $priceRubles;
}
$priceRubles = 150;
$payable = addDelivery($priceRubles, 50);
echo 'Цена в каталоге: ', $priceRubles, ' руб.', "\n";
echo 'К оплате с доставкой: ', $payable, ' руб.', "\n";
Ожидаемый результат
Цена в каталоге: 150 руб.
К оплате с доставкой: 200 руб.
Присваивание внутри функции изменило местный параметр. Внешняя payable получила возвращённые 200. Местная переменная не обязана называться так же, как внешняя: соответствие создают аргументы, а не совпадение имён.
Функция не видит обычную внешнюю переменную лишь потому, что её объявление написано ниже присваивания. Условие if и цикл foreach, напротив, сами по себе не создают такой отдельной области. Следующий пример проверяет место создания подписи, пока не вводя global.
<?php
$stock = 2;
$caption = 'Нет в наличии';
if ($stock > 0) {
$caption = 'В наличии: ' . $stock . ' уп.';
}
echo $caption, "\n";
Ожидаемый результат
В наличии: 2 уп.
Присваивание внутри if меняет ту же caption из текущей области. Исходная подпись существует до условия, поэтому при stock = 0 тоже будет что вывести. Это отличается от собственной местной переменной функции.
global: явная связь с состоянием одного выполнения
В старом обработчике несколько действий отмечают подготовленные документы общим счётчиком. Конструкция global $preparedCount; связывает местное имя с одноимённой переменной глобальной области. После неё изменение действительно видно снаружи. Эта функция выполняет побочное действие и не возвращает готовую подпись.
<?php
$preparedCount = 0;
function markPreparedDocument(): void
{
global $preparedCount;
$preparedCount = $preparedCount + 1;
}
markPreparedDocument();
markPreparedDocument();
echo 'Подготовлено документов: ', $preparedCount, "\n";
Ожидаемый результат
Подготовлено документов: 2
Первый вызов изменил глобальное число 0 → 1, второй 1 → 2. : void сообщает об отсутствии полезного результата; внешняя программа читает изменённый счётчик отдельно. Пример только отмечает подготовку, а не создаёт файлы документов.
У этого решения скрытая зависимость от имени preparedCount и общего состояния. Для двух независимых наборов удобнее явный параметр и возвращённое значение либо отдельный объект состояния после изучения ООП. global не разделяет память между всеми посетителями сайта и не сохраняет число в базе. Ниже подробно разобраны $GLOBALS, include, static и ссылочные крайние случаи.
Граница функции — граница имен
У каждой функции своё рабочее место
Два магазина считают цены с разными настройками. Передача аргументов позволяет выполнить оба расчёта рядом, не смешав данные.
- Расчёт для магазина А
Вызов получает цену 100 и ставку 10%.
- Локальные переменные
Параметры принадлежат этому вызову функции.
- Расчёт для магазина Б
Другой вызов получает цену 100 и ставку 20%.
- Свои локальные переменные
Настройки А не нужно перезаписывать ради магазина Б.
Глобальная переменная — общая точка доступа за пределами функции. global явно подключает её внутри функции, но создаёт зависимость от общего состояния. В главе разобрано, где такой доступ встречается и почему параметры часто проще проверять.
Сегодня скрипт считает цену для одного магазина, завтра — для двух магазинов с разными настройками. Если функция незаметно берёт ставку из общей переменной, результат зависит от того, кто изменил её последним. Если ставка передана аргументом, два расчёта можно выполнить рядом и проверить отдельно. Область видимости объясняет, откуда конкретная функция вообще получает данные, а не просто задаёт ещё одно правило синтаксиса.
Вне функций переменная относится к глобальной области текущего выполнения. Внутри функции есть собственная локальная область: одноименное $price в функции не обязано быть ценой из вызывающего кода. Блоки if и foreach сами по себе не создают отдельной области переменных. После цикла переменная элемента остается определенной, а после foreach по ссылке может оставаться привязанной к массиву. Файл include наследует область того места, где его подключили; отдельный файл не равен изоляции.
- Контроллер получает настройки магазина
- Передает ставку аргументом
- Функция рассчитывает результат
- Контроллер решает, куда его вывести
<?php
function grossPrice(int $netKopecks, int $taxPercent): int {
$extraKopecks = intdiv($netKopecks * $taxPercent, 100);
return $netKopecks + $extraKopecks;
}
$firstShop = grossPrice(10000, 20);
$secondShop = grossPrice(10000, 0);
echo 'Магазин A, итог: ', $firstShop, ' коп.', "\n";
echo 'Магазин B, итог: ', $secondShop, ' коп.', "\n";
echo 'Разница: ', $firstShop - $secondShop, ' коп.', "\n";
Ожидаемый результат
Магазин A, итог: 12000 коп.
Магазин B, итог: 10000 коп.
Разница: 2000 коп.
Первый вызов получает местные netKopecks=10000 и taxPercent=20; intdiv возвращает 2000, затем return — 12000 в firstShop. Второй вызов получает собственный taxPercent=0 и возвращает 10000 в secondShop. Настройки первого не протекают во второй. Оба готовых результата используются для подписей и разницы 2000. Это учебная формула процентов; реальные налоги и округление задаются условиями задачи, а не самой областью видимости.
Где global действительно встречается
Разберите вложенный вызов в конце примера справа налево по зависимостям: сначала legacyCurrency читает настройки и возвращает строку RUB; затем priceLabel получает 15000 и эту строку в своих параметрах; intdiv(15000,100) даёт 150, конкатенация формирует «150 RUB», return передаёт её внешнему echo. Такой формат допускает только целые рубли: остаток копеек эта учебная функция не показывает.
В процедурном приложении или WordPress-плагине global бывает частью уже существующего API: например, $wpdb предоставляет объект доступа к базе. Адаптер к такому окружению может прочитать глобальную зависимость и передать ее дальше обычным аргументом или в конструктор. Это осознанная граница совместимости. Делать глобальными корзину, текущего покупателя и промежуточные суммы ради экономии параметров неудобно: порядок вызовов начинает менять результат, а тесты вынуждены восстанавливать скрытое состояние.
<?php
declare(strict_types=1);
$legacyShopSettings = ['currency' => 'RUB'];
function legacyCurrency(): string
{
global $legacyShopSettings;
$currency = $legacyShopSettings['currency'] ?? null;
if (!is_string($currency)) {
throw new RuntimeException('Не настроена валюта');
}
return $currency;
}
function priceLabel(int $kopecks, string $currency): string
{
return intdiv($kopecks, 100) . ' ' . $currency;
}
echo priceLabel(15000, legacyCurrency()), "\n";
Ожидаемый результат
150 RUB
Только адаптер знает имя глобальной переменной. Бизнес-функция получает валюту явно. В реальном WordPress глобальный объект берут после загрузки WordPress; этот самостоятельный пример моделирует зависимость без доступа к сайту.
$GLOBALS дает доступ к глобальной таблице по имени, а global связывает локальное имя с глобальной переменной. Это не механизм постоянного хранения между запросами и не способ сделать переменную видимой во всех функциях разом. Суперглобальные $_GET, $_POST, $_SERVER доступны без global, но их содержимое все равно следует принять и проверить на границе приложения. Не передавайте весь $_REQUEST глубоко в расчет заказа.
Локальная static: память между вызовами
<?php
function nextLocalNumber(): int {
static $number = 0;
$number += 1;
return $number;
}
$firstLine = nextLocalNumber();
echo $firstLine, '. Проверен адрес', "\n";
$secondLine = nextLocalNumber();
echo $secondLine, '. Подготовлена упаковка', "\n";
Ожидаемый результат
1. Проверен адрес
2. Подготовлена упаковка
При первом выполнении инициализируется местный number=0, затем +=1 даёт 1 и return передаёт его в firstLine. Второй вызов продолжает с сохранённого 1 и возвращает 2 в secondLine. Переменная number остаётся внутри функции; внешний код получает только числа. Отчёт здесь учебный: программа отмечает заданные сообщения, а не проверяет реальный адрес. Новый самостоятельный запуск начнёт нумерацию заново, поэтому это не общий ID заказов.
Кеширование через static допустимо, когда ключ учитывает все входы и понятно время жизни. Если в длинном воркере хранить там «текущего клиента», следующая задача может получить старые данные. Лучше хранить состояние в объекте с явным жизненным циклом или передавать контекст задачи. Константа подходит для неизменного правила, конфигурационный объект — для настроек среды; ни тот ни другой не должен прятать изменяемую корзину.
Выбор и проверка
| Нужно | Подход |
|---|---|
| Передать данные расчета | Параметр и return |
| Настроить сервис один раз | Конструктор и внедрение зависимости |
| Захватить ставку для callback | Замыкание use или fn |
| Поддержать старое API | Узкий адаптер к global |
| Сохранить данные между HTTP-запросами | Сессия/база/кеш с явным ключом |
Попробуйте сами. Две функции печатают цены, используя global $currency. Как проверить RUB и EUR без влияния одного теста на другой?
Разбор решения
Вынесите форматирование в priceLabel($amount, $currency). Сохраните global только в адаптере старого интерфейса. Два теста вызывают чистую функцию с разными аргументами и не меняют общую переменную.