Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
17 changes: 0 additions & 17 deletions src/DependencyInjection/CompilerPass/ApiPlatformCompilerPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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');
Expand All @@ -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);
}
}
Expand All @@ -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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading