Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.44% covered (success)
94.44%
85 / 90
57.14% covered (warning)
57.14%
4 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
MetadataFactory
94.44% covered (success)
94.44%
85 / 90
57.14% covered (warning)
57.14%
4 / 7
47.38
0.00% covered (danger)
0.00%
0 / 1
 __construct
n/a
0 / 0
n/a
0 / 0
1
 getMetadataStrategy
96.97% covered (success)
96.97%
32 / 33
0.00% covered (danger)
0.00%
0 / 1
17
 getScalarForType
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 getMetadataStrategyForType
92.31% covered (success)
92.31%
36 / 39
0.00% covered (danger)
0.00%
0 / 1
19.16
 getMethodMetadata
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getCreationMetadata
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getModificationMetadata
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getResultMetadata
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2namespace Apie\Core\Metadata;
3
4use Apie\Core\ApieLib;
5use Apie\Core\Context\ApieContext;
6use Apie\Core\Context\MetadataFieldHashmap;
7use Apie\Core\Enums\ScalarType;
8use Apie\Core\Exceptions\InvalidTypeException;
9use Apie\Core\Metadata\Fields\ConstructorParameter;
10use Apie\Core\Metadata\Strategy\AliasStrategy;
11use Apie\Core\Metadata\Strategy\BuiltInPhpClassStrategy;
12use Apie\Core\Metadata\Strategy\CompositeValueObjectStrategy;
13use Apie\Core\Metadata\Strategy\CustomObjectStrategy;
14use Apie\Core\Metadata\Strategy\DatePeriodStrategy;
15use Apie\Core\Metadata\Strategy\DtoStrategy;
16use Apie\Core\Metadata\Strategy\EnumStrategy;
17use Apie\Core\Metadata\Strategy\ExceptionStrategy;
18use Apie\Core\Metadata\Strategy\FileUriStrategy;
19use Apie\Core\Metadata\Strategy\ItemHashmapStrategy;
20use Apie\Core\Metadata\Strategy\ItemListObjectStrategy;
21use Apie\Core\Metadata\Strategy\PolymorphicEntityStrategy;
22use Apie\Core\Metadata\Strategy\RegularObjectStrategy;
23use Apie\Core\Metadata\Strategy\ScalarStrategy;
24use Apie\Core\Metadata\Strategy\UnionTypeStrategy;
25use Apie\Core\Metadata\Strategy\UploadedFileStrategy;
26use Apie\Core\Metadata\Strategy\ValueObjectStrategy;
27use Apie\TypeConverter\ReflectionTypeFactory;
28use LogicException;
29use ReflectionClass;
30use ReflectionIntersectionType;
31use ReflectionMethod;
32use ReflectionNamedType;
33use ReflectionType;
34use ReflectionUnionType;
35
36final class MetadataFactory
37{
38    /**
39     * @codeCoverageIgnore
40     */
41    private function __construct()
42    {
43    }
44
45    /**
46     * @param ReflectionClass<covariant object> $class
47     */
48    public static function getMetadataStrategy(ReflectionClass $class): StrategyInterface
49    {
50        if (AliasStrategy::supports($class)) {
51            return new AliasStrategy($class->name);
52        }
53        if (BuiltInPhpClassStrategy::supports($class)) {
54            return new BuiltInPhpClassStrategy($class);
55        }
56        if (FileUriStrategy::supports($class)) {
57            return new FileUriStrategy($class);
58        }
59        if (ScalarStrategy::supports($class)) {
60            return new ScalarStrategy(ScalarType::STDCLASS);
61        }
62        if (EnumStrategy::supports($class)) {
63            return new EnumStrategy($class);
64        }
65        if (PolymorphicEntityStrategy::supports($class)) {
66            return new PolymorphicEntityStrategy($class);
67        }
68        if (CompositeValueObjectStrategy::supports($class)) {
69            return new CompositeValueObjectStrategy($class);
70        }
71        if (ItemListObjectStrategy::supports($class)) {
72            return new ItemListObjectStrategy($class);
73        }
74        if (ItemHashmapStrategy::supports($class)) {
75            return new ItemHashmapStrategy($class);
76        }
77        if (DtoStrategy::supports($class)) {
78            return new DtoStrategy($class);
79        }
80        if (ValueObjectStrategy::supports($class)) {
81            return new ValueObjectStrategy($class);
82        }
83        if (CustomObjectStrategy::supports($class)) {
84            return new CustomObjectStrategy($class);
85        }
86        if (ExceptionStrategy::supports($class)) {
87            return new ExceptionStrategy($class);
88        }
89        if (UploadedFileStrategy::supports($class)) {
90            return new UploadedFileStrategy($class);
91        }
92        if (DatePeriodStrategy::supports($class)) {
93            return new DatePeriodStrategy($class);
94        }
95        if (RegularObjectStrategy::supports($class)) {
96            return new RegularObjectStrategy($class);
97        }
98
99        throw new InvalidTypeException($class->name, 'Apie supported object');
100    }
101
102    public static function getScalarForType(?ReflectionType $typehint, bool $nullable = true): ScalarType
103    {
104        if ($typehint === null) {
105            return ScalarType::MIXED;
106        }
107        return self::getMetadataStrategyForType($typehint)
108            ->getResultMetadata(new ApieContext())
109            ->toScalarType($nullable);
110    }
111
112    public static function getMetadataStrategyForType(ReflectionType $typehint): StrategyInterface
113    {
114        if ($typehint instanceof ReflectionUnionType) {
115            $metadata = [];
116            foreach ($typehint->getTypes() as $type) {
117                $metadata[] = self::getMetadataStrategyForType($type)->getCreationMetadata(new ApieContext());
118            }
119            return new UnionTypeStrategy(...$metadata);
120        }
121        if ($typehint instanceof ReflectionIntersectionType) {
122            throw new LogicException('Intersection typehints are not supported yet');
123        }
124        assert($typehint instanceof ReflectionNamedType);
125        if (ApieLib::hasAlias($typehint->getName())) {
126            $strategy = self::getMetadataStrategyForType(
127                ReflectionTypeFactory::createReflectionType(
128                    ApieLib::getAlias($typehint->getName())
129                )
130            );
131            return $typehint->allowsNull()
132                ? new UnionTypeStrategy($strategy, new ScalarMetadata(ScalarType::NULLVALUE))
133                : $strategy;
134        }
135        if ($typehint->isBuiltin()) {
136            if ($typehint->getName() === 'null') {
137                return new ScalarStrategy(ScalarType::NULLVALUE);
138            }
139            if ($typehint->getName() === 'mixed') {
140                return new ScalarStrategy(ScalarType::MIXED);
141            }
142            $strategy = new ScalarStrategy(
143                match ($typehint->getName()) {
144                    'string' => ScalarType::STRING,
145                    'float' => ScalarType::FLOAT,
146                    'int' => ScalarType::INTEGER,
147                    'array' => ScalarType::ARRAY,
148                    'mixed' => ScalarType::MIXED,
149                    'bool' => ScalarType::BOOLEAN,
150                    'true' => ScalarType::BOOLEAN,
151                    'false' => ScalarType::BOOLEAN,
152                    default => throw new InvalidTypeException($typehint->getName(), 'string|float|int|null|array|mixed|bool')
153                }
154            );
155        } else {
156            $strategy = self::getMetadataStrategy(new ReflectionClass($typehint->getName()));
157        }
158        if ($typehint->allowsNull()) {
159            return new UnionTypeStrategy($strategy, new ScalarMetadata(ScalarType::NULLVALUE));
160        }
161
162        return $strategy;
163    }
164
165    public static function getMethodMetadata(ReflectionMethod $method, ApieContext $context): MetadataInterface
166    {
167        $fields = [];
168        foreach ($method->getParameters() as $parameter) {
169            $fields[$parameter->name] = new ConstructorParameter($parameter);
170        }
171        return new CompositeMetadata(new MetadataFieldHashmap($fields), $method->getDeclaringClass());
172    }
173
174    /**
175     * @param ReflectionClass<covariant object>|ReflectionType $typehint
176     */
177    public static function getCreationMetadata(ReflectionClass|ReflectionType $typehint, ApieContext $context): MetadataInterface
178    {
179        if ($typehint instanceof ReflectionType) {
180            return self::getMetadataStrategyForType($typehint)->getCreationMetadata($context);
181        }
182        return self::getMetadataStrategy($typehint)->getCreationMetadata($context);
183    }
184
185    /**
186     * @param ReflectionClass<covariant object>|ReflectionType $typehint
187     */
188    public static function getModificationMetadata(ReflectionClass|ReflectionType $typehint, ApieContext $context): MetadataInterface
189    {
190        if ($typehint instanceof ReflectionType) {
191            return self::getMetadataStrategyForType($typehint)->getModificationMetadata($context);
192        }
193        return self::getMetadataStrategy($typehint)->getModificationMetadata($context);
194    }
195
196    /**
197     * @param ReflectionClass<covariant object>|ReflectionType $typehint
198     */
199    public static function getResultMetadata(ReflectionClass|ReflectionType $typehint, ApieContext $context): MetadataInterface
200    {
201        if ($typehint instanceof ReflectionType) {
202            return self::getMetadataStrategyForType($typehint)->getResultMetadata($context);
203        }
204        return self::getMetadataStrategy($typehint)->getResultMetadata($context);
205    }
206}