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

TDBMSchemaAnalyzer::getDefaultLockFilePath()   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
    private $lockFilePath;
25
    private $connection;
26
27
    /**
28
     * @var Schema
29
     */
30
    private $schema;
31
32
    /**
33
     * @var string
34
     */
35
    private $cachePrefix;
36
37
    /**
38
     * @var Cache
39
     */
40
    private $cache;
41
42
    /**
43
     * @var SchemaAnalyzer
44
     */
45
    private $schemaAnalyzer;
46
47
    /**
48
     * @var SchemaVersionControlService
49
     */
50
    private $schemaVersionControlService;
51
52
    /**
53
     * @param Connection     $connection     The DBAL DB connection to use
54
     * @param Cache          $cache          A cache service to be used
55
     * @param SchemaAnalyzer $schemaAnalyzer The schema analyzer that will be used to find shortest paths...
56
     *                                       Will be automatically created if not passed
57
     */
58
    public function __construct(Connection $connection, Cache $cache, SchemaAnalyzer $schemaAnalyzer, ?string $lockFilePath = null)
59
    {
60
        $this->connection = $connection;
61
        $this->cache = $cache;
62
        $this->schemaAnalyzer = $schemaAnalyzer;
63
        $this->lockFilePath = $lockFilePath ?: self::getDefaultLockFilePath();
64
        $this->schemaVersionControlService = new SchemaVersionControlService($this->connection, $this->lockFilePath);
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
82
    public static function getDefaultLockFilePath(): string
83
    {
84
        return RootProjectLocator::getRootLocationPath().'tdbm.lock.yml';
85
    }
86
87
    public function getLockFilePath(): string
88
    {
89
        return $this->lockFilePath;
90
    }
91
92
    /**
93
     * Returns the (cached) schema.
94
     */
95
    public function getSchema(bool $ignoreCache = false): Schema
96
    {
97
        if ($this->schema === null) {
98
            $cacheKey = $this->getCachePrefix().'_immutable_schema';
99
            if (!$ignoreCache && $this->cache->contains($cacheKey)) {
100
                $this->schema = $this->cache->fetch($cacheKey);
101
            } elseif (!file_exists($this->getLockFilePath())) {
102
                throw new TDBMException('No tdbm lock file found. Please regenerate DAOs and Beans.');
103
            } else {
104
                $this->schema = $this->schemaVersionControlService->loadSchemaFile();
105
                ImmutableCaster::castSchemaToImmutable($this->schema);
106
                $this->cache->save($cacheKey, $this->schema);
107
            }
108
        }
109
110
        return $this->schema;
111
    }
112
113
    public function generateLockFile(): void
114
    {
115
        $this->schemaVersionControlService->dumpSchema();
116
        \chmod($this->getLockFilePath(), '664');
117
    }
118
119
    /**
120
     * Returns the list of pivot tables linked to table $tableName.
121
     *
122
     * @param string $tableName
123
     *
124
     * @return string[]
125
     */
126
    public function getPivotTableLinkedToTable(string $tableName): array
127
    {
128
        $cacheKey = $this->getCachePrefix().'_pivottables_link_'.$tableName;
129
        if ($this->cache->contains($cacheKey)) {
130
            return $this->cache->fetch($cacheKey);
131
        }
132
133
        $pivotTables = [];
134
135
        $junctionTables = $this->schemaAnalyzer->detectJunctionTables(true);
136
        foreach ($junctionTables as $table) {
137
            $fks = $table->getForeignKeys();
138
            foreach ($fks as $fk) {
139
                if ($fk->getForeignTableName() == $tableName) {
140
                    $pivotTables[] = $table->getName();
141
                    break;
142
                }
143
            }
144
        }
145
146
        $this->cache->save($cacheKey, $pivotTables);
147
148
        return $pivotTables;
149
    }
150
151
    /**
152
     * Returns the list of foreign keys pointing to the table represented by this bean, excluding foreign keys
153
     * from junction tables and from inheritance.
154
     * It will also suppress doubles if 2 foreign keys are using the same columns.
155
     *
156
     * @return ForeignKeyConstraint[]
157
     */
158
    public function getIncomingForeignKeys(string $tableName): array
159
    {
160
        $junctionTables = $this->schemaAnalyzer->detectJunctionTables(true);
161
        $junctionTableNames = array_map(function (Table $table) {
162
            return $table->getName();
163
        }, $junctionTables);
164
        $childrenRelationships = $this->schemaAnalyzer->getChildrenRelationships($tableName);
165
166
        $fks = [];
167
        foreach ($this->getSchema()->getTables() as $table) {
168
            $uniqueForeignKeys = $this->removeDuplicates($table->getForeignKeys());
169
            foreach ($uniqueForeignKeys as $fk) {
170
                if ($fk->getForeignTableName() === $tableName) {
171
                    if (in_array($fk->getLocalTableName(), $junctionTableNames)) {
172
                        continue;
173
                    }
174
                    foreach ($childrenRelationships as $childFk) {
175
                        if ($fk->getLocalTableName() === $childFk->getLocalTableName() && $fk->getUnquotedLocalColumns() === $childFk->getUnquotedLocalColumns()) {
176
                            continue 2;
177
                        }
178
                    }
179
                    $fks[] = $fk;
180
                }
181
            }
182
        }
183
184
        return $fks;
185
    }
186
187
    /**
188
     * Remove duplicate foreign keys (assumes that all foreign yes are from the same local table)
189
     *
190
     * @param ForeignKeyConstraint[] $foreignKeys
191
     * @return ForeignKeyConstraint[]
192
     */
193
    private function removeDuplicates(array $foreignKeys): array
194
    {
195
        $fks = [];
196
        foreach ($foreignKeys as $foreignKey) {
197
            $key = implode('__`__', $foreignKey->getUnquotedLocalColumns());
198
            if (!isset($fks[$key])) {
199
                $fks[$key] = $foreignKey;
200
            }
201
        }
202
203
        return array_values($fks);
204
    }
205
}
206