Перейти к содержимому
Поиск
Php Разработка

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

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

Александр НекрасовИзменено:
Практический мануал PHP: от переменных и функций к объектной модели интернет-магазина
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 67 · От обычной ссылки к времени жизни кэша

WeakReference и WeakMap: владение, кэш и освобождение объектов

Почему объект остаётся в памяти или исчезает раньше ожидания; слабые ссылки, служебные данные, null и все методы двух классов.

Наблюдать за объектом, не продлевая ему жизнь

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

Обычный владелец и слабый наблюдатель

$product сохраняет объект, а $weak только позволяет получить его, пока он существует.

  1. $product = new stdClass()

    Обычная переменная удерживает созданный объект.

  2. WeakReference::create($product)

    Создаёт наблюдателя без дополнительного владения целью.

  3. $weak->get()

    Пока цель жива, возвращает тот же объект.

  4. unset($product)

    В простом примере исчезает последний обычный владелец.

  5. $weak->get() → null

    Наблюдатель остался, но цели уже нет.

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

Долгоживущий обработчик открывает карточки товаров и хранит служебные сведения для отладки. Если служебный список сохраняет каждый объект обычной ссылкой, все карточки остаются в памяти даже после окончания работы с ними. Слабая ссылка позволяет спросить «этот объект ещё существует?» и получить его, пока он нужен кому-то ещё. Она не обязана сохранять объект до вашего следующего обращения.

Сначала вспомним создание объекта. $product = (object) ["title" => "Чай"] преобразует массив в простой объект stdClass с полем title. $editor = $product даёт вторую переменную, через которую доступен тот же объект. unset удаляет конкретную переменную; пока другой владелец сохраняет объект, тот остаётся. WeakReference создаётся специальным методом create, а get возвращает объект или null. Это не PHP-ссылка с символом &: ссылка на переменную решает другую задачу.

Владелец и наблюдатель имеют разные роли
  1. $product → обычная ссылка удерживает объект
  2. WeakReference::create($product) → наблюдатель не удерживает объект
  3. unset($product) → последний владелец исчез
  4. $watch->get() → null, если других владельцев нет
Диагностика не должна удерживать закрытую карточкуСамостоятельный пример
<?php
declare(strict_types=1);

$product = (object) ['title' => 'Чай'];
$watch = WeakReference::create($product);
echo 'Открыта карточка: ', $watch->get()->title, "\n";
unset($product);
echo $watch->get() === null ? "Карточка освобождена; наблюдатель остался\n" : "Карточка ещё нужна\n";
Ожидаемый результат
Открыта карточка: Чай
Карточка освобождена; наблюдатель остался

WeakReference::create создаёт слабого наблюдателя. get возвращает объект, пока есть обычные владельцы, иначе null. unset убирает конкретную переменную, а не уничтожает объект независимо от остальных связей. Для первого результата сильных владельцев больше не было.

Вторая обычная переменная продолжает владеть карточкойСамостоятельный пример
<?php
declare(strict_types=1);

$product = (object) ['title' => 'Чай'];
$editor = $product;
$watch = WeakReference::create($product);
unset($product);
echo 'Редактор ещё работает: ', $watch->get()->title, "\n";
unset($editor);
echo $watch->get() === null ? "Последний владелец ушёл: карточка освобождена\n" : "Есть другой владелец\n";
Ожидаемый результат
Редактор ещё работает: Чай
Последний владелец ушёл: карточка освобождена

Обычное присваивание $editor = $product даёт вторую сильную связь. Слабость относится только к watch; наличие WeakReference не отменяет обычных владельцев. Эта модель объясняет, почему unset одной переменной не всегда уменьшает память ожидаемым образом.

Объект можно представить как документ, обычные ссылки — как открытые карточки доступа, а слабую ссылку — как запись «проверь, существует ли документ». Это аналогия владения, не буквальная схема памяти PHP. Удаление переменной не означает принудительное удаление объекта, а сборка циклов может отложить освобождение.

