Александр Некрасов · Автономное руководство

PHP: полный мануал с примерами, функциями и ООП

Разделы этой страницы
  1. Переменные PHP с нуля: имя, значение и область видимости
  2. Первая переменная: подготовить подпись получателя заказа
  3. Из того же значения собрать понятную памятку
  4. Изменение данных: прошлый вывод и новое значение
  5. Сохранить прежнее значение и объяснить изменение человеку
  6. Как назвать переменную и почему Name не то же, что name
  7. Текст, число, ответ да/нет и null
  8. Кавычки, подстановка и соединение текста
  9. Неопределённая переменная, isset и unset
  10. empty не означает «пользователь ничего не ввёл»
  11. Запасное значение: оператор ??
  12. Несколько значений: ключ массива не отдельная переменная
  13. Ссылки и области: короткая карта углубления
  14. Ссылка: два имени одного изменяемого значения
  15. Область видимости: где действует имя
  16. global и $GLOBALS: явный доступ к общему состоянию
  17. include/require наследуют область места включения
  18. Локальная static: сохранить значение до следующего вызова
  19. Static у замыкания и унаследованного метода
  20. Global/static: запись значения и перепривязка имени различаются
  21. Переменные переменных: прочитать динамическое имя
  22. Динамическое имя свойства: другая структура данных
  23. Предопределённые массивы: откуда приходят данные
  24. GET/POST: имя поля превращается в ключ массива
  25. Группы, множественные поля и нормализация имён
  26. Cookie: значение от браузера, а не общая память PHP
  27. Комбинации изученных правил в задачах сайта
  28. Читаемость и стоимость: что выбирать в обычном коде
  29. Предсказать, запустить и объяснить
  30. Код и руководство с собой
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 7 · С нуля → углубление

Переменные PHP с нуля: имя, значение и область видимости

От первой строки $name до чтения, замены, копирования, ссылок, областей и внешних данных.

Первая переменная: подготовить подпись получателя заказа

Представьте простую памятку о заказе: сначала в ней будет имя получателя, затем статус. Сегодня имя известно заранее — Анна. Нужно записать его один раз и обратиться к нему дальше по понятному имени. Переменная связывает имя в программе с текущим значением. Начнём с единственного значения и единственного вывода; форма сайта и функции пока не нужны.

Имя переменной и её содержимое
  1. В коде читаем строку «Анна».
  2. Присваивание связывает имя $recipient со значением Анна.
  3. echo читает значение через имя $recipient.
  4. В результате выполнения файла появляется Анна.
Знак в $recipient = 'Анна';Смысл
$Начало записи переменной; в тексте результата этого знака нет.
recipientВыбранное автором имя «получатель»; его используют одинаково при записи и чтении.
=Присвоить значение справа имени слева. Это действие, а не математическое равенство.
'Анна'Строка: кавычки ограничивают текст в исходнике, само значение состоит из букв Анна.
;Конец инструкции. Следующая инструкция начинает следующее действие.
Записать имя и прочитать егоСамостоятельный пример
<?php
$recipient = 'Анна';
echo $recipient;
Ожидаемый результат
Анна

<?php открывает PHP-код. Присваивание ничего не выводит. Команда языка echo отправляет прочитанное значение в вывод. Сохраните весь пример в отдельный .php и запустите способом из главы установки; в консоли появится ровно Анна, без кавычек.

Полезная модель — подписанная ячейка: recipient соответствует подписи, Анна — текущему содержимому. Это модель смысла программы, не устройство памяти. PHP позже сможет связать несколько имён ссылкой или хранить объект; такие случаи отдельно меняют модель.

Из того же значения собрать понятную памятку

Одного имени недостаточно для пользователя. Добавим подпись «Получатель» и строку о заказе. Запятая в echo разделяет части вывода; она сама не печатается. Пробел после двоеточия находится внутри кавычек. "\n" — текстовая запись перевода строки в двойных кавычках. В CLI это новая строка; оформление переносов в HTML будет отдельной темой.

Прочитать имя дважды без повторного присваиванияСамостоятельный пример
<?php
$recipient = 'Анна';
echo 'Получатель: ', $recipient, "\n";
echo $recipient, ', заказ готов к выдаче', "\n";
Ожидаемый результат
Получатель: Анна
Анна, заказ готов к выдаче

Первый echo получает три части: подпись, Анна, перевод строки. Второй читает то же значение снова. Чтение не опустошает переменную. Если заменить единственную Анна в присваивании на Мария, оба обращения будут к Марии.

Не заключайте имя переменной в одинарные кавычки, если нужно её значение: '$recipient' означает буквальные символы $recipient. Кавычки вокруг самого текста и обращение к переменной выполняют разные задачи.

Изменение данных: прошлый вывод и новое значение

Наглядная модель

Имя остаётся, значение меняется

В примере ниже одна переменная $name. По шагам видно, что именно прочитает каждый echo.

  1. $name = 'Анна'

    Коробка с подписью name: текущее значение — строка Анна.

  2. Первый echo: Анна

    Читаем значение и выводим. Чтение не опустошает переменную.

  3. $name = 'Мария'

    Та же подпись name. Новое присваивание заменило значение на Мария.

  4. Второй echo: Мария

    Читаем новое значение. Ранее выведенный текст Анна остаётся в результате.

Коробка — аналогия имени и значения, не схема размещения в памяти. Ссылки и объекты имеют отдельные правила.

Покупатель попросил выдать заказ Марии. Запишем новое имя в ту же переменную. Файл исполняется последовательно: то, что уже выведено до изменения, не перепечатывается автоматически. Так можно увидеть старую и новую памятку.

Показать получателя до и после исправленияСамостоятельный пример
<?php
$recipient = 'Анна';
echo 'Было: ', $recipient, "\n";
$recipient = 'Мария';
echo 'Стало: ', $recipient, "\n";
Ожидаемый результат
Было: Анна
Стало: Мария

