Skip to content

Commit 7004101

Browse files
authored
Merge pull request #2 from emulgeator/array_of_custom_map
Added readme and handling of array of custom mapped types
2 parents f653e65 + 481f68c commit 7004101

File tree

10 files changed

+344
-13
lines changed

10 files changed

+344
-13
lines changed

README.md

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# Array to Class Mapper
2+
A simple library which automatically maps a given multi dimensional array to a given class.
3+
4+
## Getting Started
5+
6+
### Installing
7+
Run `composer require emulgeator/array-to-class-mapper` to add this library as a dependency to your project
8+
9+
10+
## Usage
11+
12+
### Simple scalar type mapping
13+
```php
14+
use Emul\ArrayToClassMapper\MapperFactory;
15+
16+
class DTO
17+
{
18+
private int $id;
19+
20+
private float $value;
21+
22+
public function __construct() {
23+
// Constructor wont be called
24+
}
25+
}
26+
27+
$arrayToMap = [
28+
'id' => '1',
29+
'value' => '1.2'
30+
];
31+
32+
$mapper = (new MapperFactory())->getMapper();
33+
34+
/** @var DTO $dto */
35+
$dto = $mapper->map($arrayToMap, DTO::class);
36+
```
37+
38+
As you'll see the library casts the values from the given array.
39+
So for example: `$dto->id` will be an integer indeed.
40+
41+
The mapper does that by checking the type of the property.
42+
If it's not set it tries to fetch this information from the **DocBlock**
43+
44+
### Class mapping
45+
```php
46+
use Emul\ArrayToClassMapper\MapperFactory;
47+
48+
require_once 'vendor/autoload.php';
49+
50+
class InnerDTO
51+
{
52+
private string $key;
53+
private string $value;
54+
}
55+
56+
class DTO
57+
{
58+
private InnerDTO $inner;
59+
}
60+
61+
$arrayToMap = [
62+
'inner' => [
63+
'key' => 'first_key',
64+
'value' => 'value'
65+
]
66+
];
67+
68+
$mapper = (new MapperFactory())->getMapper();
69+
70+
/** @var DTO $dto */
71+
$dto = $mapper->map($arrayToMap, DTO::class);
72+
```
73+
74+
### Complex type mapping
75+
In case of complex types you both DocBlock and type hinting can be used. See the previous example modified to have a series of inner objects:
76+
```php
77+
use Emul\ArrayToClassMapper\MapperFactory;
78+
79+
require_once 'vendor/autoload.php';
80+
81+
class InnerDTO
82+
{
83+
private string $key;
84+
private string $value;
85+
}
86+
87+
class DTO
88+
{
89+
private int $id;
90+
91+
/** @var InnerDTO[] */
92+
private array $inner = [];
93+
}
94+
95+
$arrayToMap = [
96+
'id' => 1,
97+
'inner' => [
98+
[
99+
'key' => 'first_key',
100+
'value' => 'value'
101+
],
102+
[
103+
'key' => 'second_key',
104+
'value' => 'value'
105+
],
106+
]
107+
];
108+
109+
$mapper = (new MapperFactory())->getMapper();
110+
111+
/** @var DTO $dto */
112+
$dto = $mapper->map($arrayToMap, DTO::class);
113+
```
114+
115+
### Defining custom mapper for specific type
116+
117+
If a data mapping is not possible by simply casting the stored value, custom mappers can be set:
118+
119+
```php
120+
use Carbon\Carbon;
121+
use Carbon\CarbonInterface;
122+
use Emul\ArrayToClassMapper\MapperFactory;
123+
124+
require_once 'vendor/autoload.php';
125+
126+
class InnerDTO
127+
{
128+
private string $key;
129+
private string $value;
130+
131+
public function __construct(string $keyPrefix, string $key, string $value)
132+
{
133+
$this->key = $keyPrefix . $key;
134+
$this->value = $value;
135+
}
136+
}
137+
138+
class DTO
139+
{
140+
private int $id;
141+
142+
/** @var InnerDTO[] */
143+
private array $values;
144+
}
145+
146+
$arrayToMap = [
147+
'id' => 1,
148+
'values' => [
149+
[
150+
'key' => 'first',
151+
'value' => '1'
152+
],
153+
],
154+
];
155+
156+
$mapper = (new MapperFactory())->getMapper();
157+
158+
$innerDTOMapper = \Closure::fromCallable(function (array $data) {
159+
return new InnerDTO('prefix_', $data['key'], $data['value']);
160+
});
161+
162+
$mapper->addCustomMapper(InnerDTO::class, $innerDTOMapper);
163+
/** @var DTO $dto */
164+
$dto = $mapper->map($arrayToMap, DTO::class);
165+
```
166+
167+
The same works if the Custom type is in an array but in this case the mapper function will receive the subarray as is:
168+
169+
```php
170+
171+
```
172+

