diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e43ace..67304be 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,7 @@
* feat(`PresetMappingFactory`): Add `fromObjectCache()` and `fromObjectCacheReversed()`.
* chore: Simplify remembering mapper.
* refactor: Deprecate serializer context.
+* feat: null to `Traversable` or `ArrayAccess` is now handled & returns empty.
## 1.0.0
diff --git a/psalm.xml b/psalm.xml
index d18deb9..c4f05cd 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -52,6 +52,8 @@
+
+
diff --git a/src/Transformer/Implementation/TraversableToArrayAccessTransformer.php b/src/Transformer/Implementation/TraversableToArrayAccessTransformer.php
index 03eccd6..4a5ec53 100644
--- a/src/Transformer/Implementation/TraversableToArrayAccessTransformer.php
+++ b/src/Transformer/Implementation/TraversableToArrayAccessTransformer.php
@@ -51,6 +51,10 @@ public function transform(
?Type $targetType,
Context $context
): mixed {
+ if ($source === null) {
+ $source = [];
+ }
+
if ($targetType === null) {
throw new InvalidArgumentException('Target type must not be null.', context: $context);
}
@@ -221,6 +225,7 @@ public function getSupportedTransformation(): iterable
$sourceTypes = [
TypeFactory::objectOfClass(\Traversable::class),
TypeFactory::array(),
+ TypeFactory::null(),
];
$targetTypes = [
diff --git a/src/Transformer/Implementation/TraversableToTraversableTransformer.php b/src/Transformer/Implementation/TraversableToTraversableTransformer.php
index 72e54ee..8a978a2 100644
--- a/src/Transformer/Implementation/TraversableToTraversableTransformer.php
+++ b/src/Transformer/Implementation/TraversableToTraversableTransformer.php
@@ -44,6 +44,10 @@ public function transform(
?Type $targetType,
Context $context
): mixed {
+ if ($source === null) {
+ $source = [];
+ }
+
if ($targetType === null) {
throw new InvalidArgumentException('Target type must not be null.', context: $context);
}
@@ -103,6 +107,7 @@ public function getSupportedTransformation(): iterable
$sourceTypes = [
TypeFactory::objectOfClass(\Traversable::class),
TypeFactory::array(),
+ TypeFactory::null(),
];
$targetTypes = [
diff --git a/tests/Fixtures/ArrayLike/ObjectWithNullCollectionProperty.php b/tests/Fixtures/ArrayLike/ObjectWithNullCollectionProperty.php
new file mode 100644
index 0000000..c266255
--- /dev/null
+++ b/tests/Fixtures/ArrayLike/ObjectWithNullCollectionProperty.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE file
+ * that was distributed with this source code.
+ */
+
+namespace Rekalogika\Mapper\Tests\Fixtures\ArrayLike;
+
+use Doctrine\Common\Collections\Collection;
+use Rekalogika\Mapper\Tests\Fixtures\Scalar\ObjectWithScalarProperties;
+
+class ObjectWithNullCollectionProperty
+{
+ /**
+ * @var null|Collection
+ */
+ public ?Collection $property = null;
+}
diff --git a/tests/Fixtures/ArrayLikeDto/ObjectWithNotNullArrayAccessPropertyDto.php b/tests/Fixtures/ArrayLikeDto/ObjectWithNotNullArrayAccessPropertyDto.php
new file mode 100644
index 0000000..5a0e010
--- /dev/null
+++ b/tests/Fixtures/ArrayLikeDto/ObjectWithNotNullArrayAccessPropertyDto.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE file
+ * that was distributed with this source code.
+ */
+
+namespace Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto;
+
+use Rekalogika\Mapper\Tests\Fixtures\ScalarDto\ObjectWithScalarPropertiesDto;
+
+class ObjectWithNotNullArrayAccessPropertyDto
+{
+ /**
+ * @var \ArrayAccess
+ */
+ public \ArrayAccess $property;
+}
diff --git a/tests/Fixtures/ArrayLikeDto/ObjectWithNotNullTraversablePropertyDto.php b/tests/Fixtures/ArrayLikeDto/ObjectWithNotNullTraversablePropertyDto.php
new file mode 100644
index 0000000..c0427d1
--- /dev/null
+++ b/tests/Fixtures/ArrayLikeDto/ObjectWithNotNullTraversablePropertyDto.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE file
+ * that was distributed with this source code.
+ */
+
+namespace Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto;
+
+use Rekalogika\Mapper\Tests\Fixtures\ScalarDto\ObjectWithScalarPropertiesDto;
+
+class ObjectWithNotNullTraversablePropertyDto
+{
+ /**
+ * @var \Traversable
+ */
+ public \Traversable $property;
+}
diff --git a/tests/IntegrationTest/TraversableToArrayAccessMappingTest.php b/tests/IntegrationTest/TraversableToArrayAccessMappingTest.php
index 260f49e..002363e 100644
--- a/tests/IntegrationTest/TraversableToArrayAccessMappingTest.php
+++ b/tests/IntegrationTest/TraversableToArrayAccessMappingTest.php
@@ -20,6 +20,7 @@
use Rekalogika\Mapper\Tests\Fixtures\ArrayLike\ObjectWithArrayProperty;
use Rekalogika\Mapper\Tests\Fixtures\ArrayLike\ObjectWithArrayPropertyWithStringKey;
use Rekalogika\Mapper\Tests\Fixtures\ArrayLike\ObjectWithCollectionProperty;
+use Rekalogika\Mapper\Tests\Fixtures\ArrayLike\ObjectWithNullCollectionProperty;
use Rekalogika\Mapper\Tests\Fixtures\ArrayLike\ObjectWithSplObjectStorageProperty;
use Rekalogika\Mapper\Tests\Fixtures\ArrayLike\ObjectWithTraversableProperties;
use Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto\ObjectWithArrayAccessPropertyDto;
@@ -30,6 +31,7 @@
use Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto\ObjectWithArrayPropertyWithCompatibleHintDto;
use Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto\ObjectWithArrayPropertyWithoutTypeHintDto;
use Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto\ObjectWithCollectionPropertyDto;
+use Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto\ObjectWithNotNullArrayAccessPropertyDto;
use Rekalogika\Mapper\Tests\Fixtures\Scalar\ObjectWithScalarProperties;
use Rekalogika\Mapper\Tests\Fixtures\ScalarDto\ObjectWithScalarPropertiesDto;
use Rekalogika\Mapper\Transformer\Model\HashTable;
@@ -177,6 +179,20 @@ public function testCollectionToArrayAccessDto(): void
$this->assertEquals(1.1, $result->property[1]?->d);
}
+ public function testNullToNotNullArrayAccessDto(): void
+ {
+ $source = new ObjectWithNullCollectionProperty();
+
+ $result = $this->mapper->map($source, ObjectWithNotNullArrayAccessPropertyDto::class);
+
+ $this->assertInstanceOf(ObjectWithNotNullArrayAccessPropertyDto::class, $result);
+
+ $property = $result->property;
+ $this->assertInstanceOf(LazyArray::class, $property);
+ // @phpstan-ignore-next-line
+ $this->assertInstanceOf(\ArrayAccess::class, $property);
+ }
+
public function testArrayToArrayInterfaceDto(): void
{
$source = new ObjectWithArrayProperty();
diff --git a/tests/IntegrationTest/TraversableToTraversableMappingTest.php b/tests/IntegrationTest/TraversableToTraversableMappingTest.php
index 2e46232..320989d 100644
--- a/tests/IntegrationTest/TraversableToTraversableMappingTest.php
+++ b/tests/IntegrationTest/TraversableToTraversableMappingTest.php
@@ -17,7 +17,9 @@
use Rekalogika\Mapper\Tests\Fixtures\ArrayLike\ObjectWithArrayProperty;
use Rekalogika\Mapper\Tests\Fixtures\ArrayLike\ObjectWithLazyDoctrineCollectionProperty;
use Rekalogika\Mapper\Tests\Fixtures\ArrayLike\ObjectWithLazyDoctrineCollectionWithPresetCountableProperty;
+use Rekalogika\Mapper\Tests\Fixtures\ArrayLike\ObjectWithNullCollectionProperty;
use Rekalogika\Mapper\Tests\Fixtures\ArrayLike\ObjectWithTraversableProperties;
+use Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto\ObjectWithNotNullTraversablePropertyDto;
use Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto\ObjectWithTraversablePropertyDto;
use Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto\ObjectWithTraversablePropertyWithoutTypeHintDto;
use Rekalogika\Mapper\Tests\Fixtures\Scalar\ObjectWithScalarProperties;
@@ -121,4 +123,15 @@ public function testArrayToTraversableWithoutTypehint(): void
$this->assertEquals(1.1, $item->d);
}
}
+
+ public function testNullToNotNullTraversableDto(): void
+ {
+ $source = new ObjectWithNullCollectionProperty();
+ $result = $this->mapper->map($source, ObjectWithNotNullTraversablePropertyDto::class);
+
+ $this->assertInstanceOf(\Traversable::class, $result->property);
+
+ $arrayResult = iterator_to_array($result->property);
+ $this->assertEmpty($arrayResult);
+ }
}