Skip to content

Commit d9e4bfc

Browse files
committed
add support of arrayOfType
1 parent b66fb03 commit d9e4bfc

14 files changed

+244
-47
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
}
1818
],
1919
"minimum-stability": "dev",
20-
"version": "0.2.0",
20+
"version": "0.3.0",
2121
"autoload": {
2222
"psr-4": {
2323
"Shureban\\LaravelObjectMapper\\": "src/"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Shureban\LaravelObjectMapper\Exceptions;
4+
5+
use Exception;
6+
use Throwable;
7+
8+
class UnknownPropertyTypeException extends Exception
9+
{
10+
/**
11+
* @param string $property
12+
* @param int $code
13+
* @param Throwable|null $previous
14+
*/
15+
public function __construct(string $property, int $code = 0, ?Throwable $previous = null)
16+
{
17+
$message = sprintf('Unknown property %s type', $property);
18+
19+
parent::__construct($message, $code, $previous);
20+
}
21+
}

src/PhpDoc.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
class PhpDoc
66
{
77
private const PropertyNameRegex = '/var(.*)?\$(?<name>\w+)/';
8-
private const TypeNameRegex = '/var\s(?<type>[\\a-zA-Z0-9]+)([\[\]]+)?\s\$?/U';
9-
private const ArrayOfRegex = '/var .*\[\] \$?/';
8+
private const TypeNameRegex = '/var (?<type>[\\a-zA-Z0-9]+)([\[\]]+)? \$?/U';
9+
private const ArrayOfRegex = '/var [\\a-zA-Z0-9]+([[]]+) \$?/';
1010

1111
private string $phpDoc;
1212

@@ -30,6 +30,14 @@ public function getPropertyName(): ?string
3030
return null;
3131
}
3232

33+
/**
34+
* @return bool
35+
*/
36+
public function hasType(): bool
37+
{
38+
return (bool)preg_match(self::TypeNameRegex, $this->phpDoc);
39+
}
40+
3341
/**
3442
* @return mixed
3543
*/
@@ -49,4 +57,12 @@ public function isArrayOf(): bool
4957
{
5058
return (bool)preg_match(self::ArrayOfRegex, $this->phpDoc);
5159
}
60+
61+
/**
62+
* @return int
63+
*/
64+
public function arrayNestedLevel(): int
65+
{
66+
return substr_count($this->phpDoc, '[]');
67+
}
5268
}

src/Types/Custom/ArrayOfType.php

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,40 @@
22

33
namespace Shureban\LaravelObjectMapper\Types\Custom;
44

5-
use Shureban\LaravelObjectMapper\ObjectMapper;
6-
use Shureban\LaravelObjectMapper\Types\SimpleTypes\ObjectType;
5+
use Shureban\LaravelObjectMapper\Types\Type;
76

8-
class ArrayOfType extends ObjectType
7+
class ArrayOfType extends Type
98
{
10-
private object $type;
9+
private Type $type;
10+
private int $nestedLevel;
1111

12-
public function __construct(object $type)
12+
/**
13+
* @param Type $type
14+
* @param int $nestedLevel
15+
*/
16+
public function __construct(Type $type, int $nestedLevel = 1)
17+
{
18+
$this->type = $type;
19+
$this->nestedLevel = $nestedLevel;
20+
}
21+
22+
/**
23+
* @return array
24+
*/
25+
public function getDefaultValue(): array
1326
{
14-
$this->type = $type;
27+
return [];
1528
}
1629

1730
/**
1831
* @param mixed $value
1932
*
20-
* @return object
33+
* @return array
2134
*/
22-
public function convert(mixed $value): object
35+
public function convert(mixed $value): array
2336
{
24-
return (new ObjectMapper($this->type))->mapFromArray($value);
37+
return array_map(fn(mixed $nestedValue) => $this->nestedLevel === 1
38+
? $this->type->convert($nestedValue)
39+
: (new static($this->type, $this->nestedLevel - 1))->convert($nestedValue), $value);
2540
}
2641
}

src/Types/Factory.php

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
use ReflectionException;
1111
use ReflectionProperty;
1212
use Shureban\LaravelObjectMapper\ClassExtraInformation;
13+
use Shureban\LaravelObjectMapper\Exceptions\UnknownPropertyTypeException;
1314
use Shureban\LaravelObjectMapper\PhpDoc;
1415
use Shureban\LaravelObjectMapper\Types\BoxTypes\CarbonType;
1516
use Shureban\LaravelObjectMapper\Types\BoxTypes\CollectionType;
1617
use Shureban\LaravelObjectMapper\Types\BoxTypes\DateTimeType;
18+
use Shureban\LaravelObjectMapper\Types\Custom\ArrayOfType;
1719
use Shureban\LaravelObjectMapper\Types\Custom\CustomType;
1820
use Shureban\LaravelObjectMapper\Types\Custom\EnumType;
1921
use Shureban\LaravelObjectMapper\Types\SimpleTypes\ArrayType;
@@ -29,17 +31,17 @@ class Factory
2931
* @param ReflectionProperty $property
3032
*
3133
* @return Type
32-
* @throws ReflectionException
34+
* @throws ReflectionException|UnknownPropertyTypeException
3335
*/
3436
public static function make(ReflectionProperty $property): Type
3537
{
3638
$phpDoc = new PhpDoc($property->getDocComment());
3739

38-
if (!$property->hasType() && is_null($phpDoc->getPropertyType())) {
40+
if (!$property->hasType() && !$phpDoc->hasType()) {
3941
return new MixedType();
4042
}
4143

42-
$type = $property->hasType() ? $property->getType()->getName() : $phpDoc->getPropertyType();
44+
$type = $phpDoc->hasType() ? $phpDoc->getPropertyType() : $property->getType()->getName();
4345
$simpleType = match ($type) {
4446
'string' => new StringType(),
4547
'float', 'double' => new FloatType(),
@@ -51,11 +53,7 @@ public static function make(ReflectionProperty $property): Type
5153
};
5254

5355
if ($simpleType !== null) {
54-
return $simpleType;
55-
}
56-
57-
if ($phpDoc->isArrayOf()) {
58-
dd(12);
56+
return $phpDoc->isArrayOf() ? new ArrayOfType($simpleType, $phpDoc->arrayNestedLevel()) : $simpleType;
5957
}
6058

6159
$boxType = match ($type) {
@@ -66,24 +64,36 @@ public static function make(ReflectionProperty $property): Type
6664
};
6765

6866
if ($boxType !== null) {
69-
return $boxType;
67+
return $phpDoc->isArrayOf() ? new ArrayOfType($boxType, $phpDoc->arrayNestedLevel()) : $boxType;
7068
}
7169

7270
if (enum_exists($type)) {
73-
return new EnumType($type);
71+
$type = new EnumType($type);
72+
73+
return $phpDoc->isArrayOf() ? new ArrayOfType($type, $phpDoc->arrayNestedLevel()) : $type;
7474
}
7575

7676
if (class_exists($type)) {
77-
return new CustomType((new ReflectionClass($type))->newInstanceWithoutConstructor());
77+
$type = new CustomType((new ReflectionClass($type))->newInstanceWithoutConstructor());
78+
79+
return $phpDoc->isArrayOf() ? new ArrayOfType($type, $phpDoc->arrayNestedLevel()) : $type;
7880
}
7981

8082
$classUses = new ClassExtraInformation($property->getDeclaringClass());
8183
$namespace = $classUses->getFullObjectUseNamespace($type);
8284

8385
if (enum_exists($namespace)) {
84-
return new EnumType($namespace);
86+
$type = new EnumType($namespace);
87+
88+
return $phpDoc->isArrayOf() ? new ArrayOfType($type, $phpDoc->arrayNestedLevel()) : $type;
89+
}
90+
91+
if (class_exists($namespace)) {
92+
$type = new CustomType((new ReflectionClass($namespace))->newInstanceWithoutConstructor());
93+
94+
return $phpDoc->isArrayOf() ? new ArrayOfType($type, $phpDoc->arrayNestedLevel()) : $type;
8595
}
8696

87-
return new CustomType((new ReflectionClass($namespace))->newInstanceWithoutConstructor());
97+
throw new UnknownPropertyTypeException($property->getName());
8898
}
8999
}

tests/Unit/ArrayOfTypeTest.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
namespace Shureban\LaravelObjectMapper\Tests\Structs;
4+
5+
use DateTime;
6+
use Shureban\LaravelObjectMapper\ObjectMapper;
7+
use Shureban\LaravelObjectMapper\Tests\Unit\Structs\ArrayOfBoxTypeClass;
8+
use Shureban\LaravelObjectMapper\Tests\Unit\Structs\ArrayOfCustomTypeClass;
9+
use Shureban\LaravelObjectMapper\Tests\Unit\Structs\ArrayOfEnumTypeClass;
10+
use Shureban\LaravelObjectMapper\Tests\Unit\Structs\ArrayOfSimpleTypeClass;
11+
use Shureban\LaravelObjectMapper\Tests\Unit\Structs\CustomTypeForArrayOf;
12+
use Shureban\LaravelObjectMapper\Tests\Unit\Structs\EnumTypeForArrayOf;
13+
use Tests\TestCase;
14+
15+
class ArrayOfTypeTest extends TestCase
16+
{
17+
public function test_SimpleType()
18+
{
19+
$this->assertEquals([1, 2, 3, 4], (new ObjectMapper(new ArrayOfSimpleTypeClass()))->mapFromJson('{"arrayOfInt": [1,2,3,4]}')->arrayOfInt);
20+
$this->assertEquals(
21+
['one' => [1, 2], 'two' => [3, 4]],
22+
(new ObjectMapper(new ArrayOfSimpleTypeClass()))->mapFromJson('{"arrayOfArrayOfInt": {"one":[1, 2], "two":[3, 4]}}')->arrayOfArrayOfInt
23+
);
24+
$this->assertEquals(
25+
['one' => ['two' => [1, 2]], 'three' => ['four' => [3, 4]]],
26+
(new ObjectMapper(new ArrayOfSimpleTypeClass()))->mapFromJson('{"arrayOfArrayOfArrayOfInt": {"one":{"two":[1,2]}, "three":{"four":[3,4]}}}')->arrayOfArrayOfArrayOfInt
27+
);
28+
}
29+
30+
public function test_BoxType()
31+
{
32+
$this->assertEquals([new DateTime('2022-01-01')], (new ObjectMapper(new ArrayOfBoxTypeClass()))->mapFromJson('{"arrayOfDate": ["2022-01-01"]}')->arrayOfDate);
33+
$this->assertEquals(
34+
['one' => [new DateTime('2022-01-01')], 'two' => [new DateTime('2022-01-01')]],
35+
(new ObjectMapper(new ArrayOfBoxTypeClass()))->mapFromJson('{"arrayOfArrayOfDate": {"one":["2022-01-01"], "two":["2022-01-01"]}}')->arrayOfArrayOfDate
36+
);
37+
$this->assertEquals(
38+
['one' => ['two' => [new DateTime('2022-01-01')]], 'three' => ['four' => [new DateTime('2022-01-01')]]],
39+
(new ObjectMapper(new ArrayOfBoxTypeClass()))->mapFromJson('{"arrayOfArrayOfArrayOfDate": {"one":{"two":["2022-01-01"]}, "three":{"four":["2022-01-01"]}}}')->arrayOfArrayOfArrayOfDate
40+
);
41+
}
42+
43+
public function test_CustomType()
44+
{
45+
$customType = new CustomTypeForArrayOf();
46+
$customType->key = 'value';
47+
48+
$this->assertEquals([$customType], (new ObjectMapper(new ArrayOfCustomTypeClass()))->mapFromJson('{"arrayOfCustomType": [{"key":"value"}]}')->arrayOfCustomType);
49+
$this->assertEquals(
50+
['one' => [$customType], 'two' => [$customType]],
51+
(new ObjectMapper(new ArrayOfCustomTypeClass()))->mapFromJson('{"arrayOfArrayOfCustomType": {"one":[{"key":"value"}], "two":[{"key":"value"}]}}')->arrayOfArrayOfCustomType
52+
);
53+
$this->assertEquals(
54+
['one' => ['two' => [$customType]], 'three' => ['four' => [$customType]]],
55+
(new ObjectMapper(new ArrayOfCustomTypeClass()))->mapFromJson('{"arrayOfArrayOfArrayOfCustomType": {"one":{"two":[{"key":"value"}]}, "three":{"four":[{"key":"value"}]}}}')->arrayOfArrayOfArrayOfCustomType
56+
);
57+
}
58+
59+
public function test_EnumType()
60+
{
61+
$this->assertEquals([EnumTypeForArrayOf::Hearts], (new ObjectMapper(new ArrayOfEnumTypeClass()))->mapFromJson('{"arrayOfEnumType": ["Hearts"]}')->arrayOfEnumType);
62+
$this->assertEquals(
63+
['one' => [EnumTypeForArrayOf::Hearts], 'two' => [EnumTypeForArrayOf::Hearts]],
64+
(new ObjectMapper(new ArrayOfEnumTypeClass()))->mapFromJson('{"arrayOfArrayOfEnumType": {"one":["Hearts"], "two":["Hearts"]}}')->arrayOfArrayOfEnumType
65+
);
66+
$this->assertEquals(
67+
['one' => ['two' => [EnumTypeForArrayOf::Hearts]], 'three' => ['four' => [EnumTypeForArrayOf::Hearts]]],
68+
(new ObjectMapper(new ArrayOfEnumTypeClass()))->mapFromJson('{"arrayOfArrayOfArrayOfEnumType": {"one":{"two":["Hearts"]}, "three":{"four":["Hearts"]}}}')->arrayOfArrayOfArrayOfEnumType
69+
);
70+
}
71+
}

tests/Unit/PhpDocTest.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,28 @@ public function test_isArrayOf()
4747
$this->assertFalse((new PhpDoc('/** @var string */'))->isArrayOf());
4848
$this->assertTrue((new PhpDoc('/** @var string[] */'))->isArrayOf());
4949
$this->assertTrue((new PhpDoc('/** @var string[][] */'))->isArrayOf());
50-
$this->assertTrue((new PhpDoc('/** @var string[][] */'))->isArrayOf());
50+
$this->assertTrue((new PhpDoc('/** @var string[][][] */'))->isArrayOf());
5151
$this->assertTrue((new PhpDoc('/** @var string[] $variable */'))->isArrayOf());
52+
$this->assertTrue((new PhpDoc(<<<DOC
53+
/**
54+
* @var string[][][] \$variable
55+
*/
56+
DOC
57+
))->isArrayOf());
58+
}
59+
60+
public function test_arrayNestedLevel()
61+
{
62+
$this->assertEquals(0, (new PhpDoc('/** @var string */'))->arrayNestedLevel());
63+
$this->assertEquals(1, (new PhpDoc('/** @var string[] */'))->arrayNestedLevel());
64+
$this->assertEquals(2, (new PhpDoc('/** @var string[][] */'))->arrayNestedLevel());
65+
$this->assertEquals(3, (new PhpDoc('/** @var string[][][] */'))->arrayNestedLevel());
66+
$this->assertEquals(1, (new PhpDoc('/** @var string[] $variable */'))->arrayNestedLevel());
67+
$this->assertEquals(3, (new PhpDoc(<<<DOC
68+
/**
69+
* @var string[][][] \$variable
70+
*/
71+
DOC
72+
))->arrayNestedLevel());
5273
}
5374
}

tests/Unit/ShouldBe/ArrayOfBoxTypesClass.php

Lines changed: 0 additions & 7 deletions
This file was deleted.

tests/Unit/ShouldBe/ArrayOfCustomTypesClass.php

Lines changed: 0 additions & 7 deletions
This file was deleted.

tests/Unit/ShouldBe/ArrayOfSimpleTypesClass.php

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)