1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Spiral Framework. |
4
|
|
|
* |
5
|
|
|
* @license MIT |
6
|
|
|
* @author Anton Titov (Wolfy-J) |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace Spiral\Database\Schemas; |
10
|
|
|
|
11
|
|
|
use Spiral\Database\Entities\Driver; |
12
|
|
|
use Spiral\Database\Schemas\Prototypes\AbstractElement; |
13
|
|
|
use Spiral\Database\Exceptions\SchemaException; |
14
|
|
|
use Spiral\Database\Injections\Fragment; |
15
|
|
|
use Spiral\Database\Injections\FragmentInterface; |
16
|
|
|
use Spiral\Database\Schemas\ColumnInterface; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Abstract column schema with read (see ColumnInterface) and write abilities. Must be implemented |
20
|
|
|
* by driver to support DBMS specific syntax and creation rules. |
21
|
|
|
* |
22
|
|
|
* Shortcuts for various column types: |
23
|
|
|
* |
24
|
|
|
* @method AbstractColumn|$this boolean() |
25
|
|
|
* @method AbstractColumn|$this integer() |
26
|
|
|
* @method AbstractColumn|$this tinyInteger() |
27
|
|
|
* @method AbstractColumn|$this bigInteger() |
28
|
|
|
* @method AbstractColumn|$this text() |
29
|
|
|
* @method AbstractColumn|$this tinyText() |
30
|
|
|
* @method AbstractColumn|$this longText() |
31
|
|
|
* @method AbstractColumn|$this double() |
32
|
|
|
* @method AbstractColumn|$this float() |
33
|
|
|
* @method AbstractColumn|$this datetime() |
34
|
|
|
* @method AbstractColumn|$this date() |
35
|
|
|
* @method AbstractColumn|$this time() |
36
|
|
|
* @method AbstractColumn|$this timestamp() |
37
|
|
|
* @method AbstractColumn|$this binary() |
38
|
|
|
* @method AbstractColumn|$this tinyBinary() |
39
|
|
|
* @method AbstractColumn|$this longBinary() |
40
|
|
|
* @method AbstractColumn|$this json() |
41
|
|
|
*/ |
42
|
|
|
abstract class AbstractColumn extends AbstractElement implements ColumnInterface |
43
|
|
|
{ |
44
|
|
|
/** |
45
|
|
|
* PHP types for phpType() method. |
46
|
|
|
*/ |
47
|
|
|
const INT = 'int'; |
48
|
|
|
const BOOL = 'bool'; |
49
|
|
|
const STRING = 'string'; |
50
|
|
|
const FLOAT = 'float'; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Abstract type aliases (for consistency). |
54
|
|
|
* |
55
|
|
|
* @var array |
56
|
|
|
*/ |
57
|
|
|
private $aliases = [ |
58
|
|
|
'int' => 'integer', |
59
|
|
|
'bigint' => 'bigInteger', |
60
|
|
|
'incremental' => 'primary', |
61
|
|
|
'bigIncremental' => 'bigPrimary', |
62
|
|
|
'bool' => 'boolean', |
63
|
|
|
'blob' => 'binary', |
64
|
|
|
]; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Association list between abstract types and native PHP types. Every non listed type will be |
68
|
|
|
* converted into string. |
69
|
|
|
* |
70
|
|
|
* @invisible |
71
|
|
|
* |
72
|
|
|
* @var array |
73
|
|
|
*/ |
74
|
|
|
private $phpMapping = [ |
75
|
|
|
self::INT => ['primary', 'bigPrimary', 'integer', 'tinyInteger', 'bigInteger'], |
76
|
|
|
self::BOOL => ['boolean'], |
77
|
|
|
self::FLOAT => ['double', 'float', 'decimal'], |
78
|
|
|
]; |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Mapping between abstract type and internal database type with it's options. Multiple abstract |
82
|
|
|
* types can map into one database type, this implementation allows us to equalize two columns |
83
|
|
|
* if they have different abstract types but same database one. Must be declared by DBMS |
84
|
|
|
* specific implementation. |
85
|
|
|
* |
86
|
|
|
* Example: |
87
|
|
|
* integer => array('type' => 'int', 'size' => 1), |
88
|
|
|
* boolean => array('type' => 'tinyint', 'size' => 1) |
89
|
|
|
* |
90
|
|
|
* @invisible |
91
|
|
|
* |
92
|
|
|
* @var array |
93
|
|
|
*/ |
94
|
|
|
protected $mapping = [ |
95
|
|
|
//Primary sequences |
96
|
|
|
'primary' => null, |
97
|
|
|
'bigPrimary' => null, |
98
|
|
|
|
99
|
|
|
//Enum type (mapped via method) |
100
|
|
|
'enum' => null, |
101
|
|
|
|
102
|
|
|
//Logical types |
103
|
|
|
'boolean' => null, |
104
|
|
|
|
105
|
|
|
//Integer types (size can always be changed with size method), longInteger has method alias |
106
|
|
|
//bigInteger |
107
|
|
|
'integer' => null, |
108
|
|
|
'tinyInteger' => null, |
109
|
|
|
'bigInteger' => null, |
110
|
|
|
|
111
|
|
|
//String with specified length (mapped via method) |
112
|
|
|
'string' => null, |
113
|
|
|
|
114
|
|
|
//Generic types |
115
|
|
|
'text' => null, |
116
|
|
|
'tinyText' => null, |
117
|
|
|
'longText' => null, |
118
|
|
|
|
119
|
|
|
//Real types |
120
|
|
|
'double' => null, |
121
|
|
|
'float' => null, |
122
|
|
|
|
123
|
|
|
//Decimal type (mapped via method) |
124
|
|
|
'decimal' => null, |
125
|
|
|
|
126
|
|
|
//Date and Time types |
127
|
|
|
'datetime' => null, |
128
|
|
|
'date' => null, |
129
|
|
|
'time' => null, |
130
|
|
|
'timestamp' => null, |
131
|
|
|
|
132
|
|
|
//Binary types |
133
|
|
|
'binary' => null, |
134
|
|
|
'tinyBinary' => null, |
135
|
|
|
'longBinary' => null, |
136
|
|
|
|
137
|
|
|
//Additional types |
138
|
|
|
'json' => null, |
139
|
|
|
]; |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Reverse mapping is responsible for generating abstact type based on database type and it's |
143
|
|
|
* options. Multiple database types can be mapped into one abstract type. |
144
|
|
|
* |
145
|
|
|
* @invisible |
146
|
|
|
* |
147
|
|
|
* @var array |
148
|
|
|
*/ |
149
|
|
|
protected $reverseMapping = [ |
150
|
|
|
'primary' => [], |
151
|
|
|
'bigPrimary' => [], |
152
|
|
|
'enum' => [], |
153
|
|
|
'boolean' => [], |
154
|
|
|
'integer' => [], |
155
|
|
|
'tinyInteger' => [], |
156
|
|
|
'bigInteger' => [], |
157
|
|
|
'string' => [], |
158
|
|
|
'text' => [], |
159
|
|
|
'tinyText' => [], |
160
|
|
|
'longText' => [], |
161
|
|
|
'double' => [], |
162
|
|
|
'float' => [], |
163
|
|
|
'decimal' => [], |
164
|
|
|
'datetime' => [], |
165
|
|
|
'date' => [], |
166
|
|
|
'time' => [], |
167
|
|
|
'timestamp' => [], |
168
|
|
|
'binary' => [], |
169
|
|
|
'tinyBinary' => [], |
170
|
|
|
'longBinary' => [], |
171
|
|
|
'json' => [], |
172
|
|
|
]; |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* DBMS specific column type. |
176
|
|
|
* |
177
|
|
|
* @var string |
178
|
|
|
*/ |
179
|
|
|
protected $type = ''; |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Indicates that column can contain null values. |
183
|
|
|
* |
184
|
|
|
* @var bool |
185
|
|
|
*/ |
186
|
|
|
protected $nullable = true; |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Default column value, may not be applied to some datatypes (for example to primary keys), |
190
|
|
|
* should follow type size and other options. |
191
|
|
|
* |
192
|
|
|
* @var mixed |
193
|
|
|
*/ |
194
|
|
|
protected $defaultValue = null; |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* Column type size, can have different meanings for different datatypes. |
198
|
|
|
* |
199
|
|
|
* @var int |
200
|
|
|
*/ |
201
|
|
|
protected $size = 0; |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Precision of column, applied only for "decimal" type. |
205
|
|
|
* |
206
|
|
|
* @var int |
207
|
|
|
*/ |
208
|
|
|
protected $precision = 0; |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* Scale of column, applied only for "decimal" type. |
212
|
|
|
* |
213
|
|
|
* @var int |
214
|
|
|
*/ |
215
|
|
|
protected $scale = 0; |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* List of allowed enum values. |
219
|
|
|
* |
220
|
|
|
* @var array |
221
|
|
|
*/ |
222
|
|
|
protected $enumValues = []; |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* {@inheritdoc} |
226
|
|
|
*/ |
227
|
|
|
public function getType() |
228
|
|
|
{ |
229
|
|
|
return $this->type; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
/** |
233
|
|
|
* {@inheritdoc} |
234
|
|
|
*/ |
235
|
|
|
public function phpType() |
236
|
|
|
{ |
237
|
|
|
$schemaType = $this->abstractType(); |
238
|
|
|
foreach ($this->phpMapping as $phpType => $candidates) { |
239
|
|
|
if (in_array($schemaType, $candidates)) { |
240
|
|
|
return $phpType; |
241
|
|
|
} |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
return self::STRING; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* {@inheritdoc} |
249
|
|
|
*/ |
250
|
|
|
public function getSize() |
251
|
|
|
{ |
252
|
|
|
return $this->size; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* {@inheritdoc} |
257
|
|
|
*/ |
258
|
|
|
public function getPrecision() |
259
|
|
|
{ |
260
|
|
|
return $this->precision; |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
/** |
264
|
|
|
* {@inheritdoc} |
265
|
|
|
*/ |
266
|
|
|
public function getScale() |
267
|
|
|
{ |
268
|
|
|
return $this->scale; |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
/** |
272
|
|
|
* {@inheritdoc} |
273
|
|
|
*/ |
274
|
|
|
public function isNullable() |
275
|
|
|
{ |
276
|
|
|
return $this->nullable; |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
/** |
280
|
|
|
* {@inheritdoc} |
281
|
|
|
*/ |
282
|
|
|
public function hasDefaultValue() |
283
|
|
|
{ |
284
|
|
|
return !is_null($this->defaultValue); |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
/** |
288
|
|
|
* {@inheritdoc} |
289
|
|
|
*/ |
290
|
|
|
public function getDefaultValue() |
291
|
|
|
{ |
292
|
|
|
if (!$this->hasDefaultValue()) { |
293
|
|
|
return null; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
if ($this->defaultValue instanceof FragmentInterface) { |
297
|
|
|
return $this->defaultValue; |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
if (in_array($this->abstractType(), ['time', 'date', 'datetime', 'timestamp'])) { |
301
|
|
|
|
302
|
|
|
//Driver specific now expression |
303
|
|
|
$nowExpression = $this->table->driver()->nowExpression(); |
304
|
|
|
if (strtolower($this->defaultValue) == strtolower($nowExpression)) { |
305
|
|
|
return new Fragment($this->defaultValue); |
306
|
|
|
} |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
switch ($this->phpType()) { |
310
|
|
|
case 'int': |
311
|
|
|
return (int)$this->defaultValue; |
312
|
|
|
case 'float': |
313
|
|
|
return (float)$this->defaultValue; |
314
|
|
|
case 'bool': |
315
|
|
|
if (strtolower($this->defaultValue) == 'false') { |
316
|
|
|
return false; |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
return (bool)$this->defaultValue; |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
return (string)$this->defaultValue; |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* Get every associated column constraint names. |
327
|
|
|
* |
328
|
|
|
* @return array |
329
|
|
|
*/ |
330
|
|
|
public function getConstraints() |
331
|
|
|
{ |
332
|
|
|
return []; |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
/** |
336
|
|
|
* Get allowed enum values. |
337
|
|
|
* |
338
|
|
|
* @return array |
339
|
|
|
*/ |
340
|
|
|
public function getEnumValues() |
341
|
|
|
{ |
342
|
|
|
return $this->enumValues; |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* DBMS specific reverse mapping must map database specific type into limited set of abstract |
347
|
|
|
* types. |
348
|
|
|
* |
349
|
|
|
* @return string |
350
|
|
|
*/ |
351
|
|
|
public function abstractType() |
352
|
|
|
{ |
353
|
|
|
foreach ($this->reverseMapping as $type => $candidates) { |
354
|
|
|
foreach ($candidates as $candidate) { |
355
|
|
|
if (is_string($candidate)) { |
356
|
|
|
if (strtolower($candidate) == strtolower($this->type)) { |
357
|
|
|
return $type; |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
continue; |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
if (strtolower($candidate['type']) != strtolower($this->type)) { |
364
|
|
|
continue; |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
foreach ($candidate as $option => $required) { |
368
|
|
|
if ($option == 'type') { |
369
|
|
|
continue; |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
if ($this->{$option} != $required) { |
373
|
|
|
continue 2; |
374
|
|
|
} |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
return $type; |
378
|
|
|
} |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
return 'unknown'; |
382
|
|
|
} |
383
|
|
|
|
384
|
|
|
/** |
385
|
|
|
* Give column new abstract type. DBMS specific implementation must map provided type into one |
386
|
|
|
* of internal database values. |
387
|
|
|
* |
388
|
|
|
* Attention, changing type of existed columns in some databases has a lot of restrictions like |
389
|
|
|
* cross type conversions and etc. Try do not change column type without a reason. |
390
|
|
|
* |
391
|
|
|
* @param string $abstract Abstract or virtual type declared in mapping. |
392
|
|
|
* |
393
|
|
|
* @return $this |
394
|
|
|
* |
395
|
|
|
* @throws SchemaException |
396
|
|
|
*/ |
397
|
|
|
public function setType($abstract) |
398
|
|
|
{ |
399
|
|
|
if (isset($this->aliases[$abstract])) { |
400
|
|
|
$abstract = $this->aliases[$abstract]; |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
if (!isset($this->mapping[$abstract])) { |
404
|
|
|
throw new SchemaException("Undefined abstract/virtual type '{$abstract}'"); |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
//Resetting all values to default state. |
408
|
|
|
$this->size = $this->precision = $this->scale = 0; |
409
|
|
|
$this->enumValues = []; |
410
|
|
|
|
411
|
|
|
if (is_string($this->mapping[$abstract])) { |
412
|
|
|
$this->type = $this->mapping[$abstract]; |
413
|
|
|
|
414
|
|
|
return $this; |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
//Additional type options |
418
|
|
|
foreach ($this->mapping[$abstract] as $property => $value) { |
419
|
|
|
$this->{$property} = $value; |
420
|
|
|
} |
421
|
|
|
|
422
|
|
|
return $this; |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
/** |
426
|
|
|
* Set column nullable/not nullable. |
427
|
|
|
* |
428
|
|
|
* @param bool $nullable |
429
|
|
|
* |
430
|
|
|
* @return $this |
431
|
|
|
*/ |
432
|
|
|
public function nullable($nullable = true) |
433
|
|
|
{ |
434
|
|
|
$this->nullable = $nullable; |
435
|
|
|
|
436
|
|
|
return $this; |
437
|
|
|
} |
438
|
|
|
|
439
|
|
|
/** |
440
|
|
|
* Change column default value (can be forbidden for some column types). |
441
|
|
|
* Use Database::TIMESTAMP_NOW to use driver specific NOW() function. |
442
|
|
|
* |
443
|
|
|
* @param mixed $value |
444
|
|
|
* |
445
|
|
|
* @return $this |
446
|
|
|
*/ |
447
|
|
|
public function defaultValue($value) |
448
|
|
|
{ |
449
|
|
|
$this->defaultValue = $value; |
450
|
|
|
if ( |
451
|
|
|
$this->abstractType() == 'timestamp' |
452
|
|
|
&& strtolower($value) == strtolower(Driver::TIMESTAMP_NOW) |
453
|
|
|
) { |
454
|
|
|
$this->defaultValue = $this->table->driver()->nowExpression(); |
455
|
|
|
} |
456
|
|
|
|
457
|
|
|
return $this; |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
/** |
461
|
|
|
* Set column as primary key and register it in parent table primary key list. |
462
|
|
|
* |
463
|
|
|
* @see TableSchema::setPrimaryKeys() |
464
|
|
|
* |
465
|
|
|
* @return $this |
466
|
|
|
*/ |
467
|
|
View Code Duplication |
public function primary() |
|
|
|
|
468
|
|
|
{ |
469
|
|
|
if (!in_array($this->name, $this->table->getPrimaryKeys())) { |
470
|
|
|
$this->table->setPrimaryKeys([$this->name]); |
471
|
|
|
} |
472
|
|
|
|
473
|
|
|
return $this->setType('primary'); |
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
/** |
477
|
|
|
* Set column as big primary key and register it in parent table primary key list. |
478
|
|
|
* |
479
|
|
|
* @see TableSchema::setPrimaryKeys() |
480
|
|
|
* |
481
|
|
|
* @return $this |
482
|
|
|
*/ |
483
|
|
View Code Duplication |
public function bigPrimary() |
|
|
|
|
484
|
|
|
{ |
485
|
|
|
if (!in_array($this->name, $this->table->getPrimaryKeys())) { |
486
|
|
|
$this->table->setPrimaryKeys([$this->name]); |
487
|
|
|
} |
488
|
|
|
|
489
|
|
|
return $this->setType('bigPrimary'); |
490
|
|
|
} |
491
|
|
|
|
492
|
|
|
/** |
493
|
|
|
* Set column as enum type and specify set of allowed values. Most of drivers will emulate enums |
494
|
|
|
* using column constraints. |
495
|
|
|
* |
496
|
|
|
* Examples: |
497
|
|
|
* $table->status->enum(['active', 'disabled']); |
498
|
|
|
* $table->status->enum('active', 'disabled'); |
499
|
|
|
* |
500
|
|
|
* @param string|array $values Enum values (array or comma separated). String values only. |
501
|
|
|
* |
502
|
|
|
* @return $this |
503
|
|
|
*/ |
504
|
|
|
public function enum($values) |
505
|
|
|
{ |
506
|
|
|
$this->setType('enum'); |
507
|
|
|
$this->enumValues = array_map('strval', is_array($values) ? $values : func_get_args()); |
508
|
|
|
|
509
|
|
|
return $this; |
510
|
|
|
} |
511
|
|
|
|
512
|
|
|
/** |
513
|
|
|
* Set column type as string with limited size. Maximum allowed size is 255 bytes, use "text" |
514
|
|
|
* abstract types for longer strings. |
515
|
|
|
* |
516
|
|
|
* Strings are perfect type to store email addresses as it big enough to store valid address |
517
|
|
|
* and |
518
|
|
|
* can be covered with unique index. |
519
|
|
|
* |
520
|
|
|
* @link http://stackoverflow.com/questions/386294/what-is-the-maximum-length-of-a-valid-email-address |
521
|
|
|
* |
522
|
|
|
* @param int $size Max string length. |
523
|
|
|
* |
524
|
|
|
* @return $this |
525
|
|
|
* |
526
|
|
|
* @throws SchemaException |
527
|
|
|
*/ |
528
|
|
|
public function string($size = 255) |
529
|
|
|
{ |
530
|
|
|
$this->setType('string'); |
531
|
|
|
|
532
|
|
|
if ($size > 255) { |
533
|
|
|
throw new SchemaException( |
534
|
|
|
"String size can't exceed 255 characters. Use text instead" |
535
|
|
|
); |
536
|
|
|
} |
537
|
|
|
|
538
|
|
|
if ($size < 0) { |
539
|
|
|
throw new SchemaException('Invalid string length value'); |
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
$this->size = (int)$size; |
543
|
|
|
|
544
|
|
|
return $this; |
545
|
|
|
} |
546
|
|
|
|
547
|
|
|
/** |
548
|
|
|
* Set column type as decimal with specific precision and scale. |
549
|
|
|
* |
550
|
|
|
* @param int $precision |
551
|
|
|
* @param int $scale |
552
|
|
|
* |
553
|
|
|
* @return $this |
554
|
|
|
* |
555
|
|
|
* @throws SchemaException |
556
|
|
|
*/ |
557
|
|
|
public function decimal($precision, $scale = 0) |
558
|
|
|
{ |
559
|
|
|
$this->setType('decimal'); |
560
|
|
|
|
561
|
|
|
if (empty($precision)) { |
562
|
|
|
throw new SchemaException('Invalid precision value'); |
563
|
|
|
} |
564
|
|
|
|
565
|
|
|
$this->precision = (int)$precision; |
566
|
|
|
$this->scale = (int)$scale; |
567
|
|
|
|
568
|
|
|
return $this; |
569
|
|
|
} |
570
|
|
|
|
571
|
|
|
/** |
572
|
|
|
* Create/get table index associated with this column. |
573
|
|
|
* |
574
|
|
|
* @return AbstractIndex |
575
|
|
|
* |
576
|
|
|
* @throws SchemaException |
577
|
|
|
*/ |
578
|
|
|
public function index() |
579
|
|
|
{ |
580
|
|
|
return $this->table->index($this->name); |
581
|
|
|
} |
582
|
|
|
|
583
|
|
|
/** |
584
|
|
|
* Create/get table index associated with this column. Index type will be forced as UNIQUE. |
585
|
|
|
* |
586
|
|
|
* @return AbstractIndex |
587
|
|
|
* |
588
|
|
|
* @throws SchemaException |
589
|
|
|
*/ |
590
|
|
|
public function unique() |
591
|
|
|
{ |
592
|
|
|
return $this->table->unique($this->name); |
593
|
|
|
} |
594
|
|
|
|
595
|
|
|
/** |
596
|
|
|
* Create/get foreign key schema associated with column and referenced foreign table and column. |
597
|
|
|
* Make sure local and outer column types are identical. |
598
|
|
|
* |
599
|
|
|
* @param string $table Foreign table name. |
600
|
|
|
* @param string $column Foreign column name (id by default). |
601
|
|
|
* |
602
|
|
|
* @return AbstractReference |
603
|
|
|
* |
604
|
|
|
* @throws SchemaException |
605
|
|
|
*/ |
606
|
|
|
public function references($table, $column = 'id') |
607
|
|
|
{ |
608
|
|
|
if ($this->phpType() != self::INT) { |
609
|
|
|
throw new SchemaException( |
610
|
|
|
'Only numeric types can be defined with foreign key constraint.' |
611
|
|
|
); |
612
|
|
|
} |
613
|
|
|
|
614
|
|
|
return $this->table->foreign($this->name)->references($table, $column); |
615
|
|
|
} |
616
|
|
|
|
617
|
|
|
/** |
618
|
|
|
* Compile column create statement. |
619
|
|
|
* |
620
|
|
|
* @return string |
621
|
|
|
*/ |
622
|
|
|
public function sqlStatement() |
623
|
|
|
{ |
624
|
|
|
$statement = [$this->getName(true), $this->type]; |
625
|
|
|
|
626
|
|
|
if ($this->abstractType() == 'enum') { |
627
|
|
|
//Enum specific column options |
628
|
|
|
if (!empty($enumDefinition = $this->prepareEnum())) { |
629
|
|
|
$statement[] = $enumDefinition; |
630
|
|
|
} |
631
|
|
|
} elseif (!empty($this->precision)) { |
632
|
|
|
$statement[] = "({$this->precision}, {$this->scale})"; |
633
|
|
|
} elseif (!empty($this->size)) { |
634
|
|
|
$statement[] = "({$this->size})"; |
635
|
|
|
} |
636
|
|
|
|
637
|
|
|
$statement[] = $this->nullable ? 'NULL' : 'NOT NULL'; |
638
|
|
|
|
639
|
|
|
if ($this->defaultValue !== null) { |
640
|
|
|
$statement[] = "DEFAULT {$this->prepareDefault()}"; |
641
|
|
|
} |
642
|
|
|
|
643
|
|
|
return implode(' ', $statement); |
644
|
|
|
} |
645
|
|
|
|
646
|
|
|
/** |
647
|
|
|
* Must compare two instances of AbstractColumn. |
648
|
|
|
* |
649
|
|
|
* @param self $initial |
650
|
|
|
* |
651
|
|
|
* @return bool |
652
|
|
|
*/ |
653
|
|
|
public function compare(self $initial) |
654
|
|
|
{ |
655
|
|
|
$normalized = clone $initial; |
656
|
|
|
$normalized->declared = $this->declared; |
657
|
|
|
|
658
|
|
|
if ($this == $normalized) { |
659
|
|
|
return true; |
660
|
|
|
} |
661
|
|
|
|
662
|
|
|
$columnVars = get_object_vars($this); |
663
|
|
|
$dbColumnVars = get_object_vars($normalized); |
664
|
|
|
|
665
|
|
|
$difference = []; |
666
|
|
|
foreach ($columnVars as $name => $value) { |
667
|
|
|
if ($name == 'defaultValue') { |
668
|
|
|
//Default values has to compared using type-casted value |
669
|
|
|
if ($this->getDefaultValue() != $initial->getDefaultValue()) { |
670
|
|
|
$difference[] = $name; |
671
|
|
|
} |
672
|
|
|
|
673
|
|
|
continue; |
674
|
|
|
} |
675
|
|
|
|
676
|
|
|
if ($value != $dbColumnVars[$name]) { |
677
|
|
|
$difference[] = $name; |
678
|
|
|
} |
679
|
|
|
} |
680
|
|
|
|
681
|
|
|
return empty($difference); |
682
|
|
|
} |
683
|
|
|
|
684
|
|
|
/** |
685
|
|
|
* Shortcut for AbstractColumn->type() method. |
686
|
|
|
* |
687
|
|
|
* @param string $type Abstract type. |
688
|
|
|
* @param array $arguments Not used. |
689
|
|
|
* |
690
|
|
|
* @return $this |
691
|
|
|
*/ |
692
|
|
|
public function __call($type, array $arguments = []) |
693
|
|
|
{ |
694
|
|
|
return $this->setType($type); |
695
|
|
|
} |
696
|
|
|
|
697
|
|
|
/** |
698
|
|
|
* Simplified way to dump information. |
699
|
|
|
* |
700
|
|
|
* @return array |
701
|
|
|
*/ |
702
|
|
|
public function __debugInfo() |
703
|
|
|
{ |
704
|
|
|
$column = [ |
705
|
|
|
'name' => $this->name, |
706
|
|
|
'declared' => $this->declared, |
707
|
|
|
'type' => [ |
708
|
|
|
'database' => $this->type, |
709
|
|
|
'schema' => $this->abstractType(), |
710
|
|
|
'php' => $this->phpType(), |
711
|
|
|
], |
712
|
|
|
]; |
713
|
|
|
|
714
|
|
|
if (!empty($this->size)) { |
715
|
|
|
$column['size'] = $this->size; |
716
|
|
|
} |
717
|
|
|
|
718
|
|
|
if ($this->nullable) { |
719
|
|
|
$column['nullable'] = true; |
720
|
|
|
} |
721
|
|
|
|
722
|
|
|
if ($this->defaultValue !== null) { |
723
|
|
|
$column['defaultValue'] = $this->getDefaultValue(); |
724
|
|
|
} |
725
|
|
|
|
726
|
|
|
if ($this->abstractType() == 'enum') { |
727
|
|
|
$column['enumValues'] = $this->enumValues; |
728
|
|
|
} |
729
|
|
|
|
730
|
|
|
if ($this->abstractType() == 'decimal') { |
731
|
|
|
$column['precision'] = $this->precision; |
732
|
|
|
$column['scale'] = $this->scale; |
733
|
|
|
} |
734
|
|
|
|
735
|
|
|
return $column; |
736
|
|
|
} |
737
|
|
|
|
738
|
|
|
/** |
739
|
|
|
* Get database specific enum type definition options. |
740
|
|
|
* |
741
|
|
|
* @return string. |
|
|
|
|
742
|
|
|
*/ |
743
|
|
|
protected function prepareEnum() |
744
|
|
|
{ |
745
|
|
|
$enumValues = []; |
746
|
|
|
foreach ($this->enumValues as $value) { |
747
|
|
|
$enumValues[] = $this->table->driver()->getPDO()->quote($value); |
748
|
|
|
} |
749
|
|
|
|
750
|
|
|
if (!empty($enumValues)) { |
751
|
|
|
return '(' . implode(', ', $enumValues) . ')'; |
752
|
|
|
} |
753
|
|
|
|
754
|
|
|
return ''; |
755
|
|
|
} |
756
|
|
|
|
757
|
|
|
/** |
758
|
|
|
* Must return driver specific default value. |
759
|
|
|
* |
760
|
|
|
* @return string |
761
|
|
|
*/ |
762
|
|
|
protected function prepareDefault() |
763
|
|
|
{ |
764
|
|
|
if (($defaultValue = $this->getDefaultValue()) === null) { |
765
|
|
|
return 'NULL'; |
766
|
|
|
} |
767
|
|
|
|
768
|
|
|
if ($defaultValue instanceof FragmentInterface) { |
769
|
|
|
return $defaultValue->sqlStatement(); |
770
|
|
|
} |
771
|
|
|
|
772
|
|
|
if ($this->phpType() == 'bool') { |
773
|
|
|
return $defaultValue ? 'TRUE' : 'FALSE'; |
774
|
|
|
} |
775
|
|
|
|
776
|
|
|
if ($this->phpType() == 'float') { |
777
|
|
|
return sprintf('%F', $defaultValue); |
778
|
|
|
} |
779
|
|
|
|
780
|
|
|
if ($this->phpType() == 'int') { |
781
|
|
|
return $defaultValue; |
782
|
|
|
} |
783
|
|
|
|
784
|
|
|
return $this->table->driver()->getPDO()->quote($defaultValue); |
785
|
|
|
} |
786
|
|
|
} |
787
|
|
|
|
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.