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