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