Кэш рефлексии
Автовайринг управляется рефлексией, а рефлексия не бесплатна. ReflectionCache
строит каждый объект рефлексии один раз и переиспользует его всё время жизни процесса — разница
между «дёшево» и «линейной ценой на запрос» в Swoole-воркере.
Почему это важно
Модель стоимости различается по рантайму:
FPM-воркер: 1 запрос → 1 построение рефлексии (нечего амортизировать)
Swoole-воркер: N запросов → 1 построение рефлексии (N-1 попаданий в кэш)Под FPM каждый запрос — свежий процесс, поэтому new ReflectionClass() случается один раз и
умирает с запросом — кэширование даёт мало. Под Swoole один воркер обслуживает тысячи запросов
в том же процессе; без кэша каждый запрос перестраивал бы тот же граф рефлексии, и эта цена
растёт линейно с трафиком. ReflectionCache гарантирует, что граф строится один раз на
воркер, независимо от числа запросов.
Что кэшируется
Четыре статических карты, каждая с ключом и ленивым заполнением при первом обращении:
| Метод | Кэширует | Ключ |
|---|---|---|
classOf($class) |
ReflectionClass |
имя класса |
enumOf($enum) |
ReflectionEnum |
имя enum |
method($class, $method) |
ReflectionMethod |
class::method |
parameters($class, $method) |
ReflectionParameter[] |
class::method (делегирует method()) |
parameters() переиспользует ту же запись, что строит method(), поэтому запрос любого из них
не дублирует работу.
Как это использует резолвер
Движок DI (ReflectionResolver) опирается на кэш в трёх точках, плюс второй слой собственной
мемоизации для извлечённых метаданных параметров (имя, тип, атрибуты, значения по умолчанию) —
так форма конструктора вычисляется один раз, а затем читается из обычного массива.
| Шаг резолвера | Вызов кэша |
|---|---|
resolve() → параметры конструктора |
classOf($class)->getConstructor() |
call() |
method($instance::class, $method) |
injectProperties() |
classOf($instance::class)->getProperties() |
Этот двухуровневый дизайн — кэшированные объекты рефлексии и кэшированные массивы параметров —
и есть причина, по которой горячий make() на уже виденном классе почти не делает работы с
рефлексией.
Это публичная утилита
ReflectionCache не внутренний. Любой слой на основе рефлексии — HTTP-контроллеры,
CLI-диспетчеры, резолверы параметров, читающие свои атрибуты — может разделять тот же кэш вместо
построения параллельного:
use Flytachi\Winter\DI\ReflectionCache;
// Резолвер параметров HTTP, читающий #[PathVariable], #[RequestBody], ...
$params = ReflectionCache::parameters($controllerClass, $action);
foreach ($params as $param) {
if ($attr = $param->getAttributes(PathVariable::class)[0] ?? null) {
// разрешить path-переменную
}
}
// Вызвать action контроллера
$method = ReflectionCache::method($controllerClass, $action);
$method->invokeArgs($controller, $resolvedArgs);Потокобезопасность
Кэш использует static-массивы. В FPM и CLI у каждого процесса своя память — нет разделения,
нет блокировок. Под Swoole все корутины в воркере разделяют память процесса, но объекты
рефлексии только для чтения после создания, поэтому параллельные чтения безопасны без
блокировок. Худший случай при гонке — две корутины оба строят одну и ту же запись по разу;
результат идентичен и идемпотентен.
Инвалидация не нужна
Формы классов не меняются в рантайме, поэтому кэшированная рефлексия не устаревает в пределах процесса. Вытеснения намеренно нет — кэш ограничен числом различных классов, которых касается воркер, а оно конечно.
Связанное
- Жизненный цикл разрешения — где рефлексия питает конструирование
- Request-scope и Swoole — другая оптимизация на воркер
- Справочник API — методы
ReflectionCache