MergeIndexes::run()   B
last analyzed

Complexity

Conditions 7
Paths 5

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 7

Importance

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