Skip to content

Commit 2c4c8e1

Browse files
committed
feat: implement metadata support
1 parent 08bf834 commit 2c4c8e1

14 files changed

Lines changed: 706 additions & 46 deletions

src/Core/Reflect/Reflector.php

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@
2222
use function Constructo\Notation\format;
2323
use function implode;
2424
use function in_array;
25+
use function is_array;
2526
use function sprintf;
2627

2728
class Reflector
2829
{
29-
private array $currentPath = [];
30+
private array $sources = [];
3031

3132
public function __construct(
3233
protected readonly SchemaFactory $factory,
@@ -44,9 +45,9 @@ public function __construct(
4445
public function reflect(string $source): Schema
4546
{
4647
$this->cache->reset();
47-
$this->currentPath = [];
48+
$this->sources = [];
4849

49-
$parameters = $this->getParameters($source);
50+
$parameters = $this->extractParameters($source);
5051
$schema = $this->factory->make();
5152
$this->introspect($parameters, $schema);
5253

@@ -74,11 +75,7 @@ protected function introspect(array $parameters, Schema $schema, ?Field $parent
7475
$chain->resolve($parameter, $field, $nestedPath);
7576

7677
$source = $field->getSource();
77-
if ($source === null) {
78-
continue;
79-
}
80-
81-
if ($this->wouldCauseCircularReference($source)) {
78+
if ($source === null || $this->wouldCauseCircularReference($source)) {
8279
continue;
8380
}
8481

@@ -91,43 +88,33 @@ protected function introspect(array $parameters, Schema $schema, ?Field $parent
9188
*/
9289
private function introspectSource(string $source, Schema $schema, Field $parent, array $path): void
9390
{
94-
$nestedParameters = $this->getParameters($source);
91+
$nestedParameters = $this->extractParameters($source);
9592
if (empty($nestedParameters)) {
9693
return;
9794
}
9895

99-
$this->currentPath[] = $source;
96+
$this->sources[] = $source;
10097
$this->introspect($nestedParameters, $schema, $parent, $path);
101-
array_pop($this->currentPath);
98+
array_pop($this->sources);
10299
}
103100

104101
private function wouldCauseCircularReference(string $source): bool
105102
{
106-
return in_array($source, $this->currentPath, true);
103+
return in_array($source, $this->sources, true);
107104
}
108105

109106
/**
110107
* @throws ReflectionException
111108
*/
112-
private function getParameters(string $source): array
109+
private function extractParameters(string $source): array
113110
{
114-
$cacheKey = sprintf("parameters:%s", $source);
115-
116-
$parameters = $this->cache->get($cacheKey);
117-
if ($parameters === null) {
118-
$parameters = $this->extractParameters($source);
119-
$this->cache->set($cacheKey, $parameters);
111+
$key = sprintf("parameters:%s", $source);
112+
$parameters = $this->cache->get($key);
113+
if (is_array($parameters)) {
114+
return $parameters;
120115
}
121-
122-
return $parameters;
123-
}
124-
125-
/**
126-
* @throws ReflectionException
127-
*/
128-
protected function extractParameters(string $source): array
129-
{
130-
return Target::createFrom($source)
116+
$parameters = Target::createFrom($source)
131117
->getReflectionParameters();
118+
return $this->cache->set($key, $parameters);
132119
}
133120
}

src/Core/Reflect/Resolver/Type/Contract/NamedTypeHandler.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,20 @@ private function handleNamedType(ReflectionParameter $parameter, Field $field):
3939
return NamedTypeResolution::NotResolved;
4040
}
4141

42-
protected function resolveBuiltinType(string $type): ?string
42+
protected function resolveBuiltinType(string $candidate): ?string
4343
{
44-
if (isset(self::BUILTIN_TYPE_MAPPING[$type])) {
45-
return self::BUILTIN_TYPE_MAPPING[$type];
44+
if (isset(self::BUILTIN_TYPE_MAPPING[$candidate])) {
45+
return self::BUILTIN_TYPE_MAPPING[$candidate];
4646
}
4747

4848
$regular = [
4949
'string',
5050
'array',
5151
];
52-
if (in_array($type, $regular, true)) {
53-
return $type;
52+
$type = null;
53+
if (in_array($candidate, $regular, true)) {
54+
$type = $candidate;
5455
}
55-
56-
return null;
56+
return $type;
5757
}
5858
}

src/Core/Reflect/Resolver/Type/DefineAttributeTypeHandler.php

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,14 @@ private function detecteAttributeType(ReflectionParameter $parameter): ?string
5151
$attribute = array_shift($attributes);
5252
assert($attribute instanceof ReflectionAttribute);
5353
$instance = $attribute->newInstance();
54+
assert($instance instanceof Define);
5455

55-
foreach ($instance->types as $type) {
56-
$rule = $this->extractRuleFromType($type);
57-
if ($rule === null) {
58-
continue;
59-
}
60-
return $rule;
56+
$types = $instance->types;
57+
if (count($types) !== 1) {
58+
return null;
6159
}
62-
63-
return null;
60+
$type = array_shift($types);
61+
return $this->extractRuleFromType($type);
6462
}
6563

6664
private function extractRuleFromType(mixed $type): ?string

src/Core/Reflect/Resolver/Type/EnumNamedTypeHandler.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
use ReflectionException;
1414
use ReflectionNamedType;
1515

16+
use function enum_exists;
17+
1618
class EnumNamedTypeHandler extends NamedTypeHandler
1719
{
1820
/**
@@ -21,7 +23,7 @@ class EnumNamedTypeHandler extends NamedTypeHandler
2123
protected function resolveNamedType(ReflectionNamedType $parameter, Field $field): NamedTypeResolution
2224
{
2325
$enumClassName = $parameter->getName();
24-
if (! is_subclass_of($enumClassName, BackedEnum::class)) {
26+
if (! enum_exists($enumClassName)) {
2527
return NamedTypeResolution::NotResolved;
2628
}
2729
return $this->resolveEnumType($enumClassName, $field);

src/Support/Reflective/Definition/TypeExtended.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ public function build(mixed $value, Closure $build): mixed;
1515
public function demolish(mixed $value, Closure $demolish): mixed;
1616

1717
public function fake(Faker $faker): ?Value;
18+
19+
public function rule(): ?string;
1820
}

tests/Core/Reflect/ReflectorTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,8 @@ public function testReflectReturnsCorrectRulesForPersonCommand(): void
264264
"dad" : [ "sometimes", "nullable", "array" ],
265265
"dad.name" : [ "sometimes", "string" ],
266266
"dad.mom" : [ "sometimes", "nullable", "array" ],
267-
"dad.dad" : [ "sometimes", "nullable", "array" ]
267+
"dad.dad" : [ "sometimes", "nullable", "array" ],
268+
"external_id" : [ "sometimes", "required" ]
268269
}';
269270
$expected = json_decode($json, true);
270271
$this->assertEquals($expected, $rules);
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Constructo\Test\Core\Reflect\Resolver\Type;
6+
7+
use Constructo\Core\Reflect\Resolver\Type\BuiltinNamedTypeHandler;
8+
use Constructo\Core\Reflect\Resolver\Type\Contract\NamedTypeResolution;
9+
use Constructo\Factory\DefaultSpecsFactory;
10+
use Constructo\Support\Metadata\Schema\Field;
11+
use Constructo\Support\Metadata\Schema\Field\Rules;
12+
use Constructo\Support\Metadata\Schema\Registry\Specs;
13+
use PHPUnit\Framework\TestCase;
14+
use ReflectionNamedType;
15+
use ReflectionParameter;
16+
use ReflectionUnionType;
17+
18+
final class BuiltinNamedTypeHandlerTest extends TestCase
19+
{
20+
private BuiltinNamedTypeHandler $handler;
21+
private Specs $specs;
22+
23+
protected function setUp(): void
24+
{
25+
$this->handler = new BuiltinNamedTypeHandler();
26+
27+
$specsData = [
28+
'string' => [],
29+
'integer' => [],
30+
'numeric' => [],
31+
'boolean' => [],
32+
'array' => [],
33+
];
34+
35+
$specsFactory = new DefaultSpecsFactory($specsData);
36+
$this->specs = $specsFactory->make();
37+
}
38+
39+
public function testResolveBuiltinStringType(): void
40+
{
41+
$parameter = $this->createParameterWithType('string', true);
42+
$field = new Field($this->specs, new Rules(), 'test');
43+
44+
$this->handler->resolve($parameter, $field);
45+
46+
$this->assertTrue($field->hasRule('string'));
47+
}
48+
49+
public function testResolveBuiltinIntType(): void
50+
{
51+
$parameter = $this->createParameterWithType('int', true);
52+
$field = new Field($this->specs, new Rules(), 'test');
53+
54+
$this->handler->resolve($parameter, $field);
55+
56+
$this->assertTrue($field->hasRule('integer'));
57+
}
58+
59+
public function testResolveBuiltinFloatType(): void
60+
{
61+
$parameter = $this->createParameterWithType('float', true);
62+
$field = new Field($this->specs, new Rules(), 'test');
63+
64+
$this->handler->resolve($parameter, $field);
65+
66+
$this->assertTrue($field->hasRule('numeric'));
67+
}
68+
69+
public function testResolveBuiltinBoolType(): void
70+
{
71+
$parameter = $this->createParameterWithType('bool', true);
72+
$field = new Field($this->specs, new Rules(), 'test');
73+
74+
$this->handler->resolve($parameter, $field);
75+
76+
$this->assertTrue($field->hasRule('boolean'));
77+
}
78+
79+
public function testResolveBuiltinArrayType(): void
80+
{
81+
$parameter = $this->createParameterWithType('array', true);
82+
$field = new Field($this->specs, new Rules(), 'test');
83+
84+
$this->handler->resolve($parameter, $field);
85+
86+
$this->assertTrue($field->hasRule('array'));
87+
}
88+
89+
public function testDoesNotResolveNonBuiltinType(): void
90+
{
91+
$parameter = $this->createParameterWithType('stdClass', false);
92+
$field = new Field($this->specs, new Rules(), 'test');
93+
94+
$this->handler->resolve($parameter, $field);
95+
96+
$this->assertFalse($field->hasRule('string'));
97+
$this->assertFalse($field->hasRule('array'));
98+
}
99+
100+
public function testDoesNotResolveUnionType(): void
101+
{
102+
$parameter = $this->createParameterWithUnionType();
103+
$field = new Field($this->specs, new Rules(), 'test');
104+
105+
$this->handler->resolve($parameter, $field);
106+
107+
$this->assertFalse($field->hasRule('string'));
108+
$this->assertFalse($field->hasRule('integer'));
109+
$this->assertFalse($field->hasRule('array'));
110+
}
111+
112+
private function createParameterWithType(string $typeName, bool $isBuiltin): ReflectionParameter
113+
{
114+
$parameter = $this->createMock(ReflectionParameter::class);
115+
$type = $this->createMock(ReflectionNamedType::class);
116+
117+
$type->method('getName')->willReturn($typeName);
118+
$type->method('isBuiltin')->willReturn($isBuiltin);
119+
120+
$parameter->method('getType')->willReturn($type);
121+
122+
return $parameter;
123+
}
124+
125+
private function createParameterWithUnionType(): ReflectionParameter
126+
{
127+
$parameter = $this->createMock(ReflectionParameter::class);
128+
$unionType = $this->createMock(ReflectionUnionType::class);
129+
130+
$parameter->method('getType')->willReturn($unionType);
131+
132+
return $parameter;
133+
}
134+
}

0 commit comments

Comments
 (0)