Во время первого echo значение — Анна. Следующее присваивание заменило его на Мария. Во время второго echo читается новое значение. Программа не хранит историю автоматически: слово «Было» осталось только в уже сформированном выводе.

Сохранить прежнее значение и объяснить изменение человеку

Для итогового сообщения потребуются сразу оба имени. До замены скопируем текущее значение в originalRecipient. Справа от = стоит переменная без кавычек: сначала PHP читает Анна, затем сохраняет прочитанный текст под новым именем.

Получить сообщение об изменении после обеих записейСамостоятельный пример
<?php
$recipient = 'Анна';
$originalRecipient = $recipient;
$recipient = 'Мария';
echo 'Получатель изменён: ', $originalRecipient, ' → ', $recipient, "\n";
Ожидаемый результат
Получатель изменён: Анна → Мария

originalRecipient хранит Анна и не следит за будущим recipient. Обе переменные содержат строки; дальнейшая замена одной не меняет другую. Получилось готовое понятное сообщение, а не только наблюдение внутреннего состояния.

Самостоятельно добавьте номер заказа 42 в отдельную переменную и получите «Заказ 42: получатель Мария». Для числа используйте $orderNumber = 42;, а в echo перечислите текст и значения через запятые. Ниже показано решение; затем продолжаем правила имён, типов и областей.

Памятка с номером заказа и получателемСамостоятельный пример
<?php
$orderNumber = 42;
$recipient = 'Мария';
echo 'Заказ ', $orderNumber, ': получатель ', $recipient, "\n";
Ожидаемый результат
Заказ 42: получатель Мария

42 записано без кавычек как число; Мария — в кавычках как текст. echo умеет показывать оба. Использование значения в подписи ещё не объясняет все операции его типа — это следующий шаг.

Как назвать переменную и почему Name не то же, что name

Обычное имя начинается с латинской буквы или подчёркивания, дальше допускаются буквы, цифры и подчёркивания. Регистр важен: $name и $Name различаются. Не начинайте с цифры, не вставляйте пробел, дефис или точку.

Выбирайте смысл: $customerName, $orderId, $stock. Имя $x уместно как координата, но мало сообщает о сумме заказа.

Регистр букв различает переменныеСамостоятельный пример
<?php
$name = 'Анна';
$Name = 'Мария';
$order2 = 42;
$_draft = 'Черновик';
echo $name, ' / ', $Name, "\n";
echo $order2, ' / ', $_draft, "\n";
Ожидаемый результат
Анна / Мария
42 / Черновик

name и Name — разные имена. Цифра внутри order2 допустима. Подчёркивание в начале тоже допустимо, но само по себе не делает переменную закрытой или системной.

Корректная записьНекорректная обычная запись
$order2, $_draft$2order — первая цифра
$customerName$customer name — пробел
$deliveryCity$delivery-city — минус не часть имени
$fileName$file.name — точка является оператором
Полное правило имён и особое имя $this

Парсер допускает также байты с кодами 128–255. Поэтому некоторые многобайтовые UTF-8-имена, включая кириллицу, технически работают; это не полноценная проверка Unicode-идентификаторов. Для совместной разработки проще предсказуемые ASCII-имена.

$this — специальная переменная объектного контекста, ей нельзя присваивать значение. До PHP 7.1 существовал путь косвенного присваивания; для современного кода он не подходит. Имена, недоступные обычной записью, технически можно адресовать динамически — это поздний раздел, а не совет по именованию.

Стиль имён и комментариев: понятный код.

Текст, число, ответ да/нет и null

Сначала достаточно узнавать формы записи. Текст заключают в кавычки, числа можно писать без них. Для дробной части в коде используется точка. true и false — логические ответы, null — явно выбранное отсутствие значения.

Тип относится к значению и влияет на операции. Пока мы не проверяем форму и не проектируем типизированные функции.

Число и строка записываются по-разномуСамостоятельный пример
<?php
$quantity = 2;
$temperature = 18.5;
$productCode = '002';
$isPaid = false;
$comment = null;
echo $quantity, "\n";
echo $temperature, "\n";
echo $productCode, "\n";
Ожидаемый результат
2
18.5
002

2 — целое число, 18.5 — дробное, "002" — текстовый код с важными ведущими нулями. false — логическое «нет», null — явно выбранное отсутствие значения. Последние два значения пока не печатаем: сначала узнаём их запись и смысл.

Обычная переменная может получить другой тип значенияСамостоятельный пример
<?php
$answer = 2;
echo $answer, "\n";
$answer = 'Два товара';
echo $answer, "\n";
Ожидаемый результат
2
Два товара

Обычной переменной можно присвоить значение другого типа. Возможность языка не означает хороший стиль для каждой задачи: quantity лучше оставлять количеством, а подпись хранить в quantityLabel.

Пустая строка '', число 0, false и null различаются, даже если простой вывод выглядит одинаково. Полные правила — следующая тема: типы и преобразования.

Кавычки, подстановка и соединение текста

Одинарные кавычки, двойные и соединение строкСамостоятельный пример
<?php
$name = 'Анна';
echo '$name', "\n";
echo "Привет, $name", "\n";
echo 'Привет, ' . $name, "\n";
echo "{$name}: заказ готов", "\n";
Ожидаемый результат
$name
Привет, Анна
Привет, Анна
Анна: заказ готов

Одинарные кавычки сохраняют буквальный $name. Двойные позволяют подставить значение; фигурные скобки точно отделяют имя. Точка соединяет строки. Запятая после echo выводит следующий фрагмент и не входит в текст.

В простых одинарных строках переменные не подставляются. В двойных подставляются, а последовательности вроде \n обрабатываются. Современная явная форма подстановки внутри двойной строки — "{$name}".

