Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.61% covered (success)
96.61%
57 / 59
75.00% covered (warning)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ColumnSelector
96.61% covered (success)
96.61%
57 / 59
75.00% covered (warning)
75.00%
6 / 8
23
0.00% covered (danger)
0.00%
0 / 1
 getColumns
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 getInternalColumns
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
4.01
 iterateOverDiscriminatorMappings
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 iterateOverChildClasses
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 getPolymorphicColumns
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 getFromSingleClass
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 sortCallback
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 rating
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2namespace Apie\HtmlBuilders\Columns;
3
4use Apie\Core\Attributes\HideIdOnOverview;
5use Apie\Core\Context\ApieContext;
6use Apie\Core\Entities\PolymorphicEntityInterface;
7use Apie\Core\Other\DiscriminatorMapping;
8use Generator;
9use ReflectionClass;
10
11final class ColumnSelector
12{
13    /**
14     * @param ReflectionClass<object> $class
15     * @return array<int, string>
16     */
17    public function getColumns(ReflectionClass $class, ApieContext $context): array
18    {
19        $done = [];
20        $columns = $this->getInternalColumns($class, $context, $done);
21        if ($class->getAttributes(HideIdOnOverview::class)) {
22            $columns = array_values(array_filter($columns, function ($value) { return $value !== 'id'; }));
23        }
24        return $columns;
25    }
26
27    /**
28     * @param ReflectionClass<object> $class
29     * @param array<string, bool> $internalDone
30     * @return array<int, string>
31     */
32    public function getInternalColumns(ReflectionClass $class, ApieContext $context, array& $internalDone): array
33    {
34        if (isset($internalDone[$class->name])) {
35            return [];
36        }
37        $internalDone[$class->name] = true;
38        $columns = $this->getFromSingleClass($class, $context);
39        usort($columns, [$this, 'sortCallback']);
40        if ($class->implementsInterface(PolymorphicEntityInterface::class)) {
41            $done = [];
42            $discriminatorColumns = $this->getPolymorphicColumns($class, $done);
43            $columns = [...array_slice($columns, 0, 1), ...$discriminatorColumns, ...array_slice($columns, 1)];
44            $done = [];
45            foreach ($this->iterateOverChildClasses($class, $done) as $childClass) {
46                $columns = [...$columns, ...$this->getInternalColumns($childClass, $context, $internalDone)];
47            }
48            $columns = array_values(array_unique($columns));
49        }
50
51        return $columns;
52    }
53
54    /**
55     * @param ReflectionClass<object> $class
56     * @return Generator<int, DiscriminatorMapping>
57     */
58    private function iterateOverDiscriminatorMappings(ReflectionClass $class): Generator
59    {
60        while ($class) {
61            $method = $class->getMethod('getDiscriminatorMapping');
62            if ($method->getDeclaringClass()->name === $class->name && !$method->isAbstract()) {
63                yield $method->invoke(null);
64            }
65            $class = $class->getParentClass();
66        }
67    }
68
69    /**
70     * @param ReflectionClass<object> $class
71     * @param array<int|string, string> $done
72     * @param-out array<int|string, string> $done
73     * @return Generator<int, ReflectionClass<PolymorphicEntityInterface>>
74     */
75    private function iterateOverChildClasses(ReflectionClass $class, array& $done): Generator
76    {
77        $method = $class->getMethod('getDiscriminatorMapping');
78        $declaredClass = $method->getDeclaringClass()->name;
79        if (in_array($declaredClass, $done) || $class->name !== $declaredClass) {
80            return;
81        }
82        $done[] = $declaredClass;
83        $mapping = $method->invoke(null);
84        foreach ($mapping->getConfigs() as $config) {
85            $refl = new ReflectionClass($config->getClassName());
86            yield $refl;
87            yield from $this->iterateOverChildClasses($refl, $done);
88        }
89    }
90
91    /**
92     * @param ReflectionClass<object> $class
93     * @param array<string, bool> $done
94     * @return array<int, string>
95     */
96    public function getPolymorphicColumns(ReflectionClass $class, array& $done): array
97    {
98        if (isset($done[$class->name])) {
99            return [];
100        }
101        $done[$class->name] = true;
102        $list = [];
103        foreach ($this->iterateOverDiscriminatorMappings($class) as $mapping) {
104            $list[] = $mapping->getPropertyName();
105            foreach ($mapping->getConfigs() as $config) {
106                array_push($list, ...$this->getPolymorphicColumns(new ReflectionClass($config->getClassName()), $done));
107            }
108        }
109        
110        return $list;
111    }
112
113    /**
114     * @param ReflectionClass<object> $class
115     * @return array<int, string>
116     */
117    private function getFromSingleClass(ReflectionClass $class, ApieContext $context): array
118    {
119        $columns = array_keys($context->getApplicableGetters($class)->toArray());
120
121        return $columns;
122    }
123
124    private function sortCallback(string $input1, string $input2): int
125    {
126        $rating1 = $this->rating($input1);
127        $rating2 = $this->rating($input2);
128
129        if ($rating1 === $rating2) {
130            return $input1 <=> $input2;
131        }
132        return $rating1 <=> $rating2;
133    }
134
135    private function rating(string $input): int
136    {
137        if (stripos($input, 'status') !== false) {
138            return -150;
139        }
140        $ratings = [
141            'id' => -300,
142            'name' => -250,
143            'email' => -200,
144            'description' => 100,
145        ];
146        return $ratings[$input] ?? 0;
147    }
148}