Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.10% covered (warning)
83.10%
59 / 71
42.86% covered (danger)
42.86%
3 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
EntityUtils
83.10% covered (warning)
83.10%
59 / 71
42.86% covered (danger)
42.86%
3 / 7
36.94
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%
18 / 18
100.00% covered (success)
100.00%
1 / 1
6
 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 ReflectionClass<PolymorphicEntityInterface>|null $base
74     * @return array<string, string>
75     */
76    public static function getDiscriminatorValues(PolymorphicEntityInterface $entity, ?ReflectionClass $base = null): array
77    {
78        if (!$base) {
79            $refl = new ReflectionClass($entity);
80            while ($refl) {
81                if ($refl->getMethod('getDiscriminatorMapping')->getDeclaringClass()->name === $refl->name) {
82                    $base = $refl;
83                }
84                $refl = $refl->getParentClass();
85            }
86        }
87        assert($base !== null);
88        $entityClass = get_class($entity);
89        $result = [];
90        $current = $base;
91        $last = null;
92        while ($current->getMethod('getDiscriminatorMapping')->getDeclaringClass()->name !== $last && $current->name !== $entityClass) {
93            /** @var DiscriminatorMapping $mapping */
94            $mapping = $current->getMethod('getDiscriminatorMapping')->invoke(null);
95            $config = $mapping->getConfigForClass($entity);
96            $result[$mapping->getPropertyName()] = $config->getDiscriminator();
97            $last = $current->getMethod('getDiscriminatorMapping')->getDeclaringClass()->name;
98            $current = new ReflectionClass($config->getClassName());
99        }
100        return $result;
101    }
102
103    /**
104     * @param ReflectionClass<PolymorphicEntityInterface> $base
105     */
106    public static function getDiscriminatorClasses(ReflectionClass $base): ReflectionClassList
107    {
108        $list = [];
109        /** @var DiscriminatorMapping $mapping */
110        $mapping = $base->getMethod('getDiscriminatorMapping')->invoke(null);
111        foreach ($mapping->getConfigs() as $config) {
112            $refl = new ReflectionClass($config->getClassName());
113            if ($refl->isInstantiable()) {
114                $list[] = $refl;
115            } else {
116                $list = [...$list, ...self::getDiscriminatorClasses($refl)];
117            }
118        }
119        return new ReflectionClassList($list);
120    }
121
122    /**
123     * Returns context related parameters of a method. This depends on the method type.
124     * A context related parameter gets his value not from user input, but from a
125     * Apie context variable.
126     *
127     * @return ReflectionParameter[]
128     */
129    public static function getContextParameters(ReflectionMethod $method): array
130    {
131        // getters: all arguments are context related
132        if (preg_match('/^(get|is|has).+/i', $method->name)) {
133            return $method->getParameters();
134        }
135        // setters: all arguments, except the last one
136        if (preg_match('/^set.+/i', $method->name)) {
137            $parameters = $method->getParameters();
138            array_pop($parameters);
139            return $parameters;
140        }
141        // other methods: constructor, actions. Only with #[Context] attributes.
142        $parameters = [];
143        foreach ($method->getParameters() as $parameter) {
144            $attributes = $parameter->getAttributes(Context::class);
145            if (!empty($attributes)) {
146                $parameters[] = $parameter;
147            }
148        }
149        return $parameters;
150    }
151
152    /**
153     * Converts discriminator mappings from an array into the linked class.
154     *
155     * @template T of PolymorphicEntityInterface
156     * @param array<string, string> $discriminators
157     * @param ReflectionClass<T> $base
158     * @return ReflectionClass<T>
159     */
160    public static function findClass(array $discriminators, ReflectionClass $base): ReflectionClass
161    {
162        /** @var DiscriminatorMapping $mapping */
163        $mapping = $base->getMethod('getDiscriminatorMapping')->invoke(null);
164        $value = $discriminators[$mapping->getPropertyName()] ?? null;
165        if (!isset($value)) {
166            throw new IndexNotFoundException($mapping->getPropertyName());
167        }
168        $current = new ReflectionClass($mapping->getClassNameFromDiscriminator($value));
169        $last = $base->name;
170        while ($current->getMethod('getDiscriminatorMapping')->getDeclaringClass()->name !== $last) {
171            $mapping = $current->getMethod('getDiscriminatorMapping')->invoke(null);
172            $value = $discriminators[$mapping->getPropertyName()] ?? null;
173            if (!isset($value)) {
174                throw new IndexNotFoundException($mapping->getPropertyName());
175            }
176            $last = $current->getMethod('getDiscriminatorMapping')->getDeclaringClass()->name;
177            $current = new ReflectionClass($mapping->getClassNameFromDiscriminator($value));
178        }
179
180        return $current;
181    }
182}