Старую форму "${name}" не используйте: она устарела с PHP 8.2. Это ограничение подстановки внутри строки; внешняя запись ${$field} у переменных переменных — другой синтаксис.

Подстановка произвольного пользовательского текста в HTML сама по себе не делает его безопасным. До этого практического случая дойдём после знакомства с внешними данными.

Неопределённая переменная, isset и unset

После первых присваиваний возникает вопрос: можно ли использовать необязательное имя получателя? Чтобы показать человеку подходящую подпись, понадобится маленький выбор. if (проверка) { действие } выполняет действие при true. else { другое действие } выбирается при false. Фигурные скобки отмечают границы обеих ветвей. Здесь проверкой станет isset; подробно условие if разобрано далее.

Чтение имени, которому в этой области ещё не присвоено значение, в PHP 8 выдаёт предупреждение Undefined variable; до PHP 8.0 это было E_NOTICE. Результат такого чтения — null, однако обычно это забытая инициализация или опечатка, а не нормальная логика.

Неисполняемый пример ошибки: echo $notCreated;. Для заведомо необязательного значения есть isset($name): скобки указывают проверяемую переменную, результат — true или false.

unset($name) удаляет имя в текущей области. Это не присваивание пустой строки и не удаление файла или записи БД.

В записи if (isset($recipient)) внутренняя пара скобок относится к проверке isset, внешняя — к условию if. Полученный true или false сразу определяет, выполнять ли следующий блок.

Выбрать подпись в четырёх состояниях имениСамостоятельный пример
<?php
if (isset($recipient)) {
    echo 'Получатель: ', $recipient, "\n";
} else {
    echo "Получатель пока не записан\n";
}
$recipient = 'Анна';
if (isset($recipient)) {
    echo 'Получатель: ', $recipient, "\n";
}
$recipient = null;
if (!isset($recipient)) {
    echo "Имя ещё предстоит уточнить\n";
}
$recipient = 'Мария';
unset($recipient);
if (!isset($recipient)) {
    echo "Временное имя удалено\n";
}
Ожидаемый результат
Получатель пока не записан
Получатель: Анна
Имя ещё предстоит уточнить
Временное имя удалено

Вначале recipient не существует; isset даёт false без чтения неопределённого значения. После записи Анна даёт true, и имя попадает в вывод. Null делает isset ложным, но не удаляет само имя. Unset удаляет имя. Знак ! означает отрицание: !isset истинно, когда isset ложно. Последующие ветви не пытаются читать удалённое значение.

isset и unset — конструкции языка со скобками. var_dump — готовая функция диагностики: bool(true) означает логическое true, NULL — null. isset($name, $city) истинно, только если обе существуют и обе не null; проверка идёт слева направо до первого отрицательного результата.

empty не означает «пользователь ничего не ввёл»

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

Состояниеissetempty
Имя отсутствуетfalsetrue
nullfalsetrue
falsetruetrue
0, 0.0, "0"truetrue
"" или []truetrue
" " — пробел; "false" — текстtruefalse
"Анна", 2truefalse
Нулевой остаток нельзя показывать как неизвестныйСамостоятельный пример
<?php
$stock = 0;
if (empty($stock)) {
    echo "Ошибочная подпись: остаток не заполнен\n";
}
if (isset($stock)) {
    echo 'Верная подпись: осталось ', $stock, ' шт.', "\n";
}
$formStock = '0';
if (empty($formStock)) {
    echo "Строка 0 тоже считается ложной для empty\n";
}
$space = ' ';
if (!empty($space)) {
    echo "Пробел проходит проверку !empty\n";
}
$word = 'false';
if (!empty($word)) {
    echo "Слово false проходит проверку !empty\n";
}
Ожидаемый результат
Ошибочная подпись: остаток не заполнен
Верная подпись: осталось 0 шт.
Строка 0 тоже считается ложной для empty
Пробел проходит проверку !empty
Слово false проходит проверку !empty

Первое сообщение намеренно неверно по смыслу магазина: empty объединяет число 0 с отсутствием. Вторая ветка использует заданный 0 в реальной подписи. Пустой ввод, выключенный флаг и нулевой остаток требуют разных правил. !empty проверяет логическую истинность, а не осмысленность текста: один пробел и слово false проходят эту проверку.

Остаток 0 может быть корректным, как false у выключенного переключателя. Ни isset, ни empty не проверяют тип поля, диапазон или права. Для известной переменной === null проверяет именно null; у отсутствующего имени такое выражение сначала выполнит чтение с предупреждением.

Запасное значение: оператор ??

Запасное значение: отсутствие не равно пустой строкеСамостоятельный пример
<?php
$caption = $missingCaption ?? 'Каталог';
echo $caption, "\n";
$caption = '';
echo '[', $caption ?? 'Каталог', "]\n";
$caption = null;
echo $caption ?? 'Каталог', "\n";
Ожидаемый результат
Каталог
[]
Каталог

?? берёт правый вариант, если слева нет переменной либо там null. Пустая строка остаётся пустой. Запасное значение не записывается в $missingCaption автоматически.

Запасной вариант полезен для необязательной подписи или фильтра. Не маскируйте им опечатки обязательного расчёта. В отличие от ??, ?: выбирает запасной вариант также для 0, "0", false и прочих ложных значений — это другая задача.

Несколько значений: ключ массива не отдельная переменная

Два товара составляют один список покупателяСамостоятельный пример
<?php
$basket = ['Чай', 'Кофе'];
echo 'Первый товар: ', $basket[0], "\n";
echo 'Второй товар: ', $basket[1], "\n";
$basket[] = 'Какао';
echo 'Добавленный товар: ', $basket[2], "\n";
Ожидаемый результат
Первый товар: Чай
Второй товар: Кофе
Добавленный товар: Какао

