You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CLAUDE.md
+36-1Lines changed: 36 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -105,6 +105,10 @@ This factory (`src/ApiPlatform/Metadata/Resource/RoutingPrefixResourceMetadataCo
105
105
-**`api_sub_level` context**: when normalising a sub-object (e.g. `ResourceMetadata` inside `MetadataNormalizer`), set `$context['api_sub_level'] = true`. Without it, `PartialCollectionViewNormalizer` injects a `"view": {"@type": "PartialCollectionView"}` entry into any array property whenever the request URI has query parameters, turning the array into a JSON object.
106
106
-**Symfony 8.2 null-for-typed-string**: Symfony 8.2 converts a null value for a non-nullable typed string property into a proper validation violation rather than a raw TypeError. Prefer `?string = null` (nullable PHP type + nullable ORM column) so null passes through deserialization to the `#[Assert\NotBlank]` validator consistently across all Symfony versions. AP4's `AbstractItemNormalizer` reads serializer metadata (including the ORM `nullable` flag), so `?string` with `nullable: false` on the ORM column still triggers the TypeError path.
107
107
-**Behat / Symfony 8.x**: The `behat/behat` and `friends-of-behat/symfony-extension` packages do not yet support Symfony 8.x (their constraints cap at `^7.0`). This holds `symfony/console`, `symfony/event-dispatcher`, `symfony/property-access`, `symfony/http-kernel`, and the full security stack at 7.x in the test environment. The production bundle code is Symfony 8.x-compatible; only the test tooling is blocked. Watch for a `behat/behat` 4.x stable release or updated `friends-of-behat/symfony-extension` to unblock.
108
+
-**Service ID convention — two mandatory exceptions**: All bundle services use stable `silverback.api_components.*` string IDs with FQCN class-name aliases. Two categories **must** keep the FQCN as the primary service ID (with the string ID as alias) because they are looked up by class name at runtime:
109
+
1.**AP4 state providers** tagged `api_platform.state_provider` and referenced as `provider: SomeClass::class` on an operation — AP4 builds its `CallableProvider` service locator keyed by tagged service ID. If the service ID is a string (not the FQCN), AP4 throws `ProviderNotFoundException`.
110
+
2.**Controller action services** tagged `controller.service_arguments` — Symfony's `RegisterControllerArgumentLocatorsPass` keys argument locators by service ID. Routes resolve controllers by FQCN; if the ID is a string the locator can't be matched and `__invoke` method argument injection fails.
111
+
Pattern: `->set(SomeClass::class)->...->tag(...)` then `->alias('silverback.api_components.*', SomeClass::class)->public()`. All other services use the reverse.
108
112
109
113
### Serialization groups
110
114
@@ -527,7 +531,7 @@ $topicBuilder->onRoutesCreated(function (array $childBuilders) use ($intro) {
527
531
528
532
### What the builder handles invisibly
529
533
530
-
-`TimestampedDataPersister->persistTimestampedFields($entity, true)` on every entity
534
+
-`TimestampedDataPersister->persistTimestampedFields($entity, true)` on entities that have the `#[Timestamped]` annotation (Layout, Page, AbstractPageData, ComponentGroup). **Not** called on `AbstractComponent` subclasses — see open issue below.
531
535
-`$manager->persist()` for all entities
532
536
- Layout/Page deduplication by reference string (calling `->layout('main', ...)` twice returns the same LayoutBuilder)
533
537
- ComponentGroup deduplication by entity IRI + location name
@@ -558,6 +562,37 @@ $topicBuilder->onRoutesCreated(function (array $childBuilders) use ($intro) {
558
562
559
563
These are known open issues with enough context to resume work without re-investigation.
560
564
565
+
### `CwaFixtureBuilder.component()` throws for non-timestamped entities — fix needed
566
+
567
+
**Context (discovered 2026-06-19):**`CwaFixtureBuilder.phaseOne()` unconditionally calls `TimestampedDataPersister->persistTimestampedFields($component, true)` for every `ComponentBuilder` registered via `->component()`. This throws `InvalidArgumentException` (`AttributeReader.findAttributeConfiguration` line 104) when the entity does not have the `#[Timestamped]` annotation. `AbstractComponent` subclasses (e.g. `HtmlContent`, `NavigationLink`, `Image`) use `#[Publishable]` but NOT `#[Timestamped]`, so `$cwa->component($htmlContent)` always crashes.
foreach ($this->componentBuilders as $componentBuilder) {
572
+
$component = $componentBuilder->getComponent();
573
+
$this->timestampedPersister->persistTimestampedFields($component, true); // throws for HtmlContent etc.
574
+
$this->persistWithAssociations($component);
575
+
...
576
+
}
577
+
```
578
+
579
+
**Fix required:**
580
+
581
+
1. Add `isConfigured(object|string $entity): bool` to `TimestampedDataPersister` (delegates to `$this->annotationReader->isConfigured($entity)` — `TimestampedAttributeReader` inherits `isConfigured` from `AttributeReader`, which catches `InvalidArgumentException` and returns false).
582
+
583
+
2. In `phaseOne()`, guard the call:
584
+
```php
585
+
if ($this->timestampedPersister->isConfigured($component)) {
The `Layout`, `Page`, and `AbstractPageData` loops in phaseOne are unaffected — those entities all have `#[Timestamped]`. Only the `componentBuilders` loop needs the guard.
591
+
592
+
**Workaround for `AppScaffold` until fixed:** Use `$cwa->persist($htmlContent)` (which calls `persistWithAssociations` without any timestamp logic) for standalone components set as PageData properties. This is the same effect as the intended `$cwa->component()` call minus the timestamp step (which is a no-op for these entities anyway since they have no `createdAt`/`modifiedAt` fields).
593
+
594
+
---
595
+
561
596
### ComponentPosition `sortValue` collision on insert — API-side normalisation needed
562
597
563
598
**Context (from Nuxt module):** When an admin inserts a component "before" or "after" an existing position, the module computes a `sortValue` for the new `ComponentPosition` that may equal an existing position's `sortValue`. For "add before X (sortValue=N)", the new position gets `sortValue=N` (same as X). For "add after X (sortValue=N)", the new position gets `sortValue=N+1` which may collide with the position immediately following X.
0 commit comments