WeakMap: сведения живут столько, сколько объект-ключ

Теперь к каждому объекту товара нужно прикрепить рассчитанную подпись, не меняя класс товара. Обычный PHP-массив принимает ключи int/string, а WeakMap принимает объект. Запись выбирается по идентичности экземпляра, не по одинаковым свойствам. Когда объект-ключ больше никому не нужен, запись удаляется автоматически вместе со служебным значением.

new WeakMap() создаёт пустую карту. $labels[$product] = "Чай — готов к публикации" записывает значение по объекту. Квадратные скобки работают благодаря ArrayAccess, count — благодаря Countable, foreach — благодаря IteratorAggregate. Карта похожа по синтаксису на массив, но её правила ключей и времени жизни другие.

Дополнительная подпись не владеет товаром
  1. $product → карточка Чай
  2. $labels[$product] = «Чай — готов к публикации» → отдельная служебная запись
  3. unset($product) → больше нет владельцев ключа
  4. Запись подписи исчезает → count($labels) = 0
Подпись кэшируется до закрытия карточкиСамостоятельный пример
<?php
declare(strict_types=1);

$product = (object) ['title' => 'Чай'];
$labels = new WeakMap();
$labels[$product] = 'Чай — готов к публикации';
echo $labels[$product], "\n";
echo 'Подписей в кэше: ', count($labels), "\n";
unset($product);
echo 'После закрытия карточки: ', count($labels), " подписей\n";
Ожидаемый результат
Чай — готов к публикации
Подписей в кэше: 1
После закрытия карточки: 0 подписей

Ключом WeakMap служит объект, а не строка и не его свойства. Запись автоматически исчезает, когда объект-ключ больше не удерживается сильными связями. Здесь count измеряет результат управления жизненным циклом, а не подменяет работу кэша.

Две карточки одного артикула имеют разные записи кэшаСамостоятельный пример
<?php
declare(strict_types=1);

$first = (object) ['sku' => 'tea'];
$second = (object) ['sku' => 'tea'];
$labels = new WeakMap();
$labels[$first] = 'Чай: подпись первой карточки';
echo $labels[$first], "\n";
echo isset($labels[$second]) ? "Вторая подпись найдена\n" : "Вторую карточку нужно обработать отдельно\n";
Ожидаемый результат
Чай: подпись первой карточки
Вторую карточку нужно обработать отдельно

Равенство sku не делает экземпляры идентичными. Если кэш нужен по бизнес-ключу tea между разными объектами или запросами, WeakMap не решает эту задачу: используйте обычный индекс/кэш с правилами инвалидирования.

Кэш вычисления: полезен для неизменяемого объекта

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

Повторное обращение использует рассчитанную подпись
  1. labelFor($product) → записи пока нет
  2. Считаем подпись → сохраняем в $labels[$product]
  3. Второй labelFor с тем же объектом → читаем кэш
  4. После удаления последнего владельца → карта очищает запись
Повторно использовать результат для одного объектаСамостоятельный пример
<?php
class Product {
    public function __construct(public readonly string $sku) {}
}
function labelFor(Product $product, WeakMap $labels): string {
    if (!isset($labels[$product])) {
        echo "Вычисление\n";
        $labels[$product] = 'Товар ' . $product->sku;
    }
    return $labels[$product];
}
$labels = new WeakMap();
$product = new Product('A-10');
echo labelFor($product, $labels), "\n";
echo labelFor($product, $labels), "\n";
unset($product);
echo 'Записей: ', count($labels), "\n";
Ожидаемый результат
Вычисление
Товар A-10
Товар A-10
Записей: 0

public readonly string в параметре конструктора создаёт доступное для чтения свойство; оно получает значение при создании и не переназначается. Функция получает карту объектом: изменение записи видно вызывающему коду без &. Для столь простой конкатенации кэш не нужен; реальная выгода появляется у дорогой повторной обработки, измеренной в вашем проекте.

