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