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

TDBMSchemaAnalyzer::generateLockFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 4
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
18
/**
19
 * This class is used to analyze the schema and return valuable information / hints.
20
 */
21
class TDBMSchemaAnalyzer
22
{
23
    private $lockFilePath;
24
    private $connection;
25
26
    /**
27
     * @var Schema
28
     */
29
    private $schema;
30
31
    /**
32
     * @var string
33
     */
34
    private $cachePrefix;
35
36
    /**
37
     * @var Cache
38
     */
39
    private $cache;
40
41
    /**
42
     * @var SchemaAnalyzer
43
     */
44
    private $schemaAnalyzer;
45
46
    /**
47
     * @var SchemaVersionControlService
48
     */
49
    private $schemaVersionControlService;
50
51
    /**
52
     * @param Connection $connection The DBAL DB connection to use
53
     * @param Cache $cache A cache service to be used
54
     * @param SchemaAnalyzer $schemaAnalyzer The schema analyzer that will be used to find shortest paths...
55
     *                                       Will be automatically created if not passed
56
     * @param string $lockFilePath The path for the lock file which will store the database schema
57
     */
58
    public function __construct(Connection $connection, Cache $cache, SchemaAnalyzer $schemaAnalyzer, string $lockFilePath)
59
    {
60
        $this->connection = $connection;
61
        $this->cache = $cache;
62
        $this->schemaAnalyzer = $schemaAnalyzer;
63
        $this->schemaVersionControlService = new SchemaVersionControlService($this->connection, $this->lockFilePath);
64
        $this->lockFilePath = $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
    public function getLockFilePath(): string
82
    {
83
        return $this->lockFilePath;
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($this->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
        \chmod($this->getLockFilePath(), 0664);
111
    }
112
113
    /**
114
     * Returns the list of pivot tables linked to table $tableName.
115
     *
116
     * @param string $tableName
117
     *
118
     * @return string[]
119
     */
120
    public function getPivotTableLinkedToTable(string $tableName): array
121
    {
122
        $cacheKey = $this->getCachePrefix().'_pivottables_link_'.$tableName;
123
        if ($this->cache->contains($cacheKey)) {
124
            return $this->cache->fetch($cacheKey);
125
        }
126
127
        $pivotTables = [];
128
129
        $junctionTables = $this->schemaAnalyzer->detectJunctionTables(true);
130
        foreach ($junctionTables as $table) {
131
            $fks = $table->getForeignKeys();
132
            foreach ($fks as $fk) {
133
                if ($fk->getForeignTableName() == $tableName) {
134
                    $pivotTables[] = $table->getName();
135
                    break;
136
                }
137
            }
138
        }
139
140
        $this->cache->save($cacheKey, $pivotTables);
141
142
        return $pivotTables;
143
    }
144
145
    /**
146
     * Returns the list of foreign keys pointing to the table represented by this bean, excluding foreign keys
147
     * from junction tables and from inheritance.
148
     * It will also suppress doubles if 2 foreign keys are using the same columns.
149
     *
150
     * @return ForeignKeyConstraint[]
151
     */
152
    public function getIncomingForeignKeys(string $tableName): array
153
    {
154
        $junctionTables = $this->schemaAnalyzer->detectJunctionTables(true);
155
        $junctionTableNames = array_map(function (Table $table) {
156
            return $table->getName();
157
        }, $junctionTables);
158
        $childrenRelationships = $this->schemaAnalyzer->getChildrenRelationships($tableName);
159
160
        $fks = [];
161
        foreach ($this->getSchema()->getTables() as $table) {
162
            $uniqueForeignKeys = $this->removeDuplicates($table->getForeignKeys());
163
            foreach ($uniqueForeignKeys as $fk) {
164
                if ($fk->getForeignTableName() === $tableName) {
165
                    if (in_array($fk->getLocalTableName(), $junctionTableNames)) {
166
                        continue;
167
                    }
168
                    foreach ($childrenRelationships as $childFk) {
169
                        if ($fk->getLocalTableName() === $childFk->getLocalTableName() && $fk->getUnquotedLocalColumns() === $childFk->getUnquotedLocalColumns()) {
170
                            continue 2;
171
                        }
172
                    }
173
                    $fks[] = $fk;
174
                }
175
            }
176
        }
177
178
        return $fks;
179
    }
180
181
    /**
182
     * Remove duplicate foreign keys (assumes that all foreign yes are from the same local table)
183
     *
184
     * @param ForeignKeyConstraint[] $foreignKeys
185
     * @return ForeignKeyConstraint[]
186
     */
187
    private function removeDuplicates(array $foreignKeys): array
188
    {
189
        $fks = [];
190
        foreach ($foreignKeys as $foreignKey) {
191
            $key = implode('__`__', $foreignKey->getUnquotedLocalColumns());
192
            if (!isset($fks[$key])) {
193
                $fks[$key] = $foreignKey;
194
            }
195
        }
196
197
        return array_values($fks);
198
    }
199
}
200