Passed
Push — 3.x ( 080893...4e5cb3 )
by Aleksei
09:48
created

MergeIndexes::run()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 4
nop 1
dl 0
loc 21
ccs 12
cts 12
cp 1
crap 5
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cycle\Annotated;
6
7
use Cycle\Annotated\Annotation\Table;
8
use Cycle\Annotated\Exception\AnnotationException;
9
use Cycle\Schema\Definition\Entity;
10
use Cycle\Schema\GeneratorInterface;
11
use Cycle\Schema\Registry;
12
use Doctrine\Common\Annotations\Reader as DoctrineReader;
13
use Spiral\Attributes\ReaderInterface;
14
use Cycle\Database\Schema\AbstractIndex;
15
use Cycle\Database\Schema\AbstractTable;
16
17
/**
18
 * Copy index definitions from Mapper/Repository to Entity.
19
 */
20
final class MergeIndexes implements GeneratorInterface
21
{
22
    private ReaderInterface $reader;
23
24 347
    public function __construct(DoctrineReader|ReaderInterface $reader = null)
25
    {
26 347
        $this->reader = ReaderFactory::create($reader);
27 347
    }
28
29 323
    public function run(Registry $registry): Registry
30
    {
31 323
        foreach ($registry as $e) {
32 323
            if ($e->getClass() === null || !$registry->hasTable($e)) {
33 24
                continue;
34
            }
35
36 323
            $this->render($registry->getTableSchema($e), $e, $e->getClass());
37
38
            // copy table declarations from related classes
39 317
            $this->render($registry->getTableSchema($e), $e, $e->getMapper());
40 317
            $this->render($registry->getTableSchema($e), $e, $e->getRepository());
41 317
            $this->render($registry->getTableSchema($e), $e, $e->getSource());
42 317
            $this->render($registry->getTableSchema($e), $e, $e->getScope());
43
44 317
            foreach ($registry->getChildren($e) as $child) {
45 181
                $this->render($registry->getTableSchema($e), $e, $child->getClass());
46
            }
47
        }
48
49 317
        return $registry;
50
    }
51
52
    /**
53
     * @param Table\Index[] $indexes
54
     */
55 171
    public function renderIndexes(AbstractTable $table, Entity $entity, array $indexes): void
56
    {
57 171
        foreach ($indexes as $index) {
58 171
            if ($index->getColumns() === []) {
59 6
                throw new AnnotationException(
60 6
                    "Invalid index definition for `{$entity->getRole()}`. Column list can't be empty."
61
                );
62
            }
63
64 165
            $columns = $this->mapColumns($entity, $index->getColumns());
65
66 165
            if ($index instanceof Table\PrimaryKey) {
67 6
                $table->setPrimaryKeys($columns);
68 6
                $entity->setPrimaryColumns($index->getColumns());
69
            } else {
70 159
                $indexSchema = $table->index($columns);
71 159
                $indexSchema->unique($index->isUnique());
72
73 159
                if ($index->getIndex() !== null) {
74 133
                    $indexSchema->setName($index->getIndex());
75
                }
76
            }
77
        }
78 165
    }
79
80
    /**
81
     * @param class-string|null   $class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string|null at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string|null.
Loading history...
82
     */
83 323
    private function render(AbstractTable $table, Entity $entity, ?string $class): void
84
    {
85 323
        if ($class === null) {
86 317
            return;
87
        }
88
89
        try {
90 323
            $reflection = new \ReflectionClass($class);
91
        } catch (\ReflectionException $e) {
92
            return;
93
        }
94
95
        try {
96
            /** @var Table|null $tableMeta */
97 323
            $tableMeta = $this->reader->firstClassMetadata($reflection, Table::class);
98
            /** @var Table\PrimaryKey|null $primaryKey */
99 323
            $primaryKey = $tableMeta
100 29
                ? $tableMeta->getPrimary()
101 310
                : $this->reader->firstClassMetadata($reflection, Table\PrimaryKey::class);
102
103
            /** @var Table\Index[] $indexMeta */
104 323
            $indexMeta = $this->reader->getClassMetadata($reflection, Table\Index::class);
105
        } catch (\Exception $e) {
106
            throw new AnnotationException($e->getMessage(), $e->getCode(), $e);
107
        }
108
109 323
        $indexes = $tableMeta === null ? [] : $tableMeta->getIndexes();
110
111 323
        if ($primaryKey !== null) {
112 12
            $indexes[] = $primaryKey;
113
        }
114
115 323
        foreach ($indexMeta as $index) {
116 146
            $indexes[] = $index;
117
        }
118
119 323
        if ($indexes === []) {
120 301
            return;
121
        }
122
123 171
        $this->renderIndexes($table, $entity, $indexes);
124 165
    }
125
126
    /**
127
     * @return string[]
128
     */
129 165
    protected function mapColumns(Entity $entity, array $columns): array
130
    {
131 165
        $result = [];
132
133 165
        foreach ($columns as $expression) {
134 165
            [$column, $order] = AbstractIndex::parseColumn($expression);
135
136 165
            $mappedName = $entity->getFields()->has($column)
137 165
                ? $entity->getFields()->get($column)->getColumn()
138 127
                : $column;
139
140
            // Re-construct `column ORDER` with mapped column name
141 165
            $result[] = $order ? "$mappedName $order" : $mappedName;
142
        }
143
144 165
        return $result;
145
    }
146
}
147