Квадратные скобки справа от = создают массив значений, запятая разделяет их. При таком списке PHP назначил ключи 0 и 1. Скобки [0] после basket читают одно значение по ключу. Пустые [] слева от = добавляют следующее значение; здесь его ключ 2. Basket — одно имя массива, а не три независимо созданные переменные.

Проверить, что поле комментария предусмотрено, даже пока оно равно nullСамостоятельный пример
<?php
$newBasket[] = 'Чай';
echo 'Автоматически созданный список: ', $newBasket[0], "\n";
$profile = ['name' => 'Анна', 'note' => null];
echo 'Покупатель: ', $profile['name'], "\n";
if (array_key_exists('note', $profile)) {
    echo "Поле комментария предусмотрено в карточке\n";
}
if (!isset($profile['note'])) {
    echo "Текст комментария пока отсутствует\n";
}
Ожидаемый результат
Автоматически созданный список: Чай
Покупатель: Анна
Поле комментария предусмотрено в карточке
Текст комментария пока отсутствует

Array_key_exists получает сначала ключ note, потом массив profile и возвращает true: ключ есть. Isset для того же элемента возвращает false, потому что значение null. Знак => связывает ключ с его значением. Автоматическое создание newBasket разрешено, но явное начальное [] помогает не смешать новый список с ранее существовавшим состоянием.

Автоматическое создание массива при добавлении к неопределённой переменной разрешено без предупреждения. Явное $basket = [] всё же фиксирует начало состояния. Особенно не рассчитывайте на «наверняка пустое» имя во включаемом файле: там оно уже могло существовать.

Ключ note — не самостоятельная переменная $note. array_key_exists различает null и отсутствующий ключ. У объектов isset/empty дополнительно связаны с видимостью и магическими методами.

Следующие темы: массивы и массив внутри массива.

Ссылки и области: короткая карта углубления

МеханизмСмыслПрименение
=Присвоить текущее значение справаОсновной начальный код
=&Связать два имени с одной переменнойЯвно изменяемые данные и некоторые API
Местная переменная функцииИмя действует внутри вызоваРазделить вычисления
global / $GLOBALSДоступ к глобальной области выполненияНапример, существующий API WordPress
Местная staticПамять между вызовами одной функцииСостояние в рамках выполнения
Суперглобальные массивыСпециальные данные доступны в разных областяхHTTP-запрос и окружение

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

Ссылка: два имени одного изменяемого значения

Два имени одной переменнойСамостоятельный пример
<?php
$name = 'Анна';
$alias =& $name;
$alias = 'Мария';
echo $name, "\n";
$name = 'Ольга';
echo $alias, "\n";
unset($alias);
$name = 'Ирина';
echo $name, "\n";
Ожидаемый результат
Мария
Ольга
Ирина

=& создаёт связь имён. Изменение через любое из них видно через другое. unset удаляет alias, сохраняя name. Ссылка PHP — не веб-ссылка и не адрес для арифметики.

Для обычной копии нужен только =. Амперсанд намеренно меняет договор. Ссылку присваивают от переменной, в том числе элемента массива; число или вычисленное выражение не являются изменяемой переменной. У результата функции должен быть договор возврата ссылки.

Объекты не передаются «по ссылке» автоматически: при обычном присваивании копируется идентификатор экземпляра. Независимость объектов — вопрос клонирования. Передача ссылок функции — отдельное объяснение.

Область видимости: где действует имя

Перед этим разделом: объявление и вызов функции, затем параметры и аргументы. В опыте ниже объявление задаёт местную область, вызов выполняет её код.

Имена за пределами функций находятся в глобальной области. У вызова функции местная область: совпадение имени не открывает внешний доступ. function описывает действие, имя со скобками его вызывает.

Одинаковое имя снаружи и внутри функцииСамостоятельный пример
<?php
$name = 'Анна';
function showLocalName() {
    $name = 'Мария';
    echo $name, "\n";
}
showLocalName();
echo $name, "\n";
Ожидаемый результат
Мария
Анна

function объявляет действие, фигурные скобки ограничивают его тело. showLocalName() вызывает действие. Местный name не изменил внешний. Сама пара фигурных скобок произвольного if не создаёт такую же отдельную область переменных.

Передать нужное значение явноСамостоятельный пример
<?php
$name = 'Анна';
function greet($personName) {
    echo 'Здравствуйте, ', $personName, "\n";
}
greet($name);
Ожидаемый результат
Здравствуйте, Анна

personName — местное имя входа функции, параметр. В greet($name) PHP читает внешнее значение и передаёт его внутрь. Функции не приходится искать данные по глобальному имени.

Тело не запускается от объявления. Параметр явно передаёт нужное значение. Замыкания захватывают окружение через use, стрелочные функции — автоматически по значению: это отдельные правила замыканий.

Присваивание только в одной условной ветке создаёт значение лишь при выполнении этой ветки. Если последующему коду оно нужно всегда, задайте начальное значение до ветвления.

global и $GLOBALS: явный доступ к общему состоянию

Для следующего опыта нужны вызовы функций и чтение элемента массива по ключу. Сначала усвойте местную область в предыдущем разделе.

После двух действий показать общий итог обработкиСамостоятельный пример
<?php
$processed = 0;
function markProcessed(): void
{
    global $processed;
    $processed = $processed + 1;
}
markProcessed();
markProcessed();
echo 'После первых двух документов: ', $processed, "\n";
function addOneMore(): void
{
    $GLOBALS['processed'] = $GLOBALS['processed'] + 1;
}
addOneMore();
echo 'Итог для журнала: обработано ', $processed, ' документа', "\n";
Ожидаемый результат
После первых двух документов: 2
Итог для журнала: обработано 3 документа

Функции ничего полезного не возвращают (: void); они намеренно меняют общее число. Global связывает местное имя processed с глобальным; $GLOBALS читает ту же переменную по строковому ключу без $. Вызывающий код использует изменённое состояние в отчёте. Самих документов пример не создаёт: он только отмечает уже выполненные действия.

