-
Notifications
You must be signed in to change notification settings - Fork 122
Expand file tree
/
Copy pathBaseMigration.php
More file actions
517 lines (464 loc) · 12.9 KB
/
BaseMigration.php
File metadata and controls
517 lines (464 loc) · 12.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
<?php
declare(strict_types=1);
/**
* MIT License
* For full license information, please view the LICENSE file that was distributed with this source code.
*/
namespace Migrations;
use Cake\Console\ConsoleIo;
use Cake\Database\Query;
use Cake\Database\Query\DeleteQuery;
use Cake\Database\Query\InsertQuery;
use Cake\Database\Query\SelectQuery;
use Cake\Database\Query\UpdateQuery;
use Migrations\Config\ConfigInterface;
use Migrations\Db\Adapter\AdapterInterface;
use Migrations\Db\Table;
use Migrations\Db\Table\ForeignKey;
use Migrations\Db\Table\Index;
use RuntimeException;
/**
* Base migration implementation
*
* Provides base functionality for migrations to extend
*/
class BaseMigration implements MigrationInterface
{
/**
* The Adapter instance
*
* @var \Migrations\Db\Adapter\AdapterInterface
*/
protected ?AdapterInterface $adapter = null;
/**
* The ConsoleIo instance
*
* @var \Cake\Console\ConsoleIo
*/
protected ?ConsoleIo $io = null;
/**
* The config instance.
*
* @var \Migrations\Config\ConfigInterface
*/
protected ?ConfigInterface $config;
/**
* List of all the table objects created by this migration
*
* @var array<\Migrations\Db\Table>
*/
protected array $tables = [];
/**
* Is migrating up prop
*
* @var bool
*/
protected bool $isMigratingUp = true;
/**
* The version number.
*
* @var int
*/
protected int $version;
/**
* Whether the tables created in this migration
* should auto-create an `id` field or not
*
* This option is global for all tables created in the migration file.
* If you set it to false, you have to manually add the primary keys for your
* tables using the Migrations\Table::addPrimaryKey() method
*
* @var bool
*/
public bool $autoId = true;
/**
* Constructor
*
* @param int|null $version The version this migration is (null for anonymous migrations)
*/
public function __construct(?int $version = null)
{
if ($version !== null) {
$this->validateVersion($version);
$this->version = $version;
}
}
/**
* {@inheritDoc}
*/
public function setAdapter(AdapterInterface $adapter)
{
$this->adapter = $adapter;
return $this;
}
/**
* {@inheritDoc}
*/
public function getAdapter(): AdapterInterface
{
if (!$this->adapter) {
throw new RuntimeException('Adapter not set.');
}
return $this->adapter;
}
/**
* {@inheritDoc}
*/
public function setIo(ConsoleIo $io)
{
$this->io = $io;
return $this;
}
/**
* {@inheritDoc}
*/
public function getIo(): ?ConsoleIo
{
return $this->io;
}
/**
* {@inheritDoc}
*/
public function getConfig(): ?ConfigInterface
{
return $this->config;
}
/**
* {@inheritDoc}
*/
public function setConfig(ConfigInterface $config)
{
$this->config = $config;
return $this;
}
/**
* {@inheritDoc}
*/
public function getName(): string
{
return static::class;
}
/**
* Sets the migration version number.
*
* @param int $version Version
* @return $this
*/
public function setVersion(int $version)
{
$this->validateVersion($version);
$this->version = $version;
return $this;
}
/**
* Gets the migration version number.
*
* @return int
*/
public function getVersion(): int
{
return $this->version;
}
/**
* Sets whether this migration is being applied or reverted
*
* @param bool $isMigratingUp True if the migration is being applied
* @return $this
*/
public function setMigratingUp(bool $isMigratingUp)
{
$this->isMigratingUp = $isMigratingUp;
return $this;
}
/**
* Hook method to decide if this migration should use transactions
*
* By default, if your driver supports transactions, a transaction will be opened
* before the migration begins, and commit when the migration completes.
*
* @return bool
*/
public function useTransactions(): bool
{
return $this->getAdapter()->hasTransactions();
}
/**
* Gets whether this migration is being applied or reverted.
* True means that the migration is being applied.
*
* @return bool
*/
public function isMigratingUp(): bool
{
return $this->isMigratingUp;
}
/**
* Executes a SQL statement and returns the number of affected rows.
*
* @param string $sql SQL
* @param array $params parameters to use for prepared query
* @return int
*/
public function execute(string $sql, array $params = []): int
{
return $this->getAdapter()->execute($sql, $params);
}
/**
* Executes a SQL statement.
*
* The return type depends on the underlying adapter being used. To improve
* IDE auto-completion possibility, you can overwrite the query method
* phpDoc in your (typically custom abstract parent) migration class, where
* you can set the return type by the adapter in your current use.
*
* @param string $sql SQL
* @param array $params parameters to use for prepared query
* @return mixed
*/
public function query(string $sql, array $params = []): mixed
{
return $this->getAdapter()->query($sql, $params);
}
/**
* Returns a new Query object that can be used to build complex SELECT, UPDATE, INSERT or DELETE
* queries and execute them against the current database.
*
* Queries executed through the query builder are always sent to the database, regardless of
* the dry-run settings.
*
* @see https://api.cakephp.org/5.2/class-Cake.Database.Query.html
* @param string $type Query
* @return \Cake\Database\Query
*/
public function getQueryBuilder(string $type): Query
{
return $this->getAdapter()->getQueryBuilder($type);
}
/**
* Returns a new SelectQuery object that can be used to build complex
* SELECT queries and execute them against the current database.
*
* Queries executed through the query builder are always sent to the database, regardless of
* the dry-run settings.
*
* @return \Cake\Database\Query\SelectQuery
*/
public function getSelectBuilder(): SelectQuery
{
return $this->getAdapter()->getSelectBuilder();
}
/**
* Returns a new InsertQuery object that can be used to build complex
* INSERT queries and execute them against the current database.
*
* Queries executed through the query builder are always sent to the database, regardless of
* the dry-run settings.
*
* @return \Cake\Database\Query\InsertQuery
*/
public function getInsertBuilder(): InsertQuery
{
return $this->getAdapter()->getInsertBuilder();
}
/**
* Returns a new UpdateQuery object that can be used to build complex
* UPDATE queries and execute them against the current database.
*
* Queries executed through the query builder are always sent to the database, regardless of
* the dry-run settings.
*
* @return \Cake\Database\Query\UpdateQuery
*/
public function getUpdateBuilder(): UpdateQuery
{
return $this->getAdapter()->getUpdateBuilder();
}
/**
* Returns a new DeleteQuery object that can be used to build complex
* DELETE queries and execute them against the current database.
*
* Queries executed through the query builder are always sent to the database, regardless of
* the dry-run settings.
*
* @return \Cake\Database\Query\DeleteQuery
*/
public function getDeleteBuilder(): DeleteQuery
{
return $this->getAdapter()->getDeleteBuilder();
}
/**
* Executes a query and returns only one row as an array.
*
* @param string $sql SQL
* @return array|false
*/
public function fetchRow(string $sql): array|false
{
return $this->getAdapter()->fetchRow($sql);
}
/**
* Executes a query and returns an array of rows.
*
* @param string $sql SQL
* @return array
*/
public function fetchAll(string $sql): array
{
return $this->getAdapter()->fetchAll($sql);
}
/**
* Create a new database.
*
* @param string $name Database Name
* @param array<string, mixed> $options Options
* @return void
*/
public function createDatabase(string $name, array $options): void
{
$this->getAdapter()->createDatabase($name, $options);
}
/**
* Drop a database.
*
* @param string $name Database Name
* @return void
*/
public function dropDatabase(string $name): void
{
$this->getAdapter()->dropDatabase($name);
}
/**
* Creates schema.
*
* This will throw an error for adapters that do not support schemas.
*
* @param string $name Schema name
* @return void
* @throws \BadMethodCallException
*/
public function createSchema(string $name): void
{
$this->getAdapter()->createSchema($name);
}
/**
* Drops schema.
*
* This will throw an error for adapters that do not support schemas.
*
* @param string $name Schema name
* @return void
* @throws \BadMethodCallException
*/
public function dropSchema(string $name): void
{
$this->getAdapter()->dropSchema($name);
}
/**
* Checks to see if a table exists.
*
* @param string $tableName Table name
* @return bool
*/
public function hasTable(string $tableName): bool
{
return $this->getAdapter()->hasTable($tableName);
}
/**
* Returns an instance of the <code>\Table</code> class.
*
* You can use this class to create and manipulate tables.
*
* @param string $tableName Table name
* @param array<string, mixed> $options Options
* @return \Migrations\Db\Table
*/
public function table(string $tableName, array $options = []): Table
{
if ($this->autoId === false) {
$options['id'] = false;
}
$table = new Table($tableName, $options, $this->getAdapter());
$this->tables[] = $table;
return $table;
}
/**
* Create a new ForeignKey object.
*
* @param string|string[] $columns Columns
* @return \Migrations\Db\Table\ForeignKey
*/
public function foreignKey(string|array $columns): ForeignKey
{
return (new ForeignKey())->setColumns($columns);
}
/**
* Create a new Index object.
*
* @param string|string[] $columns Columns
* @return \Migrations\Db\Table\Index
*/
public function index(string|array $columns): Index
{
return (new Index())->setColumns($columns);
}
/**
* Perform checks on the migration, printing a warning
* if there are potential problems.
*
* @return void
*/
public function preFlightCheck(): void
{
if (method_exists($this, MigrationInterface::CHANGE)) {
if (
method_exists($this, MigrationInterface::UP) ||
method_exists($this, MigrationInterface::DOWN)
) {
$io = $this->getIo();
if ($io) {
$io->out(
'<comment>warning</comment> Migration contains both change() and up()/down() methods.' .
' <warning>Ignoring up() and down()</warning>.',
);
}
}
}
}
/**
* Perform checks on the migration after completion
*
* Right now, the only check is whether all changes were committed
*
* @return void
*/
public function postFlightCheck(): void
{
foreach ($this->tables as $table) {
if ($table->hasPendingActions()) {
throw new RuntimeException(sprintf('Migration %s_%s has pending actions after execution!', $this->getVersion(), $this->getName()));
}
}
}
/**
* {@inheritDoc}
*/
public function shouldExecute(): bool
{
return true;
}
/**
* Makes sure the version int is within range for valid datetime.
* This is required to have a meaningful order in the overview.
*
* @param int $version Version
* @return void
*/
protected function validateVersion(int $version): void
{
$length = strlen((string)$version);
if ($length === 14) {
return;
}
throw new RuntimeException('Invalid version `' . $version . '`, should be in format `YYYYMMDDHHMMSS` (length of 14).');
}
}