Когда «слабый» кэш всё-таки удерживает объект

Слабым является именно ключ карты. Значение сохраняется обычным образом. Если это значение содержит объект-ключ, замыкание с use ($object) или другой путь обратно к нему, объект остаётся достижим через саму карту. Аналогично get() из WeakReference отдаёт обычный объект: сохранение результата в переменной временно делает её новым владельцем.

Обратная сильная связь отменяет желаемую очистку
  1. WeakMap → значение хранит $product
  2. Значение → обычная ссылка на объект-ключ
  3. unset внешней переменной не убирает эту связь
  4. Удаление записи разрывает путь; объект может освободиться
Ссылка из значения кэша обратно на ключ мешает очисткеСамостоятельный пример
<?php
declare(strict_types=1);

$product = (object) ['title' => 'Чай'];
$watch = WeakReference::create($product);
$cache = new WeakMap();
$cache[$product] = ['product' => $product, 'label' => 'Чай'];
unset($product);
echo $watch->get() === null ? "Карточка освобождена\n" : "Значение кэша всё ещё держит карточку\n";
$held = $watch->get();
unset($cache[$held], $held);
echo $watch->get() === null ? "Обратная связь удалена: карточка освобождена\n" : "Остался владелец\n";
Ожидаемый результат
Значение кэша всё ещё держит карточку
Обратная связь удалена: карточка освобождена

Слабым является ключ WeakMap, но значение хранится обычным образом. Массив в значении содержит сильную связь с тем же товаром. После удаления записи и временной переменной held эта связь исчезает. Обычно храните только производную подпись, которая не ссылается обратно на ключ.

Сохранённый результат get становится обычным владельцемСамостоятельный пример
<?php
declare(strict_types=1);

$product = (object) ['title' => 'Чай'];
$watch = WeakReference::create($product);
$temporary = $watch->get();
unset($product);
echo 'Временная обработка: ', $temporary->title, "\n";
unset($temporary);
echo $watch->get() === null ? "Обработка закончена: карточка освобождена\n" : "Карточка ещё нужна\n";
Ожидаемый результат
Временная обработка: Чай
Обработка закончена: карточка освобождена

get не создаёт копию и не возвращает специальный слабый объект. Его результат — обычный доступ к товару; пока temporary хранит его, товар существует. Это полезно на время конкретного действия, но длительное сохранение результата отменит ожидаемую очистку.

После foreach переменная ключа также может удерживать последний объект. Обнулите/удалите её, если она больше не нужна, и проверьте остальные владельцы. Не копируйте все ключи в массив ради диагностики очистки: вы сами создадите сильные ссылки на них.

Сборка циклов, диагностика и выбор контейнера

Фраза «два объекта ссылаются друг на друга, значит они никогда не удалятся» для современного PHP неверна: сборщик умеет находить недостижимые циклы. Но живой контейнер, который по-прежнему хранит объект, не является недостижимым циклом. Сначала выясните, кто владелец, затем выбирайте WeakReference или WeakMap; не заменяйте все ссылки слабыми автоматически.

Недостижимый цикл отличается от живого владельца
  1. Объект хранит ссылку на себя
  2. Внешняя переменная удалена, остался только цикл
  3. gc_collect_cycles() собирает недостижимый цикл
  4. WeakReference::get() возвращает null
Недостижимая самоссылка может быть собранаСамостоятельный пример
<?php
declare(strict_types=1);

gc_enable();
$card = (object) ['title' => 'Чай'];
$card->self = $card;
$watch = WeakReference::create($card);
unset($card);
gc_collect_cycles();
echo $watch->get() === null ? "Недостижимая карточка с циклом освобождена\n" : "Карточка остаётся достижимой\n";
Ожидаемый результат
Недостижимая карточка с циклом освобождена

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

