Examples for common upgrade wizards

Upgrade wizard to replace switchable controller actions

Switchable controller actions in Extbase plugins have been deprecated with TYPO3 v10.3 (see also Deprecation: #89463 - Switchable Controller Actions) and removed with TYPO3 v12.4.

On migration of existing installations using plugins with switchable controller actions all plugins have to be changed to a new type. It is recommended to also change them from being defined via field list-type to field CType.

See also Registration of frontend plugins.

The following upgrade wizard can be run on any installation which still has plugins of the outdated type and configuration. It is then not needed anymore to upgrade the plugins manually:

EXT:my_extension/Classes/Upgrades/SwitchableControllerActionUpgradeWizard.php
<?php

namespace MyVendor\MyExtension\Upgrades;

use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Install\Attribute\UpgradeWizard;
use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;

#[UpgradeWizard('myExtension_switchableControllerActionUpgradeWizard')]
final class SwitchableControllerActionUpgradeWizard implements UpgradeWizardInterface
{
    private const TABLE = 'tt_content';
    private const PLUGIN = 'myextension_myplugin';
    public function __construct(
        private readonly ConnectionPool $connectionPool,
        private readonly FlexFormService $flexFormService,
    ) {}

    public function executeUpdate(): bool
    {
        $result = 0;
        $result += $this->migratePlugin(self::PLUGIN, 'MyController->list', 'myextension_mycontrollerlist');
        $result += $this->migratePlugin(self::PLUGIN, 'MyController->overview;MyController->detail', 'myextension_mycontrolleroverviewdetail');
        return $result > 0;
    }

    private function migratePlugin(string $plugin, string $switchable, string $newCType): int
    {
        $updated = 0;
        $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE);
        $result = $queryBuilder
            ->select('*')
            ->from(self::TABLE)
            ->where(
                $queryBuilder->expr()->eq('list_type', $queryBuilder->createNamedParameter($plugin)),
            )
            ->executeQuery();
        while ($row = $result->fetchAssociative()) {
            if (!is_string($row['pi_flexform'] ?? false)) {
                continue;
            }
            $flexform = $this->loadFlexForm($row['pi_flexform']);
            if (!isset($flexform['switchableControllerActions']) || $flexform['switchableControllerActions'] !== $switchable) {
                continue;
            }
            $updated++;
            $this->connectionPool->getConnectionForTable('tt_content')
                ->update(
                    self::TABLE,
                    [ // set
                        'CType' => $newCType,
                        'list_type' => '',
                    ],
                    [ 'uid' => (int)$row['uid'] ], // where
                );
        }
        return $updated;
    }
    private function loadFlexForm(string $flexFormString): array
    {
        return $this->flexFormService
            ->convertFlexFormContentToArray($flexFormString);
    }

    public function updateNecessary(): bool
    {
        $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE);
        $queryBuilder
            ->count('uid')
            ->from(self::TABLE)
            ->where(
                $queryBuilder->expr()->eq('list_type', self::PLUGIN),
            )
            ->executeQuery()
            ->fetchOne();
        return true;
    }

    public function getTitle(): string
    {
        return 'Migrate MyExtension plugins';
    }

    public function getDescription(): string
    {
        return 'Migrate MyExtension plugins from switchable controller actions to specific plugins';
    }

    /**
     * @return string[]
     */
    public function getPrerequisites(): array
    {
        return [];
    }
}
Copied!

You find real world examples of such upgrade wizards for example in georgringer/news : \\GeorgRinger\\News\\Updates\\PluginUpdater.