Passed
Pull Request — 3.x (#43)
by Aleksei
14:43
created

MergeIndexes::run()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
eloc 15
c 0
b 0
f 0
nc 5
nop 1
dl 0
loc 26
ccs 12
cts 12
cp 1
crap 7
rs 8.8333
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 718
    public function __construct(DoctrineReader|ReaderInterface $reader = null)
25
    {
26 718
        $this->reader = ReaderFactory::create($reader);
27 718
    }
28
29 670
    public function run(Registry $registry): Registry
30
    {
31 670
        foreach ($registry as $e) {
32 670
            if ($e->getClass() === null || !$registry->hasTable($e)) {
33 72
                continue;
34
            }
35
36 670
            $this->render($registry->getTableSchema($e), $e, $e->getClass());
37
38
            // copy table declarations from related classes
39 658
            $this->render($registry->getTableSchema($e), $e, $e->getMapper());
40 658
            $this->render($registry->getTableSchema($e), $e, $e->getRepository());
41 658
            $this->render($registry->getTableSchema($e), $e, $e->getSource());
42 658
            $this->render($registry->getTableSchema($e), $e, $e->getScope());
43
44 658
            foreach ($registry->getChildren($e) as $child) {
45 362
                if (!$child->isChildOfSingleTableInheritance() && $registry->hasEntity($child->getRole())) {
46
                    $tableSchema = $registry->getTableSchema($child);
47
                } else {
48
                    $tableSchema = $registry->getTableSchema($e);
49 658
                }
50
                $this->render($tableSchema, $e, $child->getClass());
51
            }
52
        }
53
54
        return $registry;
55 358
    }
56
57 358
    /**
58 358
     * @param Table\Index[] $indexes
59 12
     */
60 12
    public function renderIndexes(AbstractTable $table, Entity $entity, array $indexes): void
61
    {
62
        foreach ($indexes as $index) {
63
            if ($index->getColumns() === []) {
64 346
                throw new AnnotationException(
65
                    "Invalid index definition for `{$entity->getRole()}`. Column list can't be empty."
66 346
                );
67 12
            }
68 12
69
            $columns = $this->mapColumns($entity, $index->getColumns());
70 334
71 334
            if ($index instanceof Table\PrimaryKey) {
72
                $table->setPrimaryKeys($columns);
73 334
                $entity->setPrimaryColumns($index->getColumns());
74 266
            } else {
75
                $indexSchema = $table->index($columns);
76
                $indexSchema->unique($index->isUnique());
77
78 346
                if ($index->getIndex() !== null) {
79
                    $indexSchema->setName($index->getIndex());
80
                }
81
            }
82
        }
83 670
    }
84
85 670
    /**
86 658
     * @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...
87
     */
88
    private function render(AbstractTable $table, Entity $entity, ?string $class): void
89
    {
90 670
        if ($class === null) {
91
            return;
92
        }
93
94
        try {
95
            $reflection = new \ReflectionClass($class);
96
        } catch (\ReflectionException $e) {
97 670
            return;
98
        }
99 670
100 74
        try {
101 644
            /** @var Table|null $tableMeta */
102
            $tableMeta = $this->reader->firstClassMetadata($reflection, Table::class);
103
            /** @var Table\PrimaryKey|null $primaryKey */
104 670
            $primaryKey = $tableMeta
105
                ? $tableMeta->getPrimary()
106
                : $this->reader->firstClassMetadata($reflection, Table\PrimaryKey::class);
107
108
            /** @var Table\Index[] $indexMeta */
109 670
            $indexMeta = $this->reader->getClassMetadata($reflection, Table\Index::class);
110
        } catch (\Exception $e) {
111 670
            throw new AnnotationException($e->getMessage(), $e->getCode(), $e);
112 24
        }
113
114
        $indexes = $tableMeta === null ? [] : $tableMeta->getIndexes();
115 670
116 292
        if ($primaryKey !== null) {
117
            $indexes[] = $primaryKey;
118
        }
119 670
120 626
        foreach ($indexMeta as $index) {
121
            $indexes[] = $index;
122
        }
123 358
124 346
        if ($indexes === []) {
125
            return;
126
        }
127
128
        $this->renderIndexes($table, $entity, $indexes);
129 346
    }
130
131 346
    /**
132
     * @return string[]
133 346
     */
134 346
    protected function mapColumns(Entity $entity, array $columns): array
135
    {
136 346
        $result = [];
137 346
138 254
        foreach ($columns as $expression) {
139
            [$column, $order] = AbstractIndex::parseColumn($expression);
140
141 346
            $mappedName = $entity->getFields()->has($column)
142
                ? $entity->getFields()->get($column)->getColumn()
143
                : $column;
144 346
145
            // Re-construct `column ORDER` with mapped column name
146
            $result[] = $order ? "$mappedName $order" : $mappedName;
147
        }
148
149
        return $result;
150
    }
151
}
152