ПотребностьПодходящий инструментКто удерживает объект
Гарантированно использовать объект позжеОбычное свойство/переменнаяВаш код
Спросить, жив ли один объектWeakReferenceДругие владельцы; сама ссылка нет
Служебные данные по идентичности объектаWeakMapКлюч слабый; значения обычные
Коллекция должна владеть участникамиSplObjectStorageКоллекция сохраняет объекты
Найти товар по строковому sku/IDМассив или явный кэшПолитика вашего контейнера

WeakReference доступен с PHP 7.4, WeakMap — с PHP 8.0; оба класса встроенные и final. Оба нельзя сериализовать. WeakMap можно клонировать: получится отдельная карта с теми же ключами и обычными правилами копирования значений, а не глубокие копии товаров. С PHP 8.4 диагностический вывод WeakReference показывает наблюдаемый объект или null. Это сведения отладочного вывода, не формат API и не обещание наличия вызываемого метода __debugInfo.

Копия карты имеет свои подписи, но те же объекты-ключиСамостоятельный пример
<?php
declare(strict_types=1);

$product = (object) ['title' => 'Чай'];
$draftLabels = new WeakMap();
$draftLabels[$product] = 'Чай: черновик';
$publicLabels = clone $draftLabels;
$publicLabels[$product] = 'Чай: опубликован';
echo $draftLabels[$product], "\n", $publicLabels[$product], "\n";
unset($product);
echo 'После закрытия: ', count($draftLabels), ' черновых и ', count($publicLabels), " публичных подписей\n";
Ожидаемый результат
Чай: черновик
Чай: опубликован
После закрытия: 0 черновых и 0 публичных подписей

Карты разные, поэтому замена строки в одной не изменила другую. Ключом обеих остаётся один product. Когда он исчез, записи исчезли в обеих картах. clone карты не клонирует каждый ключ.

Дальше: клонирование и вложенные объекты, диагностика через Reflection, измерение памяти и времени. Для обычной небольшой страницы заранее добавлять слабые ссылки не требуется: сначала должна появиться конкретная задача владения или кэширования.

Сначала найти владельца, затем выбирать слабую связь

Объект и наблюдатель
  1. product → карточка Чай: сильная связь
  2. watch ⇢ карточка Чай: наблюдение без продления жизни
  3. get может дать временную сильную связь
  4. Удаление всех сильных владельцев освобождает карточку
  5. Наблюдатель после этого возвращает null

Это модель достижимости, а не обещание точного числа байтов памяти после каждой строки. WeakReference полезен наблюдателю, WeakMap — производным сведениям по идентичности объекта; SplObjectStorage нужен, когда контейнер сам должен владеть объектами.

Попробуйте. Оставьте $editor = $product перед unset($product) в примере WeakMap. Когда исчезнет подпись?

Разобрать решение

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

Функции и методы этой темы

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

WeakReference::__constructПрямой new WeakReference запрещён; объект создают через create с конкретной целью.

PHP 7.4+ · Core

WeakReference::__construct() — запрещает прямое создание

Прямой new WeakReference запрещён; объект создают через create с конкретной целью.

Параметр / результатДоговор
ПараметрыНет.
РезультатПопытка new WeakReference выбрасывает Error.
Правильное созданиеWeakReference::create($object) получает существующий объект.
Слабая ссылка должна знать свою цель
  1. new WeakReference → Error
  2. Создан $product
  3. create($product) → готовая слабая ссылка
Увидеть запрет обычного конструктораСамостоятельный пример
<?php
try { new WeakReference(); }
catch (Error $error) { echo "Используйте create\n"; }
Ожидаемый результат
Используйте create

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

Создать наблюдателя через фабричный методСамостоятельный пример
<?php
$product = new stdClass();
$weak = WeakReference::create($product);
echo $weak->get() === $product ? "Наблюдаем этот товар\n" : "Нет объекта\n";
Ожидаемый результат
Наблюдаем этот товар

Статический create вызывается через имя класса и возвращает объект WeakReference. Обычный конструктор создавать вручную не нужно.