Новое глобальное имя ещё не означает готовый заголовокСамостоятельный пример
<?php
function connectGlobalName(): void
{
    global $reportTitle;
}
connectGlobalName();
if (array_key_exists('reportTitle', $GLOBALS)) {
    echo "Имя reportTitle создано в глобальной области\n";
}
if (!isset($reportTitle)) {
    echo "Заголовок ещё не готов: используем общий вариант\n";
}
echo 'Заголовок отчёта: ', $reportTitle ?? 'Сводка заказов', "\n";
Ожидаемый результат
Имя reportTitle создано в глобальной области
Заголовок ещё не готов: используем общий вариант
Заголовок отчёта: Сводка заказов

Вызов создал имя со значением null. Ключ в GLOBALS есть, но isset ложен. Оператор ?? выбирает общую строку для вывода, не присваивая её reportTitle. Явная инициализация в одном месте понятнее случайного появления через global.

global может перечислить имена через запятую и создаёт местные ссылки на одноимённые глобальные переменные. Если имени ещё нет, оно появляется с null. $GLOBALS — специальный массив; ему не нужен global внутри функции.

В WordPress встречается предоставленный системой объект $wpdb: global даёт доступ к нему внутри функции. Это использование существующего API. Для собственной функции расчёта чаще яснее параметр и return: зависимости видны и не зависят от прошлых вызовов.

В PHP 8.1+ нельзя заменить $GLOBALS целиком; изменяют конкретные элементы. Глобальная область не является общим постоянным хранилищем всех запросов. Для состояния между запросами нужны БД, сессия или другое хранилище по задаче.

Выбор зависимости: область видимости и глобальное состояние.

include/require наследуют область места включения

Включаемый PHP-файл видит область подключающей строки и способен изменить её переменные. Поэтому шаблону нужен договор о входах. require выбирают для обязательного файла; различия с include и *_once — в управлении выполнением.

Включённый файл наследует область места включенияСамостоятельный пример
<?php
$caption = 'Общий каталог';
require __DIR__ . '/caption.php';
function showSaleCaption() {
    $caption = 'Распродажа';
    require __DIR__ . '/caption.php';
}
showSaleCaption();
echo 'Снаружи: ', $caption, "\n";
Ожидаемый результат
Общий каталог
Распродажа
Снаружи: Общий каталог

require выполняет второй PHP-файл; __DIR__ — папка текущего файла. caption.php включён в архив и только выводит $caption. При первом подключении он видит глобальный caption, при втором — местный caption функции.

global во включённом внутри функции файле связывает местное имя с глобальным. Имя файла само по себе не создаёт изолированную область переменных.

Локальная static: сохранить значение до следующего вызова

Перед static изучите вызов и возврат результата. Сравните: обычное присваивание нуля повторялось бы при каждом вызове; static выполняет начальную установку один раз.

Каждый вызов получает следующий номер строки отчётаСамостоятельный пример
<?php
function nextLineNumber(): int
{
    static $number = 0;
    $number = $number + 1;
    return $number;
}
$first = nextLineNumber();
echo $first, '. Чай — 2 упаковки', "\n";
$second = nextLineNumber();
echo $second, '. Кофе — 1 упаковка', "\n";
echo nextLineNumber(), '. Какао — 3 упаковки', "\n";
Ожидаемый результат
1. Чай — 2 упаковки
2. Кофе — 1 упаковка
3. Какао — 3 упаковки

Первый вызов устанавливает static number = 0, увеличивает до 1 и возвращает 1 во внешний first. Второй сохраняет прежнее местное состояние, увеличивает до 2 и возвращает его в second. Третий return 3 используется непосредственно как часть echo. Если заменить static обычным присваиванием 0, все строки получат номер 1. Это нумерация данного выполнения, не уникальный номер заказа во всех запросах.

Имя number остаётся местным. Static не делает его глобальным. В обычном веб-запросе состояние относится к текущему выполнению; в долгом worker живёт дольше, поэтому необходимо продумать сброс.

До PHP 8.3 инициализатор static должен быть константным выражением. С 8.3 допустимы динамические выражения, например static $length = strlen("TEA");. В переносимых начальных примерах используется ноль.

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

Глубина вложенных вызовов возвращается назадСамостоятельный пример
<?php
function showDepth($remaining) {
    static $depth = 0;
    $depth = $depth + 1;
    echo $depth, "\n";
    if ($remaining > 1) {
        showDepth($remaining - 1);
    }
    $depth = $depth - 1;
}
showDepth(2);
showDepth(1);
Ожидаемый результат
1
2
1

if выполняет блок при истинном условии; > сравнивает числа. Первый вызов входит в себя ещё раз, затем каждый уровень уменьшает depth. Второй обход снова начинается с 1. При исключениях восстановление состояния надо гарантировать; часто проще передавать depth параметром.

Для реального обхода дерева параметр depth часто проще скрытого состояния. Подробнее: рекурсия и остановка.

Static у замыкания и унаследованного метода

Справочное продолжение после функции в переменной, класса и экземпляра и наследования. Поведение одного вида static нельзя без проверки переносить на другой.

Static принадлежит конкретному замыканиюСамостоятельный пример
<?php
function createCounter() {
    return function () {
        static $number = 0;
        $number = $number + 1;
        return $number;
    };
}
$firstCounter = createCounter();
$secondCounter = createCounter();
echo $firstCounter(), "\n";
echo $firstCounter(), "\n";
echo $secondCounter(), "\n";
Ожидаемый результат
1
2
1

Безымянная функция возвращается как значение и вызывается через переменную. У двух созданных Closure собственные счётчики. Создание нового замыкания перед каждым вызовом каждый раз начнёт отсчёт заново.

