TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
TableBuilder.php
Go to the documentation of this file.
1 <?php
2 declare(strict_types=1);
3 
5 
6 /*
7  * This file is part of the TYPO3 CMS project.
8  *
9  * It is free software; you can redistribute it and/or modify it under
10  * the terms of the GNU General Public License, either version 2
11  * of the License, or any later version.
12  *
13  * For the full copyright and license information, please read the
14  * LICENSE.txt file that was distributed with this source code.
15  *
16  * The TYPO3 project - inspiring people to share!
17  */
18 
19 use Doctrine\DBAL\Platforms\AbstractPlatform;
20 use Doctrine\DBAL\Platforms\MySqlPlatform;
21 use Doctrine\DBAL\Schema\Column;
22 use Doctrine\DBAL\Schema\Index;
23 use Doctrine\DBAL\Schema\Table;
24 use Doctrine\DBAL\Types\Type;
30 use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType;
36 
42 {
46  protected $table;
47 
51  protected $platform;
52 
60  public function __construct(AbstractPlatform $platform = null)
61  {
62  // Register custom data types as no connection might have
63  // been established yet so the types would not be available
64  // when building tables/columns.
65  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
66 
67  foreach ($connectionPool->getCustomDoctrineTypes() as $type => $className) {
68  if (!Type::hasType($type)) {
69  Type::addType($type, $className);
70  }
71  }
72  $this->platform = $platform ?: GeneralUtility::makeInstance(MySqlPlatform::class);
73  }
74 
84  public function create(CreateTableStatement $tableStatement): Table
85  {
86  $this->table = GeneralUtility::makeInstance(
87  Table::class,
88  $tableStatement->tableName->getQuotedName(),
89  [],
90  [],
91  [],
92  0,
93  $this->buildTableOptions($tableStatement->tableOptions)
94  );
95 
96  foreach ($tableStatement->createDefinition->items as $item) {
97  switch (get_class($item)) {
98  case CreateColumnDefinitionItem::class:
99  $this->addColumn($item);
100  break;
101  case CreateIndexDefinitionItem::class:
102  $this->addIndex($item);
103  break;
104  case CreateForeignKeyDefinitionItem::class:
105  $this->addForeignKey($item);
106  break;
107  default:
108  throw new \RuntimeException(
109  'Unknown item definition of type "' . get_class($item) . '" encountered.',
110  1472044085
111  );
112  }
113  }
114 
115  return $this->table;
116  }
117 
124  protected function addColumn(CreateColumnDefinitionItem $item): Column
125  {
126  $column = $this->table->addColumn(
127  $item->columnName->getQuotedName(),
128  $this->getDoctrineColumnTypeName($item->dataType)
129  );
130 
131  $column->setNotnull(!$item->allowNull);
132  $column->setAutoincrement((bool)$item->autoIncrement);
133  $column->setComment($item->comment);
134 
135  // Set default value (unless it's an auto increment column)
136  if ($item->hasDefaultValue && !$column->getAutoincrement()) {
137  $column->setDefault($item->defaultValue);
138  }
139 
140  if ($item->dataType->getLength()) {
141  $column->setLength($item->dataType->getLength());
142  }
143 
144  if ($item->dataType->getPrecision() >= 0) {
145  $column->setPrecision($item->dataType->getPrecision());
146  }
147 
148  if ($item->dataType->getScale() >= 0) {
149  $column->setScale($item->dataType->getScale());
150  }
151 
152  if ($item->dataType->isUnsigned()) {
153  $column->setUnsigned(true);
154  }
155 
156  // Select CHAR/VARCHAR or BINARY/VARBINARY
157  if ($item->dataType->isFixed()) {
158  $column->setFixed(true);
159  }
160 
161  if ($item->dataType instanceof DataType\EnumDataType
162  || $item->dataType instanceof DataType\SetDataType
163  ) {
164  $column->setPlatformOption('unquotedValues', $item->dataType->getValues());
165  }
166 
167  if ($item->index) {
168  $this->table->addIndex([$item->columnName->getQuotedName()]);
169  }
170 
171  if ($item->unique) {
172  $this->table->addUniqueIndex([$item->columnName->getQuotedName()]);
173  }
174 
175  if ($item->primary) {
176  $this->table->setPrimaryKey([$item->columnName->getQuotedName()]);
177  }
178 
179  if ($item->reference !== null) {
181  [$item->columnName->getQuotedName()],
182  $item->reference
183  );
184  }
185 
186  return $column;
187  }
188 
195  protected function addIndex(CreateIndexDefinitionItem $item): Index
196  {
197  $indexName = $item->indexName->getQuotedName();
198 
199  $columnNames = array_map(
200  function (IndexColumnName $columnName) {
201  if ($columnName->length) {
202  return $columnName->columnName->getQuotedName() . '(' . $columnName->length . ')';
203  }
204  return $columnName->columnName->getQuotedName();
205  },
206  $item->columnNames
207  );
208 
209  if ($item->isPrimary) {
210  $this->table->setPrimaryKey($columnNames);
211  $index = $this->table->getPrimaryKey();
212  } else {
214  Index::class,
215  $indexName,
216  $columnNames,
217  $item->isUnique,
218  $item->isPrimary
219  );
220 
221  if ($item->isFulltext) {
222  $index->addFlag('fulltext');
223  } elseif ($item->isSpatial) {
224  $index->addFlag('spatial');
225  }
226 
227  $this->table = GeneralUtility::makeInstance(
228  Table::class,
229  $this->table->getQuotedName($this->platform),
230  $this->table->getColumns(),
231  array_merge($this->table->getIndexes(), [strtolower($indexName) => $index]),
232  $this->table->getForeignKeys(),
233  0,
234  $this->table->getOptions()
235  );
236  }
237 
238  return $index;
239  }
240 
247  {
248  $indexName = $item->indexName->getQuotedName() ?: null;
249  $localColumnNames = array_map(
250  function (IndexColumnName $columnName) {
251  return $columnName->columnName->getQuotedName();
252  },
253  $item->columnNames
254  );
255  $this->addForeignKeyConstraint($localColumnNames, $item->reference, $indexName);
256  }
257 
265  protected function addForeignKeyConstraint(
266  array $localColumnNames,
267  ReferenceDefinition $referenceDefinition,
268  string $indexName = null
269  ) {
270  $foreignTableName = $referenceDefinition->tableName->getQuotedName();
271  $foreignColumNames = array_map(
272  function (IndexColumnName $columnName) {
273  return $columnName->columnName->getQuotedName();
274  },
275  $referenceDefinition->columnNames
276  );
277 
278  $options = [
279  'onDelete' => $referenceDefinition->onDelete,
280  'onUpdate' => $referenceDefinition->onUpdate,
281  ];
282 
283  $this->table->addForeignKeyConstraint(
284  $foreignTableName,
285  $localColumnNames,
286  $foreignColumNames,
287  $options,
288  $indexName
289  );
290  }
291 
297  protected function getDoctrineColumnTypeName(DataType\AbstractDataType $dataType): string
298  {
299  $doctrineType = null;
300  switch (get_class($dataType)) {
301  case DataType\TinyIntDataType::class:
302  // TINYINT is MySQL specific and mapped to a standard SMALLINT
303  case DataType\SmallIntDataType::class:
304  $doctrineType = Type::SMALLINT;
305  break;
306  case DataType\MediumIntDataType::class:
307  // MEDIUMINT is MySQL specific and mapped to a standard INT
308  case DataType\IntegerDataType::class:
309  $doctrineType = Type::INTEGER;
310  break;
311  case DataType\BigIntDataType::class:
312  $doctrineType = Type::BIGINT;
313  break;
314  case DataType\BinaryDataType::class:
315  case DataType\VarBinaryDataType::class:
316  // CHAR/VARCHAR is determined by "fixed" column property
317  $doctrineType = Type::BINARY;
318  break;
319  case DataType\TinyBlobDataType::class:
320  case DataType\MediumBlobDataType::class:
321  case DataType\BlobDataType::class:
322  case DataType\LongBlobDataType::class:
323  // Actual field type is determined by field length
324  $doctrineType = Type::BLOB;
325  break;
326  case DataType\DateDataType::class:
327  $doctrineType = Type::DATE;
328  break;
329  case DataType\TimestampDataType::class:
330  case DataType\DateTimeDataType::class:
331  // TIMESTAMP or DATETIME are determined by "version" column property
332  $doctrineType = Type::DATETIME;
333  break;
334  case DataType\NumericDataType::class:
335  case DataType\DecimalDataType::class:
336  $doctrineType = Type::DECIMAL;
337  break;
338  case DataType\RealDataType::class:
339  case DataType\FloatDataType::class:
340  case DataType\DoubleDataType::class:
341  $doctrineType = Type::FLOAT;
342  break;
343  case DataType\TimeDataType::class:
344  $doctrineType = Type::TIME;
345  break;
346  case DataType\TinyTextDataType::class:
347  case DataType\MediumTextDataType::class:
348  case DataType\TextDataType::class:
349  case DataType\LongTextDataType::class:
350  $doctrineType = Type::TEXT;
351  break;
352  case DataType\CharDataType::class:
353  case DataType\VarCharDataType::class:
354  $doctrineType = Type::STRING;
355  break;
356  case DataType\EnumDataType::class:
357  $doctrineType = EnumType::TYPE;
358  break;
359  case DataType\SetDataType::class:
360  $doctrineType = SetType::TYPE;
361  break;
362  case DataType\JsonDataType::class:
363  // JSON is not supported in Doctrine 2.5, mapping to the more generic TEXT type
364  $doctrineType = SetType::TEXT;
365  break;
366  case DataType\YearDataType::class:
367  // The YEAR data type is MySQL specific and offers little to no benefit.
368  // The two-digit year logic implemented in this data type (1-69 mapped to
369  // 2001-2069, 70-99 mapped to 1970-1999) can be easily implemented in the
370  // application and for all other accounts it's an integer with a valid
371  // range of 1901 to 2155.
372  // Using a SMALLINT covers the value range and ensures database compatibility.
373  $doctrineType = SetType::SMALLINT;
374  break;
375  default:
376  throw new \RuntimeException(
377  'Unsupported data type: ' . get_class($dataType) . '!',
378  1472046376
379  );
380  }
381 
382  return $doctrineType;
383  }
384 
391  protected function buildTableOptions(array $tableOptions): array
392  {
393  $options = [];
394 
395  if (!empty($tableOptions['engine'])) {
396  $options['engine'] = (string)$tableOptions['engine'];
397  }
398  if (!empty($tableOptions['character_set'])) {
399  $options['charset'] = (string)$tableOptions['character_set'];
400  }
401  if (!empty($tableOptions['collation'])) {
402  $options['collate'] = (string)$tableOptions['collation'];
403  }
404  if (!empty($tableOptions['auto_increment'])) {
405  $options['auto_increment'] = (string)$tableOptions['auto_increment'];
406  }
407  if (!empty($tableOptions['comment'])) {
408  $options['comment'] = (string)$tableOptions['comment'];
409  }
410  if (!empty($tableOptions['row_format'])) {
411  $options['row_format'] = (string)$tableOptions['row_format'];
412  }
413 
414  return $options;
415  }
416 }
create(CreateTableStatement $tableStatement)
addForeignKeyConstraint(array $localColumnNames, ReferenceDefinition $referenceDefinition, string $indexName=null)
addForeignKey(CreateForeignKeyDefinitionItem $item)
__construct(AbstractPlatform $platform=null)
getDoctrineColumnTypeName(DataType\AbstractDataType $dataType)
addIndex(CreateIndexDefinitionItem $item)
addColumn(CreateColumnDefinitionItem $item)
static makeInstance($className,...$constructorArguments)