Рядом: WeakReference::create, WeakReference::get. Модель владения.

WeakReference::createПолучить слабую ссылку на существующий объект, не продлевая его жизнь.

PHP 7.4+ · Core

WeakReference::create(object $object): WeakReference

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

Параметр / результатДоговор
$objectЛюбой объект; массив, строка и число дадут TypeError.
РезультатWeakReference. Если слабая ссылка на этот же объект уже существует, возвращается существующий экземпляр WeakReference.
Не сохраняетВладение целью. Созданный на лету объект без других владельцев может исчезнуть сразу.
Одна цель — наблюдатель без владения
  1. $product существует
  2. create($product) → $weak
  3. Повторный create для того же объекта → тот же WeakReference
  4. Время жизни цели определяют обычные ссылки
Повторное создание для того же объектаСамостоятельный пример
<?php
$product = new stdClass();
$first = WeakReference::create($product);
$second = WeakReference::create($product);
var_dump($first === $second);
Ожидаемый результат
bool(true)

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

Слабая ссылка без владельца не сохраняет новую карточкуСамостоятельный пример
<?php
declare(strict_types=1);

$lost = WeakReference::create((object) ['title' => 'Чай']);
echo $lost->get() === null ? "Карточка сразу освободилась: владельца нет\n" : "Карточка доступна\n";
$product = (object) ['title' => 'Кофе'];
$owned = WeakReference::create($product);
echo 'Владелец сохранил: ', $owned->get()->title, "\n";
Ожидаемый результат
Карточка сразу освободилась: владельца нет
Владелец сохранил: Кофе

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

Рядом: WeakReference::get, WeakReference::__construct. Модель владения.

WeakReference::getПолучить живой объект либо null, если цель уже освобождена.

PHP 7.4+ · Core

WeakReference::get(): ?object

Получить живой объект либо null, если цель уже освобождена.

Параметр / результатДоговор
ПараметрыНет.
РезультатТот же объект или null. Обозначение ?object включает оба варианта.
Сохранение результатаПолученный объект в обычной переменной удерживается на время её жизни.
ПрактикаПрочитайте один раз в локальную переменную, проверьте null, затем работайте с объектом.
Проверка и работа используют один сохранённый результат
  1. get() → $live
  2. Если null → пропустить необязательное уведомление
  3. Если объект → прочитать его данные
  4. unset($live) → больше не удерживаем временно
Безопасно прочитать необязательную цельСамостоятельный пример
<?php
$window = (object) ['title' => 'Заказы'];
$weak = WeakReference::create($window);
$live = $weak->get();
if ($live !== null) { echo $live->title, "\n"; }
unset($live, $window);
var_dump($weak->get());
Ожидаемый результат
Заказы
NULL

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

Исчезнувшая цель — явная ветка выполненияСамостоятельный пример
<?php
$subscriber = (object) ['name' => 'Отчёт'];
$weak = WeakReference::create($subscriber);
unset($subscriber);
$live = $weak->get();
echo $live === null ? "Подписчик завершён\n" : "Можно уведомить\n";
Ожидаемый результат
Подписчик завершён

Не обещайте тип object там, где get может вернуть null. Верный договор вашей обёртки — ?object либо явное исключение с понятным смыслом.

Рядом: WeakReference::create, WeakMap::offsetGet. Модель владения.

WeakMap::offsetSetЗаписать или заменить служебное значение по объекту-ключу.

PHP 8.0+ · Core

WeakMap::offsetSet(object $object, mixed $value): void

Записать или заменить служебное значение по объекту-ключу.

