1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* components |
4
|
|
|
* |
5
|
|
|
* @author Wolfy-J |
6
|
|
|
*/ |
7
|
|
|
namespace Spiral\Database\Schemas\Prototypes; |
8
|
|
|
|
9
|
|
|
use Interop\Container\ContainerInterface; |
10
|
|
|
use Psr\Log\LoggerInterface; |
11
|
|
|
use Spiral\Database\Entities\AbstractHandler; |
12
|
|
|
use Spiral\Database\Entities\Driver; |
13
|
|
|
use Spiral\Database\Exceptions\HandlerException; |
14
|
|
|
use Spiral\Database\Exceptions\SchemaException; |
15
|
|
|
use Spiral\Database\Schemas\ColumnInterface; |
16
|
|
|
use Spiral\Database\Schemas\IndexInterface; |
17
|
|
|
use Spiral\Database\Schemas\ReferenceInterface; |
18
|
|
|
use Spiral\Database\Schemas\StateComparator; |
19
|
|
|
use Spiral\Database\Schemas\TableInterface; |
20
|
|
|
use Spiral\Database\Schemas\TableState; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* AbstractTable class used to describe and manage state of specified table. It provides ability to |
24
|
|
|
* get table introspection, update table schema and automatically generate set of diff operations. |
25
|
|
|
* |
26
|
|
|
* Most of table operation like column, index or foreign key creation/altering will be applied when |
27
|
|
|
* save() method will be called. |
28
|
|
|
* |
29
|
|
|
* Column configuration shortcuts: |
30
|
|
|
* |
31
|
|
|
* @method AbstractColumn primary($column) |
32
|
|
|
* @method AbstractColumn bigPrimary($column) |
33
|
|
|
* @method AbstractColumn enum($column, array $values) |
34
|
|
|
* @method AbstractColumn string($column, $length = 255) |
35
|
|
|
* @method AbstractColumn decimal($column, $precision, $scale) |
36
|
|
|
* @method AbstractColumn boolean($column) |
37
|
|
|
* @method AbstractColumn integer($column) |
38
|
|
|
* @method AbstractColumn tinyInteger($column) |
39
|
|
|
* @method AbstractColumn bigInteger($column) |
40
|
|
|
* @method AbstractColumn text($column) |
41
|
|
|
* @method AbstractColumn tinyText($column) |
42
|
|
|
* @method AbstractColumn longText($column) |
43
|
|
|
* @method AbstractColumn double($column) |
44
|
|
|
* @method AbstractColumn float($column) |
45
|
|
|
* @method AbstractColumn datetime($column) |
46
|
|
|
* @method AbstractColumn date($column) |
47
|
|
|
* @method AbstractColumn time($column) |
48
|
|
|
* @method AbstractColumn timestamp($column) |
49
|
|
|
* @method AbstractColumn binary($column) |
50
|
|
|
* @method AbstractColumn tinyBinary($column) |
51
|
|
|
* @method AbstractColumn longBinary($column) |
52
|
|
|
*/ |
53
|
|
|
abstract class AbstractTable implements TableInterface |
54
|
|
|
{ |
55
|
|
|
/** |
56
|
|
|
* Table states. |
57
|
|
|
*/ |
58
|
|
|
const STATUS_NEW = 0; |
59
|
|
|
const STATUS_EXISTS = 1; |
60
|
|
|
const STATUS_DROPPED = 2; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Indication that table is exists and current schema is fetched from database. |
64
|
|
|
* |
65
|
|
|
* @var int |
66
|
|
|
*/ |
67
|
|
|
private $status = self::STATUS_NEW; |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* Database specific tablePrefix. Required for table renames. |
71
|
|
|
* |
72
|
|
|
* @var string |
73
|
|
|
*/ |
74
|
|
|
private $prefix = ''; |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @invisible |
78
|
|
|
* |
79
|
|
|
* @var Driver |
80
|
|
|
*/ |
81
|
|
|
protected $driver = null; |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Initial table state. |
85
|
|
|
* |
86
|
|
|
* @invisible |
87
|
|
|
* @var TableState |
88
|
|
|
*/ |
89
|
|
|
protected $initial = null; |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* Currently defined table state. |
93
|
|
|
* |
94
|
|
|
* @invisible |
95
|
|
|
* @var TableState |
96
|
|
|
*/ |
97
|
|
|
protected $current = null; |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* @param Driver $driver Parent driver. |
101
|
|
|
* @param string $name Table name, must include table prefix. |
102
|
|
|
* @param string $prefix Database specific table prefix. |
103
|
|
|
*/ |
104
|
|
|
public function __construct(Driver $driver, string $name, string $prefix) |
105
|
|
|
{ |
106
|
|
|
$this->driver = $driver; |
107
|
|
|
$this->prefix = $prefix; |
108
|
|
|
|
109
|
|
|
//Initializing states |
110
|
|
|
$this->initial = new TableState($this->prefix . $name); |
111
|
|
|
$this->current = new TableState($this->prefix . $name); |
112
|
|
|
|
113
|
|
|
if ($this->driver->hasTable($this->getName())) { |
114
|
|
|
$this->status = self::STATUS_EXISTS; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
if ($this->exists()) { |
118
|
|
|
//Initiating table schema |
119
|
|
|
$this->initSchema($this->initial); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
$this->setState($this->initial); |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* Get instance of associated driver. |
127
|
|
|
* |
128
|
|
|
* @return Driver |
129
|
|
|
*/ |
130
|
|
|
public function getDriver(): Driver |
131
|
|
|
{ |
132
|
|
|
return $this->driver; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Return database specific table prefix. |
137
|
|
|
* |
138
|
|
|
* @return string |
139
|
|
|
*/ |
140
|
|
|
public function getPrefix(): string |
141
|
|
|
{ |
142
|
|
|
return $this->prefix; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* @return StateComparator |
147
|
|
|
*/ |
148
|
|
|
public function getComparator(): StateComparator |
149
|
|
|
{ |
150
|
|
|
return new StateComparator($this->initial, $this->current); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* Check if table schema has been modified since synchronization. |
155
|
|
|
* |
156
|
|
|
* @return bool |
157
|
|
|
*/ |
158
|
|
|
protected function hasChanges(): bool |
159
|
|
|
{ |
160
|
|
|
return $this->getComparator()->hasChanges(); |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* {@inheritdoc} |
165
|
|
|
*/ |
166
|
|
|
public function exists(): bool |
167
|
|
|
{ |
168
|
|
|
return $this->status == self::STATUS_EXISTS || $this->status == self::STATUS_DROPPED; |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* Table status (see codes above). |
173
|
|
|
* |
174
|
|
|
* @return int |
175
|
|
|
*/ |
176
|
|
|
public function getStatus(): int |
177
|
|
|
{ |
178
|
|
|
return $this->status; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Sets table name. Use this function in combination with save to rename table. |
183
|
|
|
* |
184
|
|
|
* @param string $name |
185
|
|
|
* |
186
|
|
|
* @return string Prefixed table name. |
187
|
|
|
*/ |
188
|
|
|
public function setName(string $name): string |
189
|
|
|
{ |
190
|
|
|
$this->current->setName($this->prefix . $name); |
191
|
|
|
|
192
|
|
|
return $this->getName(); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* {@inheritdoc} |
197
|
|
|
*/ |
198
|
|
|
public function getName(): string |
199
|
|
|
{ |
200
|
|
|
return $this->current->getName(); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Table name before rename. |
205
|
|
|
* |
206
|
|
|
* @return string |
207
|
|
|
*/ |
208
|
|
|
public function getInitialName(): string |
209
|
|
|
{ |
210
|
|
|
return $this->initial->getName(); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* Declare table as dropped, you have to sync table using "save" method in order to apply this |
215
|
|
|
* change. |
216
|
|
|
*/ |
217
|
|
|
public function declareDropped() |
218
|
|
|
{ |
219
|
|
|
if ($this->status == self::STATUS_NEW) { |
220
|
|
|
throw new SchemaException("Unable to drop non existed table"); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
//Declaring as dropper |
224
|
|
|
$this->status = self::STATUS_DROPPED; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Set table primary keys. Operation can only be applied for newly created tables. Now every |
229
|
|
|
* database might support compound indexes. |
230
|
|
|
* |
231
|
|
|
* @param array $columns |
232
|
|
|
* |
233
|
|
|
* @return self |
234
|
|
|
*/ |
235
|
|
|
public function setPrimaryKeys(array $columns): AbstractTable |
236
|
|
|
{ |
237
|
|
|
//Originally i were forcing an exception when primary key were changed, now we should |
238
|
|
|
//force it when table will be synced |
239
|
|
|
|
240
|
|
|
//Updating primary keys in current state |
241
|
|
|
$this->current->setPrimaryKeys($columns); |
242
|
|
|
|
243
|
|
|
return $this; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* {@inheritdoc} |
248
|
|
|
*/ |
249
|
|
|
public function getPrimaryKeys(): array |
250
|
|
|
{ |
251
|
|
|
return $this->current->getPrimaryKeys(); |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* {@inheritdoc} |
256
|
|
|
*/ |
257
|
|
|
public function hasColumn(string $name): bool |
258
|
|
|
{ |
259
|
|
|
return $this->current->hasColumn($name); |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
/** |
263
|
|
|
* {@inheritdoc} |
264
|
|
|
* |
265
|
|
|
* @return AbstractColumn[] |
266
|
|
|
*/ |
267
|
|
|
public function getColumns(): array |
268
|
|
|
{ |
269
|
|
|
return $this->current->getColumns(); |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* {@inheritdoc} |
274
|
|
|
*/ |
275
|
|
|
public function hasIndex(array $columns = []): bool |
276
|
|
|
{ |
277
|
|
|
return $this->current->hasIndex($columns); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* {@inheritdoc} |
282
|
|
|
* |
283
|
|
|
* @return AbstractIndex[] |
284
|
|
|
*/ |
285
|
|
|
public function getIndexes(): array |
286
|
|
|
{ |
287
|
|
|
return $this->current->getIndexes(); |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* {@inheritdoc} |
292
|
|
|
*/ |
293
|
|
|
public function hasForeign(string $column): bool |
294
|
|
|
{ |
295
|
|
|
return $this->current->hasForeign($column); |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
/** |
299
|
|
|
* {@inheritdoc} |
300
|
|
|
* |
301
|
|
|
* @return AbstractReference[] |
302
|
|
|
*/ |
303
|
|
|
public function getForeigns(): array |
304
|
|
|
{ |
305
|
|
|
return $this->current->getForeigns(); |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
/** |
309
|
|
|
* {@inheritdoc} |
310
|
|
|
*/ |
311
|
|
|
public function getDependencies(): array |
312
|
|
|
{ |
313
|
|
|
$tables = []; |
314
|
|
|
foreach ($this->current->getForeigns() as $foreign) { |
315
|
|
|
$tables[] = $foreign->getForeignTable(); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
return $tables; |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
/** |
322
|
|
|
* Get/create instance of AbstractColumn associated with current table. |
323
|
|
|
* |
324
|
|
|
* Attention, renamed column will be available by it's old name until being synced! |
325
|
|
|
* |
326
|
|
|
* Examples: |
327
|
|
|
* $table->column('name')->string(); |
328
|
|
|
* |
329
|
|
|
* @param string $name |
330
|
|
|
* |
331
|
|
|
* @return AbstractColumn |
332
|
|
|
*/ |
333
|
|
|
public function column(string $name): AbstractColumn |
334
|
|
|
{ |
335
|
|
|
if ($this->current->hasColumn($name)) { |
336
|
|
|
//Column already exists |
337
|
|
|
return $this->current->findColumn($name); |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
$column = $this->createColumn($name); |
341
|
|
|
$this->current->registerColumn($column); |
342
|
|
|
|
343
|
|
|
return $column; |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* Shortcut for column() method. |
348
|
|
|
* |
349
|
|
|
* @param string $column |
350
|
|
|
* |
351
|
|
|
* @return AbstractColumn |
352
|
|
|
*/ |
353
|
|
|
public function __get(string $column) |
354
|
|
|
{ |
355
|
|
|
return $this->column($column); |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
/** |
359
|
|
|
* Column creation/altering shortcut, call chain is identical to: |
360
|
|
|
* AbstractTable->column($name)->$type($arguments). |
361
|
|
|
* |
362
|
|
|
* Example: |
363
|
|
|
* $table->string("name"); |
364
|
|
|
* $table->text("some_column"); |
365
|
|
|
* |
366
|
|
|
* @param string $type |
367
|
|
|
* @param array $arguments Type specific parameters. |
368
|
|
|
* |
369
|
|
|
* @return AbstractColumn |
370
|
|
|
*/ |
371
|
|
|
public function __call(string $type, array $arguments) |
372
|
|
|
{ |
373
|
|
|
return call_user_func_array( |
374
|
|
|
[$this->column($arguments[0]), $type], |
375
|
|
|
array_slice($arguments, 1) |
376
|
|
|
); |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
/** |
380
|
|
|
* Get/create instance of AbstractIndex associated with current table based on list of forming |
381
|
|
|
* column names. |
382
|
|
|
* |
383
|
|
|
* Example: |
384
|
|
|
* $table->index('key'); |
385
|
|
|
* $table->index('key', 'key2'); |
386
|
|
|
* $table->index(['key', 'key2']); |
387
|
|
|
* |
388
|
|
|
* @param mixed $columns Column name, or array of columns. |
389
|
|
|
* |
390
|
|
|
* @return AbstractIndex |
391
|
|
|
* |
392
|
|
|
* @throws SchemaException |
393
|
|
|
*/ |
394
|
|
|
public function index($columns): AbstractIndex |
395
|
|
|
{ |
396
|
|
|
$columns = is_array($columns) ? $columns : func_get_args(); |
397
|
|
|
|
398
|
|
|
foreach ($columns as $column) { |
399
|
|
|
if (!$this->hasColumn($column)) { |
400
|
|
|
throw new SchemaException("Undefined column '{$column}' in '{$this->getName()}'"); |
401
|
|
|
} |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
if ($this->hasIndex($columns)) { |
405
|
|
|
return $this->current->findIndex($columns); |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
$index = $this->createIndex($this->createIdentifier('index', $columns)); |
409
|
|
|
$index->columns($columns); |
410
|
|
|
$this->current->registerIndex($index); |
411
|
|
|
|
412
|
|
|
return $index; |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
/** |
416
|
|
|
* Get/create instance of AbstractReference associated with current table based on local column |
417
|
|
|
* name. |
418
|
|
|
* |
419
|
|
|
* @param string $column |
420
|
|
|
* |
421
|
|
|
* @return AbstractReference |
422
|
|
|
* |
423
|
|
|
* @throws SchemaException |
424
|
|
|
*/ |
425
|
|
|
public function foreign(string $column): AbstractReference |
426
|
|
|
{ |
427
|
|
|
if (!$this->hasColumn($column)) { |
428
|
|
|
throw new SchemaException("Undefined column '{$column}' in '{$this->getName()}'"); |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
if ($this->hasForeign($column)) { |
432
|
|
|
return $this->current->findForeign($column); |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
$foreign = $this->createForeign($this->createIdentifier('foreign', [$column])); |
436
|
|
|
$foreign->column($column); |
437
|
|
|
|
438
|
|
|
$this->current->registerReference($foreign); |
439
|
|
|
|
440
|
|
|
//Let's ensure index existence to performance and compatibility reasons |
441
|
|
|
$this->index($column); |
442
|
|
|
|
443
|
|
|
return $foreign; |
444
|
|
|
} |
445
|
|
|
|
446
|
|
|
/** |
447
|
|
|
* Rename column (only if column exists). |
448
|
|
|
* |
449
|
|
|
* @param string $column |
450
|
|
|
* @param string $name New column name. |
451
|
|
|
* |
452
|
|
|
* @return self |
453
|
|
|
* |
454
|
|
|
* @throws SchemaException |
455
|
|
|
*/ |
456
|
|
|
public function renameColumn(string $column, string $name): AbstractTable |
457
|
|
|
{ |
458
|
|
|
if (!$this->hasColumn($column)) { |
459
|
|
|
throw new SchemaException("Undefined column '{$column}' in '{$this->getName()}'"); |
460
|
|
|
} |
461
|
|
|
|
462
|
|
|
//Rename operation is simple about declaring new name |
463
|
|
|
$this->column($column)->setName($name); |
464
|
|
|
|
465
|
|
|
return $this; |
466
|
|
|
} |
467
|
|
|
|
468
|
|
|
/** |
469
|
|
|
* Rename index (only if index exists). |
470
|
|
|
* |
471
|
|
|
* @param array $columns Index forming columns. |
472
|
|
|
* @param string $name New index name. |
473
|
|
|
* |
474
|
|
|
* @return self |
475
|
|
|
* |
476
|
|
|
* @throws SchemaException |
477
|
|
|
*/ |
478
|
|
|
public function renameIndex(array $columns, string $name): AbstractTable |
479
|
|
|
{ |
480
|
|
|
if ($this->hasIndex($columns)) { |
481
|
|
|
throw new SchemaException( |
482
|
|
|
"Undefined index ['" . join("', '", $columns) . "'] in '{$this->getName()}'" |
483
|
|
|
); |
484
|
|
|
} |
485
|
|
|
|
486
|
|
|
//Declaring new index name |
487
|
|
|
$this->index($columns)->setName($name); |
488
|
|
|
|
489
|
|
|
return $this; |
490
|
|
|
} |
491
|
|
|
|
492
|
|
|
/** |
493
|
|
|
* Drop column by it's name. |
494
|
|
|
* |
495
|
|
|
* @param string $column |
496
|
|
|
* |
497
|
|
|
* @return self |
498
|
|
|
* |
499
|
|
|
* @throws SchemaException |
500
|
|
|
*/ |
501
|
|
View Code Duplication |
public function dropColumn(string $column): AbstractTable |
|
|
|
|
502
|
|
|
{ |
503
|
|
|
if (empty($schema = $this->current->findColumn($column))) { |
504
|
|
|
throw new SchemaException("Undefined column '{$column}' in '{$this->getName()}'"); |
505
|
|
|
} |
506
|
|
|
|
507
|
|
|
//Dropping column from current schema |
508
|
|
|
$this->current->forgetColumn($schema); |
509
|
|
|
|
510
|
|
|
return $this; |
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
/** |
514
|
|
|
* Drop index by it's forming columns. |
515
|
|
|
* |
516
|
|
|
* @param array $columns |
517
|
|
|
* |
518
|
|
|
* @return self |
519
|
|
|
* |
520
|
|
|
* @throws SchemaException |
521
|
|
|
*/ |
522
|
|
|
public function dropIndex(array $columns): AbstractTable |
523
|
|
|
{ |
524
|
|
|
if (empty($schema = $this->current->findIndex($columns))) { |
525
|
|
|
throw new SchemaException( |
526
|
|
|
"Undefined index ['" . join("', '", $columns) . "'] in '{$this->getName()}'" |
527
|
|
|
); |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
//Dropping index from current schema |
531
|
|
|
$this->current->forgetIndex($schema); |
532
|
|
|
|
533
|
|
|
return $this; |
534
|
|
|
} |
535
|
|
|
|
536
|
|
|
/** |
537
|
|
|
* Drop foreign key by it's name. |
538
|
|
|
* |
539
|
|
|
* @param string $column |
540
|
|
|
* |
541
|
|
|
* @return self |
542
|
|
|
* |
543
|
|
|
* @throws SchemaException |
544
|
|
|
*/ |
545
|
|
View Code Duplication |
public function dropForeign($column): AbstractTable |
|
|
|
|
546
|
|
|
{ |
547
|
|
|
if (!empty($schema = $this->current->findForeign($column))) { |
548
|
|
|
throw new SchemaException( |
549
|
|
|
"Undefined FK on '{$column}' in '{$this->getName()}'" |
550
|
|
|
); |
551
|
|
|
} |
552
|
|
|
|
553
|
|
|
//Dropping foreign from current schema |
554
|
|
|
$this->current->forgetForeign($schema); |
|
|
|
|
555
|
|
|
|
556
|
|
|
return $this; |
557
|
|
|
} |
558
|
|
|
|
559
|
|
|
/** |
560
|
|
|
* Reset table state to new form. |
561
|
|
|
* |
562
|
|
|
* @param TableState $state Use null to flush table schema. |
563
|
|
|
* |
564
|
|
|
* @return self|$this |
565
|
|
|
*/ |
566
|
|
|
public function setState(TableState $state = null): AbstractTable |
567
|
|
|
{ |
568
|
|
|
$this->current = new TableState($this->initial->getName()); |
569
|
|
|
|
570
|
|
|
if (!empty($state)) { |
571
|
|
|
$this->current->setName($state->getName()); |
572
|
|
|
$this->current->syncState($state); |
573
|
|
|
} |
574
|
|
|
|
575
|
|
|
return $this; |
576
|
|
|
} |
577
|
|
|
|
578
|
|
|
/** |
579
|
|
|
* Reset table state to it initial form. |
580
|
|
|
* |
581
|
|
|
* @return self|$this |
582
|
|
|
*/ |
583
|
|
|
public function resetState(): AbstractTable |
584
|
|
|
{ |
585
|
|
|
$this->setState($this->initial); |
586
|
|
|
|
587
|
|
|
return $this; |
588
|
|
|
} |
589
|
|
|
|
590
|
|
|
/** |
591
|
|
|
* Save table schema including every column, index, foreign key creation/altering. If table |
592
|
|
|
* does not exist it must be created. If table declared as dropped it will be removed from |
593
|
|
|
* the database. |
594
|
|
|
* |
595
|
|
|
* @param int $behaviour Operation to be performed while table being saved. In some |
596
|
|
|
* cases (when multiple tables are being updated) it is |
597
|
|
|
* reasonable to drop foreing keys and indexes prior to |
598
|
|
|
* dropping related columns. See sync bus class to get more |
599
|
|
|
* details. |
600
|
|
|
* @param LoggerInterface $logger Optional, aggregates messages for data syncing. |
601
|
|
|
* |
602
|
|
|
* @throws HandlerException |
603
|
|
|
*/ |
604
|
|
|
public function save(int $behaviour = AbstractHandler::DO_ALL, LoggerInterface $logger = null) |
605
|
|
|
{ |
606
|
|
|
//We need an instance of Handler of dbal operations |
607
|
|
|
$handler = $this->driver->getHandler($logger); |
608
|
|
|
|
609
|
|
|
if ($this->status == self::STATUS_DROPPED) { |
610
|
|
|
//We don't need syncer for this operation |
611
|
|
|
$handler->dropTable($this); |
612
|
|
|
|
613
|
|
|
//Flushing status |
614
|
|
|
$this->status = self::STATUS_NEW; |
615
|
|
|
|
616
|
|
|
return; |
617
|
|
|
} |
618
|
|
|
|
619
|
|
|
//Ensure that columns references to valid indexes and et |
620
|
|
|
$prepared = $this->prepareSchema(); |
621
|
|
|
|
622
|
|
|
if ($this->status == self::STATUS_NEW) { |
623
|
|
|
//Executing table creation |
624
|
|
|
$handler->createTable($prepared); |
625
|
|
|
$this->status = self::STATUS_EXISTS; |
626
|
|
|
} else { |
627
|
|
|
//Executing table syncing |
628
|
|
|
if ($this->hasChanges()) { |
629
|
|
|
$handler->syncTable($prepared, $behaviour); |
630
|
|
|
} |
631
|
|
|
|
632
|
|
|
$prepared->status = self::STATUS_EXISTS; |
633
|
|
|
} |
634
|
|
|
|
635
|
|
|
//Syncing our schemas |
636
|
|
|
$this->initial->syncState($prepared->current); |
637
|
|
|
} |
638
|
|
|
|
639
|
|
|
/** |
640
|
|
|
* Ensure that no wrong indexes left in table. |
641
|
|
|
* |
642
|
|
|
* @return AbstractTable |
643
|
|
|
*/ |
644
|
|
|
protected function prepareSchema() |
645
|
|
|
{ |
646
|
|
|
//To make sure that no pre-sync modifications will be reflected on current table |
647
|
|
|
$target = clone $this; |
648
|
|
|
|
649
|
|
|
/* |
650
|
|
|
* In cases where columns are removed we have to automatically remove related indexes and |
651
|
|
|
* foreign keys. |
652
|
|
|
*/ |
653
|
|
|
foreach ($this->getComparator()->droppedColumns() as $column) { |
654
|
|
|
foreach ($target->getIndexes() as $index) { |
655
|
|
|
if (in_array($column->getName(), $index->getColumns())) { |
656
|
|
|
$target->current->forgetIndex($index); |
657
|
|
|
} |
658
|
|
|
} |
659
|
|
|
|
660
|
|
|
foreach ($target->getForeigns() as $foreign) { |
661
|
|
|
if ($column->getName() == $foreign->getColumn()) { |
662
|
|
|
$target->current->forgetForeign($foreign); |
663
|
|
|
} |
664
|
|
|
} |
665
|
|
|
} |
666
|
|
|
|
667
|
|
|
//We also have to adjusts indexes and foreign keys |
668
|
|
|
foreach ($this->getComparator()->alteredColumns() as $pair) { |
669
|
|
|
/** |
670
|
|
|
* @var AbstractColumn $initial |
671
|
|
|
* @var AbstractColumn $name |
672
|
|
|
*/ |
673
|
|
|
list($name, $initial) = $pair; |
674
|
|
|
|
675
|
|
|
foreach ($target->getIndexes() as $index) { |
676
|
|
|
if (in_array($initial->getName(), $index->getColumns())) { |
677
|
|
|
$columns = $index->getColumns(); |
678
|
|
|
|
679
|
|
|
//Replacing column name |
680
|
|
|
foreach ($columns as &$column) { |
681
|
|
|
if ($column == $initial->getName()) { |
682
|
|
|
$column = $name->getName(); |
683
|
|
|
} |
684
|
|
|
|
685
|
|
|
unset($column); |
686
|
|
|
} |
687
|
|
|
|
688
|
|
|
$index->columns($columns); |
689
|
|
|
} |
690
|
|
|
} |
691
|
|
|
|
692
|
|
|
foreach ($target->getForeigns() as $foreign) { |
693
|
|
|
if ($initial->getName() == $foreign->getColumn()) { |
694
|
|
|
$foreign->column($name->getName()); |
695
|
|
|
} |
696
|
|
|
} |
697
|
|
|
} |
698
|
|
|
|
699
|
|
|
return $target; |
700
|
|
|
} |
701
|
|
|
|
702
|
|
|
/** |
703
|
|
|
* @return AbstractColumn|string |
704
|
|
|
*/ |
705
|
|
|
public function __toString(): string |
706
|
|
|
{ |
707
|
|
|
return $this->getName(); |
708
|
|
|
} |
709
|
|
|
|
710
|
|
|
/** |
711
|
|
|
* Cloning schemas as well. |
712
|
|
|
*/ |
713
|
|
|
public function __clone() |
714
|
|
|
{ |
715
|
|
|
$this->initial = clone $this->initial; |
716
|
|
|
$this->current = clone $this->current; |
717
|
|
|
} |
718
|
|
|
|
719
|
|
|
/** |
720
|
|
|
* @return array |
721
|
|
|
*/ |
722
|
|
|
public function __debugInfo() |
723
|
|
|
{ |
724
|
|
|
return [ |
725
|
|
|
'name' => $this->getName(), |
726
|
|
|
'primaryKeys' => $this->getPrimaryKeys(), |
727
|
|
|
'columns' => array_values($this->getColumns()), |
728
|
|
|
'indexes' => array_values($this->getIndexes()), |
729
|
|
|
'references' => array_values($this->getForeigns()), |
730
|
|
|
]; |
731
|
|
|
} |
732
|
|
|
|
733
|
|
|
/** |
734
|
|
|
* Populate table schema with values from database. |
735
|
|
|
* |
736
|
|
|
* @param TableState $state |
737
|
|
|
*/ |
738
|
|
|
protected function initSchema(TableState $state) |
739
|
|
|
{ |
740
|
|
|
foreach ($this->fetchColumns() as $column) { |
741
|
|
|
$state->registerColumn($column); |
|
|
|
|
742
|
|
|
} |
743
|
|
|
|
744
|
|
|
foreach ($this->fetchIndexes() as $index) { |
745
|
|
|
$state->registerIndex($index); |
|
|
|
|
746
|
|
|
} |
747
|
|
|
|
748
|
|
|
foreach ($this->fetchReferences() as $foreign) { |
749
|
|
|
$state->registerReference($foreign); |
|
|
|
|
750
|
|
|
} |
751
|
|
|
|
752
|
|
|
$state->setPrimaryKeys($this->fetchPrimaryKeys()); |
753
|
|
|
|
754
|
|
|
//DBMS specific initialization can be placed here |
755
|
|
|
} |
756
|
|
|
|
757
|
|
|
/** |
758
|
|
|
* Fetch index declarations from database. |
759
|
|
|
* |
760
|
|
|
* @return ColumnInterface[] |
761
|
|
|
*/ |
762
|
|
|
abstract protected function fetchColumns(): array; |
763
|
|
|
|
764
|
|
|
/** |
765
|
|
|
* Fetch index declarations from database. |
766
|
|
|
* |
767
|
|
|
* @return IndexInterface[] |
768
|
|
|
*/ |
769
|
|
|
abstract protected function fetchIndexes(): array; |
770
|
|
|
|
771
|
|
|
/** |
772
|
|
|
* Fetch references declaration from database. |
773
|
|
|
* |
774
|
|
|
* @return ReferenceInterface[] |
775
|
|
|
*/ |
776
|
|
|
abstract protected function fetchReferences(): array; |
777
|
|
|
|
778
|
|
|
/** |
779
|
|
|
* Fetch names of primary keys from table. |
780
|
|
|
* |
781
|
|
|
* @return array |
782
|
|
|
*/ |
783
|
|
|
abstract protected function fetchPrimaryKeys(): array; |
784
|
|
|
|
785
|
|
|
/** |
786
|
|
|
* Create column with a given name. |
787
|
|
|
* |
788
|
|
|
* @param string $name |
789
|
|
|
* |
790
|
|
|
* @return AbstractColumn |
791
|
|
|
*/ |
792
|
|
|
abstract protected function createColumn(string $name): AbstractColumn; |
793
|
|
|
|
794
|
|
|
/** |
795
|
|
|
* Create index for a given set of columns. |
796
|
|
|
* |
797
|
|
|
* @param string $name |
798
|
|
|
* |
799
|
|
|
* @return AbstractIndex |
800
|
|
|
*/ |
801
|
|
|
abstract protected function createIndex(string $name): AbstractIndex; |
802
|
|
|
|
803
|
|
|
/** |
804
|
|
|
* Create reference on a given column set. |
805
|
|
|
* |
806
|
|
|
* @param string $name |
807
|
|
|
* |
808
|
|
|
* @return AbstractReference |
809
|
|
|
*/ |
810
|
|
|
abstract protected function createForeign(string $name): AbstractReference; |
811
|
|
|
|
812
|
|
|
/** |
813
|
|
|
* Generate unique name for indexes and foreign keys. |
814
|
|
|
* |
815
|
|
|
* @param string $type |
816
|
|
|
* @param array $columns |
817
|
|
|
* |
818
|
|
|
* @return string |
819
|
|
|
*/ |
820
|
|
|
protected function createIdentifier(string $type, array $columns): string |
821
|
|
|
{ |
822
|
|
|
$name = $this->getName() . '_' . $type . '_' . join('_', $columns) . '_' . uniqid(); |
823
|
|
|
|
824
|
|
|
if (strlen($name) > 64) { |
825
|
|
|
//Many DBMS has limitations on identifier length |
826
|
|
|
$name = md5($name); |
827
|
|
|
} |
828
|
|
|
|
829
|
|
|
return $name; |
830
|
|
|
} |
831
|
|
|
} |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.