Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.33% covered (warning)
83.33%
60 / 72
42.86% covered (danger)
42.86%
3 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
EntityUtils
83.33% covered (warning)
83.33%
60 / 72
42.86% covered (danger)
42.86%
3 / 7
38.04
0.00% covered (danger)
0.00%
0 / 1
 __construct
n/a
0 / 0
n/a
0 / 0
1
 isEntity
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
4.59
 isNonPolymorphicEntity
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 isPolymorphicEntity
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
4.59
 getDiscriminatorValues
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
7
 getDiscriminatorClasses
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 getContextParameters
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 findClass
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
4.01
1<?php
2namespace Apie\Core\Utils;
3
4use Apie\Core\Attributes\Context;
5use Apie\Core\Entities\EntityInterface;
6use Apie\Core\Entities\PolymorphicEntityInterface;
7use Apie\Core\Exceptions\IndexNotFoundException;
8use Apie\Core\Lists\ReflectionClassList;
9use Apie\Core\Other\DiscriminatorMapping;
10use ReflectionClass;
11use ReflectionException;
12use ReflectionMethod;
13use ReflectionParameter;
14use ReflectionProperty;
15use ReflectionType;
16
17final class EntityUtils
18{
19    /**
20     * @codeCoverageIgnore
21     */
22    private function __construct()
23    {
24    }
25
26    /**
27     * @param string|ReflectionClass<object>|ReflectionProperty|ReflectionType|ReflectionMethod $input
28     */
29    public static function isEntity(string|ReflectionClass|ReflectionProperty|ReflectionType|ReflectionMethod $input): bool
30    {
31        try {
32            $class = ConverterUtils::toReflectionClass($input);
33        } catch (ReflectionException) {
34            return false;
35        }
36        return $class !== null
37            && $class->implementsInterface(EntityInterface::class)
38            && !$class->isInterface();
39    }
40
41    /**
42     * @param string|ReflectionClass<object>|ReflectionProperty|ReflectionType|ReflectionMethod $input
43     */
44    public static function isNonPolymorphicEntity(string|ReflectionClass|ReflectionProperty|ReflectionType|ReflectionMethod $input): bool
45    {
46        try {
47            $class = ConverterUtils::toReflectionClass($input);
48        } catch (ReflectionException) {
49            return false;
50        }
51        return $class !== null
52            && !$class->implementsInterface(PolymorphicEntityInterface::class)
53            && $class->implementsInterface(EntityInterface::class)
54            && !$class->isInterface();
55    }
56
57    /**
58     * @param string|ReflectionClass<object>|ReflectionProperty|ReflectionType|ReflectionMethod $input
59     */
60    public static function isPolymorphicEntity(string|ReflectionClass|ReflectionProperty|ReflectionType|ReflectionMethod $input): bool
61    {
62        try {
63            $class = ConverterUtils::toReflectionClass($input);
64        } catch (ReflectionException) {
65            return false;
66        }
67        return $class !== null
68            && $class->implementsInterface(PolymorphicEntityInterface::class)
69            && !$class->isInterface();
70    }
71
72    /**
73     * @param PolymorphicEntityInterface|ReflectionClass<PolymorphicEntityInterface> $entity
74     * @param ReflectionClass<PolymorphicEntityInterface>|null $base
75     * @return array<string, string>
76     */
77    public static function getDiscriminatorValues(PolymorphicEntityInterface|ReflectionClass $entity, ?ReflectionClass $base = null): array
78    {
79        $currentRefl = $entity instanceof ReflectionClass ? $entity : new ReflectionClass($entity);
80        if (!$base) {
81            $refl = $currentRefl;
82            while ($refl) {
83                if ($refl->getMethod('getDiscriminatorMapping')->getDeclaringClass()->name === $refl->name) {
84                    $base = $refl;
85                }
86                $refl = $refl->getParentClass();
87            }
88        }
89        assert($base !== null);
90        $entityClass = $currentRefl->name;
91        $result = [];
92        $current = $base;
93        $last = null;
94        while ($current->getMethod('getDiscriminatorMapping')->getDeclaringClass()->name !== $last && $current->name !== $entityClass) {
95            /** @var DiscriminatorMapping $mapping */
96            $mapping = $current->getMethod('getDiscriminatorMapping')->invoke(null);
97            $config = $mapping->getConfigForClass($entity);
98            $result[$mapping->getPropertyName()] = $config->getDiscriminator();
99            $last = $current->getMethod('getDiscriminatorMapping')->getDeclaringClass()->name;
100            $current = new ReflectionClass($config->getClassName());
101        }
102        return $result;
103    }
104
105    /**
106     * @param ReflectionClass<PolymorphicEntityInterface> $base
107     */
108    public static function getDiscriminatorClasses(ReflectionClass $base): ReflectionClassList
109    {
110        $list = [];
111        /** @var DiscriminatorMapping $mapping */
112        $mapping = $base->getMethod('getDiscriminatorMapping')->invoke(null);
113        foreach ($mapping->getConfigs() as $config) {
114            $refl = new ReflectionClass($config->getClassName());
115            if ($refl->isInstantiable()) {
116                $list[] = $refl;
117            } else {
118                $list = [...$list, ...self::getDiscriminatorClasses($refl)];
119            }
120        }
121        return new ReflectionClassList($list);
122    }
123
124    /**
125     * Returns context related parameters of a method. This depends on the method type.
126     * A context related parameter gets his value not from user input, but from a
127     * Apie context variable.
128     *
129     * @return ReflectionParameter[]
130     */
131    public static function getContextParameters(ReflectionMethod $method): array
132    {
133        // getters: all arguments are context related
134        if (preg_match('/^(get|is|has).+/i', $method->name)) {
135            return $method->getParameters();
136        }
137        // setters: all arguments, except the last one
138        if (preg_match('/^set.+/i', $method->name)) {
139            $parameters = $method->getParameters();
140            array_pop($parameters);
141            return $parameters;
142        }
143        // other methods: constructor, actions. Only with #[Context] attributes.
144        $parameters = [];
145        foreach ($method->getParameters() as $parameter) {
146            $attributes = $parameter->getAttributes(Context::class);
147            if (!empty($attributes)) {
148                $parameters[] = $parameter;
149            }
150        }
151        return $parameters;
152    }
153
154    /**
155     * Converts discriminator mappings from an array into the linked class.
156     *
157     * @template T of PolymorphicEntityInterface
158     * @param array<string, string> $discriminators
159     * @param ReflectionClass<T> $base
160     * @return ReflectionClass<T>
161     */
162    public static function findClass(array $discriminators, ReflectionClass $base): ReflectionClass
163    {
164        /** @var DiscriminatorMapping $mapping */
165        $mapping = $base->getMethod('getDiscriminatorMapping')->invoke(null);
166        $value = $discriminators[$mapping->getPropertyName()] ?? null;
167        if (!isset($value)) {
168            throw new IndexNotFoundException($mapping->getPropertyName());
169        }
170        $current = new ReflectionClass($mapping->getClassNameFromDiscriminator($value));
171        $last = $base->name;
172        while ($current->getMethod('getDiscriminatorMapping')->getDeclaringClass()->name !== $last) {
173            $mapping = $current->getMethod('getDiscriminatorMapping')->invoke(null);
174            $value = $discriminators[$mapping->getPropertyName()] ?? null;
175            if (!isset($value)) {
176                throw new IndexNotFoundException($mapping->getPropertyName());
177            }
178            $last = $current->getMethod('getDiscriminatorMapping')->getDeclaringClass()->name;
179            $current = new ReflectionClass($mapping->getClassNameFromDiscriminator($value));
180        }
181
182        return $current;
183    }
184}