{
    "component": "symfony/cache",
    "version": "7.4.23",
    "kind": "recorded-real-php-run",
    "adapter": "FilesystemAdapter + TagAwareAdapter",
    "scenarios": [
        {
            "id": "hit",
            "title": "01 · Callback и попадание",
            "purpose": "Два чтения карточки товара обращаются к загрузчику только один раз.",
            "events": [
                {
                    "action": "Первое get",
                    "result": {
                        "id": 42,
                        "price": 12500
                    },
                    "callbackCalls": 1
                },
                {
                    "action": "Повторное get",
                    "result": {
                        "id": 42,
                        "price": 12500
                    },
                    "callbackCalls": 1
                }
            ],
            "checks": {
                "calls": 1,
                "reads": 2,
                "isHit": true
            },
            "code": "$cache = new FilesystemAdapter('catalog_v1', 0, $cacheDirectory);\n$value = $cache->get('product.42', function (ItemInterface $item): array {\n    $item->expiresAfter(300);\n    return ['id' => 42, 'price' => 12500];\n}, 0); // beta=0 makes this particular learning experiment deterministic"
        },
        {
            "id": "ttl",
            "title": "02 · Истечение TTL",
            "purpose": "Короткое время жизни действительно истекает во время PHP-прогона.",
            "events": [
                {
                    "action": "Записать с TTL 1 секунда",
                    "result": {
                        "revision": 1
                    },
                    "callbackCalls": 1
                },
                {
                    "action": "Прочитать после реального ожидания 2 секунд",
                    "result": {
                        "revision": 2
                    },
                    "callbackCalls": 2
                }
            ],
            "checks": {
                "expiredAfterWait": true,
                "calls": 2,
                "waitSeconds": 2
            },
            "code": "$cache->get('summary', function (ItemInterface $item) {\n    $item->expiresAfter(1);\n    return loadSummary();\n}, 0);\nsleep(2); // Only for the small CLI experiment, never in a web request.\n$miss = !$cache->getItem('summary')->isHit();"
        },
        {
            "id": "delete",
            "title": "03 · Обновление источника и delete",
            "purpose": "Изменение базы само по себе не обновляет уже сохранённую карточку.",
            "events": [
                {
                    "action": "Сохранить первоначальную цену",
                    "value": 12500
                },
                {
                    "action": "Источник изменён; get пока возвращает кеш",
                    "sourcePrice": 14000,
                    "value": 12500
                },
                {
                    "action": "delete и следующее get",
                    "value": 14000,
                    "callbackCalls": 2
                }
            ],
            "checks": {
                "stalePrice": 12500,
                "freshPrice": 14000,
                "calls": 2
            },
            "code": "// First commit the new price in the source of truth.\n$repository->updatePrice(42, 14000);\n$cache->delete('product.42');\n// The following get() recomputes the value on demand."
        },
        {
            "id": "tags",
            "title": "04 · Связанные данные и теги",
            "purpose": "Одна категория обновляет карточки и список, не затрагивая другую категорию.",
            "events": [
                {
                    "action": "Загрузить два объекта category.7 и один category.9",
                    "callbackCalls": {
                        "a": 1,
                        "b": 1,
                        "other": 1
                    }
                },
                {
                    "action": "invalidateTags(category.7), затем повторное чтение",
                    "callbackCalls": {
                        "a": 2,
                        "b": 2,
                        "other": 1
                    }
                }
            ],
            "checks": {
                "calls": {
                    "a": 2,
                    "b": 2,
                    "other": 1
                }
            },
            "code": "$cache = new TagAwareAdapter(new FilesystemAdapter('catalog_v1', 0, $cacheDirectory));\n$cache->get('product.42', function (ItemInterface $item) {\n    $item->expiresAfter(300);\n    $item->tag(['product.42', 'category.7']);\n    return loadProduct(42);\n}, 0);\n$cache->invalidateTags(['category.7']);"
        },
        {
            "id": "namespace",
            "title": "05 · Пространства имён",
            "purpose": "Одинаковые ключи разных клиентов не смешиваются внутри отдельных пулов.",
            "events": [
                {
                    "action": "Одинаковый ключ summary в двух namespace",
                    "tenantA": {
                        "tenant": "A",
                        "orders": 12
                    },
                    "tenantB": {
                        "tenant": "B",
                        "orders": 7
                    }
                },
                {
                    "action": "clear только tenant_a_v1",
                    "tenantAHit": false,
                    "tenantBHit": true
                }
            ],
            "checks": {
                "tenantAHit": false,
                "tenantBHit": true
            },
            "code": "$tenantA = new FilesystemAdapter('tenant_a_v1', 0, $cacheDirectory);\n$tenantB = new FilesystemAdapter('tenant_b_v1', 0, $cacheDirectory);\n// Both may use key \"summary\". Namespace separation does not replace authorization.\n$tenantA->clear(); // Does not clear tenant_b_v1."
        },
        {
            "id": "negative",
            "title": "06 · Отрицательное кеширование",
            "purpose": "Коротко сохраняем результат «не найдено», чтобы не повторять бесполезный запрос.",
            "events": [
                {
                    "action": "Два запроса отсутствующего товара",
                    "first": null,
                    "second": null,
                    "callbackCalls": 1,
                    "isHit": true
                }
            ],
            "checks": {
                "cachedNull": true,
                "calls": 1,
                "isHit": true
            },
            "code": "$product = $cache->get('product.404', function (ItemInterface $item): ?array {\n    $item->expiresAfter(30);\n    return $repository->find(404); // null means a confirmed missing product.\n}, 0);\n// A network failure is different from a successful lookup returning null."
        },
        {
            "id": "error",
            "title": "07 · Ошибка загрузчика",
            "purpose": "Исключение не превращается автоматически в успешное кешированное значение.",
            "events": [
                {
                    "action": "Callback выбрасывает исключение",
                    "error": "Учебный источник временно недоступен",
                    "isHit": false
                },
                {
                    "action": "Следующая успешная попытка",
                    "result": {
                        "status": "ok",
                        "value": 100
                    },
                    "callbackCalls": 2
                }
            ],
            "checks": {
                "errorWasCaught": true,
                "calls": 2,
                "nextAttempt": "ok"
            },
            "code": "try {\n    $rates = $cache->get('rates', function (ItemInterface $item) {\n        $item->expiresAfter(60);\n        return $api->fetchRates(); // May throw; handle the failure deliberately.\n    });\n} catch (\\RuntimeException $error) {\n    // Report temporary unavailability; do not pretend this is a zero rate.\n}"
        },
        {
            "id": "beta",
            "title": "08 · Управляемый пересчёт beta",
            "purpose": "Сравниваем детерминированные крайние режимы; вероятностную конкуренцию здесь не измеряем.",
            "events": [
                {
                    "action": "get с beta 0",
                    "result": {
                        "revision": 1
                    },
                    "callbackCalls": 1
                },
                {
                    "action": "get с beta 0",
                    "result": {
                        "revision": 1
                    },
                    "callbackCalls": 1
                },
                {
                    "action": "get с beta INF",
                    "result": {
                        "revision": 2
                    },
                    "callbackCalls": 2
                },
                {
                    "action": "get с beta 0",
                    "result": {
                        "revision": 2
                    },
                    "callbackCalls": 2
                }
            ],
            "checks": {
                "calls": 2,
                "testedBeta": [
                    "0",
                    "INF"
                ],
                "concurrencyBenchmark": false
            },
            "code": "$value = $cache->get('popular', $loader, 0);   // Disable probabilistic early recompute.\n$fresh = $cache->get('popular', $loader, INF); // Force recompute on this get().\n// Default beta=1 is probabilistic; test it under your actual workload."
        },
        {
            "id": "prune",
            "title": "09 · Prune и физическая очистка",
            "purpose": "Истечение срока и освобождение файлов на диске — разные события.",
            "events": [
                {
                    "action": "Записать короткую и длинную запись",
                    "files": 2
                },
                {
                    "action": "После 2 секунд выполнить prune",
                    "success": true,
                    "files": 1,
                    "longIsHit": true
                }
            ],
            "checks": {
                "beforeFiles": 2,
                "afterFiles": 1,
                "liveItemRetained": true
            },
            "code": "$cache = new FilesystemAdapter('catalog_v1', 0, $cacheDirectory);\n$success = $cache->prune(); // Remove expired entries, keep valid data.\n// Run from a dedicated CLI job with write access to this specific cache directory."
        }
    ],
    "assertions": 10,
    "externalServicesUsed": false,
    "timingBenchmark": false
}