src/Mapper.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ private function castArray(array $array, DocBlockType $docblockType): array
100100

101101
$castedArray[] = $castedItem;
102102
}
103+
} elseif (!$docblockType->isSingle()) {
104+
foreach ($array as $item) {
105+
$castedArray[] = $this->castCustom($item, $docblockType->getName());
106+
}
103107
} else {
104108
foreach ($array as $item) {
105109
$castedArray[] = $this->map($item, $docblockType->getName());

src/MapperFactory.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Emul\ArrayToClassMapper;
5+
6+
use Emul\ArrayToClassMapper\DocBlock\DocBlockParser;
7+
8+
class MapperFactory
9+
{
10+
public function getMapper(): Mapper
11+
{
12+
return new Mapper(new DocBlockParser());
13+
}
14+
}

test.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
use Emul\ArrayToClassMapper\MapperFactory;
5+
6+
require_once 'vendor/autoload.php';
7+
8+
class InnerDTO
9+
{
10+
private string $key;
11+
private string $value;
12+
13+
public function __construct(string $keyPrefix, string $key, string $value)
14+
{
15+
$this->key = $keyPrefix . $key;
16+
$this->value = $value;
17+
}
18+
}
19+
20+
class DTO
21+
{
22+
private int $id;
23+
24+
/** @var InnerDTO[] */
25+
private array $values;
26+
}
27+
28+
$arrayToMap = [
29+
'id' => 1,
30+
'values' => [
31+
[
32+
'key' => 'first',
33+
'value' => '1'
34+
],
35+
],
36+
];
37+
38+
$mapper = (new MapperFactory())->getMapper();
39+
40+
$innerDTOMapper = \Closure::fromCallable(function (array $data) {
41+
return new InnerDTO('prefix_', $data['key'], $data['value']);
42+
});
43+
44+
$mapper->addCustomMapper(InnerDTO::class, $innerDTOMapper);
45+
/** @var DTO $dto */
46+
$dto = $mapper->map($arrayToMap, DTO::class);
47+
48+
echo '<pre>';
49+
var_dump($dto);
50+
echo '</pre>';
51+
exit;

test/Unit/DocBlock/DocBlockParserTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,20 @@ public function testGetTypeWhenNullableTypeGiven_shouldReturnProperly(string $do
7373
$this->assertSame($expectedType, $type->getName());
7474
}
7575

76+
public function testGetTypeWhenTypedArray_shouldParseProperly()
77+
{
78+
$parser = new DocBlockParser();
79+
80+
$type = $parser->getType('
81+
/** @var int[] */
82+
');
83+
84+
$this->assertSame(true, $type->isBuiltIn());
85+
$this->assertSame(false, $type->isSingle());
86+
$this->assertSame(false, $type->isNullable());
87+
$this->assertSame('int', $type->getName());
88+
}
89+
7690
public function testGetTypeWhenMultiLine_shouldParseProperly()
7791
{
7892
$parser = new DocBlockParser();

test/Unit/MapperTest.php

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
use Emul\ArrayToClassMapper\Mapper;
88
use Emul\ArrayToClassMapper\DocBlock\DocBlockParser;
99
use Emul\ArrayToClassMapper\DocBlock\Entity\DocBlockType;
10-
use Emul\ArrayToClassMapper\Test\Unit\Stub\CustomDocBlockTypeArrayStub;
10+
use Emul\ArrayToClassMapper\Test\Unit\Stub\ClassDocBlockTypedArrayStub;
11+
use Emul\ArrayToClassMapper\Test\Unit\Stub\CustomDocBlockTypedArrayStub;
1112
use Emul\ArrayToClassMapper\Test\Unit\Stub\CustomDocBlockTypedStub;
13+
use Emul\ArrayToClassMapper\Test\Unit\Stub\CustomStub;
1214
use Emul\ArrayToClassMapper\Test\Unit\Stub\CustomTypedStub;
13-
use Emul\ArrayToClassMapper\Test\Unit\Stub\ScalarDocBlockTypeArrayStub;
15+
use Emul\ArrayToClassMapper\Test\Unit\Stub\ScalarDocBlockTypedArrayStub;
1416
use Emul\ArrayToClassMapper\Test\Unit\Stub\ScalarDocBlockTypedStub;
1517
use Emul\ArrayToClassMapper\Test\Unit\Stub\ScalarTypedStub;
1618
use Emul\ArrayToClassMapper\Test\Unit\Stub\TypelessArrayStub;
@@ -67,8 +69,8 @@ public function testMapWhenArrayTypedPropertyGivenWithBuiltInDockBlockType_shoul
6769
'scalarTypedArray' => ['1', '2'],
6870
];
6971

70-
/** @var ScalarDocBlockTypeArrayStub $result */
71-
$result = $mapper->map($input, ScalarDocBlockTypeArrayStub::class);
72+
/** @var ScalarDocBlockTypedArrayStub $result */
73+
$result = $mapper->map($input, ScalarDocBlockTypedArrayStub::class);
7274

7375
$this->assertSame([1, 2], $result->getScalarTypedArray());
7476
}
@@ -84,15 +86,15 @@ public function testMapWhenArrayTypedPropertyGivenWithCustomDockBlockType_should
8486
);
8587

8688
$input = [
87-
'customArray' => [
89+
'objectArray' => [
8890
['int' => 1],
8991
['int' => 2],
9092
],
9193
];
9294

93-
/** @var CustomDocBlockTypeArrayStub $result */
94-
$result = $mapper->map($input, CustomDocBlockTypeArrayStub::class);
95-
$mappedArray = $result->getCustomArray();
95+
/** @var ClassDocBlockTypedArrayStub $result */
96+
$result = $mapper->map($input, ClassDocBlockTypedArrayStub::class);
97+
$mappedArray = $result->getObjectArray();
9698

9799
$this->assertCount(2, $mappedArray);
98100
$this->assertSame(1, $mappedArray[0]->getInt());
@@ -167,6 +169,39 @@ public function testMapWhenCustomDocBlockTypedPropertyGiven_shouldMapWithGivenMa
167169
$this->assertSame($currentTime, $result->getCurrentTime()->toDateTimeString());
168170
}
169171

172+
public function testMapWhenCustomDocBlockTypedArrayPropertyGiven_shouldMapWithGivenMapper()
173+
{
174+
$this->expectTypeRetrievedFromDocBlock(
175+
'/** @var \Emul\ArrayToClassMapper\Test\Unit\Stub\CustomStub[] */',
176+
new DocBlockType('\Emul\ArrayToClassMapper\Test\Unit\Stub\CustomStub', false, false, false)
177+
);
178+
$this->expectTypeRetrievedFromDocBlock('', null);
179+
180+
$input = [
181+
'objectArray' => [
182+
['key' => 'first', 'value' => '1'],
183+
['key' => 'second', 'value' => '2'],
184+
]
185+
];
186+
$customMapper = \Closure::fromCallable(function (array $data) {
187+
return new CustomStub('prefix_', $data['key'], $data['value']);
188+
});
189+
190+
$mapper = $this->getMapper();
191+
$mapper->addCustomMapper(CustomStub::class, $customMapper);
192+
193+
/** @var CustomDocBlockTypedArrayStub $result */
194+
$result = $mapper->map($input, CustomDocBlockTypedArrayStub::class);
195+
196+
$this->assertCount(2, $result->getObjectArray());
197+
$this->assertInstanceOf(CustomStub::class, $result->getObjectArray()[0]);
198+
$this->assertInstanceOf(CustomStub::class, $result->getObjectArray()[1]);
199+
$this->assertSame('prefix_first', $result->getObjectArray()[0]->getKey());
200+
$this->assertSame('prefix_1', $result->getObjectArray()[0]->getValue());
201+
$this->assertSame('prefix_second', $result->getObjectArray()[1]->getKey());
202+
$this->assertSame('prefix_2', $result->getObjectArray()[1]->getValue());
203+
}
204+
170205
private function expectTypeRetrievedFromDocBlock(string $docBlock, ?DocBlockType $expectedResult)
171206
{
172207
$this->docBlockParser->shouldReceive('getType')->with($docBlock)->andReturn($expectedResult);

test/Unit/Stub/CustomDocBlockTypeArrayStub.php renamed to test/Unit/Stub/ClassDocBlockTypedArrayStub.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33

44
namespace Emul\ArrayToClassMapper\Test\Unit\Stub;
55

6-
class CustomDocBlockTypeArrayStub
6+
class ClassDocBlockTypedArrayStub
77
{
88
/** @var \Emul\ArrayToClassMapper\Test\Unit\Stub\ScalarTypedStub[] */
9-
private array $customArray;
9+
private array $objectArray;
1010

11-
public function getCustomArray(): array
11+
public function getObjectArray(): array
1212
{
13-
return $this->customArray;
13+
return $this->objectArray;
1414
}
1515
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Emul\ArrayToClassMapper\Test\Unit\Stub;
5+
6+
class CustomDocBlockTypedArrayStub
7+
{
8+
/** @var \Emul\ArrayToClassMapper\Test\Unit\Stub\CustomStub[] */
9+
private array $objectArray;
10+
11+
public function getObjectArray(): array
12+
{
13+
return $this->objectArray;
14+
}
15+
}

0 commit comments

Comments
 (0)