1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* Platine Database |
5
|
|
|
* |
6
|
|
|
* Platine Database is the abstraction layer using PDO with support of query and schema builder |
7
|
|
|
* |
8
|
|
|
* This content is released under the MIT License (MIT) |
9
|
|
|
* |
10
|
|
|
* Copyright (c) 2020 Platine Database |
11
|
|
|
* |
12
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy |
13
|
|
|
* of this software and associated documentation files (the "Software"), to deal |
14
|
|
|
* in the Software without restriction, including without limitation the rights |
15
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
16
|
|
|
* copies of the Software, and to permit persons to whom the Software is |
17
|
|
|
* furnished to do so, subject to the following conditions: |
18
|
|
|
* |
19
|
|
|
* The above copyright notice and this permission notice shall be included in all |
20
|
|
|
* copies or substantial portions of the Software. |
21
|
|
|
* |
22
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
23
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
24
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
25
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
26
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
27
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
28
|
|
|
* SOFTWARE. |
29
|
|
|
*/ |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* @file Schema.php |
33
|
|
|
* |
34
|
|
|
* The database schema class |
35
|
|
|
* |
36
|
|
|
* @package Platine\Database |
37
|
|
|
* @author Platine Developers Team |
38
|
|
|
* @copyright Copyright (c) 2020 |
39
|
|
|
* @license http://opensource.org/licenses/MIT MIT License |
40
|
|
|
* @link http://www.iacademy.cf |
41
|
|
|
* @version 1.0.0 |
42
|
|
|
* @filesource |
43
|
|
|
*/ |
44
|
|
|
|
45
|
|
|
declare(strict_types=1); |
46
|
|
|
|
47
|
|
|
namespace Platine\Database; |
48
|
|
|
|
49
|
|
|
use Platine\Database\Schema\AlterTable; |
50
|
|
|
use Platine\Database\Schema\CreateTable; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Class Schema |
54
|
|
|
* @package Platine\Database |
55
|
|
|
*/ |
56
|
|
|
class Schema |
57
|
|
|
{ |
58
|
|
|
/** |
59
|
|
|
* The Connection instance |
60
|
|
|
* @var Connection |
61
|
|
|
*/ |
62
|
|
|
protected Connection $connection; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* The list of tables |
66
|
|
|
* @var array<string, string> |
67
|
|
|
*/ |
68
|
|
|
protected array $tables = []; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* The list of views |
72
|
|
|
* @var array<string, string> |
73
|
|
|
*/ |
74
|
|
|
protected array $views = []; |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* The current database name |
78
|
|
|
* @var string |
79
|
|
|
*/ |
80
|
|
|
protected string $databaseName = ''; |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* The current table columns |
84
|
|
|
* @var array<string, array<string, array<string, string>>> |
85
|
|
|
*/ |
86
|
|
|
protected array $columns = []; |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* Class constructor |
90
|
|
|
* @param Connection $connection |
91
|
|
|
*/ |
92
|
|
|
public function __construct(Connection $connection) |
93
|
|
|
{ |
94
|
|
|
$this->connection = $connection; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* |
99
|
|
|
* @return string |
100
|
|
|
*/ |
101
|
|
|
public function getDatabaseName(): string |
102
|
|
|
{ |
103
|
|
|
if ($this->databaseName === '') { |
104
|
|
|
$driver = $this->connection->getDriver(); |
105
|
|
|
$result = $driver->getDatabaseName(); |
106
|
|
|
|
107
|
|
|
if (isset($result['result'])) { |
108
|
|
|
$this->databaseName = $result['result']; |
|
|
|
|
109
|
|
|
} else { |
110
|
|
|
$this->databaseName = $this->connection->column( |
111
|
|
|
$result['sql'], |
112
|
|
|
$result['params'] |
113
|
|
|
); |
114
|
|
|
} |
115
|
|
|
} |
116
|
|
|
return $this->databaseName; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Check whether the given table exists |
121
|
|
|
* @param string $table |
122
|
|
|
* @param bool $skipCache |
123
|
|
|
* @return bool |
124
|
|
|
*/ |
125
|
|
|
public function hasTable(string $table, bool $skipCache = false): bool |
126
|
|
|
{ |
127
|
|
|
$list = $this->getTables($skipCache); |
128
|
|
|
|
129
|
|
|
return isset($list[strtolower($table)]); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Check whether the given view exists |
134
|
|
|
* @param string $view |
135
|
|
|
* @param bool $skipCache |
136
|
|
|
* @return bool |
137
|
|
|
*/ |
138
|
|
|
public function hasView(string $view, bool $skipCache = false): bool |
139
|
|
|
{ |
140
|
|
|
$list = $this->getViews($skipCache); |
141
|
|
|
|
142
|
|
|
return isset($list[strtolower($view)]); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Return the list of tables for the current database |
147
|
|
|
* @param bool $skipCache whether to use the cached data or not |
148
|
|
|
* @return array<string, string> |
149
|
|
|
*/ |
150
|
|
|
public function getTables(bool $skipCache = false): array |
151
|
|
|
{ |
152
|
|
|
if ($skipCache) { |
153
|
|
|
$this->tables = []; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
if (empty($this->tables)) { |
157
|
|
|
$driver = $this->connection->getDriver(); |
158
|
|
|
$databaseName = $this->getDatabaseName(); |
159
|
|
|
$sql = $driver->getTables($databaseName); |
160
|
|
|
|
161
|
|
|
$results = $this->connection |
162
|
|
|
->query($sql['sql'], $sql['params']) |
163
|
|
|
->fetchNum(); |
164
|
|
|
|
165
|
|
|
while ($result = $results->next()) { |
166
|
|
|
$this->tables[strtolower($result[0])] = $result[0]; |
167
|
|
|
} |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
return $this->tables; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Return the list of views for the current database |
175
|
|
|
* @param bool $skipCache whether to use the cached data or not |
176
|
|
|
* @return array<string, string> |
177
|
|
|
*/ |
178
|
|
|
public function getViews(bool $skipCache = false): array |
179
|
|
|
{ |
180
|
|
|
if ($skipCache) { |
181
|
|
|
$this->views = []; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
if (empty($this->views)) { |
185
|
|
|
$driver = $this->connection->getDriver(); |
186
|
|
|
$databaseName = $this->getDatabaseName(); |
187
|
|
|
$sql = $driver->getViews($databaseName); |
188
|
|
|
|
189
|
|
|
$results = $this->connection |
190
|
|
|
->query($sql['sql'], $sql['params']) |
191
|
|
|
->fetchNum(); |
192
|
|
|
|
193
|
|
|
while ($result = $results->next()) { |
194
|
|
|
$this->views[strtolower($result[0])] = $result[0]; |
195
|
|
|
} |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
return $this->views; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Return the list of columns for the given table |
203
|
|
|
* @param string $table |
204
|
|
|
* @param bool $skipCache |
205
|
|
|
* @param bool $names whether to return only the columns names |
206
|
|
|
* @return string[]|array<string, array<string, string>> |
207
|
|
|
*/ |
208
|
|
|
public function getColumns( |
209
|
|
|
string $table, |
210
|
|
|
bool $skipCache = false, |
211
|
|
|
bool $names = true |
212
|
|
|
) { |
213
|
|
|
if ($skipCache) { |
214
|
|
|
unset($this->columns[$table]); |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
if (!$this->hasTable($table, $skipCache)) { |
218
|
|
|
return []; |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
if (!isset($this->columns[$table])) { |
222
|
|
|
$driver = $this->connection->getDriver(); |
223
|
|
|
$databaseName = $this->getDatabaseName(); |
224
|
|
|
$sql = $driver->getColumns($databaseName, $table); |
225
|
|
|
|
226
|
|
|
$results = $this->connection |
227
|
|
|
->query($sql['sql'], $sql['params']) |
228
|
|
|
->fetchAssoc(); |
229
|
|
|
|
230
|
|
|
/** @var array<string, array<string, string>> $columns */ |
231
|
|
|
$columns = []; |
232
|
|
|
|
233
|
|
|
while ( |
234
|
|
|
/** @var array<string, string>> $col */ |
235
|
|
|
$col = $results->next() |
236
|
|
|
) { |
237
|
|
|
$columns[$col['name']] = [ |
238
|
|
|
'name' => $col['name'], |
239
|
|
|
'type' => $col['type'], |
240
|
|
|
]; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
$this->columns[$table] = $columns; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
return $names ? array_keys($this->columns[$table]) : $this->columns[$table]; |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* Return the list of columns for the given view |
251
|
|
|
* @param string $view |
252
|
|
|
* @param bool $skipCache |
253
|
|
|
* @param bool $names whether to return only the columns names |
254
|
|
|
* @return string[]|array<string, array<string, string>> |
255
|
|
|
*/ |
256
|
|
|
public function getViewColumns( |
257
|
|
|
string $view, |
258
|
|
|
bool $skipCache = false, |
259
|
|
|
bool $names = true |
260
|
|
|
) { |
261
|
|
|
if ($skipCache) { |
262
|
|
|
unset($this->columns[$view]); |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
if (!$this->hasView($view, $skipCache)) { |
266
|
|
|
return []; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
if (!isset($this->columns[$view])) { |
270
|
|
|
$driver = $this->connection->getDriver(); |
271
|
|
|
$databaseName = $this->getDatabaseName(); |
272
|
|
|
$sql = $driver->getViewColumns($databaseName, $view); |
273
|
|
|
|
274
|
|
|
$results = $this->connection |
275
|
|
|
->query($sql['sql'], $sql['params']) |
276
|
|
|
->fetchAssoc(); |
277
|
|
|
|
278
|
|
|
/** @var array<string, array<string, string>> $columns */ |
279
|
|
|
$columns = []; |
280
|
|
|
|
281
|
|
|
while ( |
282
|
|
|
/** @var array<string, string>> $col */ |
283
|
|
|
$col = $results->next() |
284
|
|
|
) { |
285
|
|
|
$columns[$col['name']] = [ |
286
|
|
|
'name' => $col['name'], |
287
|
|
|
'type' => $col['type'], |
288
|
|
|
]; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
$this->columns[$view] = $columns; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
return $names ? array_keys($this->columns[$view]) : $this->columns[$view]; |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* Create new table |
299
|
|
|
* @param string $table |
300
|
|
|
* @param callable $callback callback to use to define the field(s) and indexes |
301
|
|
|
* @return void |
302
|
|
|
*/ |
303
|
|
|
public function create(string $table, callable $callback): void |
304
|
|
|
{ |
305
|
|
|
$driver = $this->connection->getDriver(); |
306
|
|
|
$schema = new CreateTable($table); |
307
|
|
|
$callback($schema); |
308
|
|
|
|
309
|
|
|
foreach ($driver->create($schema) as $result) { |
310
|
|
|
$this->connection->exec($result['sql'], $result['params']); |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
//clear all tables list |
314
|
|
|
$this->tables = []; |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* Alter table definition |
319
|
|
|
* @param string $table |
320
|
|
|
* @param callable $callback callback to use to add/remove the field(s) or indexes |
321
|
|
|
* @return void |
322
|
|
|
*/ |
323
|
|
|
public function alter(string $table, callable $callback): void |
324
|
|
|
{ |
325
|
|
|
$driver = $this->connection->getDriver(); |
326
|
|
|
$schema = new AlterTable($table); |
327
|
|
|
$callback($schema); |
328
|
|
|
|
329
|
|
|
//clear all columns for this table |
330
|
|
|
unset($this->columns[strtolower($table)]); |
331
|
|
|
|
332
|
|
|
foreach ($driver->alter($schema) as $result) { |
333
|
|
|
$this->connection->exec($result['sql'], $result['params']); |
334
|
|
|
} |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
/** |
338
|
|
|
* Rename the table |
339
|
|
|
* @param string $table |
340
|
|
|
* @param string $newName |
341
|
|
|
* @return void |
342
|
|
|
*/ |
343
|
|
|
public function renameTable(string $table, string $newName): void |
344
|
|
|
{ |
345
|
|
|
$driver = $this->connection->getDriver(); |
346
|
|
|
$result = $driver->renameTable($table, $newName); |
347
|
|
|
$this->connection->exec($result['sql'], $result['params']); |
348
|
|
|
|
349
|
|
|
//clear all columns for this table |
350
|
|
|
unset($this->columns[strtolower($table)]); |
351
|
|
|
|
352
|
|
|
//clear all tables list |
353
|
|
|
$this->tables = []; |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
/** |
357
|
|
|
* Drop the table |
358
|
|
|
* @param string $table |
359
|
|
|
* @return void |
360
|
|
|
*/ |
361
|
|
|
public function drop(string $table): void |
362
|
|
|
{ |
363
|
|
|
$driver = $this->connection->getDriver(); |
364
|
|
|
$result = $driver->drop($table); |
365
|
|
|
$this->connection->exec($result['sql'], $result['params']); |
366
|
|
|
|
367
|
|
|
//clear all columns for this table |
368
|
|
|
unset($this->columns[strtolower($table)]); |
369
|
|
|
|
370
|
|
|
//clear all tables list |
371
|
|
|
$this->tables = []; |
372
|
|
|
} |
373
|
|
|
|
374
|
|
|
/** |
375
|
|
|
* Truncate the table |
376
|
|
|
* @param string $table |
377
|
|
|
* @return void |
378
|
|
|
*/ |
379
|
|
|
public function truncate(string $table): void |
380
|
|
|
{ |
381
|
|
|
$driver = $this->connection->getDriver(); |
382
|
|
|
$result = $driver->truncate($table); |
383
|
|
|
$this->connection->exec($result['sql'], $result['params']); |
384
|
|
|
} |
385
|
|
|
} |
386
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.