Параметр / результатДоговор
$objectОбъект-ключ. Строковые/числовые ключи не поддерживаются.
$valueЛюбое PHP-значение, включая null и объект. Значение удерживается обычной сильной связью.
РезультатНичего не возвращает. Запись $map[$object] = $value делает ту же операцию.
Замена / ошибкиСуществующая запись заменяется. $map[] = $value без объекта даёт Error; неверный тип аргумента метода — TypeError.
Повторная запись меняет значение того же экземпляра
  1. $map[$product] = «черновик»
  2. offsetSet($product, «готово»)
  3. Количество записей остаётся 1
  4. offsetGet → «готово»
Обновить метку объектаСамостоятельный пример
<?php
$product = new stdClass();
$map = new WeakMap();
$map->offsetSet($product, 'Черновик');
$map[$product] = 'Готово';
echo $map[$product], ': ', count($map), "\n";
Ожидаемый результат
Готово: 1

Строка и метод — два синтаксических способа одной операции. Повторное присваивание не создаёт ещё один экземпляр ключа.

Записать только вычисленные данные, не сам ключСамостоятельный пример
<?php
$product = (object) ['sku' => 'A-10'];
$map = new WeakMap();
$map[$product] = ['label' => 'Товар ' . $product->sku];
echo $map[$product]['label'], "\n";
unset($product);
echo count($map), "\n";
Ожидаемый результат
Товар A-10
0

Значение содержит строку, без обратной ссылки на товар. Если сохранить туда сам $product или замыкание, которое его захватило, карта может удержать ключ через значение.

Рядом: WeakMap::offsetGet, WeakMap::offsetExists, WeakMap::offsetUnset. Модель владения.

WeakMap::offsetGetПрочитать значение по конкретному объекту; отсутствие записи вызывает Error.

PHP 8.0+ · Core

WeakMap::offsetGet(object $object): mixed

Прочитать значение по конкретному объекту; отсутствие записи вызывает Error.

Параметр / результатДоговор
$objectОбъект, для которого ранее создана запись; равный по свойствам другой экземпляр не подходит.
РезультатСохранённое значение любого типа, включая null.
ОшибкаError при отсутствующем ключе; TypeError для не-объекта.
СинтаксисОбычное чтение $map[$object] обращается к той же записи. ?? может задать запасной вариант для отсутствия/null.
Чтение требует совпадения идентичности
  1. $product записан с ценой 900
  2. offsetGet($product) → 900
  3. Другой new stdClass → записи нет → Error
Прочитать стоимость и обработать отсутствующую записьСамостоятельный пример
<?php
$product = new stdClass();
$other = new stdClass();
$map = new WeakMap();
$map[$product] = 900;
echo $map->offsetGet($product), "\n";
try { $map->offsetGet($other); }
catch (Error $error) { echo "Сначала вычислите значение\n"; }
Ожидаемый результат
900
Сначала вычислите значение

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

Обновить вложенный счётчик у существующей записиСамостоятельный пример
<?php
$product = new stdClass();
$map = new WeakMap();
$map[$product] = ['views' => 1];
$map[$product]['views']++;
echo $map[$product]['views'], "\n";
Ожидаемый результат
2

WeakMap поддерживает такое изменение существующей записи через скобки. Это не общее обещание для всех пользовательских ArrayAccess: контракт их offsetGet может отличаться. При проектировании своей коллекции этот нюанс нужно проверять отдельно.

Рядом: WeakMap::offsetSet, WeakMap::offsetExists, WeakMap::getIterator. Модель владения.

WeakMap::offsetExistsПроверить запись с ненулевым значением; null требует отдельной модели наличия.

PHP 8.0+ · Core

WeakMap::offsetExists(object $object): bool

Проверить запись с ненулевым значением; null требует отдельной модели наличия.

Параметр / результатДоговор
$objectОбъект для проверки. Не объект даёт TypeError.
Результат на PHP 8.5.11true при существующем значении, отличном от null. Отсутствие и сохранённый null дают false; isset($map[$object]) ведёт себя так же.
Нюанс документацииКраткое официальное описание говорит о нахождении объекта в карте. Реальное отличие null показано ниже: запись учитывается в count и читается offsetGet, хотя offsetExists false.
Для отрицательного кэшаХраните обёртку вроде ["value" => null], если нужно отличать «уже проверено, результата нет» от «ещё не проверяли».
null и отсутствие сходятся в false, но не в количестве
  1. offsetSet($product, null)
  2. count → 1; offsetGet → null
  3. offsetExists → false
  4. Обёртка [value => null] → offsetExists true
