Der vierte und letzte Teil dieser Blogreihe: Unsere neuen Container und Inhaltselemente sind konfiguriert und einsatzbereit. Aber im Projekt existieren immer noch die alten Datensätze, die mit Gridelements und deren Kindelementen erstellt wurden.
Daher zeigen wir euch diesmal, wie ihr bestehende Daten migrieren könnt.
Weitere Teile:
Mögliche Wege der Migration
In unserem konkreten Projekt haben wir Daten auf drei Arten migriert:
1. Mittels einfachem SQL-Skript
Wenn es nur um kleine Anpassungen in der Datenbank ging, haben wir diese über SQL-Skripte durchgeführt. Beispielsweise hatten wir im Zuge des Upgrades Backend-Layouts umbenannt; deren Identifier mussten dann in den Seiteneigenschaften aktualisiert werden.
1 2 |
UPDATE `pages` SET `backend_layout` = 'pagets__1_column' WHERE `backend_layout` = 'pagets__7'; UPDATE `pages` SET `backend_layout_next_level` = 'pagets__1_column' WHERE `backend_layout_next_level` = 'pagets__7'; |
Auf ähnliche Weise haben wir auch einige Layouts und Frames durch neue Lösungen ersetzt.
2. Manuelles Anlegen der neuen Datensätze
Klar, Automatisierung ist immer zu bevorzugen. Aber seien wir ehrlich: bei einer geringen Menge von Datensätzen geht das schneller, als ein Update-Skript zu schreiben. Dieses Vorgehen ist allerdings auch nicht in jedem Projekt möglich.
3. Mithilfe eigener TYPO3 Upgrade Wizards
Upgrade Wizards kennt ihr aus dem TYPO3 Install Tool. Bei einem Wechsel von TYPO3 v9 auf v10 müsst ihr zum Beispiel die Datensätze aus der veralteten Tabelle “pages_language_overlay
” in die Tabelle “pages” migrieren.
TYPO3 stellt ein Interface bereit, mit dem Entwickler eigene Upgrade Wizards ergänzen können.
Ein Upgrade Wizard eignet sich für komplexe Migrationen:
- Anlegen neuer Datensätze in einer beliebigen Tabelle
- Änderung des CTypes
- Kopieren alter Daten in neue Tabellen(-felder)
- Aktualisierung von FAL-Relationen
- Anpassung von Feldwerten
- Löschen der alten Datensätze
- Beschränkung der Migration auf bestimmte CTypes, Eltern-Gridelemente, …
Unser vollständiges Beispiel weiter unten wird das sehr anschaulich zeigen.
Aufbau eines TYPO3 Upgrade Wizard
Ein gut verständliches Tutorial zur Erstellung eigener Upgrade Wizards findet ihr in der offiziellen TYPO3-Dokumentation.
Daher führen wir hier nur ein paar Eckdaten auf:
- Eigene Upgrade Wizards können im Sitepackage oder anderen Extensions ergänzt werden.
- Vor Ausführung des Upgrades lässt sich prüfen, ob eine Migration notwendig ist.
- Die Reihenfolge auszuführender Upgrade Wizards lässt sich bei Bedarf festlegen.
Es kann auch weiterhelfen, sich andere Upgrade Wizards aus dem TYPO3 Core oder aus Extensions näher anzusehen.
Beispiel: Migration von Gridelements zum Tab-Element des Bootstrap Package
Den folgenden, mustergültigen Upgrade Wizard hat meine Kollegin Mirena Peneva geschrieben.
Die Ausgangslage im Projekt:
- Ein einspaltiges Gridelement “Tab-Container”
- Jedes dieser Gridelemente kann mehrere Inhaltselemente vom Typ “Text & Images” (CType “
textpic
”) beinhalten - Einige Inhaltselemente beinhalten Bilder (FAL-Relationen)
- Im Frontend werden die so gruppierten Inhalte als Reiter (“Tabs”) ausgegeben
Unser Ziel:
- Verwendung des Tab-Elements (mit Inline-Elementen) aus dem Bootstrap Package

