1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
15: namespace Cake\Database\Schema;
16:
17: use Cake\Database\Connection;
18: use Cake\Database\Exception;
19: use Cake\Database\Type;
20:
21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31:
32: class TableSchema implements TableSchemaInterface, SqlGeneratorInterface
33: {
34:
35: 36: 37: 38: 39:
40: protected $_table;
41:
42: 43: 44: 45: 46:
47: protected $_columns = [];
48:
49: 50: 51: 52: 53:
54: protected $_typeMap = [];
55:
56: 57: 58: 59: 60:
61: protected $_indexes = [];
62:
63: 64: 65: 66: 67:
68: protected $_constraints = [];
69:
70: 71: 72: 73: 74:
75: protected $_options = [];
76:
77: 78: 79: 80: 81:
82: protected $_temporary = false;
83:
84: 85: 86: 87: 88:
89: const LENGTH_TINY = 255;
90:
91: 92: 93: 94: 95:
96: const LENGTH_MEDIUM = 16777215;
97:
98: 99: 100: 101: 102:
103: const LENGTH_LONG = 4294967295;
104:
105: 106: 107: 108: 109:
110: public static $columnLengths = [
111: 'tiny' => self::LENGTH_TINY,
112: 'medium' => self::LENGTH_MEDIUM,
113: 'long' => self::LENGTH_LONG
114: ];
115:
116: 117: 118: 119: 120: 121:
122: protected static $_columnKeys = [
123: 'type' => null,
124: 'baseType' => null,
125: 'length' => null,
126: 'precision' => null,
127: 'null' => null,
128: 'default' => null,
129: 'comment' => null,
130: ];
131:
132: 133: 134: 135: 136:
137: protected static $_columnExtras = [
138: 'string' => [
139: 'fixed' => null,
140: 'collate' => null,
141: ],
142: 'text' => [
143: 'collate' => null,
144: ],
145: 'tinyinteger' => [
146: 'unsigned' => null,
147: ],
148: 'smallinteger' => [
149: 'unsigned' => null,
150: ],
151: 'integer' => [
152: 'unsigned' => null,
153: 'autoIncrement' => null,
154: ],
155: 'biginteger' => [
156: 'unsigned' => null,
157: 'autoIncrement' => null,
158: ],
159: 'decimal' => [
160: 'unsigned' => null,
161: ],
162: 'float' => [
163: 'unsigned' => null,
164: ],
165: ];
166:
167: 168: 169: 170: 171: 172:
173: protected static $_indexKeys = [
174: 'type' => null,
175: 'columns' => [],
176: 'length' => [],
177: 'references' => [],
178: 'update' => 'restrict',
179: 'delete' => 'restrict',
180: ];
181:
182: 183: 184: 185: 186:
187: protected static $_validIndexTypes = [
188: self::INDEX_INDEX,
189: self::INDEX_FULLTEXT,
190: ];
191:
192: 193: 194: 195: 196:
197: protected static $_validConstraintTypes = [
198: self::CONSTRAINT_PRIMARY,
199: self::CONSTRAINT_UNIQUE,
200: self::CONSTRAINT_FOREIGN,
201: ];
202:
203: 204: 205: 206: 207:
208: protected static $_validForeignKeyActions = [
209: self::ACTION_CASCADE,
210: self::ACTION_SET_NULL,
211: self::ACTION_SET_DEFAULT,
212: self::ACTION_NO_ACTION,
213: self::ACTION_RESTRICT,
214: ];
215:
216: 217: 218: 219: 220:
221: const CONSTRAINT_PRIMARY = 'primary';
222:
223: 224: 225: 226: 227:
228: const CONSTRAINT_UNIQUE = 'unique';
229:
230: 231: 232: 233: 234:
235: const CONSTRAINT_FOREIGN = 'foreign';
236:
237: 238: 239: 240: 241:
242: const INDEX_INDEX = 'index';
243:
244: 245: 246: 247: 248:
249: const INDEX_FULLTEXT = 'fulltext';
250:
251: 252: 253: 254: 255:
256: const ACTION_CASCADE = 'cascade';
257:
258: 259: 260: 261: 262:
263: const ACTION_SET_NULL = 'setNull';
264:
265: 266: 267: 268: 269:
270: const ACTION_NO_ACTION = 'noAction';
271:
272: 273: 274: 275: 276:
277: const ACTION_RESTRICT = 'restrict';
278:
279: 280: 281: 282: 283:
284: const ACTION_SET_DEFAULT = 'setDefault';
285:
286: 287: 288: 289: 290: 291:
292: public function __construct($table, array $columns = [])
293: {
294: $this->_table = $table;
295: foreach ($columns as $field => $definition) {
296: $this->addColumn($field, $definition);
297: }
298: }
299:
300: 301: 302:
303: public function name()
304: {
305: return $this->_table;
306: }
307:
308: 309: 310:
311: public function addColumn($name, $attrs)
312: {
313: if (is_string($attrs)) {
314: $attrs = ['type' => $attrs];
315: }
316: $valid = static::$_columnKeys;
317: if (isset(static::$_columnExtras[$attrs['type']])) {
318: $valid += static::$_columnExtras[$attrs['type']];
319: }
320: $attrs = array_intersect_key($attrs, $valid);
321: $this->_columns[$name] = $attrs + $valid;
322: $this->_typeMap[$name] = $this->_columns[$name]['type'];
323:
324: return $this;
325: }
326:
327: 328: 329:
330: public function removeColumn($name)
331: {
332: unset($this->_columns[$name], $this->_typeMap[$name]);
333:
334: return $this;
335: }
336:
337: 338: 339:
340: public function columns()
341: {
342: return array_keys($this->_columns);
343: }
344:
345: 346: 347: 348: 349: 350: 351:
352: public function column($name)
353: {
354: deprecationWarning('TableSchema::column() is deprecated. Use TableSchema::getColumn() instead.');
355:
356: return $this->getColumn($name);
357: }
358:
359: 360: 361:
362: public function getColumn($name)
363: {
364: if (!isset($this->_columns[$name])) {
365: return null;
366: }
367: $column = $this->_columns[$name];
368: unset($column['baseType']);
369:
370: return $column;
371: }
372:
373: 374: 375: 376: 377: 378: 379: 380: 381:
382: public function columnType($name, $type = null)
383: {
384: deprecationWarning('TableSchema::columnType() is deprecated. Use TableSchema::setColumnType() or TableSchema::getColumnType() instead.');
385:
386: if ($type !== null) {
387: $this->setColumnType($name, $type);
388: }
389:
390: return $this->getColumnType($name);
391: }
392:
393: 394: 395:
396: public function getColumnType($name)
397: {
398: if (!isset($this->_columns[$name])) {
399: return null;
400: }
401:
402: return $this->_columns[$name]['type'];
403: }
404:
405: 406: 407:
408: public function setColumnType($name, $type)
409: {
410: if (!isset($this->_columns[$name])) {
411: return $this;
412: }
413:
414: $this->_columns[$name]['type'] = $type;
415: $this->_typeMap[$name] = $type;
416:
417: return $this;
418: }
419:
420: 421: 422:
423: public function hasColumn($name)
424: {
425: return isset($this->_columns[$name]);
426: }
427:
428: 429: 430:
431: public function baseColumnType($column)
432: {
433: if (isset($this->_columns[$column]['baseType'])) {
434: return $this->_columns[$column]['baseType'];
435: }
436:
437: $type = $this->getColumnType($column);
438:
439: if ($type === null) {
440: return null;
441: }
442:
443: if (Type::getMap($type)) {
444: $type = Type::build($type)->getBaseType();
445: }
446:
447: return $this->_columns[$column]['baseType'] = $type;
448: }
449:
450: 451: 452:
453: public function typeMap()
454: {
455: return $this->_typeMap;
456: }
457:
458: 459: 460:
461: public function isNullable($name)
462: {
463: if (!isset($this->_columns[$name])) {
464: return true;
465: }
466:
467: return ($this->_columns[$name]['null'] === true);
468: }
469:
470: 471: 472:
473: public function defaultValues()
474: {
475: $defaults = [];
476: foreach ($this->_columns as $name => $data) {
477: if (!array_key_exists('default', $data)) {
478: continue;
479: }
480: if ($data['default'] === null && $data['null'] !== true) {
481: continue;
482: }
483: $defaults[$name] = $data['default'];
484: }
485:
486: return $defaults;
487: }
488:
489: 490: 491: 492:
493: public function addIndex($name, $attrs)
494: {
495: if (is_string($attrs)) {
496: $attrs = ['type' => $attrs];
497: }
498: $attrs = array_intersect_key($attrs, static::$_indexKeys);
499: $attrs += static::$_indexKeys;
500: unset($attrs['references'], $attrs['update'], $attrs['delete']);
501:
502: if (!in_array($attrs['type'], static::$_validIndexTypes, true)) {
503: throw new Exception(sprintf('Invalid index type "%s" in index "%s" in table "%s".', $attrs['type'], $name, $this->_table));
504: }
505: if (empty($attrs['columns'])) {
506: throw new Exception(sprintf('Index "%s" in table "%s" must have at least one column.', $name, $this->_table));
507: }
508: $attrs['columns'] = (array)$attrs['columns'];
509: foreach ($attrs['columns'] as $field) {
510: if (empty($this->_columns[$field])) {
511: $msg = sprintf(
512: 'Columns used in index "%s" in table "%s" must be added to the Table schema first. ' .
513: 'The column "%s" was not found.',
514: $name,
515: $this->_table,
516: $field
517: );
518: throw new Exception($msg);
519: }
520: }
521: $this->_indexes[$name] = $attrs;
522:
523: return $this;
524: }
525:
526: 527: 528:
529: public function indexes()
530: {
531: return array_keys($this->_indexes);
532: }
533:
534: 535: 536: 537: 538: 539: 540:
541: public function index($name)
542: {
543: deprecationWarning('TableSchema::index() is deprecated. Use TableSchema::getIndex() instead.');
544:
545: return $this->getIndex($name);
546: }
547:
548: 549: 550:
551: public function getIndex($name)
552: {
553: if (!isset($this->_indexes[$name])) {
554: return null;
555: }
556:
557: return $this->_indexes[$name];
558: }
559:
560: 561: 562:
563: public function primaryKey()
564: {
565: foreach ($this->_constraints as $name => $data) {
566: if ($data['type'] === static::CONSTRAINT_PRIMARY) {
567: return $data['columns'];
568: }
569: }
570:
571: return [];
572: }
573:
574: 575: 576: 577:
578: public function addConstraint($name, $attrs)
579: {
580: if (is_string($attrs)) {
581: $attrs = ['type' => $attrs];
582: }
583: $attrs = array_intersect_key($attrs, static::$_indexKeys);
584: $attrs += static::$_indexKeys;
585: if (!in_array($attrs['type'], static::$_validConstraintTypes, true)) {
586: throw new Exception(sprintf('Invalid constraint type "%s" in table "%s".', $attrs['type'], $this->_table));
587: }
588: if (empty($attrs['columns'])) {
589: throw new Exception(sprintf('Constraints in table "%s" must have at least one column.', $this->_table));
590: }
591: $attrs['columns'] = (array)$attrs['columns'];
592: foreach ($attrs['columns'] as $field) {
593: if (empty($this->_columns[$field])) {
594: $msg = sprintf(
595: 'Columns used in constraints must be added to the Table schema first. ' .
596: 'The column "%s" was not found in table "%s".',
597: $field,
598: $this->_table
599: );
600: throw new Exception($msg);
601: }
602: }
603:
604: if ($attrs['type'] === static::CONSTRAINT_FOREIGN) {
605: $attrs = $this->_checkForeignKey($attrs);
606:
607: if (isset($this->_constraints[$name])) {
608: $this->_constraints[$name]['columns'] = array_unique(array_merge(
609: $this->_constraints[$name]['columns'],
610: $attrs['columns']
611: ));
612:
613: if (isset($this->_constraints[$name]['references'])) {
614: $this->_constraints[$name]['references'][1] = array_unique(array_merge(
615: (array)$this->_constraints[$name]['references'][1],
616: [$attrs['references'][1]]
617: ));
618: }
619:
620: return $this;
621: }
622: } else {
623: unset($attrs['references'], $attrs['update'], $attrs['delete']);
624: }
625:
626: $this->_constraints[$name] = $attrs;
627:
628: return $this;
629: }
630:
631: 632: 633:
634: public function dropConstraint($name)
635: {
636: if (isset($this->_constraints[$name])) {
637: unset($this->_constraints[$name]);
638: }
639:
640: return $this;
641: }
642:
643: 644: 645: 646: 647:
648: public function hasAutoincrement()
649: {
650: foreach ($this->_columns as $column) {
651: if (isset($column['autoIncrement']) && $column['autoIncrement']) {
652: return true;
653: }
654: }
655:
656: return false;
657: }
658:
659: 660: 661: 662: 663: 664: 665:
666: protected function _checkForeignKey($attrs)
667: {
668: if (count($attrs['references']) < 2) {
669: throw new Exception('References must contain a table and column.');
670: }
671: if (!in_array($attrs['update'], static::$_validForeignKeyActions)) {
672: throw new Exception(sprintf('Update action is invalid. Must be one of %s', implode(',', static::$_validForeignKeyActions)));
673: }
674: if (!in_array($attrs['delete'], static::$_validForeignKeyActions)) {
675: throw new Exception(sprintf('Delete action is invalid. Must be one of %s', implode(',', static::$_validForeignKeyActions)));
676: }
677:
678: return $attrs;
679: }
680:
681: 682: 683:
684: public function constraints()
685: {
686: return array_keys($this->_constraints);
687: }
688:
689: 690: 691: 692: 693: 694: 695:
696: public function constraint($name)
697: {
698: deprecationWarning('TableSchema::constraint() is deprecated. Use TableSchema::getConstraint() instead.');
699:
700: return $this->getConstraint($name);
701: }
702:
703: 704: 705:
706: public function getConstraint($name)
707: {
708: if (!isset($this->_constraints[$name])) {
709: return null;
710: }
711:
712: return $this->_constraints[$name];
713: }
714:
715: 716: 717:
718: public function setOptions($options)
719: {
720: $this->_options = array_merge($this->_options, $options);
721:
722: return $this;
723: }
724:
725: 726: 727:
728: public function getOptions()
729: {
730: return $this->_options;
731: }
732:
733: 734: 735: 736: 737: 738: 739: 740: 741: 742:
743: public function options($options = null)
744: {
745: deprecationWarning('TableSchema::options() is deprecated. Use TableSchema::setOptions() or TableSchema::getOptions() instead.');
746:
747: if ($options !== null) {
748: return $this->setOptions($options);
749: }
750:
751: return $this->getOptions();
752: }
753:
754: 755: 756:
757: public function setTemporary($temporary)
758: {
759: $this->_temporary = (bool)$temporary;
760:
761: return $this;
762: }
763:
764: 765: 766:
767: public function isTemporary()
768: {
769: return $this->_temporary;
770: }
771:
772: 773: 774: 775: 776: 777: 778:
779: public function temporary($temporary = null)
780: {
781: deprecationWarning(
782: 'TableSchema::temporary() is deprecated. ' .
783: 'Use TableSchema::setTemporary()/isTemporary() instead.'
784: );
785: if ($temporary !== null) {
786: return $this->setTemporary($temporary);
787: }
788:
789: return $this->isTemporary();
790: }
791:
792: 793: 794:
795: public function createSql(Connection $connection)
796: {
797: $dialect = $connection->getDriver()->schemaDialect();
798: $columns = $constraints = $indexes = [];
799: foreach (array_keys($this->_columns) as $name) {
800: $columns[] = $dialect->columnSql($this, $name);
801: }
802: foreach (array_keys($this->_constraints) as $name) {
803: $constraints[] = $dialect->constraintSql($this, $name);
804: }
805: foreach (array_keys($this->_indexes) as $name) {
806: $indexes[] = $dialect->indexSql($this, $name);
807: }
808:
809: return $dialect->createTableSql($this, $columns, $constraints, $indexes);
810: }
811:
812: 813: 814:
815: public function dropSql(Connection $connection)
816: {
817: $dialect = $connection->getDriver()->schemaDialect();
818:
819: return $dialect->dropTableSql($this);
820: }
821:
822: 823: 824:
825: public function truncateSql(Connection $connection)
826: {
827: $dialect = $connection->getDriver()->schemaDialect();
828:
829: return $dialect->truncateTableSql($this);
830: }
831:
832: 833: 834:
835: public function addConstraintSql(Connection $connection)
836: {
837: $dialect = $connection->getDriver()->schemaDialect();
838:
839: return $dialect->addConstraintSql($this);
840: }
841:
842: 843: 844:
845: public function dropConstraintSql(Connection $connection)
846: {
847: $dialect = $connection->getDriver()->schemaDialect();
848:
849: return $dialect->dropConstraintSql($this);
850: }
851:
852: 853: 854: 855: 856:
857: public function __debugInfo()
858: {
859: return [
860: 'table' => $this->_table,
861: 'columns' => $this->_columns,
862: 'indexes' => $this->_indexes,
863: 'constraints' => $this->_constraints,
864: 'options' => $this->_options,
865: 'typeMap' => $this->_typeMap,
866: 'temporary' => $this->_temporary,
867: ];
868: }
869: }
870:
871:
872: class_alias('Cake\Database\Schema\TableSchema', 'Cake\Database\Schema\Table');
873: