From 097de7a0bd4dee6d8006503a44d61578d99d1e89 Mon Sep 17 00:00:00 2001 From: Nicolas Joubert Date: Fri, 9 Oct 2026 10:47:37 +0200 Subject: [PATCH] fix(transformer) #243 Fix RecursivePropertySetterTransformer: a `\stdClass` item without the property was replaced by a copy in the output, so the input object was not modified; the property is now added to the item itself. Update documentation, add tests. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 1 + .../recursive_property_setter_transformer.md | 2 +- .../RecursivePropertySetterTransformer.php | 5 ++- ...RecursivePropertySetterTransformerTest.php | 33 +++++++++++++++++++ 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdf7f535..8a8a7084 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Latest ## Fixes * [#143](https://github.com/cleverage/process-bundle/issues/143) Fix InputIteratorTask: an `\IteratorAggregate` input whose `getIterator()` does not return an `\Iterator` (e.g. another `\IteratorAggregate`) is iterated instead of failing with a `TypeError`. Update documentation, add tests. +* [#243](https://github.com/cleverage/process-bundle/issues/243) Fix RecursivePropertySetterTransformer: a `\stdClass` item without the property was replaced by a copy in the output, so the input object was not modified; the property is now added to the item itself. Update documentation, add tests. v5.1 ----- diff --git a/docs/reference/transformers/recursive_property_setter_transformer.md b/docs/reference/transformers/recursive_property_setter_transformer.md index cad98ddc..c54f0920 100644 --- a/docs/reference/transformers/recursive_property_setter_transformer.md +++ b/docs/reference/transformers/recursive_property_setter_transformer.md @@ -56,6 +56,6 @@ Notes Keys of `set_properties` are property paths written with the PropertyAccessor on each item: use the `[key]` notation for array items and the `property` notation for objects. For `\stdClass` items, a property that cannot be written is -added to the object. +added to the object itself (a nested path such as `parent.id` is added as a `parent.id` property). Object items are modified in place, so the objects of the input are modified too. diff --git a/src/Transformer/Object/RecursivePropertySetterTransformer.php b/src/Transformer/Object/RecursivePropertySetterTransformer.php index 4a924666..1402b6aa 100644 --- a/src/Transformer/Object/RecursivePropertySetterTransformer.php +++ b/src/Transformer/Object/RecursivePropertySetterTransformer.php @@ -66,9 +66,8 @@ public function transform(mixed $value, array $options = []): mixed $this->accessor->setValue($item, $propertyPath, $propertyValue); } catch (NoSuchPropertyException $e) { if ($item instanceof \stdClass) { - $item = (object) array_merge((array) $item, [ - $propertyPath => $propertyValue, - ]); + // Add the property to the item itself, to modify it in place + $item->{$propertyPath} = $propertyValue; } else { throw $e; } diff --git a/tests/Transformer/Object/RecursivePropertySetterTransformerTest.php b/tests/Transformer/Object/RecursivePropertySetterTransformerTest.php index 9914ef76..4c49784d 100644 --- a/tests/Transformer/Object/RecursivePropertySetterTransformerTest.php +++ b/tests/Transformer/Object/RecursivePropertySetterTransformerTest.php @@ -84,6 +84,39 @@ public function testMissingPropertyIsAddedToStdClassItems(): void self::assertEquals([(object) ['label' => 'A', 'parentId' => 7]], $result); } + public function testMissingPropertyIsAddedInPlaceToStdClassItems(): void + { + $transformer = $this->createTransformer(); + $options = $this->resolveOptions($transformer, [ + 'iterator' => 'items', + 'set_properties' => ['parentId' => 'id'], + ]); + $item = (object) ['label' => 'A']; + + $result = $transformer->transform((object) ['id' => 7, 'items' => [$item]], $options); + + self::assertIsArray($result); + self::assertSame($item, $result[0]); + self::assertSame(7, $item->parentId); + } + + public function testMissingNestedPropertyPathIsAddedAsPropertyToStdClassItems(): void + { + $transformer = $this->createTransformer(); + $options = $this->resolveOptions($transformer, [ + 'iterator' => 'items', + 'set_properties' => ['parent.id' => 'id'], + ]); + + $item = (object) ['label' => 'A']; + + $result = $transformer->transform((object) ['id' => 7, 'items' => [$item]], $options); + + self::assertIsArray($result); + self::assertSame($item, $result[0]); + self::assertEquals((object) ['label' => 'A', 'parent.id' => 7], $item); + } + public function testTraversableCollectionIsSupported(): void { $transformer = $this->createTransformer();