Skip to content

Commit 90d8b6a

Browse files
committed
ReflectorMetaSource: validate that definitions are not used on constants/methods/parameters
1 parent cc34cf2 commit 90d8b6a

File tree

6 files changed

+183
-5
lines changed

6 files changed

+183
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2424
- `load()` accepts `StructureGroup` as a 2nd parameter
2525
- `MetaLoader`
2626
- resolve meta from all sources to validate them, even though only one is used
27+
- `AnnotationsMetaSource`, `AttributesMetaSource`
28+
- validate that definitions are not used on constants/methods/parameters
2729
- Compile meta
2830
- `CompileMeta`
2931
- `getFields()` returns list of fields, including grouped and ordered duplicates (from child classes,

src/Meta/Source/ReflectorMetaSource.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
use Orisai\ObjectMapper\Modifiers\ModifierDefinition;
1919
use Orisai\ObjectMapper\Rules\RuleDefinition;
2020
use Orisai\ReflectionMeta\Reader\MetaReader;
21+
use Orisai\ReflectionMeta\Structure\Structure;
2122
use Orisai\ReflectionMeta\Structure\StructureGroup;
2223
use Orisai\SourceMap\AboveReflectorSource;
2324
use Orisai\SourceMap\ReflectorSource;
2425
use ReflectionClass;
2526
use function get_class;
2627
use function sprintf;
28+
use const PHP_VERSION_ID;
2729

2830
/**
2931
* @internal
@@ -40,6 +42,8 @@ public function __construct(MetaReader $reader)
4042

4143
public function load(ReflectionClass $rootClass, StructureGroup $group): CompileMeta
4244
{
45+
$this->checkUnsupportedReflectors($rootClass, $group);
46+
4347
$sources = [];
4448
foreach ($group->getClasses() as $structure) {
4549
$sources[] = $structure->getSource();
@@ -55,6 +59,63 @@ public function load(ReflectionClass $rootClass, StructureGroup $group): Compile
5559
);
5660
}
5761

62+
/**
63+
* @param ReflectionClass<covariant MappedObject> $rootClass
64+
*/
65+
private function checkUnsupportedReflectors(ReflectionClass $rootClass, StructureGroup $group): void
66+
{
67+
foreach ($group->getGroupedConstants() as $groupedConstant) {
68+
foreach ($groupedConstant as $constantStructure) {
69+
$reflector = $constantStructure->getSource()->getReflector();
70+
$definitions = $this->reader->readConstant($reflector, MetaDefinition::class);
71+
72+
if ($definitions !== []) {
73+
$this->throwUnsupportedReflector($rootClass, $constantStructure, 'constants');
74+
}
75+
}
76+
}
77+
78+
foreach ($group->getGroupedMethods() as $groupedMethod) {
79+
foreach ($groupedMethod as $methodStructure) {
80+
$reflector = $methodStructure->getSource()->getReflector();
81+
$definitions = $this->reader->readMethod($reflector, MetaDefinition::class);
82+
83+
if ($definitions !== []) {
84+
$this->throwUnsupportedReflector($rootClass, $methodStructure, 'methods');
85+
}
86+
87+
foreach ($methodStructure->getParameters() as $parameterStructure) {
88+
$parameterReflector = $parameterStructure->getSource()->getReflector();
89+
90+
// Promoted properties from constructor are duplicated
91+
if (PHP_VERSION_ID >= 8_00_00 && $parameterReflector->isPromoted()) {
92+
continue;
93+
}
94+
95+
$parameterDefinitions = $this->reader->readParameter($parameterReflector, MetaDefinition::class);
96+
if ($parameterDefinitions !== []) {
97+
$this->throwUnsupportedReflector($rootClass, $parameterStructure, 'parameters');
98+
}
99+
}
100+
}
101+
}
102+
}
103+
104+
/**
105+
* @param ReflectionClass<covariant MappedObject> $rootClass
106+
* @return never
107+
*/
108+
private function throwUnsupportedReflector(ReflectionClass $rootClass, Structure $structure, string $type): void
109+
{
110+
$message = Message::create()
111+
->withContext("Resolving metadata of mapped object '{$rootClass->getName()}'.")
112+
->withProblem("Definitions are not allowed on $type.")
113+
->withSolution("Remove definition from '{$structure->getSource()->toString()}'.");
114+
115+
throw InvalidArgument::create()
116+
->withMessage($message);
117+
}
118+
58119
/**
59120
* @param ReflectionClass<covariant MappedObject> $rootClass
60121
* @return list<ClassCompileMeta>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Tests\Orisai\ObjectMapper\Doubles\Invalid;
4+
5+
use Orisai\ObjectMapper\MappedObject;
6+
use Tests\Orisai\ObjectMapper\Doubles\Definition\TargetLessRuleDefinition;
7+
8+
final class DefinitionAboveConstantVO implements MappedObject
9+
{
10+
11+
/** @TargetLessRuleDefinition() */
12+
#[TargetLessRuleDefinition]
13+
public const Test = 'test';
14+
15+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Tests\Orisai\ObjectMapper\Doubles\Invalid;
4+
5+
use Orisai\ObjectMapper\MappedObject;
6+
use Tests\Orisai\ObjectMapper\Doubles\Definition\TargetLessRuleDefinition;
7+
8+
final class DefinitionAboveMethodVO implements MappedObject
9+
{
10+
11+
#[TargetLessRuleDefinition]
12+
public function test(): void
13+
{
14+
// Noop
15+
}
16+
17+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Tests\Orisai\ObjectMapper\Doubles\Invalid;
4+
5+
use Orisai\ObjectMapper\MappedObject;
6+
use Tests\Orisai\ObjectMapper\Doubles\Definition\TargetLessRuleDefinition;
7+
8+
final class DefinitionAboveParameterVO implements MappedObject
9+
{
10+
11+
public function test(#[TargetLessRuleDefinition] string $test,): void
12+
{
13+
// Noop
14+
}
15+
16+
}

tests/Unit/Meta/Source/ReflectorMetaSourceTest.php

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,37 @@
66
use Orisai\Exceptions\Logic\InvalidArgument;
77
use Orisai\ObjectMapper\MappedObject;
88
use Orisai\ObjectMapper\Meta\Source\AnnotationsMetaSource;
9+
use Orisai\ObjectMapper\Meta\Source\AttributesMetaSource;
910
use Orisai\ObjectMapper\Meta\Source\ReflectorMetaSource;
1011
use Orisai\ReflectionMeta\Structure\StructureBuilder;
1112
use Orisai\ReflectionMeta\Structure\StructureFlattener;
1213
use Orisai\ReflectionMeta\Structure\StructureGroup;
1314
use Orisai\ReflectionMeta\Structure\StructureGrouper;
1415
use PHPUnit\Framework\TestCase;
1516
use ReflectionClass;
17+
use Tests\Orisai\ObjectMapper\Doubles\Invalid\DefinitionAboveConstantVO;
18+
use Tests\Orisai\ObjectMapper\Doubles\Invalid\DefinitionAboveMethodVO;
19+
use Tests\Orisai\ObjectMapper\Doubles\Invalid\DefinitionAboveParameterVO;
1620
use Tests\Orisai\ObjectMapper\Doubles\Invalid\RuleAboveClassChildVO;
1721
use Tests\Orisai\ObjectMapper\Doubles\Invalid\RuleAboveClassVO;
1822
use Tests\Orisai\ObjectMapper\Doubles\Invalid\UnsupportedClassDefinitionVO;
1923
use Tests\Orisai\ObjectMapper\Doubles\Invalid\UnsupportedPropertyDefinitionVO;
24+
use const PHP_VERSION_ID;
2025

2126
final class ReflectorMetaSourceTest extends TestCase
2227
{
2328

24-
private ReflectorMetaSource $source;
29+
private ReflectorMetaSource $annotationsSource;
30+
31+
private ReflectorMetaSource $attributesSource;
2532

2633
protected function setUp(): void
2734
{
28-
$this->source = new AnnotationsMetaSource();
35+
$this->annotationsSource = new AnnotationsMetaSource();
36+
37+
if (PHP_VERSION_ID >= 8_00_00) {
38+
$this->attributesSource = new AttributesMetaSource();
39+
}
2940
}
3041

3142
/**
@@ -40,6 +51,62 @@ private function createStructureGroup(ReflectionClass $class): StructureGroup
4051
);
4152
}
4253

54+
/**
55+
* @param class-string<MappedObject> $class
56+
*
57+
* @dataProvider provideUnsupportedDefinition
58+
*/
59+
public function testUnsupportedDefinitionLocation(string $class, string $errorMessage): void
60+
{
61+
if (PHP_VERSION_ID < 8_00_00) {
62+
self::markTestSkipped('Attributes are supported on PHP 8.0+');
63+
}
64+
65+
$reflector = new ReflectionClass($class);
66+
$group = $this->createStructureGroup($reflector);
67+
68+
$this->expectException(InvalidArgument::class);
69+
$this->expectExceptionMessage($errorMessage);
70+
71+
$this->attributesSource->load($reflector, $group);
72+
}
73+
74+
public function provideUnsupportedDefinition(): Generator
75+
{
76+
yield [
77+
DefinitionAboveConstantVO::class,
78+
<<<'MSG'
79+
Context: Resolving metadata of mapped object
80+
'Tests\Orisai\ObjectMapper\Doubles\Invalid\DefinitionAboveConstantVO'.
81+
Problem: Definitions are not allowed on constants.
82+
Solution: Remove definition from
83+
'Tests\Orisai\ObjectMapper\Doubles\Invalid\DefinitionAboveConstantVO::Test'.
84+
MSG,
85+
];
86+
87+
yield [
88+
DefinitionAboveMethodVO::class,
89+
<<<'MSG'
90+
Context: Resolving metadata of mapped object
91+
'Tests\Orisai\ObjectMapper\Doubles\Invalid\DefinitionAboveMethodVO'.
92+
Problem: Definitions are not allowed on methods.
93+
Solution: Remove definition from
94+
'Tests\Orisai\ObjectMapper\Doubles\Invalid\DefinitionAboveMethodVO->test()'.
95+
MSG,
96+
];
97+
98+
yield [
99+
DefinitionAboveParameterVO::class,
100+
<<<'MSG'
101+
Context: Resolving metadata of mapped object
102+
'Tests\Orisai\ObjectMapper\Doubles\Invalid\DefinitionAboveParameterVO'.
103+
Problem: Definitions are not allowed on parameters.
104+
Solution: Remove definition from
105+
'Tests\Orisai\ObjectMapper\Doubles\Invalid\DefinitionAboveParameterVO->test(test)'.
106+
MSG,
107+
];
108+
}
109+
43110
/**
44111
* @param class-string<MappedObject> $class
45112
*
@@ -60,7 +127,7 @@ public function testUnsupportedDefinitionType(string $class): void
60127
. "'Orisai\ObjectMapper\Rules\RuleDefinition'.",
61128
);
62129

63-
$this->source->load($reflector, $group);
130+
$this->annotationsSource->load($reflector, $group);
64131
}
65132

66133
public function provideUnsupportedDefinitionType(): Generator
@@ -90,7 +157,7 @@ public function testRuleAboveClassRelativeName(): void
90157
MSG,
91158
);
92159

93-
$this->source->load($reflector, $group);
160+
$this->annotationsSource->load($reflector, $group);
94161
}
95162

96163
public function testRuleAboveClassFullName(): void
@@ -111,7 +178,7 @@ public function testRuleAboveClassFullName(): void
111178
MSG,
112179
);
113180

114-
$this->source->load($reflector, $group);
181+
$this->annotationsSource->load($reflector, $group);
115182
}
116183

117184
}

0 commit comments

Comments
 (0)