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