Запись с null и результат проверки наличия различаютсяСамостоятельный пример
<?php
declare(strict_types=1);

$product = (object) ['title' => 'Чай'];
$prices = new WeakMap();
$prices[$product] = null;
echo 'Записей проверки цены: ', count($prices), "\n";
echo $prices->offsetExists($product) ? "Есть ненулевой результат\n" : "Ненулевого результата нет\n";
echo $prices->offsetGet($product) === null ? "Проверка сохранила отсутствие цены\n" : "Цена найдена\n";
Ожидаемый результат
Записей проверки цены: 1
Ненулевого результата нет
Проверка сохранила отсутствие цены

offsetExists и isset используют здесь null-чувствительный договор. Запись физически существует и offsetGet возвращает её null. Если нужно отличить «не проверяли» от «проверили, цены нет», храните ненулевую оболочку, показанную следующим примером.

Запомнить отрицательный результат поиска ценыСамостоятельный пример
<?php
declare(strict_types=1);

$product = (object) ['title' => 'Чай'];
$prices = new WeakMap();
$prices[$product] = ['value' => null];
if ($prices->offsetExists($product)) {
    echo "Повторный запрос поставщику не нужен\n";
    echo $prices[$product]['value'] === null ? "Цена пока не опубликована\n" : "Цена получена\n";
}
Ожидаемый результат
Повторный запрос поставщику не нужен
Цена пока не опубликована

Оболочка-массив не равна null, поэтому наличие записи подтверждает выполненную проверку. Внутренний null остаётся отрицательным результатом. В реальном кэше определите срок действия: цена может появиться, пока объект товара продолжает жить.

Рядом: WeakMap::offsetGet, WeakMap::offsetSet, WeakMap::count. Модель владения.

WeakMap::offsetUnsetУдалить одну служебную запись, сохранив сам объект, если у него есть владельцы.

PHP 8.0+ · Core

WeakMap::offsetUnset(object $object): void

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

Параметр / результатДоговор
$objectОбъект-ключ удаляемой записи.
РезультатВозвращаемого значения нет. Отсутствующий объект-ключ не создаёт ошибку.
Синтаксисunset($map[$object]) выполняет ту же операцию.
Жизненный циклУдаляется связь карты с данными, а не внешний объект. Значение может освободиться, если других владельцев нет.
Инвалидация кэша не удаляет товар
  1. Ключ $product → старая подпись
  2. offsetUnset($product) → записи больше нет
  3. $product всё ещё существует
  4. Следующее обращение пересчитает подпись
Инвалидировать данные после изменения товараСамостоятельный пример
<?php
$product = (object) ['title' => 'Старое имя'];
$map = new WeakMap();
$map[$product] = $product->title;
$product->title = 'Новое имя';
$map->offsetUnset($product);
echo $product->title, "\n";
echo count($map), "\n";
Ожидаемый результат
Новое имя
0

Автоматическая очистка по времени жизни не обновляет изменяемый объект. Здесь приложение знает о смене данных и явно удаляет устаревший кэш.

Повторная очистка допустимаСамостоятельный пример
<?php
$product = new stdClass();
$map = new WeakMap();
$map[$product] = 'готово';
unset($map[$product]);
$map->offsetUnset($product);
echo count($map), "\n";
Ожидаемый результат
0

Повторное удаление не требует придумывать временную запись. Но аргумент метода по-прежнему должен быть объектом.

Рядом: WeakMap::offsetSet, WeakMap::count, WeakMap::offsetExists. Модель владения.

WeakMap::countУзнать число текущих живых записей, включая записи со значением null.

