‪TYPO3CMS  11.5
SchemaMigrator.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Doctrine\DBAL\Exception as DBALException;
21 use Doctrine\DBAL\Platforms\SqlitePlatform;
22 use Doctrine\DBAL\Schema\Schema;
23 use Doctrine\DBAL\Schema\SchemaDiff;
24 use Doctrine\DBAL\Schema\Table;
25 use Doctrine\DBAL\Types\IntegerType;
31 
39 {
43  protected ‪$schema = [];
44 
59  public function ‪getUpdateSuggestions(array $statements, bool $remove = false): array
60  {
61  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
62  $tables = $this->‪parseCreateTableStatements($statements);
63 
64  $updateSuggestions = [];
65 
66  foreach ($connectionPool->getConnectionNames() as $connectionName) {
67  $this->‪adoptDoctrineAutoincrementDetectionForSqlite($tables, $connectionPool->getConnectionByName($connectionName));
68  $connectionMigrator = ‪ConnectionMigrator::create(
69  $connectionName,
70  $tables
71  );
72 
73  $updateSuggestions[$connectionName] =
74  $connectionMigrator->getUpdateSuggestions($remove);
75  }
76 
77  return $updateSuggestions;
78  }
79 
93  public function ‪getSchemaDiffs(array $statements): array
94  {
95  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
96  $tables = $this->‪parseCreateTableStatements($statements);
97 
98  $schemaDiffs = [];
99 
100  foreach ($connectionPool->getConnectionNames() as $connectionName) {
101  $connectionMigrator = ‪ConnectionMigrator::create(
102  $connectionName,
103  $tables
104  );
105  $schemaDiffs[$connectionName] = $connectionMigrator->getSchemaDiff();
106  }
107 
108  return $schemaDiffs;
109  }
110 
125  public function ‪migrate(array $statements, array $selectedStatements): array
126  {
127  $result = [];
128  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
129  $updateSuggestionsPerConnection = array_replace_recursive(
130  $this->‪getUpdateSuggestions($statements),
131  $this->‪getUpdateSuggestions($statements, true)
132  );
133 
134  foreach ($updateSuggestionsPerConnection as $connectionName => $updateSuggestions) {
135  unset($updateSuggestions['tables_count'], $updateSuggestions['change_currentValue']);
136  $updateSuggestions = array_merge(...array_values($updateSuggestions));
137  $statementsToExecute = array_intersect_key($updateSuggestions, $selectedStatements);
138  if (count($statementsToExecute) === 0) {
139  continue;
140  }
141 
142  $connection = $connectionPool->getConnectionByName($connectionName);
143  foreach ($statementsToExecute as $hash => $statement) {
144  try {
145  $connection->executeUpdate($statement);
146  } catch (DBALException $e) {
147  $result[$hash] = $e->getPrevious()->getMessage();
148  }
149  }
150  }
151 
152  return $result;
153  }
154 
170  public function ‪install(array $statements, bool $createOnly = false): array
171  {
172  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
173  $tables = $this->‪parseCreateTableStatements($statements);
174  $result = [];
175 
176  foreach ($connectionPool->getConnectionNames() as $connectionName) {
177  $connectionMigrator = ‪ConnectionMigrator::create(
178  $connectionName,
179  $tables
180  );
181 
182  $lastResult = $connectionMigrator->install($createOnly);
183  $result = array_merge($result, $lastResult);
184  }
185 
186  return $result;
187  }
188 
196  public function ‪importStaticData(array $statements, bool $truncate = false): array
197  {
198  $result = [];
199  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
200  $insertStatements = [];
201 
202  foreach ($statements as $statement) {
203  // Only handle insert statements and extract the table at the same time. Extracting
204  // the table name is required to perform the inserts on the right connection.
205  if (preg_match('/^INSERT\s+INTO\s+`?(\w+)`?(.*)/i', $statement, $matches)) {
206  [, $tableName, $sqlFragment] = $matches;
207  $insertStatements[$tableName][] = sprintf(
208  'INSERT INTO %s %s',
209  $connectionPool->getConnectionForTable($tableName)->quoteIdentifier($tableName),
210  rtrim($sqlFragment, ';')
211  );
212  }
213  }
214 
215  foreach ($insertStatements as $tableName => $perTableStatements) {
216  $connection = $connectionPool->getConnectionForTable($tableName);
217 
218  if ($truncate) {
219  $connection->truncate($tableName);
220  }
221 
222  foreach ((array)$perTableStatements as $statement) {
223  try {
224  $connection->executeStatement($statement);
225  $result[$statement] = '';
226  } catch (DBALException $e) {
227  $result[$statement] = $e->getPrevious()->getMessage();
228  }
229  }
230  }
231 
232  return $result;
233  }
234 
246  public function ‪parseCreateTableStatements(array $statements): array
247  {
248  $tables = [];
249  foreach ($statements as $statement) {
250  $createTableParser = GeneralUtility::makeInstance(Parser::class, $statement);
251 
252  // We need to keep multiple table definitions at this point so
253  // that Extensions can modify existing tables.
254  try {
255  $tables[] = $createTableParser->parse();
256  } catch (‪StatementException $statementException) {
257  // Enrich the error message with the full invalid statement
258  throw new ‪StatementException(
259  $statementException->getMessage() . ' in statement: ' . LF . $statement,
260  1476171315,
261  $statementException
262  );
263  }
264  }
265 
266  // Flatten the array of arrays by one level
267  $tables = array_merge(...$tables);
268 
269  // Add default TCA fields
270  $defaultTcaSchema = GeneralUtility::makeInstance(DefaultTcaSchema::class);
271  $tables = $defaultTcaSchema->enrich($tables);
272  // Ensure the default TCA fields are ordered
273  foreach ($tables as $k => $table) {
274  $prioritizedColumnNames = $defaultTcaSchema->getPrioritizedFieldNames($table->getName());
275  // no TCA table
276  if (empty($prioritizedColumnNames)) {
277  continue;
278  }
279 
280  $prioritizedColumns = [];
281  $nonPrioritizedColumns = [];
282 
283  foreach ($table->getColumns() as $columnObject) {
284  if (in_array($columnObject->getName(), $prioritizedColumnNames, true)) {
285  $prioritizedColumns[] = $columnObject;
286  } else {
287  $nonPrioritizedColumns[] = $columnObject;
288  }
289  }
290 
291  $tables[$k] = new Table(
292  $table->getName(),
293  array_merge($prioritizedColumns, $nonPrioritizedColumns),
294  $table->getIndexes(),
295  $table->getForeignKeys(),
296  0,
297  $table->getOptions()
298  );
299  }
300 
301  return $tables;
302  }
303 
315  protected function ‪adoptDoctrineAutoincrementDetectionForSqlite(array $tables, ‪Connection $connection): void
316  {
317  if (!($connection->getDatabasePlatform() instanceof SqlitePlatform)) {
318  return;
319  }
320  array_walk($tables, static function (Table $table): void {
321  $primaryColumns = $table->hasPrimaryKey() ? $table->getPrimaryKey()->getColumns() : [];
322  $primaryKeyColumnCount = count($primaryColumns);
323  $firstPrimaryKeyColumnName = $primaryColumns[0] ?? '';
324  $singlePrimaryKeyColumn = $table->hasColumn($firstPrimaryKeyColumnName)
325  ? $table->getColumn($firstPrimaryKeyColumnName)
326  : null;
327  ‪if ($primaryKeyColumnCount === 1
328  && $singlePrimaryKeyColumn !== null
329  && $singlePrimaryKeyColumn->getType() instanceof IntegerType
330  ) {
331  $singlePrimaryKeyColumn->setAutoincrement(true);
332  }
333  });
334  }
335 }
‪TYPO3\CMS\Core\Database\Schema\ConnectionMigrator\create
‪static ConnectionMigrator create(string $connectionName, array $tables)
Definition: ConnectionMigrator.php:78
‪TYPO3\CMS\Core\Database\Schema\Exception\StatementException
Definition: StatementException.php:24
‪TYPO3\CMS\Core\Database\Schema\SchemaMigrator\importStaticData
‪array importStaticData(array $statements, bool $truncate=false)
Definition: SchemaMigrator.php:195
‪TYPO3\CMS\Core\Database\Schema\SchemaMigrator\$schema
‪Schema[] $schema
Definition: SchemaMigrator.php:42
‪TYPO3\CMS\Core\Database\Schema\SchemaMigrator\getSchemaDiffs
‪SchemaDiff[] getSchemaDiffs(array $statements)
Definition: SchemaMigrator.php:92
‪TYPO3\CMS\Core\Database\Schema
Definition: Comparator.php:18
‪TYPO3\CMS\Core\Database\Schema\Parser\Parser
Definition: Parser.php:71
‪TYPO3\CMS\Core\Database\Schema\SchemaMigrator\adoptDoctrineAutoincrementDetectionForSqlite
‪adoptDoctrineAutoincrementDetectionForSqlite(array $tables, Connection $connection)
Definition: SchemaMigrator.php:314
‪TYPO3\CMS\Core\Database\Schema\SchemaMigrator
Definition: SchemaMigrator.php:39
‪TYPO3\CMS\Core\Database\Schema\SchemaMigrator\getUpdateSuggestions
‪array[] getUpdateSuggestions(array $statements, bool $remove=false)
Definition: SchemaMigrator.php:58
‪TYPO3\CMS\Core\Database\Schema\SchemaMigrator\install
‪array[] install(array $statements, bool $createOnly=false)
Definition: SchemaMigrator.php:169
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:38
‪if
‪if(PHP_SAPI !=='cli')
Definition: checkNamespaceIntegrity.php:26
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Core\Database\Schema\SchemaMigrator\parseCreateTableStatements
‪Table[] parseCreateTableStatements(array $statements)
Definition: SchemaMigrator.php:245
‪TYPO3\CMS\Core\Database\Schema\SchemaMigrator\migrate
‪array migrate(array $statements, array $selectedStatements)
Definition: SchemaMigrator.php:124