ChangeColumn.php 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. <?php
  2. namespace Illuminate\Database\Schema\Grammars;
  3. use RuntimeException;
  4. use Doctrine\DBAL\Types\Type;
  5. use Illuminate\Support\Fluent;
  6. use Doctrine\DBAL\Schema\Table;
  7. use Illuminate\Database\Connection;
  8. use Doctrine\DBAL\Schema\Comparator;
  9. use Illuminate\Database\Schema\Blueprint;
  10. use Doctrine\DBAL\Schema\AbstractSchemaManager as SchemaManager;
  11. class ChangeColumn
  12. {
  13. /**
  14. * Compile a change column command into a series of SQL statements.
  15. *
  16. * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar
  17. * @param \Illuminate\Database\Schema\Blueprint $blueprint
  18. * @param \Illuminate\Support\Fluent $command
  19. * @param \Illuminate\Database\Connection $connection
  20. * @return array
  21. *
  22. * @throws \RuntimeException
  23. */
  24. public static function compile($grammar, Blueprint $blueprint, Fluent $command, Connection $connection)
  25. {
  26. if (! $connection->isDoctrineAvailable()) {
  27. throw new RuntimeException(sprintf(
  28. 'Changing columns for table "%s" requires Doctrine DBAL; install "doctrine/dbal".',
  29. $blueprint->getTable()
  30. ));
  31. }
  32. $tableDiff = static::getChangedDiff(
  33. $grammar, $blueprint, $schema = $connection->getDoctrineSchemaManager()
  34. );
  35. if ($tableDiff !== false) {
  36. return (array) $schema->getDatabasePlatform()->getAlterTableSQL($tableDiff);
  37. }
  38. return [];
  39. }
  40. /**
  41. * Get the Doctrine table difference for the given changes.
  42. *
  43. * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar
  44. * @param \Illuminate\Database\Schema\Blueprint $blueprint
  45. * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema
  46. * @return \Doctrine\DBAL\Schema\TableDiff|bool
  47. */
  48. protected static function getChangedDiff($grammar, Blueprint $blueprint, SchemaManager $schema)
  49. {
  50. $current = $schema->listTableDetails($grammar->getTablePrefix().$blueprint->getTable());
  51. return (new Comparator)->diffTable(
  52. $current, static::getTableWithColumnChanges($blueprint, $current)
  53. );
  54. }
  55. /**
  56. * Get a copy of the given Doctrine table after making the column changes.
  57. *
  58. * @param \Illuminate\Database\Schema\Blueprint $blueprint
  59. * @param \Doctrine\DBAL\Schema\Table $table
  60. * @return \Doctrine\DBAL\Schema\Table
  61. */
  62. protected static function getTableWithColumnChanges(Blueprint $blueprint, Table $table)
  63. {
  64. $table = clone $table;
  65. foreach ($blueprint->getChangedColumns() as $fluent) {
  66. $column = static::getDoctrineColumn($table, $fluent);
  67. // Here we will spin through each fluent column definition and map it to the proper
  68. // Doctrine column definitions - which is necessary because Laravel and Doctrine
  69. // use some different terminology for various column attributes on the tables.
  70. foreach ($fluent->getAttributes() as $key => $value) {
  71. if (! is_null($option = static::mapFluentOptionToDoctrine($key))) {
  72. if (method_exists($column, $method = 'set'.ucfirst($option))) {
  73. $column->{$method}(static::mapFluentValueToDoctrine($option, $value));
  74. }
  75. }
  76. }
  77. }
  78. return $table;
  79. }
  80. /**
  81. * Get the Doctrine column instance for a column change.
  82. *
  83. * @param \Doctrine\DBAL\Schema\Table $table
  84. * @param \Illuminate\Support\Fluent $fluent
  85. * @return \Doctrine\DBAL\Schema\Column
  86. */
  87. protected static function getDoctrineColumn(Table $table, Fluent $fluent)
  88. {
  89. return $table->changeColumn(
  90. $fluent['name'], static::getDoctrineColumnChangeOptions($fluent)
  91. )->getColumn($fluent['name']);
  92. }
  93. /**
  94. * Get the Doctrine column change options.
  95. *
  96. * @param \Illuminate\Support\Fluent $fluent
  97. * @return array
  98. */
  99. protected static function getDoctrineColumnChangeOptions(Fluent $fluent)
  100. {
  101. $options = ['type' => static::getDoctrineColumnType($fluent['type'])];
  102. if (in_array($fluent['type'], ['text', 'mediumText', 'longText'])) {
  103. $options['length'] = static::calculateDoctrineTextLength($fluent['type']);
  104. }
  105. return $options;
  106. }
  107. /**
  108. * Get the doctrine column type.
  109. *
  110. * @param string $type
  111. * @return \Doctrine\DBAL\Types\Type
  112. */
  113. protected static function getDoctrineColumnType($type)
  114. {
  115. $type = strtolower($type);
  116. switch ($type) {
  117. case 'biginteger':
  118. $type = 'bigint';
  119. break;
  120. case 'smallinteger':
  121. $type = 'smallint';
  122. break;
  123. case 'mediumtext':
  124. case 'longtext':
  125. $type = 'text';
  126. break;
  127. case 'binary':
  128. $type = 'blob';
  129. break;
  130. }
  131. return Type::getType($type);
  132. }
  133. /**
  134. * Calculate the proper column length to force the Doctrine text type.
  135. *
  136. * @param string $type
  137. * @return int
  138. */
  139. protected static function calculateDoctrineTextLength($type)
  140. {
  141. switch ($type) {
  142. case 'mediumText':
  143. return 65535 + 1;
  144. case 'longText':
  145. return 16777215 + 1;
  146. default:
  147. return 255 + 1;
  148. }
  149. }
  150. /**
  151. * Get the matching Doctrine option for a given Fluent attribute name.
  152. *
  153. * @param string $attribute
  154. * @return string|null
  155. */
  156. protected static function mapFluentOptionToDoctrine($attribute)
  157. {
  158. switch ($attribute) {
  159. case 'type':
  160. case 'name':
  161. return;
  162. case 'nullable':
  163. return 'notnull';
  164. case 'total':
  165. return 'precision';
  166. case 'places':
  167. return 'scale';
  168. default:
  169. return $attribute;
  170. }
  171. }
  172. /**
  173. * Get the matching Doctrine value for a given Fluent attribute.
  174. *
  175. * @param string $option
  176. * @param mixed $value
  177. * @return mixed
  178. */
  179. protected static function mapFluentValueToDoctrine($option, $value)
  180. {
  181. return $option == 'notnull' ? ! $value : $value;
  182. }
  183. }