Skip to content

Commit 20046c9

Browse files
Roman3349f3l1x
authored andcommitted
Add missing generics, use reflections instead of variable property access
Signed-off-by: Roman Ondráček <[email protected]>
1 parent 403a8c5 commit 20046c9

File tree

10 files changed

+112
-46
lines changed

10 files changed

+112
-46
lines changed

phpstan.neon

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,8 @@ parameters:
1010
- .docs
1111

1212
reportMaybesInPropertyPhpDocTypes: false
13-
checkGenericClassInNonGenericObjectType: false
1413

1514
ignoreErrors:
16-
# Intended property access - required for reflection
17-
- '#^Variable property access on (\$this|static)\(Apitte\\Core\\Mapping\\(Request|Response)\\BasicEntity\)\.$#'
18-
- '#^Variable property access on Apitte\\Core\\Mapping\\Request\\BasicEntity\.$#'
19-
20-
# Phpstan bug
21-
- message: '#^Parameter \#1 \$argument of class ReflectionClass constructor expects class-string<T of object>|T of object, string given\.$#'
22-
path: %currentWorkingDirectory%/src/Core/DI/Loader/DoctrineAnnotationLoader.php
23-
2415
# Missing strict comparison
2516
- '#^Only booleans are allowed in#'
2617

@@ -48,13 +39,6 @@ parameters:
4839
# This should not happen because null is returned on error
4940
- '#Method Apitte\\Core\\Utils\\Helpers::slashless\(\) should return string but returns string\|null\.#'
5041

51-
# To pass php8.0 tests where library doesn't have isSingle()
52-
- message: """
53-
#^Call to deprecated method isSingle\\(\\) of class Nette\\\\Utils\\\\Type\\:
54-
use isSimple\\(\\)$#
55-
"""
56-
path: %currentWorkingDirectory%/src/OpenApi/SchemaDefinition/Entity/EntityAdapter.php
57-
5842
# Nette changed return typehint
5943
- message: "#^Method Apitte\\\\OpenApi\\\\SchemaDefinition\\\\Entity\\\\EntityAdapter\\:\\:getNativePropertyType\\(\\) should return string but returns array\\<string\\>\\|string\\.$#"
6044
path: %currentWorkingDirectory%/src/OpenApi/SchemaDefinition/Entity/EntityAdapter.php

src/Core/DI/Loader/DoctrineAnnotationLoader.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public function load(SchemaBuilder $builder): SchemaBuilder
4343

4444
// Iterate over all controllers
4545
foreach ($controllers as $def) {
46+
/** @var class-string|null $type */
4647
$type = $def->getType();
4748

4849
if ($type === null) {
@@ -71,6 +72,10 @@ public function load(SchemaBuilder $builder): SchemaBuilder
7172
return $builder;
7273
}
7374

75+
/**
76+
* @param class-string $class
77+
* @return ReflectionClass<object>
78+
*/
7479
protected function analyseClass(string $class): ReflectionClass
7580
{
7681
// Analyse only new-ones
@@ -88,7 +93,7 @@ protected function analyseClass(string $class): ReflectionClass
8893
];
8994

9095
// Get all parents
91-
/** @var string[] $parents */
96+
/** @var class-string[] $parents */
9297
$parents = class_parents($class);
9398
$reflections = [];
9499

@@ -122,11 +127,17 @@ protected function analyseClass(string $class): ReflectionClass
122127
return $classRef;
123128
}
124129

130+
/**
131+
* @param ReflectionClass<object> $class
132+
*/
125133
protected function acceptController(ReflectionClass $class): bool
126134
{
127135
return is_subclass_of($class->getName(), IController::class);
128136
}
129137

138+
/**
139+
* @param ReflectionClass<object> $class
140+
*/
130141
protected function parseControllerClassAnnotations(Controller $controller, ReflectionClass $class): void
131142
{
132143
// Read class annotations
@@ -187,6 +198,9 @@ protected function parseControllerClassAnnotations(Controller $controller, Refle
187198
}
188199
}
189200