Унаследованный без замены метод разделяет staticСамостоятельный пример
<?php
class BaseCounter {
    public static function next() {
        static $number = 0;
        $number = $number + 1;
        return $number;
    }
}
class ChildCounter extends BaseCounter {}
echo BaseCounter::next(), "\n";
echo ChildCounter::next(), "\n";
Ожидаемый результат
1
2

Продолжение после ООП: ChildCounter наследует метод, не переопределяя его. С PHP 8.1 такой метод разделяет static с родителем. static перед function означает статический метод, перед $number — его местную память.

Замыкание — отдельный объект вызываемого кода, его счётчик принадлежит этому экземпляру. Унаследованный без переопределения метод разделяет static с родителем с 8.1. Продолжение: замыкания и статические методы.

Global/static: запись значения и перепривязка имени различаются

Здесь используются уже объяснённые global, static и =&, а также вызовы функций. Этот справочный случай можно отложить до задачи со ссылками.

= записывает значение через существующую связь; =& меняет, с чем связано местное имя. Это важно, когда имя уже связано через global или static.

Перепривязать имя — не то же, что записать значениеСамостоятельный пример
<?php
$caption = 'Старый';
function rebindLocalAlias() {
    global $caption;
    $local = 'Новый';
    $caption =& $local;
}
rebindLocalAlias();
echo $caption, "\n";
function assignGlobalValue() {
    global $caption;
    $caption = 'Новый';
}
assignGlobalValue();
echo $caption, "\n";
Ожидаемый результат
Старый
Новый

global связал местное имя с глобальным. Новое =& перенаправило местный псевдоним на другую переменную; прежняя глобальная не изменилась. Обычное = во второй функции записало значение через сохранённую связь.

Связь с местной переменной не сохраняется в staticСамостоятельный пример
<?php
function rebindStatic() {
    static $saved = null;
    echo $saved ?? 'null', "\n";
    $local = 9;
    $saved =& $local;
}
rebindStatic();
rebindStatic();
function assignStatic() {
    static $saved = null;
    echo $saved ?? 'null', "\n";
    $saved = 9;
}
assignStatic();
assignStatic();
Ожидаемый результат
null
null
null
9

Новое связывание местного имени не записало 9 в сохраняемое static-хранилище. Обычное присваивание во второй функции сохранило его. Это правило чтения редкого кода, не рекомендуемое устройство кэша.

Удалить местный псевдоним или глобальное имяСамостоятельный пример
<?php
$caption = 'Каталог';
function removeLocalAlias() {
    global $caption;
    unset($caption);
}
removeLocalAlias();
echo $caption, "\n";
function removeGlobalName() {
    unset($GLOBALS['caption']);
}
removeGlobalName();
var_dump(isset($caption));
Ожидаемый результат
Каталог
bool(false)

Первый unset снял местное имя, созданное global. Второй явно удалил глобальную переменную. У параметра по ссылке удаление местного имени тоже не удаляет переменную вызывающего кода.

Unset местной static тоже не очищает сохранённое значение: при следующем вызове прежнее состояние восстановится. Для сброса присвойте согласованное начальное значение.

ОпытПоследовательность внутриЧто проверяется снаружи
rebindLocalAliasglobal связывает caption; =& перенаправляет только местное имя на localГлобальный caption остался Старый.
assignGlobalValueglobal связывает caption; = записывает через эту связьГлобальный caption стал Новый.
rebindStatic дваждыМестное имя saved перепривязано, сохранённое static-значение не перезаписаноОба вызова показывают null.
assignStatic дваждыПервый = сохраняет 9 в static-хранилищеВторой вызов читает 9.
unset местного alias / GLOBALSВ первом случае убирается местная связь, во втором — глобальное имяСначала Каталог ещё читается, затем isset глобального caption ложно.

Переменные переменных: прочитать динамическое имя

Два шага динамического имени
  1. Field содержит текст city. Это имя другого значения, а не сам город.
  2. Запись $$field использует city как имя, поэтому присваивание создаёт $city.
  3. Чтение $city и ${$field} даёт один город Казань.
  4. Когда поля являются данными задачи, явный $data[$key] хранит их в одном массиве и облегчает проверку ключей.

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

Имя берётся из другой переменнойСамостоятельный пример
<?php
$field = 'city';
$$field = 'Казань';
echo $city, "\n";
echo ${$field}, "\n";
${'delivery-city'} = 'Пермь';
echo ${'delivery-city'}, "\n";
Ожидаемый результат
Казань
Казань
Пермь

$field даёт строку city; дополнительный $ использует её как имя переменной. Фигурные скобки точно отделяют выражение имени. Такая форма технически адресует и имя с дефисом, недоступное обычной записью. Для полей формы обычно понятнее массив.

Сначала выбрать имя или сначала элемент?Самостоятельный пример
<?php
$fields = ['city'];
$city = 'Казань';
echo ${$fields[0]}, "\n";
$name = 'cities';
$cities = ['Казань', 'Пермь'];
echo ${$name}[1], "\n";
$data = ['city' => 'Казань'];
$key = 'city';
echo $data[$key], "\n";
Ожидаемый результат
Казань
Пермь
Казань

Сначала fields[0] даёт имя city. Во втором случае name выбирает массив cities, затем [1] — его элемент. Последний вариант хранит данные в одном явном массиве и обычно проще.

Для неоднозначного $$a[1] указывайте намерение: ${$a[1]} выбирает имя по a[1], а ${$a}[1] сначала выбирает переменную, затем её элемент.

Не создавайте переменные из произвольных имён запроса: они могут затереть состояние. Массив с проверенными ключами контролировать проще. Суперглобальные массивы не получают особую доступность через переменные переменных внутри функций и методов; обращайтесь к ним напрямую. $this также нельзя адресовать динамически.

Динамическое имя свойства: другая структура данных

