Passed
Pull Request — master (#171)
by ARP
03:29
created

TDBMSchemaAnalyzer::generateLockFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
declare(strict_types=1);
3
4
namespace TheCodingMachine\TDBM;
5
6
use BrainDiminished\SchemaVersionControl\SchemaVersionControlService;
7
use Doctrine\Common\Cache\Cache;
8
use Doctrine\DBAL\Connection;
9
use Doctrine\DBAL\Schema\Column;
10
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
11
use Doctrine\DBAL\Schema\Schema;
12
use Doctrine\DBAL\Schema\Table;
13
use Doctrine\DBAL\Types\DateType;
14
use Doctrine\DBAL\Types\Type;
15
use Mouf\Database\SchemaAnalyzer\SchemaAnalyzer;
16
use TheCodingMachine\TDBM\Utils\ImmutableCaster;
17
use TheCodingMachine\TDBM\Utils\RootProjectLocator;
18
19
/**
20
 * This class is used to analyze the schema and return valuable information / hints.
21
 */
22
class TDBMSchemaAnalyzer
23
{
24
    const schemaFileName =  'tdbm.lock.yml';
25
26
    private $connection;
27
28
    /**
29
     * @var Schema
30
     */
31
    private $schema;
32
33
    /**
34
     * @var string
35
     */
36
    private $cachePrefix;
37
38
    /**
39
     * @var Cache
40
     */
41
    private $cache;
42
43
    /**
44
     * @var SchemaAnalyzer
45
     */
46
    private $schemaAnalyzer;
47
48
    /**
49
     * @var SchemaVersionControlService
50
     */
51
    private $schemaVersionControlService;
52
53
    /**
54
     * @param Connection     $connection     The DBAL DB connection to use
55
     * @param Cache          $cache          A cache service to be used
56
     * @param SchemaAnalyzer $schemaAnalyzer The schema analyzer that will be used to find shortest paths...
57
     *                                       Will be automatically created if not passed
58
     */
59
    public function __construct(Connection $connection, Cache $cache, SchemaAnalyzer $schemaAnalyzer)
60
    {
61
        $this->connection = $connection;
62
        $this->cache = $cache;
63
        $this->schemaAnalyzer = $schemaAnalyzer;
64
        $this->schemaVersionControlService = new SchemaVersionControlService($this->connection, self::getLockFilePath());
65
    }
66
67
    /**
68
     * Returns a unique ID for the current connection. Useful for namespacing cache entries in the current connection.
69
     *
70
     * @return string
71
     */
72
    public function getCachePrefix(): string
73
    {
74
        if ($this->cachePrefix === null) {
75
            $this->cachePrefix = hash('md4', $this->connection->getHost().'-'.$this->connection->getPort().'-'.$this->connection->getDatabase().'-'.$this->connection->getDriver()->getName());
76
        }
77
78
        return $this->cachePrefix;
79
    }
80
81
    public static function getLockFilePath(): string
82
    {
83
        return RootProjectLocator::getRootLocationPath().self::schemaFileName;
84
    }
85
86
    /**
87
     * Returns the (cached) schema.
88
     */
89
    public function getSchema(bool $ignoreCache = false): Schema
90
    {
91
        if ($this->schema === null) {
92
            $cacheKey = $this->getCachePrefix().'_immutable_schema';
93
            if (!$ignoreCache && $this->cache->contains($cacheKey)) {
94
                $this->schema = $this->cache->fetch($cacheKey);
95
            } elseif (!file_exists(self::getLockFilePath())) {
96
                throw new TDBMException('No tdbm lock file found. Please regenerate DAOs and Beans.');
97
            } else {
98
                $this->schema = $this->schemaVersionControlService->loadSchemaFile();
99
                ImmutableCaster::castSchemaToImmutable($this->schema);
100
                $this->cache->save($cacheKey, $this->schema);
101
            }
102
        }
103
104
        return $this->schema;
105
    }
106
107
    public function generateLockFile(): void
108
    {
109
        $this->schemaVersionControlService->dumpSchema();
110
    }
111
112
    /**
113
     * Returns the list of pivot tables linked to table $tableName.
114
     *
115
     * @param string $tableName
116
     *
117
     * @return string[]
118
     */
119
    public function getPivotTableLinkedToTable(string $tableName): array
120
    {
121
        $cacheKey = $this->getCachePrefix().'_pivottables_link_'.$tableName;
122
        if ($this->cache->contains($cacheKey)) {
123
            return $this->cache->fetch($cacheKey);
124
        }
125
126
        $pivotTables = [];
127
128
        $junctionTables = $this->schemaAnalyzer->detectJunctionTables(true);
129
        foreach ($junctionTables as $table) {
130
            $fks = $table->getForeignKeys();
131
            foreach ($fks as $fk) {
132
                if ($fk->getForeignTableName() == $tableName) {
133
                    $pivotTables[] = $table->getName();
134
                    break;
135
                }
136
            }
137
        }
138
139
        $this->cache->save($cacheKey, $pivotTables);
140
141
        return $pivotTables;
142
    }
143
144
    /**
145
     * Returns the list of foreign keys pointing to the table represented by this bean, excluding foreign keys
146
     * from junction tables and from inheritance.
147
     * It will also suppress doubles if 2 foreign keys are using the same columns.
148
     *
149
     * @return ForeignKeyConstraint[]
150
     */
151
    public function getIncomingForeignKeys(string $tableName): array
152
    {
153
        $junctionTables = $this->schemaAnalyzer->detectJunctionTables(true);
154
        $junctionTableNames = array_map(function (Table $table) {
155
            return $table->getName();
156
        }, $junctionTables);
157
        $childrenRelationships = $this->schemaAnalyzer->getChildrenRelationships($tableName);
158
159
        $fks = [];
160
        foreach ($this->getSchema()->getTables() as $table) {
161
            $uniqueForeignKeys = $this->removeDuplicates($table->getForeignKeys());
162
            foreach ($uniqueForeignKeys as $fk) {
163
                if ($fk->getForeignTableName() === $tableName) {
164
                    if (in_array($fk->getLocalTableName(), $junctionTableNames)) {
165
                        continue;
166
                    }
167
                    foreach ($childrenRelationships as $childFk) {
168
                        if ($fk->getLocalTableName() === $childFk->getLocalTableName() && $fk->getUnquotedLocalColumns() === $childFk->getUnquotedLocalColumns()) {
169
                            continue 2;
170
                        }
171
                    }
172
                    $fks[] = $fk;
173
                }
174
            }
175
        }
176
177
        return $fks;
178
    }
179
180
    /**
181
     * Remove duplicate foreign keys (assumes that all foreign yes are from the same local table)
182
     *
183
     * @param ForeignKeyConstraint[] $foreignKeys
184
     * @return ForeignKeyConstraint[]
185
     */
186
    private function removeDuplicates(array $foreignKeys): array
187
    {
188
        $fks = [];
189
        foreach ($foreignKeys as $foreignKey) {
190
            $key = implode('__`__', $foreignKey->getUnquotedLocalColumns());
191
            if (!isset($fks[$key])) {
192
                $fks[$key] = $foreignKey;
193
            }
194
        }
195
196
        return array_values($fks);
197
    }
198
}
199