‪TYPO3CMS  9.5
DatabaseRowsUpdateWizard.php
Go to the documentation of this file.
1 <?php
2 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 
25 
45 {
49  protected ‪$rowUpdater = [
50  L10nModeUpdater::class,
51  ImageCropUpdater::class,
52  RteLinkSyntaxUpdater::class,
53  ];
54 
59  public function ‪getAvailableRowUpdater(): array
60  {
61  return ‪$this->rowUpdater;
62  }
63 
67  public function ‪getIdentifier(): string
68  {
69  return 'databaseRowsUpdateWizard';
70  }
71 
75  public function ‪getTitle(): string
76  {
77  return 'Execute database migrations on single rows';
78  }
79 
84  public function ‪getDescription(): string
85  {
86  $rowUpdaterNotExecuted = $this->‪getRowUpdatersToExecute();
87  $description = 'Row updaters that have not been executed:';
88  foreach ($rowUpdaterNotExecuted as $rowUpdateClassName) {
89  ‪$rowUpdater = GeneralUtility::makeInstance($rowUpdateClassName);
90  if (!‪$rowUpdater instanceof ‪RowUpdaterInterface) {
91  throw new \RuntimeException(
92  'Row updater must implement RowUpdaterInterface',
93  1484066647
94  );
95  }
96  $description .= LF . ‪$rowUpdater->getTitle();
97  }
98  return $description;
99  }
100 
104  public function ‪updateNecessary(): bool
105  {
106  return !empty($this->‪getRowUpdatersToExecute());
107  }
108 
112  public function ‪getPrerequisites(): array
113  {
114  return [
115  DatabaseUpdatedPrerequisite::class
116  ];
117  }
118 
126  public function ‪executeUpdate(): bool
127  {
128  $registry = GeneralUtility::makeInstance(Registry::class);
129 
130  // If rows from the target table that is updated and the sys_registry table are on the
131  // same connection, the row update statement and sys_registry position update will be
132  // handled in a transaction to have an atomic operation in case of errors during execution.
133  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
134  $connectionForSysRegistry = $connectionPool->getConnectionForTable('sys_registry');
135 
137  $rowUpdaterInstances = [];
138  // Single row updater instances are created only once for this method giving
139  // them a chance to set up local properties during hasPotentialUpdateForTable()
140  // and using that in updateTableRow()
141  foreach ($this->‪getRowUpdatersToExecute() as ‪$rowUpdater) {
142  $rowUpdaterInstance = GeneralUtility::makeInstance(‪$rowUpdater);
143  if (!$rowUpdaterInstance instanceof ‪RowUpdaterInterface) {
144  throw new \RuntimeException(
145  'Row updater must implement RowUpdaterInterface',
146  1484071612
147  );
148  }
149  $rowUpdaterInstances[] = $rowUpdaterInstance;
150  }
151 
152  // Scope of the row updater is to update all rows that have TCA,
153  // our list of tables is just the list of loaded TCA tables.
154  $listOfAllTables = array_keys(‪$GLOBALS['TCA']);
155 
156  // In case the PHP ended for whatever reason, fetch the last position from registry
157  // and throw away all tables before that start point.
158  sort($listOfAllTables);
159  reset($listOfAllTables);
160  $firstTable = current($listOfAllTables);
161  $startPosition = $this->‪getStartPosition($firstTable);
162  foreach ($listOfAllTables as $key => $table) {
163  if ($table === $startPosition['table']) {
164  break;
165  }
166  unset($listOfAllTables[$key]);
167  }
168 
169  // Ask each row updater if it potentially has field updates for rows of a table
170  $tableToUpdaterList = [];
171  foreach ($listOfAllTables as $table) {
172  foreach ($rowUpdaterInstances as $updater) {
173  if ($updater->hasPotentialUpdateForTable($table)) {
174  if (!is_array($tableToUpdaterList[$table])) {
175  $tableToUpdaterList[$table] = [];
176  }
177  $tableToUpdaterList[$table][] = $updater;
178  }
179  }
180  }
181 
182  // Iterate through all rows of all tables that have potential row updaters attached,
183  // feed each single row to each updater and finally update each row in database if
184  // a row updater changed a fields
185  foreach ($tableToUpdaterList as $table => $updaters) {
187  $connectionForTable = $connectionPool->getConnectionForTable($table);
188  $queryBuilder = $connectionPool->getQueryBuilderForTable($table);
189  $queryBuilder->getRestrictions()->removeAll();
190  $queryBuilder->select('*')
191  ->from($table)
192  ->orderBy('uid');
193  if ($table === $startPosition['table']) {
194  $queryBuilder->where(
195  $queryBuilder->expr()->gt('uid', $queryBuilder->createNamedParameter($startPosition['uid']))
196  );
197  }
198  $statement = $queryBuilder->execute();
199  $rowCountWithoutUpdate = 0;
200  while ($row = $rowBefore = $statement->fetch()) {
201  foreach ($updaters as $updater) {
202  $row = $updater->updateTableRow($table, $row);
203  }
204  $updatedFields = array_diff_assoc($row, $rowBefore);
205  if (empty($updatedFields)) {
206  // Updaters changed no field of that row
207  $rowCountWithoutUpdate++;
208  if ($rowCountWithoutUpdate >= 200) {
209  // Update startPosition if there were many rows without data change
210  $startPosition = [
211  'table' => $table,
212  'uid' => $row['uid'],
213  ];
214  $registry->set('installUpdateRows', 'rowUpdatePosition', $startPosition);
215  $rowCountWithoutUpdate = 0;
216  }
217  } else {
218  $rowCountWithoutUpdate = 0;
219  $startPosition = [
220  'table' => $table,
221  'uid' => $rowBefore['uid'],
222  ];
223  if ($connectionForSysRegistry === $connectionForTable) {
224  // Target table and sys_registry table are on the same connection, use a transaction
225  $connectionForTable->beginTransaction();
226  try {
227  $connectionForTable->update(
228  $table,
229  $updatedFields,
230  [
231  'uid' => $rowBefore['uid'],
232  ]
233  );
234  $connectionForTable->update(
235  'sys_registry',
236  [
237  'entry_value' => serialize($startPosition),
238  ],
239  [
240  'entry_namespace' => 'installUpdateRows',
241  'entry_key' => 'rowUpdatePosition',
242  ]
243  );
244  $connectionForTable->commit();
245  } catch (\‪Exception $up) {
246  $connectionForTable->rollBack();
247  throw $up;
248  }
249  } else {
250  // Different connections for table and sys_registry -> execute two
251  // distinct queries and hope for the best.
252  $connectionForTable->update(
253  $table,
254  $updatedFields,
255  [
256  'uid' => $rowBefore['uid'],
257  ]
258  );
259  $connectionForSysRegistry->update(
260  'sys_registry',
261  [
262  'entry_value' => serialize($startPosition),
263  ],
264  [
265  'entry_namespace' => 'installUpdateRows',
266  'entry_key' => 'rowUpdatePosition',
267  ]
268  );
269  }
270  }
271  }
272  }
273 
274  // Ready with updates, remove position information from sys_registry
275  $registry->remove('installUpdateRows', 'rowUpdatePosition');
276  // Mark row updaters that were executed as done
277  foreach ($rowUpdaterInstances as $updater) {
278  $this->‪setRowUpdaterExecuted($updater);
279  }
280 
281  return true;
282  }
283 
289  protected function ‪getRowUpdatersToExecute(): array
290  {
291  $doneRowUpdater = GeneralUtility::makeInstance(Registry::class)->get('installUpdateRows', 'rowUpdatersDone', []);
292  return array_diff($this->rowUpdater, $doneRowUpdater);
293  }
294 
300  protected function ‪setRowUpdaterExecuted(‪RowUpdaterInterface $updater)
301  {
302  $registry = GeneralUtility::makeInstance(Registry::class);
303  $doneRowUpdater = $registry->get('installUpdateRows', 'rowUpdatersDone', []);
304  $doneRowUpdater[] = get_class($updater);
305  $registry->set('installUpdateRows', 'rowUpdatersDone', $doneRowUpdater);
306  }
307 
315  protected function ‪getStartPosition(string $firstTable): array
316  {
317  $registry = GeneralUtility::makeInstance(Registry::class);
318  $startPosition = $registry->get('installUpdateRows', 'rowUpdatePosition', []);
319  if (empty($startPosition)) {
320  $startPosition = [
321  'table' => $firstTable,
322  'uid' => 0,
323  ];
324  $registry->set('installUpdateRows', 'rowUpdatePosition', $startPosition);
325  }
326  return $startPosition;
327  }
328 }
‪TYPO3\CMS\Install\Updates\DatabaseRowsUpdateWizard\getAvailableRowUpdater
‪string[] getAvailableRowUpdater()
Definition: DatabaseRowsUpdateWizard.php:58
‪TYPO3\CMS\Install\Updates\RepeatableInterface
Definition: RepeatableInterface.php:25
‪TYPO3\CMS\Install\Updates\RowUpdater\L10nModeUpdater
Definition: L10nModeUpdater.php:32
‪TYPO3\CMS\Install\Updates\DatabaseRowsUpdateWizard\getIdentifier
‪string getIdentifier()
Definition: DatabaseRowsUpdateWizard.php:66
‪TYPO3\CMS\Install\Updates\RowUpdater\RowUpdaterInterface
Definition: RowUpdaterInterface.php:22
‪TYPO3\CMS\Install\Updates\DatabaseRowsUpdateWizard\getDescription
‪string getDescription()
Definition: DatabaseRowsUpdateWizard.php:83
‪TYPO3\CMS\Core\Registry
Definition: Registry.php:32
‪TYPO3\CMS\Install\Updates\DatabaseRowsUpdateWizard\getStartPosition
‪array getStartPosition(string $firstTable)
Definition: DatabaseRowsUpdateWizard.php:314
‪TYPO3\CMS\Install\Updates\DatabaseRowsUpdateWizard\updateNecessary
‪bool updateNecessary()
Definition: DatabaseRowsUpdateWizard.php:103
‪TYPO3\CMS\Install\Updates
Definition: AbstractDownloadExtensionUpdate.php:3
‪TYPO3\CMS\Install\Updates\DatabaseRowsUpdateWizard
Definition: DatabaseRowsUpdateWizard.php:45
‪TYPO3\CMS\Install\Updates\DatabaseRowsUpdateWizard\$rowUpdater
‪array $rowUpdater
Definition: DatabaseRowsUpdateWizard.php:48
‪TYPO3\CMS\Install\Updates\DatabaseRowsUpdateWizard\getTitle
‪string getTitle()
Definition: DatabaseRowsUpdateWizard.php:74
‪TYPO3\CMS\Install\Updates\DatabaseRowsUpdateWizard\getRowUpdatersToExecute
‪array getRowUpdatersToExecute()
Definition: DatabaseRowsUpdateWizard.php:288
‪TYPO3\CMS\Install\Updates\DatabaseRowsUpdateWizard\setRowUpdaterExecuted
‪setRowUpdaterExecuted(RowUpdaterInterface $updater)
Definition: DatabaseRowsUpdateWizard.php:299
‪TYPO3\CMS\Install\Updates\DatabaseRowsUpdateWizard\executeUpdate
‪bool executeUpdate()
Definition: DatabaseRowsUpdateWizard.php:125
‪TYPO3\CMS\Install\Updates\RowUpdater\ImageCropUpdater
Definition: ImageCropUpdater.php:31
‪TYPO3\CMS\Install\Updates\DatabaseRowsUpdateWizard\getPrerequisites
‪string[] getPrerequisites()
Definition: DatabaseRowsUpdateWizard.php:111
‪TYPO3\CMS\Core\Resource\Exception
Definition: Exception.php:21
‪TYPO3\CMS\Install\Updates\UpgradeWizardInterface
Definition: UpgradeWizardInterface.php:22
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:44
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45