TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
SchemaMigrator.php
Go to the documentation of this file.
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Core\Database\Schema;
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 
18 use Doctrine\DBAL\DBALException;
19 use Doctrine\DBAL\Schema\Schema;
20 use Doctrine\DBAL\Schema\SchemaDiff;
21 use Doctrine\DBAL\Schema\Table;
26 
34 {
38  protected $schema = [];
39 
56  public function getUpdateSuggestions(array $statements, bool $remove = false): array
57  {
58  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
59  $tables = $this->parseCreateTableStatements($statements);
60 
61  $updateSuggestions = [];
62 
63  foreach ($connectionPool->getConnectionNames() as $connectionName) {
64  $connectionMigrator = ConnectionMigrator::create(
65  $connectionName,
66  $tables
67  );
68 
69  $updateSuggestions[$connectionName] =
70  $connectionMigrator->getUpdateSuggestions($remove);
71  }
72 
73  return $updateSuggestions;
74  }
75 
91  public function getSchemaDiffs(array $statements): array
92  {
93  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
94  $tables = $this->parseCreateTableStatements($statements);
95 
96  $schemaDiffs = [];
97 
98  foreach ($connectionPool->getConnectionNames() as $connectionName) {
99  $connectionMigrator = ConnectionMigrator::create(
100  $connectionName,
101  $tables
102  );
103  $schemaDiffs[$connectionName] = $connectionMigrator->getSchemaDiff();
104  }
105 
106  return $schemaDiffs;
107  }
108 
125  public function migrate(array $statements, array $selectedStatements): array
126  {
127  $result = [];
128  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
129  $updateSuggestionsPerConnection = array_merge_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 
172  public function install(array $statements, bool $createOnly = false): array
173  {
174  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
175  $tables = $this->parseCreateTableStatements($statements);
176  $result = [];
177 
178  foreach ($connectionPool->getConnectionNames() as $connectionName) {
179  $connectionMigrator = ConnectionMigrator::create(
180  $connectionName,
181  $tables
182  );
183 
184  $lastResult = $connectionMigrator->install($createOnly);
185  $result = array_merge($result, $lastResult);
186  }
187 
188  return $result;
189  }
190 
198  public function importStaticData(array $statements, bool $truncate = false): array
199  {
200  $result = [];
201  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
202  $insertStatements = [];
203 
204  foreach ($statements as $statement) {
205  // Only handle insert statements and extract the table at the same time. Extracting
206  // the table name is required to perform the inserts on the right connection.
207  if (preg_match('/^INSERT\s+INTO\s+`?(\w+)`?(.*)/i', $statement, $matches)) {
208  list(, $tableName, $sqlFragment) = $matches;
209  $insertStatements[$tableName][] = sprintf(
210  'INSERT INTO %s %s',
211  $connectionPool->getConnectionForTable($tableName)->quoteIdentifier($tableName),
212  rtrim($sqlFragment, ';')
213  );
214  }
215  }
216 
217  foreach ($insertStatements as $tableName => $perTableStatements) {
218  $connection = $connectionPool->getConnectionForTable($tableName);
219 
220  if ($truncate) {
221  $connection->truncate($tableName);
222  }
223 
224  foreach ((array)$perTableStatements as $statement) {
225  try {
226  $connection->executeUpdate($statement);
227  $result[$statement] = '';
228  } catch (DBALException $e) {
229  $result[$statement] = $e->getPrevious()->getMessage();
230  }
231  }
232  }
233 
234  return $result;
235  }
236 
250  public function parseCreateTableStatements(array $statements): array
251  {
252  $tables = [];
253  foreach ($statements as $statement) {
254  $createTableParser = GeneralUtility::makeInstance(Parser::class, $statement);
255 
256  // We need to keep multiple table definitions at this point so
257  // that Extensions can modify existing tables.
258  try {
259  $tables[] = $createTableParser->parse();
260  } catch (StatementException $statementException) {
261  // Enrich the error message with the full invalid statement
262  throw new StatementException(
263  $statementException->getMessage() . ' in statement ' . $statement,
264  1476171315,
265  $statementException
266  );
267  }
268  }
269 
270  // Flatten the array of arrays by one level
271  return array_merge(...$tables);
272  }
273 }
migrate(array $statements, array $selectedStatements)
getUpdateSuggestions(array $statements, bool $remove=false)
install(array $statements, bool $createOnly=false)
static create(string $connectionName, array $tables)
importStaticData(array $statements, bool $truncate=false)
static makeInstance($className,...$constructorArguments)