Aufgaben:
- Alle betroffenen Elemente auswählen und deren CType entsprechend ändern
- Neue Inline-Elemente für die bisherigen Gridelements-Kindelemente erstellen und bestehende Inhalte dahin migrieren
- Falls vorhanden, FAL-Relationen in der Tabelle “
sys_file_reference
” mit dem neuen Inline-Element verknüpfen - Alte Datensätze löschen
ext_localconf.php:
1 2 |
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['tabs'] = \MyProject\Sitepackage\Updates\MigrateTabs::class; |
Classes/Updates/MigrateTabs.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
<?php namespace MyProject\Sitepackage\Updates; use Exception; use InvalidArgumentException; use RuntimeException; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Install\Updates\UpgradeWizardInterface; /** * Migrates existing "Tab Container" content elements to CType "tab". * In TYPO3 v8, the website contains a gridelements container with layout "Tab Container". * The contents of this container have to be migrated to use the CType "tab" for bootstrap package tabs. * "Tab Container" content elements are restricted to colPos "0" in all backend layouts. * */ class MigrateTabs implements UpgradeWizardInterface { /** * @var int */ protected int $gridElementBELayout = 14; /** * @var string */ protected string $tableTab = 'tt_content'; /** * @var string */ protected string $targetTableTabItem = 'tx_bootstrappackage_tab_item'; /** * @var string */ protected string $targetCTypeTab = 'tab'; /** * @var int */ protected int $colPos = 0; /** * @var string */ protected string $sourceFieldNameImage = 'image'; /** * @var string */ protected string $targetFieldNameImage = 'media'; /** * Return the identifier for this wizard * This should be the same string as used in the ext_localconf class registration * * @return string */ public function getIdentifier(): string { return 'tabs'; } /** * Return the speaking name of this wizard * * @return string */ public function getTitle(): string { return 'Tabs: Migrate existing tabs with Grid Layout "Tab Container"'; } /** * Return the description for this wizard * * @return string */ public function getDescription(): string { return 'Migrate existing tabs with Grid Layout "Tab Container"'; } /** * Execute the update * * Called when a wizard reports that an update is necessary * * @return bool Whether everything went smoothly or not */ public function executeUpdate(): bool { $this->migrateTabs(); return true; } /** * Migrate existing content elements to new CType "tab". * 1. Get all gridelements containers with Grid Layout "Tab Container" and change their CType to "tab". * 2. Add a new content element for each of the container's children. * 3. Migrate the fields "header", "bodytext" and "image" to the fields in the new elements: * "header", "bodytext" and "media" */ protected function migrateTabs() { $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->tableTab); $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); /* @var QueryBuilder $queryBuilder */ $queryBuilder = $connectionPool->getQueryBuilderForTable($this->tableTab); $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class)); try { // 1. Get all gridelements containers with Grid Layout "Tab Container" $gridElementsContainers = $queryBuilder ->select('*') ->from($this->tableTab) ->where( $queryBuilder->expr()->eq('tx_gridelements_backend_layout', $this->gridElementBELayout) ) ->orderBy('uid') ->execute() ->fetchAllAssociative(); foreach ($gridElementsContainers as $container) { // and change their CType to "tab" $fields = [ 'CType' => $this->targetCTypeTab, 'tx_gridelements_container' => 0, 'header' => 'Tab Container', 'header_layout' => 100, 'colPos' => $this->colPos ]; $updatedRows = $connection->update( $this->tableTab, $fields, ['uid' => $container['uid']] ); if ($updatedRows > 0) { // Get all children elements in the selected gridelements containers $tabItems = $queryBuilder ->select('*') ->from($this->tableTab) ->where( $queryBuilder->expr()->eq('tx_gridelements_container', $container['uid']) ) ->orderBy('uid') ->execute(); foreach ($tabItems as $item) { // 2. Add a new content element for each of the container's children $queryBuilderTabItem = $connectionPool->getQueryBuilderForTable($this->targetTableTabItem); $insertSuccessful = $queryBuilderTabItem ->insert($this->targetTableTabItem) ->values([ 'pid' => $item['pid'], 'tt_content' => $item['tx_gridelements_container'], 'header' => $item['header'], 'bodytext' => $item['bodytext'], 'tstamp' => $GLOBALS['EXEC_TIME'], 'crdate' => $GLOBALS['EXEC_TIME'], 'mediaorient' => 'right', $this->targetFieldNameImage => $item[$this->sourceFieldNameImage] ]) ->execute(); if ($insertSuccessful) { // Migrate existing images $newItemUid = $queryBuilder->getConnection()->lastInsertId(); $fileReferenceQueryBuilder = $connectionPool->getQueryBuilderForTable('sys_file_reference'); $fileReferenceQueryBuilder ->update('sys_file_reference') ->where( $queryBuilderTabItem->expr()->andX( $queryBuilderTabItem->expr()->eq( 'fieldname', $queryBuilderTabItem->quote($this->sourceFieldNameImage) ), $queryBuilderTabItem->expr()->eq('uid_foreign', $item['uid']), $queryBuilderTabItem->expr()->eq( 'tablenames', $queryBuilder->quote($this->tableTab) ) ) ) ->set('uid_foreign', $newItemUid) ->set('tablenames', $this->targetTableTabItem) ->set('fieldname', $this->targetFieldNameImage) ->execute(); } } // Delete old tab items $queryBuilderTabItemsOld = $connectionPool->getQueryBuilderForTable($this->tableTab); $queryBuilderTabItemsOld ->delete($this->tableTab) ->where( $queryBuilderTabItemsOld->expr()->eq('tx_gridelements_container', $container['uid']) ) ->execute(); } } } catch (Exception $e) { throw new RuntimeException( 'Database query failed. Error was: ' . $e->getMessage(), 1605857008 ); } } /** * Is an update necessary? * * Is used to determine whether a wizard needs to be run. * Check if data for migration exists. * * @return bool */ public function updateNecessary(): bool { $updateNeeded = false; if ($this->checkIfWizardIsRequired()) { $updateNeeded = true; } return $updateNeeded; } /** * Returns an array of class names of prerequisite classes * * @return string[] */ public function getPrerequisites(): array { return []; } /** * Check if there are gridelements containers with matching Grid Layout. * * @return bool * @throws InvalidArgumentException */ protected function checkIfWizardIsRequired(): bool { $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); $queryBuilder = $connectionPool->getQueryBuilderForTable($this->tableTab); $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class)); $numberOfEntries = $queryBuilder ->count('uid') ->from($this->tableTab) ->where( $queryBuilder->expr()->eq('tx_gridelements_backend_layout', $this->gridElementBELayout) ) ->execute() ->fetchOne(); return $numberOfEntries > 0; } } |
Dieser Upgrade Wizard ist eine praktische Blaupause, die ihr an euer Projekt anpassen könnt. Etwas Erfahrung mit dem TYPO3 QueryBuilder ist von Vorteil; es bietet sich aber auch eine gute Gelegenheit zur Einarbeitung.
Wir wünschen euch viel Erfolg beim Ausprobieren! Habt ihr noch Fragen zum Thema?
Bildquellen
- Tab-Element: www.bootstrap-package.com
- Beitragsbild „Migration“: Sebastian Klein, MFC
Sehr guter und umfangreicher Artikel. Danke