Для этого позднего раздела сначала нужны объект и его свойства и ключи массива. Запись (object) превращает подготовленный массив в простой объект stdClass; -> читает его свойство.

Выбрать свойство существующего объекта по имениСамостоятельный пример
<?php
$record = (object) [
    'city' => 'Казань',
    'delivery-city' => 'Пермь',
    'labels' => ['основной', 'запасной'],
];
$property = 'city';
echo $record->$property, "\n";
echo $record->{'delivery-' . $property}, "\n";
$property = 'labels';
echo $record->{$property}[1], "\n";
Ожидаемый результат
Казань
Пермь
запасной

(object) создаёт простую запись stdClass, -> читает свойство. Имя property берётся из текущей области кода. Скобки ограничивают составное имя; [1] применяется к массиву в свойстве. Такие имена могут прийти из JSON или XML.

Свойство принадлежит объекту, переменная с именем берётся из области вызывающего кода. $record->{$property}[1] выбирает свойство, потом элемент; $record->{$property[1]} сначала берёт символ/элемент property как имя свойства.

Так читают JSON/XML с необычными ключами. Динамический доступ не отменяет видимость. Создание новых динамических свойств пользовательского класса — другая тема с ограничениями современных PHP; здесь только чтение свойств stdClass.

Предопределённые массивы: откуда приходят данные

Суперглобальные доступны внутри функций без global. Их наполнение зависит от режима запуска и настроек. Доступность имени не обещает наличие всех ключей и не подтверждает доверие к данным.

ИмяНазначение
$_GETQuery string URL, в том числе GET-форма
$_POSTПоля поддерживаемой POST-формы, не произвольный JSON
$_FILESСведения о файлах multipart-загрузки
$_COOKIECookie, присланные в текущем запросе
$_SESSIONДанные открытой/восстановленной сессии
$_SERVERЗапрос и серверное окружение; набор зависит от SAPI
$_ENVПеременные окружения, если наполнение включено настройками
$_REQUESTСмесь GET/POST/COOKIE по настройкам
$GLOBALSИмена глобальной области выполнения

Выбирайте конкретный источник. request_order/variables_order влияют на состав объединённых данных; $_REQUEST скрывает происхождение. Модель запроса: первая PHP-страница.

GET/POST: имя поля превращается в ключ массива

Практический этап после ключей массива, условия if и вызова готовой функции. В HTTP-примере также предполагается запущенный PHP-сервер.

Браузер отправляет имена и значения, PHP разбирает их в массив запроса. name="name" не создаёт вашу обычную $name: её вы заполняете явно.

В /greeting.php?name=Anna часть после ? становится $_GET. Внешнее значение обычно строка либо массив строк. "2" не получает тип int только из-за вида числа.

Структура GET-данных без сети и браузераСамостоятельный пример
<?php
parse_str('name=Anna&quantity=2&filters[color][]=green&filters[color][]=blue', $query);
echo $query['name'], "\n";
var_dump($query['quantity']);
echo $query['filters']['color'][0], "\n";
echo $query['filters']['color'][1], "\n";
Ожидаемый результат
Anna
string(1) "2"
green
blue

parse_str — готовая функция разбора строки параметров URL. Результат она записывает во второй аргумент $query. Это локальный опыт. quantity остаётся строкой; скобки в именах формируют вложенные массивы. Повторное urldecode без причины изменит уже декодированные данные ещё раз.

Форма: name задаёт ключ данныхФрагмент или команда
<form action="greeting.php" method="get">
  <label for="visitor-name">Ваше имя</label>
  <input id="visitor-name" name="name" type="text" maxlength="100">
  <button type="submit">Показать приветствие</button>
</form>

HTML-фрагмент размещают на странице веб-сервера. name задаёт ключ, id связывает поле с label. GET отправляет параметры в URL. maxlength помогает интерфейсу, но не заменяет серверную проверку.

greeting.php: прочитать строку и безопасно показатьПример для указанного окружения
<?php
header('Content-Type: text/html; charset=UTF-8');
$rawName = $_GET['name'] ?? '';
if (!is_string($rawName)) {
    http_response_code(400);
    echo 'Имя должно быть строкой';
    exit;
}
$name = trim($rawName);
if ($name === '' || strlen($name) > 200) {
    http_response_code(400);
    echo 'Укажите имя длиной от 1 до 200 байт';
    exit;
}
echo '<p>Здравствуйте, ',
    htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'),
    '</p>';

Контекстный HTTP-пример. is_string отклоняет name[]=...; trim убирает крайние пробелы; strlen задаёт предел в байтах; htmlspecialchars кодирует текст для HTML. Это просмотр приветствия без изменения состояния. Для сохраняющей формы дополнительно нужны метод, права, CSRF и остальные правила операции.

Здесь if выбирает ветку, ! означает «не», is_string проверяет строку, === сравнивает точно, || означает «или». Готовые функции вызывают записью имя(аргументы). Продолжение: условия, операторы, функции.

$_POST не делает данные доверенными. У изменяющей состояние формы нужны метод, права, CSRF по модели авторизации, ограничения и правила полей. Полная схема — безопасные формы. JSON читают отдельно через php://input.

Группы, множественные поля и нормализация имён

Входное имяСтруктура PHP
profile[name]$_POST["profile"]["name"]
colors[] у multiple select$_POST["colors"] — выбранные значения
first.name / delivery cityfirst_name / delivery_city на верхнем уровне
profile[name]tailprofile["name"], хвост игнорируется
send.x / send.y кнопки imagesend_x / send_y — координаты
Точки, пробелы и хвост после скобок меняют ключиСамостоятельный пример
<?php
parse_str('first.name=Anna&delivery+city=Kazan&profile[name]tail=Maria&send.x=12&send.y=5', $query);
echo $query['first_name'], "\n";
echo $query['delivery_city'], "\n";
echo $query['profile']['name'], "\n";
echo $query['send_x'], ',', $query['send_y'], "\n";
parse_str('profile[first.name]=Anna&profile[last+name]=Maria', $nested);
echo $nested['profile']['first.name'], ' ', $nested['profile']['last name'], "\n";
Ожидаемый результат
Anna
Kazan
Maria
12,5
Anna Maria

