Skip to content

Commit b5a5019

Browse files
committed
Added computed properties
1 parent 46ba4bb commit b5a5019

19 files changed

+237
-76
lines changed

.github/workflows/php-tests.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: PHP Composer
1+
name: PHP Composer CI Tests
22

33
on:
44
push:
@@ -38,9 +38,6 @@ jobs:
3838
- name: Install dependencies
3939
run: composer install --prefer-dist --no-progress
4040

41-
- name: Get directory
42-
run: ls -la
43-
4441
- name: Run PHPUnit
4542
run: composer run-script ci-test
4643

.github/workflows/phpstan-tests.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: PHP Composer PHPStan Tests
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
build:
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Setup PHP
20+
uses: shivammathur/setup-php@v2
21+
with:
22+
php-version: 8.4
23+
24+
- name: Cache Composer packages
25+
id: composer-cache
26+
uses: actions/cache@v3
27+
with:
28+
path: vendor
29+
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
30+
restore-keys: |
31+
${{ runner.os }}-php-
32+
33+
- name: Install dependencies
34+
run: composer install --prefer-dist --no-progress
35+
36+
- name: Run PHPStan
37+
run: composer run-script phpstan

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
}
2828
},
2929
"scripts": {
30-
"ci-test": "XDEBUG_MODE=coverage vendor/bin/phpunit --testsuite=ci --configuration phpunit.xml"
30+
"ci-test": "XDEBUG_MODE=coverage vendor/bin/phpunit --testsuite=ci --configuration phpunit.xml",
31+
"phpstan": "vendor/bin/phpstan analyse --configuration phpstan.neon"
3132
}
3233
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Attributes\PropertyAttributes;
4+
5+
use Attribute;
6+
7+
#[Attribute(Attribute::TARGET_PROPERTY)]
8+
class Computed
9+
{
10+
}

src/Attributes/PropertyAttributes/Types/ArrayOfBackedEnums.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
1111
class ArrayOfBackedEnums
1212
{
13-
/** @var ReflectionEnum */
13+
/** @var array<string, ReflectionEnum<BackedEnum>> */
1414
private static array $_enumReflections = [];
1515

1616
/** @var list<class-string<BackedEnum>> $enums */
17-
private(set) public array $enums;
17+
private(set) array $enums;
1818

19-
/** @var ReflectionEnum */
20-
private(set) public array $resolvedBackedEnumReflections = [];
19+
/** @var array<string, ReflectionEnum<BackedEnum>> */
20+
private(set) array $resolvedBackedEnumReflections = [];
2121

2222
/**
2323
* @param class-string<BackedEnum>|list<class-string<BackedEnum>> $enums

src/Attributes/PropertyAttributes/Types/ArrayOfData.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
1313
class ArrayOfData
1414
{
15-
/** @var ReflectionClass */
15+
/** @var array<string, ReflectionClass<Data>> */
1616
private static array $_dataReflections = [];
1717

1818
/** @var list<class-string<Data>> */
19-
private(set) public array $dataClasses;
19+
private(set) array $dataClasses;
2020

21-
/** @var ReflectionClass */
22-
private(set) public array $resolvedDataReflections = [];
21+
/** @var array<string, ReflectionClass<Data>> */
22+
private(set) array $resolvedDataReflections = [];
2323

2424
/**
2525
* @param class-string<Data>|list<class-string<Data>> $dataClasses

src/Attributes/PropertyAttributes/Types/ArrayOfDateTimes.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@
1313
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
1414
class ArrayOfDateTimes
1515
{
16-
/** @var ReflectionClass */
16+
/** @var array<string, ReflectionClass<DateTime|DateTimeImmutable>> */
1717
private static array $_dateTimeReflections = [];
1818

1919
/** @var list<class-string<DateTime|DateTimeImmutable>> */
20-
private(set) public array $dateTimes;
20+
private(set) array $dateTimes;
2121

22-
/** @var ReflectionClass */
23-
private(set) public array $resolvedDateTimeReflections = [];
22+
/** @var array<string, ReflectionClass<DateTime|DateTimeImmutable>> */
23+
private(set) array $resolvedDateTimeReflections = [];
2424

2525
/**
2626
* @param class-string<DateTime|DateTimeImmutable>|list<class-string<DateTime|DateTimeImmutable>> $dateTimes

src/Attributes/PropertyAttributes/Types/ArrayOfScalarTypes.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
class ArrayOfScalarTypes
1111
{
1212
/** @var list<Type> $types */
13-
private(set) public array $types;
13+
private(set) array $types;
1414

1515
/**
1616
* @param Type|list<Type> $types

src/Concerns/BaseData.php

Lines changed: 51 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -29,41 +29,64 @@ final public static function from(mixed $value): static
2929

3030
/** @var ClassContext<static> $context */
3131
$context = ClassContext::getInstance(new ReflectionClass(static::class));
32-
$instance = $context->newInstanceWithoutConstructor();
3332

34-
foreach ($context->properties as $propertyContext) {
35-
$serializers = $propertyContext->serializers();
33+
return $context->hasComputedProperties
34+
? static::instanceWithConstructorCallFrom($context, $value)
35+
: static::instanceWithoutConstructorFrom($context, $value);
36+
} catch (Throwable $e) {
37+
throw new DeserializeException($e->getMessage(), $e->getCode(), $e);
38+
}
39+
}
3640

37-
if (!$serializers) {
38-
throw new DeserializeException(
39-
code: DeserializeException::NO_SERIALIZERS_ERROR_CODE
40-
);
41-
}
41+
/**
42+
* @param ClassContext<static> $context
43+
* @param array<string, mixed> $value
44+
*
45+
* @throws Throwable
46+
*/
47+
protected static function instanceWithoutConstructorFrom(ClassContext $context, array $value): static
48+
{
49+
$instance = $context->newInstanceWithoutConstructor();
4250

43-
$propertyName = $propertyContext->propertyName;
44-
$propertyDeserialized = false;
45-
foreach ($serializers as $serializer) {
46-
try {
47-
$propertyValue = $serializer->deserialize($propertyContext, $value);
51+
foreach ($context->properties as $propertyContext) {
52+
$propertyName = $propertyContext->propertyName;
4853

49-
$instance->{$propertyName} = $propertyValue;
54+
$instance->{$propertyName} = $propertyContext->deserializeFrom($value);
55+
}
5056

51-
$propertyDeserialized = true;
57+
return $instance;
58+
}
5259

53-
break;
54-
} catch (DeserializeException) {
55-
}
56-
}
60+
/**
61+
* @param ClassContext<static> $context
62+
* @param array<string, mixed> $value
63+
*
64+
* @throws Throwable
65+
*/
66+
protected static function instanceWithConstructorCallFrom(ClassContext $context, array $value): static
67+
{
68+
/** @var array<string, mixed> $args */
69+
$args = [];
5770

58-
if (!$propertyDeserialized) {
59-
throw new DeserializeException("Could not deserialize value for property: $propertyName");
60-
}
71+
foreach ($context->constructorParams as $paramName) {
72+
$propertyContext = $context->properties[$paramName] ?? null;
73+
74+
if (!$propertyContext) {
75+
throw new DeserializeException(
76+
"Could not find property context for constructor param: $paramName"
77+
);
6178
}
6279

63-
return $instance;
64-
} catch (Throwable $e) {
65-
throw new DeserializeException($e->getMessage(), $e->getCode(), $e);
80+
$args[$paramName] = $propertyContext->deserializeFrom($value);
81+
}
82+
83+
$instance = $context->newInstanceWithConstructorCall(...$args);
84+
85+
if (!$instance instanceof static) {
86+
throw new DeserializeException('Could not create instance of ' . static::class);
6687
}
88+
89+
return $instance;
6790
}
6891

6992
/**
@@ -77,36 +100,14 @@ final public function jsonSerialize(): array
77100
$context = ClassContext::getInstance(new ReflectionClass($this));
78101

79102
$serializableArray = [];
80-
81103
foreach ($context->properties as $propertyContext) {
82-
$serializers = $propertyContext->serializers();
83-
84-
if (!$serializers) {
85-
throw new SerializeException(
86-
code: SerializeException::NO_SERIALIZERS_ERROR_CODE
87-
);
104+
if ($propertyContext->isComputed) {
105+
continue;
88106
}
89107

90108
$propertyName = $propertyContext->propertyName;
91-
$propertySerialized = false;
92-
foreach ($serializers as $serializer) {
93-
try {
94-
$propertyValue = $serializer->serialize($propertyContext, $this);
95-
96-
$serializableArray[$propertyName] = $propertyValue[$propertyName];
97109

98-
$propertySerialized = true;
99-
100-
break;
101-
} catch (SerializeException) {
102-
}
103-
}
104-
105-
if (!$propertySerialized) {
106-
throw new SerializeException(
107-
"Could not serialize property: $propertyName",
108-
);
109-
}
110+
$serializableArray[$propertyName] = $propertyContext->serializeFrom($this)[$propertyName];
110111
}
111112

112113
return $serializableArray;

src/Contexts/ClassContext.php

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Nuxtifyts\PhpDto\Exceptions\UnsupportedTypeException;
66
use ReflectionClass;
77
use ReflectionException;
8+
use ReflectionParameter;
89

910
/**
1011
* @template T of object
@@ -22,7 +23,10 @@ class ClassContext
2223
/**
2324
* @var array<string, PropertyContext>
2425
*/
25-
protected readonly array $_properties;
26+
protected(set) readonly array $properties;
27+
28+
/** @var list<string> List of param names */
29+
public readonly array $constructorParams;
2630

2731
/**
2832
* @param ReflectionClass<T> $_reflectionClass
@@ -32,12 +36,20 @@ class ClassContext
3236
final private function __construct(
3337
protected readonly ReflectionClass $_reflectionClass
3438
) {
35-
$this->_properties = self::getPropertyContexts($this->_reflectionClass);
39+
$this->properties = self::getPropertyContexts($this->_reflectionClass);
40+
$this->constructorParams = array_map(
41+
static fn (ReflectionParameter $param) => $param->getName(),
42+
$this->_reflectionClass->getConstructor()?->getParameters() ?? [],
43+
);
3644
}
3745

38-
/** @var array<string, PropertyContext> */
39-
public array $properties {
40-
get => $this->_properties;
46+
public bool $hasComputedProperties {
47+
get => count(
48+
array_filter(
49+
$this->properties,
50+
static fn (PropertyContext $property) => $property->isComputed
51+
)
52+
) > 0;
4153
}
4254

4355
/**
@@ -86,4 +98,12 @@ public function newInstanceWithoutConstructor(): mixed
8698
{
8799
return $this->_reflectionClass->newInstanceWithoutConstructor();
88100
}
101+
102+
/**
103+
* @throws ReflectionException
104+
*/
105+
public function newInstanceWithConstructorCall(mixed ...$args): mixed
106+
{
107+
return $this->_reflectionClass->newInstance(...$args);
108+
}
89109
}

0 commit comments

Comments
 (0)