Skip to content

Commit

Permalink
Add mapCollection method to base interface (#225)
Browse files Browse the repository at this point in the history
I had a lot of feedback that people wants to map a collection of items
into something else and most of the time they don't really like the fact
they have to do the `foreach` themselves.

So here is a `mapCollection(array $collection, string $target, array
$context = []): array` method that allows mapping collection of items.
  • Loading branch information
Korbeil authored Feb 17, 2025
2 parents 44cfd9c + 67ea884 commit 467c803
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- [GH#223](https://github.com/jolicode/automapper/pull/223) Handle array to Doctrine Collection transformations
- [GH#225](https://github.com/jolicode/automapper/pull/225) Add mapCollection method to base interface

## [9.2.1] - 2025-01-31
### Fixed
Expand Down
14 changes: 14 additions & 0 deletions src/AutoMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,20 @@ public function map(array|object $source, string|array|object $target, array $co
return $this->getMapper($sourceType, $targetType)->map($source, $context);
}

public function mapCollection(iterable $collection, string $target, array $context = []): array
{
$output = [];
foreach ($collection as $k => $item) {
if (\is_array($item) && 'array' === $target) {
throw new InvalidMappingException('Cannot map this value, both source and target are array.');
}

$output[$k] = $this->map($item, $target, $context);
}

return $output;
}

/**
* @param TransformerFactoryInterface[] $transformerFactories
* @param ProviderInterface[] $providers
Expand Down
12 changes: 12 additions & 0 deletions src/AutoMapperInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,16 @@ interface AutoMapperInterface
* @return ($target is class-string|Target ? Target|null : array<mixed>|null)
*/
public function map(array|object $source, string|array|object $target, array $context = []): array|object|null;

/**
* @template Source of object
* @template Target of object
*
* @param iterable<array<mixed>>|iterable<Source> $collection
* @param class-string<Target>|'array' $target
* @param MapperContextArray $context
*
* @return ($target is class-string ? array<Target> : array<mixed>)
*/
public function mapCollection(iterable $collection, string $target, array $context = []): array;
}
97 changes: 97 additions & 0 deletions tests/AutoMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1663,4 +1663,101 @@ public function testArrayToDoctrineCollections(): void
self::assertEquals('Valentina', $library->books[1]->name);
self::assertEquals('Imbalance', $library->books[2]->name);
}

public function testMapCollectionFromArray(): void
{
$this->buildAutoMapper(mapPrivatePropertiesAndMethod: true);

$users = [
[
'id' => 1,
'address' => [
'city' => 'Toulon',
],
'createdAt' => '1987-04-30T06:00:00Z',
],
[
'id' => 2,
'address' => [
'city' => 'Nantes',
],
'createdAt' => '1991-10-01T06:00:00Z',
],
];

/** @var array<Fixtures\UserDTO> $userDtos */
$userDtos = $this->autoMapper->mapCollection($users, Fixtures\UserDTO::class);
self::assertCount(2, $userDtos);
self::assertEquals(1, $userDtos[0]->id);
self::assertInstanceOf(AddressDTO::class, $userDtos[0]->address);
self::assertSame('Toulon', $userDtos[0]->address->city);
self::assertInstanceOf(\DateTimeInterface::class, $userDtos[0]->createdAt);
self::assertEquals(1987, $userDtos[0]->createdAt->format('Y'));
self::assertEquals(2, $userDtos[1]->id);
self::assertInstanceOf(AddressDTO::class, $userDtos[1]->address);
self::assertSame('Nantes', $userDtos[1]->address->city);
self::assertInstanceOf(\DateTimeInterface::class, $userDtos[1]->createdAt);
self::assertEquals(1991, $userDtos[1]->createdAt->format('Y'));
}

public function testMapCollectionFromArrayCustomDateTime(): void
{
$this->buildAutoMapper(classPrefix: 'CustomDateTime_', dateTimeFormat: 'U');

$customFormat = 'U';
$users = [
[
'id' => 1,
'address' => [
'city' => 'Toulon',
],
'createdAt' => \DateTime::createFromFormat(\DateTime::RFC3339, '1987-04-30T06:00:00Z')->format($customFormat),
],
[
'id' => 2,
'address' => [
'city' => 'Nantes',
],
'createdAt' => \DateTime::createFromFormat(\DateTime::RFC3339, '1991-10-01T06:00:00Z')->format($customFormat),
],
];

/** @var array<Fixtures\UserDTO> $userDtos */
$userDtos = $this->autoMapper->mapCollection($users, Fixtures\UserDTO::class);
self::assertCount(2, $userDtos);

self::assertInstanceOf(Fixtures\UserDTO::class, $userDtos[0]);
self::assertEquals(\DateTime::createFromFormat(\DateTime::RFC3339, '1987-04-30T06:00:00Z')->format($customFormat), $userDtos[0]->createdAt->format($customFormat));
self::assertInstanceOf(Fixtures\UserDTO::class, $userDtos[1]);
self::assertEquals(\DateTime::createFromFormat(\DateTime::RFC3339, '1991-10-01T06:00:00Z')->format($customFormat), $userDtos[1]->createdAt->format($customFormat));
}

public function testMapCollectionToArray(): void
{
$users = [];
$address = new Address();
$address->setCity('Toulon');
$user = new Fixtures\User(1, 'yolo', '13');
$user->address = $address;
$user->addresses[] = $address;
$users[] = $user;
$address = new Address();
$address->setCity('Nantes');
$user = new Fixtures\User(10, 'yolo', '13');
$user->address = $address;
$user->addresses[] = $address;
$users[] = $user;

$userDatas = $this->autoMapper->mapCollection($users, 'array');

self::assertIsArray($userDatas);
self::assertIsArray($userDatas[0]);
self::assertIsArray($userDatas[1]);
self::assertEquals(1, $userDatas[0]['id']);
self::assertEquals(10, $userDatas[1]['id']);
self::assertIsArray($userDatas[0]['address']);
self::assertIsString($userDatas[0]['createdAt']);
self::assertIsArray($userDatas[1]['address']);
self::assertIsString($userDatas[1]['createdAt']);
}
}
5 changes: 5 additions & 0 deletions tests/Normalizer/AutoMapperNormalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ public function map(array|object|null $source, string|array|object $target, arra
return $context;
}

public function mapCollection(iterable $collection, string $target, array $context = []): array
{
return $context;
}

public function getMapper(string $source, string $target): MapperInterface
{
return new class() implements MapperInterface {
Expand Down

0 comments on commit 467c803

Please sign in to comment.