PHP 8.0+ · Core

WeakMap::count(): int

Узнать число текущих живых записей, включая записи со значением null.

Параметр / результатДоговор
ПараметрыНет.
РезультатНеотрицательное число записей в карте. count($map) вызывает тот же договор Countable.
ИзмененияЧисло уменьшается при удалении записи и при освобождении ключа.
Не измеряетРазмер памяти, число всех когда-либо обработанных объектов или количество успешных результатов.
Счётчик относится к текущей карте
  1. Два живых ключа → 2
  2. Один ключ освобождён → 1
  3. Оставшаяся запись равна null → всё равно 1
Автоматическая очистка меняет количествоСамостоятельный пример
<?php
$first = new stdClass();
$second = new stdClass();
$map = new WeakMap();
$map[$first] = 'готово';
$map[$second] = null;
echo $map->count(), "\n";
unset($first);
echo count($map), "\n";
Ожидаемый результат
2
1

Оставшийся null считается записью. Поэтому count нельзя трактовать как число offsetExists true для всех возможных ключей.

Переменная foreach может удерживать последний ключСамостоятельный пример
<?php
$product = new stdClass();
$map = new WeakMap();
$map[$product] = 'готово';
foreach ($map as $key => $value) { echo $value, "\n"; }
unset($product);
echo count($map), "\n";
unset($key, $value);
echo count($map), "\n";
Ожидаемый результат
готово
1
0

После цикла $key остался обычной переменной со ссылкой на объект. Карта очистилась только после удаления этого последнего владельца. Так диагностический обход сам может изменить наблюдаемое время жизни.

Рядом: WeakMap::getIterator, WeakMap::offsetUnset, WeakMap::offsetExists. Модель владения.

WeakMap::getIteratorПолучить обход карты; ключами foreach будут объекты, значениями — связанные данные.

PHP 8.0+ · Core

WeakMap::getIterator(): Iterator

Получить обход карты; ключами foreach будут объекты, значениями — связанные данные.

Параметр / результатДоговор
ПараметрыНет.
РезультатВнешний объект итератора для карты. Официальная страница указывает Exception при неудаче получения.
Обычное использованиеforeach ($map as $object => $value) запрашивает обход автоматически.
ОсторожноПрочитанный ключ становится обычной ссылкой в переменной. Обход не является неизменяемым снимком для бизнес-отчёта.
Ключ остаётся объектом во время обхода
  1. Карта содержит $product → «готово»
  2. getIterator() даёт обход
  3. foreach получает $object и $value
  4. После цикла освобождаем временные ссылки
Пройти карту явным итераторомСамостоятельный пример
<?php
$product = (object) ['sku' => 'A-10'];
$map = new WeakMap();
$map[$product] = 'Готово';
$iterator = $map->getIterator();
foreach ($iterator as $object => $label) { echo $object->sku, ': ', $label, "\n"; }
unset($iterator, $object, $label);
Ожидаемый результат
A-10: Готово

Здесь $object — сам товар, не его числовой ID. Не передавайте такой итератор в обычное преобразование массива с сохранением ключей: массив PHP не принимает объект-ключ.

Преобразовать карту в обычный список для отчётаСамостоятельный пример
<?php
$product = (object) ['sku' => 'A-10'];
$map = new WeakMap();
$map[$product] = 'Готово';
$rows = [];
foreach ($map as $object => $label) { $rows[] = ['sku' => $object->sku, 'label' => $label]; }
unset($object, $label);
echo json_encode($rows, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR), "\n";
Ожидаемый результат
[{"sku":"A-10","label":"Готово"}]

Для отчёта выбраны стабильные скалярные данные. Полученный список уже имеет собственное время жизни и расход памяти; экспорт всех записей снова создаёт полную коллекцию.

Рядом: WeakMap::count, WeakMap::offsetGet, WeakMap::offsetUnset. Модель владения.

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

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

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

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

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