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