201+
/**
202+
* @param ReflectionClass<object> $reflectionClass
203+
*/
190204
protected function parseControllerMethodsAnnotations(Controller $controller, ReflectionClass $reflectionClass): void
191205
{
192206
// Iterate over all methods in class

src/Core/Mapping/Request/AbstractEntity.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,21 @@
55
use ArrayIterator;
66
use IteratorAggregate;
77

8+
/**
9+
* @template TKey of string|int
10+
* @template TValue of mixed
11+
* @implements IteratorAggregate<TKey, TValue>
12+
*/
813
abstract class AbstractEntity implements IRequestEntity, IteratorAggregate
914
{
1015

1116
/**
12-
* @return mixed[]
17+
* @return array<TKey, TValue>
1318
*/
1419
abstract public function toArray(): array;
1520

1621
/**
17-
* @return ArrayIterator<int|string, mixed>
22+
* @return ArrayIterator<TKey, TValue>
1823
*/
1924
public function getIterator(): ArrayIterator
2025
{

src/Core/Mapping/Request/BasicEntity.php

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
use Nette\Utils\JsonException;
1010
use TypeError;
1111

12+
/**
13+
* @template TKey of string|int
14+
* @template TValue of mixed
15+
* @extends AbstractEntity<TKey, TValue>
16+
*/
1217
abstract class BasicEntity extends AbstractEntity
1318
{
1419

@@ -23,7 +28,7 @@ public function getRequestProperties(): array
2328
}
2429

2530
/**
26-
* @return BasicEntity|null
31+
* @return BasicEntity<TKey, TValue>|null
2732
*/
2833
public function fromRequest(ApiRequest $request): ?IRequestEntity
2934
{
@@ -39,8 +44,8 @@ public function fromRequest(ApiRequest $request): ?IRequestEntity
3944
}
4045

4146
/**
42-
* @param array<string, mixed> $data
43-
* @return static
47+
* @param array<TKey, TValue> $data
48+
* @return static<TKey, TValue>
4449
*/
4550
public function factory(array $data): self
4651
{
@@ -49,20 +54,35 @@ public function factory(array $data): self
4954
// Fill properties with real data
5055
$properties = $inst->getRequestProperties();
5156
foreach ($properties as $property) {
52-
if (!array_key_exists($property['name'], $data)) {
57+
/** @var TKey $propName */
58+
$propName = $property['name'];
59+
if (!array_key_exists($propName, $data)) {
5360
continue;
5461
}
5562

56-
$value = $data[$property['name']];
63+
$value = $data[$propName];
5764

5865
// Normalize & convert value (only not null values)
5966
if ($value !== null) {
60-
$value = $this->normalize($property['name'], $value);
67+
$value = $this->normalize($propName, $value);
6168
}
6269

6370
// Fill single property
6471
try {
65-
$inst->{$property['name']} = $value;
72+
$propNameStr = (string) $propName;
73+
if (property_exists($inst, $propNameStr)) {
74+
$ref = new \ReflectionProperty($inst, $propNameStr);
75+
$wasAccessible = $ref->isPublic();
76+
if (!$wasAccessible) {
77+
$ref->setAccessible(true);
78+
}
79+
$ref->setValue($inst, $value);
80+
if (!$wasAccessible) {
81+
$ref->setAccessible(false);
82+
}
83+
} elseif (method_exists($inst, '__set')) {
84+
$inst->__set($propName, $value);
85+
}
6686
} catch (TypeError) {
6787
// do nothing, entity will be invalid if something is missing and ValidationException will be thrown
6888
}
@@ -71,13 +91,18 @@ public function factory(array $data): self
7191
return $inst;
7292
}
7393

74-
protected function normalize(string $property, mixed $value): mixed
94+
/**
95+
* @param TKey $property
96+
* @param TValue $value
97+
* @return TValue
98+
*/
99+
protected function normalize(int|string $property, mixed $value): mixed
75100
{
76101
return $value;
77102
}
78103

79104
/**
80-
* @return static
105+
* @return static<TKey, TValue>
81106
*/
82107
protected function fromBodyRequest(ApiRequest $request): self
83108
{
@@ -91,7 +116,7 @@ protected function fromBodyRequest(ApiRequest $request): self
91116
}
92117

93118
/**
94-
* @return static
119+
* @return static<TKey, TValue>
95120
*/
96121
protected function fromGetRequest(ApiRequest $request): self
97122
{

src/Core/Mapping/Response/AbstractEntity.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,21 @@
55
use ArrayIterator;
66
use IteratorAggregate;
77

8+
/**
9+
* @template TKey of string|int
10+
* @template TValue of mixed
11+
* @implements IteratorAggregate<TKey, TValue>
12+
*/
813
abstract class AbstractEntity implements IResponseEntity, IteratorAggregate
914
{
1015

1116
/**
12-
* @return mixed[]
17+
* @return array<TKey, TValue>
1318
*/
1419
abstract public function toArray(): array;
1520

1621
/**
17-
* @return ArrayIterator<int|string, mixed>
22+
* @return ArrayIterator<TKey, TValue>
1823
*/
1924
public function getIterator(): ArrayIterator
2025
{

src/Core/Mapping/Response/BasicEntity.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,26 @@
44

55
use Apitte\Core\Mapping\TReflectionProperties;
66

7+
/**
8+
* @template TKey of string|int
9+
* @template TValue of mixed
10+
* @extends AbstractEntity<TKey, TValue>
11+
*/
712
abstract class BasicEntity extends AbstractEntity
813
{
914

1015
use TReflectionProperties;
1116

1217
/**
13-
* @return mixed[]
18+
* @return array<TKey, array<string, mixed>>
1419
*/
1520
public function getResponseProperties(): array
1621
{
1722
return $this->getProperties();
1823
}
1924

2025
/**
21-
* @return mixed[]
26+
* @return array<TKey, TValue>
2227
*/
2328
public function toResponse(): array
2429
{

src/Core/Mapping/TReflectionProperties.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,20 @@ public function toArray(): array
5353
{
5454
$data = [];
5555
$properties = $this->getProperties();
56+
$rf = new ReflectionObject($this);
5657

5758
foreach ($properties as $property) {
58-
if (!isset($this->{$property['name']})) {
59+
if (!$rf->hasProperty($property['name'])) {
5960
continue;
6061
}
6162

62-
$data[$property['name']] = $this->{$property['name']};
63+
$propRf = $rf->getProperty($property['name']);
64+
65+
if (!$propRf->isInitialized($this)) {
66+
continue;
67+
}
68+
69+
$data[$property['name']] = $propRf->getValue($this);
6370
}
6471

6572
return $data;

src/Core/Mapping/Validator/BasicValidator.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public function validate(object $entity): void
2525

2626
if ($violations !== []) {
2727
$fields = [];
28+
2829
foreach ($violations as $property => $messages) {
2930
$fields[$property] = count($messages) > 1 ? $messages : $messages[0];
3031
}
@@ -35,6 +36,7 @@ public function validate(object $entity): void
3536
}
3637

3738
/**
39+
* @param BasicEntity<int|string, mixed> $entity
3840
* @return string[][]
3941
*/
4042
protected function validateProperties(BasicEntity $entity): array
@@ -47,8 +49,22 @@ protected function validateProperties(BasicEntity $entity): array
4749
$propertyRf = $rf->getProperty($propertyName);
4850
$doc = (string) $propertyRf->getDocComment();
4951

50-
if (str_contains($doc, '@required') && $entity->{$propertyName} === null) {
51-
$violations[$propertyName][] = 'This value should not be null.';
52+
if (str_contains($doc, '@required')) {
53+
$wasAccessible = $propertyRf->isPublic();
54+
55+
if (!$wasAccessible) {
56+
$propertyRf->setAccessible(true);
57+
}
58+
59+
$value = $propertyRf->getValue($entity);
60+
61+
if (!$wasAccessible) {
62+
$propertyRf->setAccessible(false);
63+
}
64+
65+
if ($value === null) {
66+
$violations[$propertyName][] = 'This value should not be null.';
67+
}
5268
}
5369
}
5470

src/Negotiation/Http/ArrayEntity.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,39 @@
88

99
/**
1010
* @template TKey of string|int
11-
* @implements IteratorAggregate<TKey, mixed>
11+
* @template TValue of mixed
12+
* @implements IteratorAggregate<TKey, TValue>
1213
*/
1314
class ArrayEntity extends AbstractEntity implements IteratorAggregate, Countable
1415
{
1516

1617
/**
17-
* @param mixed[] $data
18+
* @param array<TKey, TValue> $data
1819
*/
1920
public function __construct(array $data)
2021
{
2122
parent::__construct($data);
2223
}
2324

2425
/**
25-
* @param mixed[] $data
26-
* @phpstan-return static<int|string>
26+
* @param array<TKey, TValue> $data
27+
* @return static<TKey>
2728
*/
2829
public static function from(array $data): static
2930
{
3031
return new static($data);
3132
}
3233

3334
/**
34-
* @return mixed[]
35+
* @return array<TKey, TValue>
3536
*/
3637
public function toArray(): array
3738
{
3839
return (array) $this->getData();
3940
}
4041

4142
/**
42-
* @return ArrayIterator<TKey, mixed>
43+
* @return ArrayIterator<TKey, TValue>
4344
*/
4445
public function getIterator(): ArrayIterator
4546
{

0 commit comments

Comments
 (0)