Skip to content

Commit 27df0f6

Browse files
authored
Release/2.0.0 (#22)
* feat: Removes unreachable code from internal classes, simplify value object unwrapping, and reorganize test suite.
1 parent cfadb8c commit 27df0f6

File tree

96 files changed

+2032
-1892
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+2032
-1892
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ permissions:
88

99
env:
1010
PHP_VERSION: '8.5'
11-
COMPOSER_ROOT_VERSION: '1.2.0'
1211

1312
jobs:
1413
build:
@@ -23,7 +22,6 @@ jobs:
2322
uses: shivammathur/setup-php@v2
2423
with:
2524
php-version: ${{ env.PHP_VERSION }}
26-
extensions: bcmath
2725
tools: composer:2
2826

2927
- name: Validate composer.json
@@ -53,7 +51,6 @@ jobs:
5351
uses: shivammathur/setup-php@v2
5452
with:
5553
php-version: ${{ env.PHP_VERSION }}
56-
extensions: bcmath
5754
tools: composer:2
5855

5956
- name: Download vendor artifact from build
@@ -78,7 +75,6 @@ jobs:
7875
uses: shivammathur/setup-php@v2
7976
with:
8077
php-version: ${{ env.PHP_VERSION }}
81-
extensions: bcmath
8278
tools: composer:2
8379

8480
- name: Download vendor artifact from build

composer.json

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,6 @@
2525
"issues": "https://github.com/tiny-blocks/mapper/issues",
2626
"source": "https://github.com/tiny-blocks/mapper"
2727
},
28-
"extra": {
29-
"branch-alias": {
30-
"dev-develop": "1.3.x-dev"
31-
}
32-
},
3328
"config": {
3429
"sort-packages": true,
3530
"allow-plugins": {
@@ -43,7 +38,7 @@
4338
},
4439
"autoload-dev": {
4540
"psr-4": {
46-
"TinyBlocks\\Mapper\\": "tests/"
41+
"Test\\TinyBlocks\\Mapper\\": "tests/"
4742
}
4843
},
4944
"require": {
@@ -53,8 +48,7 @@
5348
"phpunit/phpunit": "^11.5",
5449
"phpstan/phpstan": "^2.1",
5550
"infection/infection": "^0.32",
56-
"tiny-blocks/collection": "1.10.*",
57-
"squizlabs/php_codesniffer": "^3.13"
51+
"squizlabs/php_codesniffer": "^4.0"
5852
},
5953
"scripts": {
6054
"test": "php -d memory_limit=2G ./vendor/bin/phpunit --configuration phpunit.xml tests",

phpstan.neon.dist

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ parameters:
44
level: 9
55
tmpDir: report/phpstan
66
ignoreErrors:
7-
- '#method#'
8-
- '#expects#'
9-
- '#should return#'
7+
- '#T of#'
8+
- '#mixed#'
9+
- '#UnitEnum#'
10+
- '#Reflection#'
11+
- '#Traversable#'
1012
- '#is used zero times#'
11-
- '#type mixed supplied#'
12-
- '#not specify its types#'
13-
- '#no value type specified#'
13+
- '#type specified in iterable type#'
1414
reportUnmatchedIgnoredErrors: false
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TinyBlocks\Mapper\Internal\Builders;
6+
7+
use ReflectionClass;
8+
use ReflectionException;
9+
use ReflectionMethod;
10+
use ReflectionParameter;
11+
use TinyBlocks\Mapper\Internal\Extractors\ReflectionExtractor;
12+
use TinyBlocks\Mapper\Internal\Mappers\Object\Casters\CasterHandler;
13+
14+
final readonly class ObjectBuilder
15+
{
16+
public function __construct(private ReflectionExtractor $extractor)
17+
{
18+
}
19+
20+
/**
21+
* @template T of object
22+
* @param class-string<T> $class
23+
* @return T
24+
* @throws ReflectionException
25+
*/
26+
public function build(iterable $iterable, string $class): object
27+
{
28+
$reflection = new ReflectionClass(objectOrClass: $class);
29+
$parameters = $this->extractor->extractConstructorParameters(class: $class);
30+
$inputProperties = iterator_to_array(iterator: $iterable);
31+
32+
$arguments = $this->buildArguments(
33+
parameters: $parameters,
34+
inputProperties: $inputProperties
35+
);
36+
37+
return $this->instantiate(reflection: $reflection, arguments: $arguments);
38+
}
39+
40+
protected function buildArguments(array $parameters, array $inputProperties): array
41+
{
42+
$arguments = [];
43+
44+
/** @var ReflectionParameter $parameter */
45+
foreach ($parameters as $parameter) {
46+
$name = $parameter->getName();
47+
$value = $inputProperties[$name] ?? null;
48+
49+
$arguments[] = $value !== null
50+
? $this->castValue(parameter: $parameter, value: $value)
51+
: $this->getDefaultValue(parameter: $parameter);
52+
}
53+
54+
return $arguments;
55+
}
56+
57+
protected function castValue(ReflectionParameter $parameter, mixed $value): mixed
58+
{
59+
$caster = new CasterHandler(parameter: $parameter);
60+
return $caster->castValue(value: $value);
61+
}
62+
63+
protected function getDefaultValue(ReflectionParameter $parameter): mixed
64+
{
65+
return $parameter->isDefaultValueAvailable()
66+
? $parameter->getDefaultValue()
67+
: null;
68+
}
69+
70+
protected function instantiate(ReflectionClass $reflection, array $arguments): object
71+
{
72+
$constructor = $reflection->getConstructor();
73+
74+
if ($constructor === null) {
75+
return $reflection->newInstance();
76+
}
77+
78+
if ($constructor->isPrivate()) {
79+
return $this->instantiateWithPrivateConstructor(
80+
reflection: $reflection,
81+
constructor: $constructor,
82+
arguments: $arguments
83+
);
84+
}
85+
86+
return $reflection->newInstanceArgs(args: $arguments);
87+
}
88+
89+
protected function instantiateWithPrivateConstructor(
90+
ReflectionClass $reflection,
91+
ReflectionMethod $constructor,
92+
array $arguments
93+
): object {
94+
$instance = $reflection->newInstanceWithoutConstructor();
95+
$constructor->invokeArgs(object: $instance, args: $arguments);
96+
return $instance;
97+
}
98+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TinyBlocks\Mapper\Internal\Detectors;
6+
7+
use DateTimeInterface;
8+
9+
final readonly class DateTimeDetector implements TypeDetector
10+
{
11+
public function matches(mixed $value): bool
12+
{
13+
return $value instanceof DateTimeInterface;
14+
}
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TinyBlocks\Mapper\Internal\Detectors;
6+
7+
use UnitEnum;
8+
9+
final readonly class EnumDetector implements TypeDetector
10+
{
11+
public function matches(mixed $value): bool
12+
{
13+
return $value instanceof UnitEnum;
14+
}
15+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TinyBlocks\Mapper\Internal\Detectors;
6+
7+
/**
8+
* Defines the contract for type detection strategies.
9+
*/
10+
interface TypeDetector
11+
{
12+
/**
13+
* Determines if the given value matches the detector's type criteria.
14+
*
15+
* @param mixed $value The value to detect.
16+
* @return bool True if the value matches, false otherwise.
17+
*/
18+
public function matches(mixed $value): bool;
19+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TinyBlocks\Mapper\Internal\Detectors;
6+
7+
use ReflectionClass;
8+
use ReflectionProperty;
9+
use UnitEnum;
10+
11+
final readonly class ValueObjectDetector implements TypeDetector
12+
{
13+
private const int SINGLE_PROPERTY = 1;
14+
15+
public function matches(mixed $value): bool
16+
{
17+
$reflection = new ReflectionClass($value);
18+
$properties = $reflection->getProperties(
19+
ReflectionProperty::IS_PUBLIC
20+
| ReflectionProperty::IS_PROTECTED
21+
| ReflectionProperty::IS_PRIVATE
22+
);
23+
24+
return !$value instanceof UnitEnum && count($properties) === self::SINGLE_PROPERTY;
25+
}
26+
}

src/Internal/Exceptions/InvalidCast.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ final class InvalidCast extends InvalidArgumentException
1111
public static function forEnumValue(int|string $value, string $class): InvalidCast
1212
{
1313
$message = sprintf('Invalid value <%s> for enum <%s>.', $value, $class);
14-
1514
return new InvalidCast(message: $message);
1615
}
1716
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TinyBlocks\Mapper\Internal\Extractors;
6+
7+
use Traversable;
8+
9+
final readonly class IterableExtractor implements PropertyExtractor
10+
{
11+
public function __construct(private ReflectionExtractor $extractor)
12+
{
13+
}
14+
15+
public function extract(object $object): array
16+
{
17+
$properties = $this->extractor->extractProperties(object: $object);
18+
19+
$candidates = array_filter(
20+
$properties,
21+
static fn(mixed $value): bool => is_array($value) || $value instanceof Traversable
22+
);
23+
24+
$iterable = reset($candidates);
25+
26+
return is_array($iterable) ? $iterable : iterator_to_array($iterable);
27+
}
28+
}

0 commit comments

Comments
 (0)