У имени верхнего уровня точки и пробелы превращаются в подчёркивания; + обозначает пробел в этой кодировке. Вложенные ключи first.name и last name в опыте сохраняются. Хвост tail после profile[name] игнорируется. Координаты кнопки image становятся send_x/send_y. Выбирайте однозначные HTML name.

Скобки в name не заменяют проверки: клиент может прислать массив вместо строки и наоборот. Разные имена после нормализации могут совпасть. Выбирайте однозначные имена полей.

Преобразуются входящие имена, не любые пробелы во всех значениях. «Анна Мария» остаётся строкой с пробелом. В обычном локальном имени точка по-прежнему отдельный оператор.

Cookie: значение от браузера, а не общая память PHP

Cookie задают через HTTP-заголовок Set-Cookie, например setcookie, до фактического вывода тела. Если браузер присылает её в следующем запросе, значение появляется в $_COOKIE. Изменение обычной переменной cookie не сохраняет.

Одинаковые имя, домен и путь определяют заменяемую cookie. При разных путях/доменах возможны одноимённые cookie. Клиент контролирует присланные значения: наличие cookie не подтверждает оплату или роль.

СлучайЧто происходит
prefs[theme] и prefs[size] через два setcookieДве отдельные cookie, которые PHP может представить массивом prefs
Одна cookie с JSONОдна строка: нужны предел размера, разбор и проверка полей
Имена входящих cookieНе URL-декодируются с PHP 7.2.34 / 7.3.23 / 7.4.11
СессияCookie обычно переносит идентификатор, состояние хранит сервер

Для нескольких значений выбирайте короткий явный формат. Не передавайте недоверенную cookie в unserialize для восстановления произвольных объектов. Цены корзины и права подтверждают серверными данными.

Пошагово о session_start, Secure/HttpOnly/SameSite и смене идентификатора: сессии и авторизация. Эта глава объясняет происхождение значений, не заменяя систему авторизации.

Комбинации изученных правил в задачах сайта

ЗадачаКомбинацияПродолжение
Показать исправленное и прежнее имяКопия originalName до замены nameПовторить опыт без &
Заголовок шаблонаНачальное значение + require в известной областиШаблоны и буфер
Принять остаток 0Присутствие, исходный текст, проверенное числоТипы и вход
Передать город в расчётПараметр вместо скрытого globalВход и результат
Узнать пользователя в другом запросеПеременная + явное хранилище состоянияСессия и cookie
Корректный остаток 0 не должен исчезатьСамостоятельный пример
<?php
$incoming = ['stock' => '0'];
$rawStock = $incoming['stock'] ?? null;
if ($rawStock === '0') {
    $stock = 0;
    $label = 'Нет в наличии';
} else {
    $stock = null;
    $label = 'Нужно проверить остаток';
}
echo $label, "\n";
var_dump($stock);
Ожидаемый результат
Нет в наличии
int(0)

Узкий опыт для "0", не общий валидатор чисел. === проверяет тип и значение, if выбирает ветку, else — другой вариант. Внешний текст и проверенное число получили разные имена. Для прочих количеств нужна полная проверка.

Читаемость и стоимость: что выбирать в обычном коде

Имена переменных сами по себе не заменяют измерения производительности. Разница между понятным $customerName и коротким $x не повод ухудшать код. Гораздо заметнее повторные запросы, ввод-вывод и ненужное удержание больших данных.

PHP умеет откладывать физическое копирование строк и массивов до изменения: это деталь реализации, а не изменение правила независимых значений. Не добавляйте & в надежде автоматически ускорить программу: связь меняет поведение и может усложнить оптимизацию.

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

При изменяемом наборе полей выбирайте массив с осмысленными ключами; при фиксированных простых значениях — обычные переменные. Динамические имена нужны для понимания существующего кода, но редко делают собственную задачу яснее.

Предсказать, запустить и объяснить

  • Сохраните копию name, затем замените исходное имя. Какими будут оба значения?
  • Создайте Name рядом с name: изменятся ли они вместе без ссылки?
  • Сравните isset/empty для 0, "0", "", null, пробела. Что не подходит как проверка корректного остатка?
  • После alias =& name удалите alias. Можно ли читать name?
  • Задайте местный name внутри функции. Что останется снаружи? Как передать вход явно?
  • Для HTML name="profile[email]" напишите путь в $_POST и назовите необходимые проверки.
Разбор
  • Копия сохранила прежнюю строку, исходная переменная получила новую.
  • Регистр различается; обычное присваивание не создаёт связь.
  • empty объединяет правильный ноль с отсутствием. Нужны тип и правила предметной области.
  • Да: удалён псевдоним alias, name осталось.
  • Внешнее имя не меняется. Вход передают аргументом, результат при необходимости возвращают через return.
  • $_POST["profile"]["email"]; проверить массивы на промежуточных уровнях, строку, формат/длину и правила операции.

Маршрут: типы → операторы → условия и циклы → функции → области на практике.

Обсуждения реальных затруднений, использованные при выборе опытов: Почему наличие имени, null и ложность — три разных вопроса.; Почему подключаемый внутри метода файл не видит внешнюю переменную автоматически.; Путаница между нормализацией имени поля и пробелами внутри самого значения.; Как не потерять корректный ноль при проверке входящих данных; старые loose-comparison советы не перенесены.; Начинающий ожидает повторной установки нуля; объяснено однократное начальное присваивание.; Потребность назвать значение по формату; вместо разрастания имён предложен массив ключей. Это постановки проблем, а не нормативная документация PHP.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

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

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.