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

Дерево руководства
Глава 222 · От переменной → к worker
Память PHP: ссылки, циклический сборщик и долгие worker
Полная ветка Garbage Collection: сначала видимый жизненный цикл объекта, затем настоящий цикл A ↔ B и измеряемая политика долгого процесса.
Почему PHP обычно освобождает память сам и где появляется цикл
PHP освобождает большинство значений автоматически. Простое значение исчезает, когда на него больше никто не ссылается; дополнительный сборщик нужен для циклов, где объекты или массивы указывают друг на друга. Поэтому gc_collect_cycles() — не команда «очистить всю память», а точечный инструмент для потенциальных циклических остатков.
- Вход: переменная, объект и ссылки между объектами
- Действие: счётчик ссылок освобождает обычные значения; cycle collector периодически исследует возможные циклы
- Результат: память возвращается allocator-у PHP, а процесс не обязан немедленно вернуть её ОС
| Ситуация | Что обычно происходит |
|---|---|
$copy = $object; unset($object); | Объект живёт, пока на него указывает $copy. |
| Последняя ссылка исчезла | Объект уничтожается сразу обычным подсчётом ссылок. |
| A → B и B → A, внешние ссылки исчезли | Счётчики внутри цикла не равны нулю; цикл становится кандидатом GC. |
| Короткий web request | Завершение запроса освобождает request memory; ручной GC редко нужен. |
| Worker обрабатывает тысячи jobs | Циклы и кэши накапливаются между заданиями; измерение и ограничение batch становятся важными. |
Счётчик ссылок: объект живёт до последнего владельца
Имя переменной не содержит объект «внутри себя»: оно ведёт к значению. Присваивание объекта другой переменной создаёт ещё одну ссылку на тот же объект, а не независимую копию. unset() удаляет одно имя; destructor вызывается, когда исчезает последняя ссылка или завершается запрос.
<?php
declare(strict_types=1);
final class Parcel
{
public function __construct(public string $code) {}
public function __destruct() { echo "Освобождён {$this->code}\n"; }
}
$first = new Parcel('A-17');
$alias = $first;
unset($first);
echo "После unset(first) объект доступен: {$alias->code}\n";
unset($alias);
echo "Последняя ссылка удалена\n";
Ожидаемый результат
После unset(first) объект доступен: A-17
Освобождён A-17
Последняя ссылка удалена
Видимый порядок доказывает границу: первый unset не уничтожил объект, второй удалил последнюю ссылку. Для отдельного объекта нужен clone, а его вложенные объекты по умолчанию остаются общими, пока __clone() не изменит это явно.
- Ссылки PHP с
&и внутренний refcount — связанные, но не тождественные понятия. - Объект присваивается как handle к одному экземпляру; массив использует copy-on-write и копируется при изменении.
debug_zval_dump()полезен только как диагностика: временные аргументы и оптимизации влияют на показываемый refcount.
Цикл A ↔ B: почему одного refcount недостаточно
После unset внешних имён уже нет, но каждый Node всё ещё удерживает другой. Сборщик хранит потенциальные корни в специальном буфере и, когда запускается, определяет, достижимы ли они извне. gc_enable() и gc_disable() управляют автоматическим запуском; явный gc_collect_cycles() всё равно можно вызвать и при отключённом автоматическом GC.
<?php
declare(strict_types=1);
gc_disable();
final class Node
{
public ?Node $next = null;
}
$first = new Node();
$second = new Node();
$first->next = $second;
$second->next = $first;
unset($first, $second);
echo 'Собрано в первый раз: ', gc_collect_cycles(), "\n";
echo 'Собрано во второй раз: ', gc_collect_cycles(), "\n";
Ожидаемый результат
Собрано в первый раз: 2
Собрано во второй раз: 0
Первый вызов освобождает два недостижимых объекта. Второй показывает ноль: «собрать ещё раз» не освобождает обычные живые значения и не является универсальным способом снизить RSS.
- Скрипт создаёт A и B
- A.next указывает на B
- B.next указывает на A
- unset удаляет только внешние имена
- cycle collector доказывает недостижимость пары и освобождает её
Функция gc_status() показывает статистику текущего процесса: поля и их точный набор зависят от версии PHP. Для диагностики фиксируйте версию, gc_enabled(), число runs/collected и память до/после одного и того же workload; не сравнивайте разные процессы только по одному RSS.
Производительность GC: измерить batch, циклы и реальную границу worker
Сборка циклов имеет цену: PHP хранит кандидатов и выполняет обход. Отключение GC иногда ускоряет короткий участок без циклов, но в долгом worker способно вызвать рост памяти. Сначала измеряйте реальный workload. memory_get_usage(true) включает память, зарезервированную allocator-ом; memory_get_peak_usage(true) — исторический максимум, который не обязан уменьшаться после unset или GC.
<?php
declare(strict_types=1);
function processBatch(int $jobs, int $payloadBytes): array
{
$peakBefore = memory_get_peak_usage(true);
for ($i = 0; $i < $jobs; $i++) {
$payload = str_repeat('x', $payloadBytes);
$checksum = hash('sha256', $payload);
unset($payload, $checksum);
}
return [
'jobs' => $jobs,
'peak_grew' => memory_get_peak_usage(true) >= $peakBefore,
'gc_enabled' => gc_enabled(),
];
}
$report = processBatch(3, 1024);
echo 'Заданий: ', $report['jobs'], "\n";
echo 'Пик не уменьшился: ', $report['peak_grew'] ? 'да' : 'нет', "\n";
echo 'GC включён: ', $report['gc_enabled'] ? 'да' : 'нет', "\n";
Ожидаемый результат
Заданий: 3
Пик не уменьшился: да
GC включён: да
Опыт детерминированно показывает семантику peak: максимум не идёт назад. Для production журналируют usage, peak, gc_status, длительность и число jobs, затем перезапускают worker по обоснованному лимиту.
| Приём | Когда разумен | Риск |
|---|---|---|
| Оставить автоматический GC | Обычное приложение и неизвестная структура объектов. | Минимальный; это безопасная отправная точка. |
gc_collect_cycles() между batch | Измерено накопление циклов после законченной порции. | Слишком частый вызов тратит CPU без пользы. |
Временно gc_disable() | Короткий доказанный участок создаёт много кандидатов, но не циклы. | При исключении или новом цикле память растёт; включать обратно в finally. |
| Перезапуск worker после N jobs/MB | Нужна предсказуемая граница и есть supervisor. | Не заменяет поиск утечки и идемпотентность задания. |
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


