Ubiquity 2.5.2
php rapid development framework
Loading...
Searching...
No Matches
DatabaseChecker.php
Go to the documentation of this file.
1<?php
3
10
22
23 private string $dbOffset;
24
26
27 private ?Database $db;
28
29 private ?array $models = null;
30
31 private ?array $metadatas = null;
32
33 private ?array $nonExistingTables = null;
34
35 private ?array $checkResults = null;
36
37 public function __construct(string $dbOffset = 'default') {
38 $this->dbOffset = $dbOffset;
39 $this->models = CacheManager::getModels(Startup::$config, true, $this->dbOffset);
40 foreach ($this->models as $model) {
41 $this->metadatas[$model] = CacheManager::getOrmModelCache($model);
42 }
43 }
44
45 public function checkDatabase(): bool {
46 try {
47 $this->db = DAO::getDatabase($this->dbOffset);
48 return $this->databaseExist = isset($this->db) && $this->db->isConnected();
49 } catch (\Exception | \Error $e) {
50 return $this->databaseExist = false;
51 }
52 }
53
54 public function getNonExistingTables(): array {
55 $existingTables = [];
56 if ($this->databaseExist) {
57 $existingTables = $this->db->getTablesName();
58 }
59 $tables = Reflexion::getAllJoinTables($this->models);
60 if (isset($this->metadatas)) {
61 foreach ($this->metadatas as $model => $metas) {
62 $tablename = $metas['#tableName'];
63 if (\array_search($tablename, $existingTables) === false && \array_search($tablename, $tables) === false) {
64 $tables[$model] = $tablename;
65 }
66 }
67 }
68 return \array_diff($tables, $existingTables);
69 }
70
71 private function _getNonExistingTables() {
72 return $this->nonExistingTables ??= $this->getNonExistingTables();
73 }
74
75 public function tableExists(string $table): bool {
76 return \array_search($table, $this->_getNonExistingTables()) === false;
77 }
78
79 public function checkAll(): array {
80 $result = [];
81 $this->databaseExist = true;
82 if (! $this->checkDatabase()) {
83 $result['database'] = $this->dbOffset;
84 }
85 $result['nonExistingTables'] = $this->_getNonExistingTables();
86 foreach ($this->models as $model) {
87 $metadatas = $this->metadatas[$model];
88 $tableName = $metadatas['#tableName'];
89 $updatedPks = $this->checkPrimaryKeys($model);
90 if (\count($updatedPks) > 0) {
91 $result['pks'][$tableName] = $updatedPks;
92 }
93 $updatedFields = $this->getUpdatedFields($model);
94 if (\count($updatedFields) > 0) {
95 $result['updatedFields'][$tableName] = $updatedFields;
96 }
97 $manyToOneUpdateds = $this->checkManyToOne($model);
98 if (\count($manyToOneUpdateds) > 0) {
99 $result['manyToOne'][$tableName] = $manyToOneUpdateds;
100 }
101 $manyToManyUpdateds = $this->checkManyToMany($model);
102 if (\count($manyToManyUpdateds) > 0) {
103 $result['manyToMany'][$tableName] = $manyToManyUpdateds;
104 }
105 }
106 return $this->checkResults = $result;
107 }
108
109 public function hasErrors(): bool {
110 if (\is_array($this->checkResults)) {
111 $ckR = $this->checkResults;
112 return ($ckR['database'] ?? false) || ($ckR['nonExistingTables'] ?? false) || ($ckR['updatedFields'] ?? false) || ($ckR['pks'] ?? false) || ($ckR['manyToOne'] ?? false) || ($ckR['manyToMany'] ?? false);
113 }
114 return false;
115 }
116
117 public function getResultDatabaseNotExist(): bool {
118 return $this->checkResults['database'] ?? false;
119 }
120
121 public function getResultNonExistingTables(): array {
122 return $this->checkResults['nonExistingTables'] ?? [];
123 }
124
125 public function getResultUpdatedFields(): array {
126 return $this->checkResults['updatedFields'] ?? [];
127 }
128
129 public function getResultPrimaryKeys(): array {
130 return $this->checkResults['pks'] ?? [];
131 }
132
133 public function getResultManyToOne(): array {
134 return $this->checkResults['manyToOne'] ?? [];
135 }
136
137 public function getResultManyToMany(): array {
138 return $this->checkResults['manyToMany'] ?? [];
139 }
140
141 public function getUpdatedFields(string $model): array {
142 $result = [];
143 $metadatas = $this->metadatas[$model];
144 $tableName = $metadatas['#tableName'];
145 if ($this->tableExists($tableName)) {
146 $fields = $metadatas['#fieldNames'];
147 $fieldTypes = $metadatas['#fieldTypes'];
148 $nullables = $metadatas['#nullable'];
149 $notSerializable = $metadatas['#notSerializable'];
150 $originalFieldInfos = [];
151 if ($this->databaseExist) {
152 $originalFieldInfos = $this->db->getFieldsInfos($tableName);
153 }
154 foreach ($fields as $member => $field) {
155 if (\array_search($member, $notSerializable) === false) {
156 $nullable = \array_search($member, $nullables) !== false;
157 $fieldInfos = [
158 'table' => $tableName,
159 'name' => $field,
160 'attributes' => [
161 'type' => $fieldTypes[$member],
162 'extra' => $nullable ? '' : 'NOT NULL'
163 ]
164 ];
165 if (! isset($originalFieldInfos[$field])) {
166 $result['missing'][$model][] = $fieldInfos;
167 } elseif ($fieldTypes[$member] !== 'mixed' && ($fieldTypes[$member] !== $originalFieldInfos[$field]['Type']) || ($originalFieldInfos[$field]['Nullable'] !== 'NO' && ! $nullable)) {
168 $result['updated'][$model][] = $fieldInfos;
169 }
170 }
171 }
172 }
173 return $result;
174 }
175
176 public function concatArrayKeyValue(array $array, callable $callable, string $sep = ',') {
177 $results = [];
178 foreach ($array as $value) {
179 $results[] = $callable($value);
180 }
181 return \implode($sep, $results);
182 }
183
184 public function checkPrimaryKeys(string $model): array {
185 $metadatas = $this->metadatas[$model];
186 $tableName = $metadatas['#tableName'];
187 if ($this->tableExists($tableName)) {
188 $pks = $metadatas['#primaryKeys'];
189 $originalPks = [];
190 if ($this->databaseExist) {
191 $originalPks = $this->db->getPrimaryKeys($tableName);
192 }
193 if (\is_array($pks)) {
194 foreach ($pks as $pk) {
195 if (\array_search($pk, $originalPks) === false) {
196 return [
197 'table' => $tableName,
198 'primaryKeys' => $pks,
199 'model' => $model
200 ];
201 }
202 }
203 }
204 }
205 return [];
206 }
207
208 public function checkManyToOne(string $model): array {
209 $metadatas = $this->metadatas[$model];
210 $manyToOnes = $metadatas['#manyToOne'] ?? [];
211 $joinColumns = $metadatas['#joinColumn'] ?? [];
212 $table = $metadatas['#tableName'];
213 $result = [];
214 if ($this->tableExists($table)) {
215 foreach ($manyToOnes as $manyToOneMember) {
216 $joinColumn = $joinColumns[$manyToOneMember];
217 $fkClass = $joinColumn['className'];
218 $fkField = $joinColumn['name'];
219 $fkTable = $this->metadatas[$fkClass]['#tableName'];
220 $fkId = $this->metadatas[$fkClass]['#primaryKeys'][0] ?? 'id';
221 $result = \array_merge($result, $this->checkFk($table, $fkField, $fkTable, $fkId));
222 }
223 }
224 return $result;
225 }
226
227 private function checkFk($table, $fkField, $fkTable, $fkId) {
228 $result = [];
229 $originalFks = [];
230 if ($this->databaseExist && $this->tableExists($table)) {
231 $originalFks = $this->db->getForeignKeys($fkTable, $fkId, $this->db->getDbName());
232 }
233 $findedFk = false;
234 foreach ($originalFks as $ofk) {
235 if ($ofk['TABLE_NAME'] === $table && $ofk['COLUMN_NAME'] === $fkField) {
236 $findedFk = true;
237 break;
238 }
239 }
240 if (! $findedFk) {
241 $result[] = [
242 'table' => $table,
243 'column' => $fkField,
244 'fkTable' => $fkTable,
245 'fkId' => $fkId
246 ];
247 }
248 return $result;
249 }
250
251 public function checkManyToMany(string $model): array {
252 $metadatas = $this->metadatas[$model];
253 $manyToManys = $metadatas['#manyToMany'] ?? [];
254 $joinTables = $metadatas['#joinTable'] ?? [];
255 $table = $metadatas['#tableName'];
256 $result = [];
257 if ($this->tableExists($table)) {
258 foreach ($manyToManys as $member => $manyToManyInfos) {
259 $joinTableInfos = $joinTables[$member];
260 $joinTableName = $joinTableInfos['name'];
261 $targetEntity = $manyToManyInfos['targetEntity'];
262 $fkTable = $this->metadatas[$targetEntity]['#tableName'];
263 $fkId = $this->metadatas[$targetEntity]['#primaryKeys'][0] ?? 'id';
264 $fkId = $joinTableInfos['inverseJoinColumns']['referencedColumnName'] ?? $fkId;
265 $fkField = $joinTableInfos['inverseJoinColumns']['name'] ?? ($fkId . \ucfirst($fkTable));
266 $result = \array_merge($result, $this->checkFk($joinTableName, $fkField, $fkTable, $fkId));
267 }
268 }
269 return $result;
270 }
271
276 public function getDb(): Database {
277 return $this->db;
278 }
279
280 public function displayAll(callable $displayCallable) {
281 $dbResults = $this->checkResults;
282
283 if (isset($dbResults['database'])) {
284 $displayCallable('error', 'database', "The database at offset <b>" . $dbResults['database'] . "</b> does not exist!");
285 }
286 if (\count($notExistingTables = $dbResults['nonExistingTables']) > 0) {
287 $notExistingTables = \array_unique($notExistingTables);
288 foreach ($notExistingTables as $model => $table) {
289 if (\is_string($model)) {
290 $displayCallable('warning', 'Missing table', "The table <b>" . $table . "</b> does not exist for the model <b>" . $model . "</b>.");
291 } else {
292 $displayCallable('warning', 'Missing table', "The table <b>" . $table . "</b> does not exist.");
293 }
294 }
295 }
296 if (\count($uFields = $this->getResultUpdatedFields()) > 0) {
297 foreach ($uFields as $table => $updatedFieldInfos) {
298 if (isset($updatedFieldInfos['missing'])) {
299 $model = \array_key_first($updatedFieldInfos['missing']);
300 if (\count($fInfos = $updatedFieldInfos['missing'][$model] ?? []) > 0) {
301 $names = $this->concatArrayKeyValue($fInfos, function ($value) {
302 return $value['name'];
303 });
304 $displayCallable('warning', 'Missing columns', "Missing fields in table <b>`$table`</b> for the model <b>`$model`</b>: <b>($names)</b>");
305 }
306 }
307 if (isset($updatedFieldInfos['updated'])) {
308 $model = \array_key_first($updatedFieldInfos['updated']);
309 if (\count($fInfos = $updatedFieldInfos['updated'][$model] ?? []) > 0) {
310 $names = $this->concatArrayKeyValue($fInfos, function ($value) {
311 return $value['name'];
312 });
313 $displayCallable('warning', 'Updated columns', "Updated fields in table <b>`$table`</b> for the model <b>`$model`</b>: <b>($names)</b>");
314 }
315 }
316 }
317 }
318 if (\count($pks = $this->getResultPrimaryKeys()) > 0) {
319 foreach ($pks as $table => $pksFieldInfos) {
320 $model = $pksFieldInfos['model'];
321 $names = implode(',', $pksFieldInfos['primaryKeys']);
322 $displayCallable('warning', 'Missing key', "Missing primary keys in table <b>`$table`</b> for the model <b>`$model`</b>: <b>($names)</b>");
323 }
324 }
325 if (\count($manyToOnes = $this->getResultManyToOne()) > 0) {
326 foreach ($manyToOnes as $table => $manyToOneFieldInfos) {
327 $names = $this->concatArrayKeyValue($manyToOneFieldInfos, function ($value) {
328 return $value['table'] . '.' . $value['column'] . ' => ' . $value['fkTable'] . '.' . $value['fkId'];
329 });
330 $displayCallable('warning', 'Missing hashtag (manyToOne)', "Missing foreign keys in table <b>`$table`</b> : <b>($names)</b>");
331 }
332 }
333
334 if (\count($manyToManys = $this->getResultManyToMany()) > 0) {
335 foreach ($manyToManys as $table => $manyToManyFieldInfos) {
336 $names = $this->concatArrayKeyValue($manyToManyFieldInfos, function ($value) {
337 return $value['table'] . '.' . $value['column'] . ' => ' . $value['fkTable'] . '.' . $value['fkId'];
338 });
339 $displayCallable('warning', 'Missing hashtag (manyToMany)', "Missing foreign keys for manyToMany with table <b>`$table`</b> : <b>($names)</b>");
340 }
341 }
342 }
343}
Manager for caches (Router, Rest, models).
Starts the framework.
Definition Startup.php:19
Ubiquity Generic database class.
Definition Database.php:25
Gateway class between database and object model.
Definition DAO.php:33
static isConnected($offset='default')
Returns true if the connection to the database is established.
Definition DAO.php:261
Reflection utilities in dev environment only.
Definition Reflexion.php:17
checkFk($table, $fkField, $fkTable, $fkId)
concatArrayKeyValue(array $array, callable $callable, string $sep=',')
__construct(string $dbOffset='default')
displayAll(callable $displayCallable)