diff --git a/CHANGELOG.md b/CHANGELOG.md index affb0706..583d74be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Every change that reaches `main` adds a line under **Unreleased**. When a versio ## Unreleased +### Changed +- Requires `api-platform/core` `^4.4.5 || ^5.0.4`, which reset the skolem IRI converter between requests themselves ([api-platform/core#8681](https://github.com/api-platform/core/pull/8681)); `ApiPlatformCompilerPass` no longer adds that `kernel.reset` tag ([#500](https://github.com/components-web-app/api-components-bundle/pull/500)) + ## [2.0.0-alpha.28](https://github.com/components-web-app/api-components-bundle/compare/2.0.0-alpha.27...2.0.0-alpha.28) - 2026-10-18 ### Changed diff --git a/CLAUDE.md b/CLAUDE.md index dca0565f..be3a0065 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -416,7 +416,7 @@ The bundle does not set `use_symfony_listeners`. API Platform's default (`false` - **Services holding request-scoped state must be tagged `kernel.reset`** explicitly, or scope the state with a `WeakMap`. FrankenPHP worker mode keeps services alive across requests. Add each such service to `tests/DependencyInjection/ServicesResetterTest.php`. - **A class that implements `ResetInterface` must be tagged `kernel.reset`, on every service of it, children of a parent definition included** (Symfony never inherits tags from a parent). `ResetInterfaceServicesTest` loads every bundle config file and fails on any that is not. Implementing the interface alone resets nothing. - **A map keyed by objects that live for one request (entities, `_metadata`) is a `WeakMap` and is also reset** (#447). The `WeakMap` alone is not enough when a value can reference its key, as `ResourceMetadata`'s violations reference the resource. Never key by `spl_object_id`: it holds nothing and is reused once the object is freed. `worker_mode_memory.feature` sends requests through one kernel and asserts both maps are empty after each services reset. -- **API Platform's `api_platform.symfony.iri_converter.skolem` is tagged `kernel.reset` by `ApiPlatformCompilerPass`** until api-platform/core#8681 is released and the bundle requires it; then remove that step. It keeps every anonymous object it gives a `/.well-known/genid/` IRI (each `_metadata` object) for the life of the worker. +- **API Platform resets its skolem IRI converter itself from 4.4.5 and 5.0.4** (api-platform/core#8681), which is why the bundle requires them: it keeps every anonymous object it gives a `/.well-known/genid/` IRI (each `_metadata` object) until a services reset. `worker_mode_memory.feature` pins it. - Optional collaborators (logger, purger) are wired as `new Reference(..., NULL_ON_INVALID_REFERENCE)`. - **Config nodes:** never put `isRequired()` on a child of a node with `addDefaultsIfNotSet()` or `canBeDisabled()`. The default is inserted without finalising, so the check never runs (#214). Give the child a real default or make the parent required. Use `->validate()` for invalid combinations. Env placeholders skip node validators, so validate again at run time. - `make:rename-component` generates through `doctrine.migrations.dependency_factory` and reads table names from `ClassMetadata`. **Never `LIKE`-match an IRI in a `json` column** (slashes are escaped). AP's `getIriFromResource()` returns a `/.well-known/genid/` blank node, not an exception, for a non-resource class. diff --git a/composer.json b/composer.json index 9013592f..3a24c782 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "ext-json": "*", "ext-pdo": "*", "ext-simplexml": "*", - "api-platform/core": "^4.4 || ^5.0", + "api-platform/core": "^4.4.5 || ^5.0.4", "cocur/slugify": "^4.7", "doctrine/dbal": "^4.4", "doctrine/doctrine-bundle": "^3.3", diff --git a/src/DependencyInjection/CompilerPass/ApiPlatformCompilerPass.php b/src/DependencyInjection/CompilerPass/ApiPlatformCompilerPass.php index 9eb179b3..8c462bd7 100644 --- a/src/DependencyInjection/CompilerPass/ApiPlatformCompilerPass.php +++ b/src/DependencyInjection/CompilerPass/ApiPlatformCompilerPass.php @@ -26,7 +26,6 @@ class ApiPlatformCompilerPass implements CompilerPassInterface private const string HTTP_CACHE_FLUSHER = 'silverback.api_components.http_cache.flusher'; private const string INVALIDATION_CLIENT_PREFIX = 'api_platform.invalidation_http_client.'; private const string EXCEPTION_TO_STATUS = 'api_platform.exception_to_status'; - private const string SKOLEM_IRI_CONVERTER = 'api_platform.symfony.iri_converter.skolem'; public function process(ContainerBuilder $container): void { @@ -35,7 +34,6 @@ public function process(ContainerBuilder $container): void $container->findDefinition(CollectionPopulator::class)->setArgument('$itemsPerPageParameterName', $itemsPerPageParameterName); if ($container->hasAlias('api_platform.http_cache.purger')) { - // we have implemented fully custom logic $container->removeDefinition('api_platform.doctrine.listener.http_cache.purge'); } else { $container->removeDefinition('silverback.api_components.http_cache.purger'); @@ -45,11 +43,8 @@ public function process(ContainerBuilder $container): void $this->appendApiPlatformDefaultExceptionStatuses($container); - $this->resetSkolemIriConverter($container); - $apiPlatformMercurePublishListener = 'api_platform.doctrine.orm.listener.mercure.publish'; if ($container->hasDefinition($apiPlatformMercurePublishListener)) { - // we have implemented fully custom logic $container->removeDefinition($apiPlatformMercurePublishListener); } } @@ -63,18 +58,6 @@ private function appendApiPlatformDefaultExceptionStatuses(ContainerBuilder $con $container->setParameter(self::EXCEPTION_TO_STATUS, $container->getParameter(self::EXCEPTION_TO_STATUS) + $defaults); } - private function resetSkolemIriConverter(ContainerBuilder $container): void - { - if (!$container->hasDefinition(self::SKOLEM_IRI_CONVERTER)) { - return; - } - - $definition = $container->getDefinition(self::SKOLEM_IRI_CONVERTER); - if (!$definition->hasTag('kernel.reset')) { - $definition->addTag('kernel.reset', ['method' => 'reset']); - } - } - private function configureHttpCacheFlusher(ContainerBuilder $container): void { if (!$container->hasDefinition(self::HTTP_CACHE_FLUSHER)) { diff --git a/tests/DependencyInjection/CompilerPass/ApiPlatformCompilerPassTest.php b/tests/DependencyInjection/CompilerPass/ApiPlatformCompilerPassTest.php index eaa4c561..66143b57 100644 --- a/tests/DependencyInjection/CompilerPass/ApiPlatformCompilerPassTest.php +++ b/tests/DependencyInjection/CompilerPass/ApiPlatformCompilerPassTest.php @@ -125,35 +125,6 @@ public function test_a_configured_status_for_a_default_exception_is_kept(): void self::assertSame(400, $exceptionToStatus[SerializerExceptionInterface::class]); } - public function test_the_skolem_iri_converter_is_reset_between_requests(): void - { - $container = $this->createContainer(); - $container->setDefinition('api_platform.symfony.iri_converter.skolem', new Definition()); - - (new ApiPlatformCompilerPass())->process($container); - - self::assertSame([['method' => 'reset']], $container->getDefinition('api_platform.symfony.iri_converter.skolem')->getTag('kernel.reset')); - } - - public function test_a_skolem_iri_converter_already_tagged_kernel_reset_keeps_its_one_tag(): void - { - $container = $this->createContainer(); - $container->setDefinition('api_platform.symfony.iri_converter.skolem', (new Definition())->addTag('kernel.reset', ['method' => 'reset'])); - - (new ApiPlatformCompilerPass())->process($container); - - self::assertSame([['method' => 'reset']], $container->getDefinition('api_platform.symfony.iri_converter.skolem')->getTag('kernel.reset')); - } - - public function test_without_a_skolem_iri_converter_nothing_is_defined(): void - { - $container = $this->createContainer(); - - (new ApiPlatformCompilerPass())->process($container); - - self::assertFalse($container->hasDefinition('api_platform.symfony.iri_converter.skolem')); - } - private function createContainer(): ContainerBuilder { $container = new ContainerBuilder();