Loading...
Loading...
Systematic TYPO3 extension upgrades to newer LTS versions. Covers Extension Scanner, Rector, Fractor, PHPStan, and testing. Use when working with extension, upgrade, fractor, rector, migration.
npx skill4agent add dirnbauer/webconsulting-skills typo3-extension-upgradeTYPO3 API First: Always use TYPO3's built-in APIs, core features, and established conventions before creating custom implementations. Do not reinvent what TYPO3 already provides. Always verify that the APIs and methods you use exist and are not deprecated in your target TYPO3 version (v13 or v14) by checking the official TYPO3 documentation.
Scope: Extension code upgrades only. NOT for TYPO3 project/core upgrades.
| Tool | Purpose | Files |
|---|---|---|
| Extension Scanner | Diagnose deprecated APIs | TYPO3 Backend |
| Rector | Automated PHP migrations | |
| Fractor | Non-PHP migrations | FlexForms, TypoScript, YAML, Fluid |
| PHPStan | Static analysis | |
# Create backup/snapshot
ddev snapshot --name=before-upgrade
# Verify git is clean
git status
# Create feature branch
git checkout -b feature/typo3-14-upgrade// composer.json
{
"require": {
"php": "^8.2",
"typo3/cms-core": "^13.0 || ^14.0"
}
}// ext_emconf.php
$EM_CONF[$_EXTKEY] = [
'constraints' => [
'depends' => [
'typo3' => '13.0.0-14.99.99',
'php' => '8.2.0-8.4.99',
],
],
];<?php
// rector.php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\LevelSetList;
use Ssch\TYPO3Rector\Set\Typo3LevelSetList;
use Ssch\TYPO3Rector\Set\Typo3SetList;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/Classes',
__DIR__ . '/Configuration',
__DIR__ . '/Tests',
])
->withSkip([
__DIR__ . '/Resources',
])
->withSets([
// PHP version upgrades
LevelSetList::UP_TO_PHP_82,
// TYPO3 upgrades
Typo3LevelSetList::UP_TO_TYPO3_13,
Typo3SetList::TYPO3_13,
])
->withImportNames();# Dry run first
vendor/bin/rector process --dry-run
# Review changes, then apply
vendor/bin/rector process
# Review git diff
git diff<?php
// fractor.php
declare(strict_types=1);
use a]9r\Fractor\Configuration\FractorConfiguration;
use a9f\Typo3Fractor\Set\Typo3LevelSetList;
return FractorConfiguration::configure()
->withPaths([
__DIR__ . '/Configuration',
__DIR__ . '/Resources',
])
->withSets([
Typo3LevelSetList::UP_TO_TYPO3_13,
]);# Dry run first
vendor/bin/fractor process --dry-run
# Review changes, then apply
vendor/bin/fractor process# Run PHP-CS-Fixer
vendor/bin/php-cs-fixer fix
# Verify no issues remain
vendor/bin/php-cs-fixer fix --dry-run# Analyze codebase
vendor/bin/phpstan analyse
# Fix any reported issues
# Then re-run until clean# Unit tests
vendor/bin/phpunit -c Tests/UnitTests.xml
# Functional tests
vendor/bin/phpunit -c Tests/FunctionalTests.xml
# Fix failing tests# Test in target TYPO3 version
ddev composer require "typo3/cms-core:^14.0" --no-update
ddev composer update
ddev typo3 cache:flush
# Test all extension functionality:
# - Backend modules
# - Frontend plugins
# - CLI commands
# - Scheduler tasks// ❌ OLD (deprecated in v13, removed in v14)
use TYPO3\CMS\Fluid\View\StandaloneView;
$view = GeneralUtility::makeInstance(StandaloneView::class);
$view->setTemplatePathAndFilename('...');
// ✅ NEW (v13/v14 compatible)
use TYPO3\CMS\Core\View\ViewFactoryInterface;
use TYPO3\CMS\Core\View\ViewFactoryData;
public function __construct(
private readonly ViewFactoryInterface $viewFactory,
) {}
public function render(ServerRequestInterface $request): string
{
$viewFactoryData = new ViewFactoryData(
templateRootPaths: ['EXT:my_ext/Resources/Private/Templates'],
request: $request,
);
$view = $this->viewFactory->create($viewFactoryData);
$view->assign('data', $data);
return $view->render('MyTemplate');
}// ❌ OLD (v12 and earlier)
public function listAction(): void
{
$this->view->assign('items', $items);
}
// ✅ NEW (v13+ required)
public function listAction(): ResponseInterface
{
$this->view->assign('items', $items);
return $this->htmlResponse();
}// ❌ OLD (hooks deprecated)
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['...']['hook'][] = MyHook::class;
// ✅ NEW (PSR-14 events)
// Configuration/Services.yaml
services:
Vendor\MyExt\EventListener\MyListener:
tags:
- name: event.listener
identifier: 'myext/my-listener'
// Or use PHP attribute
#[AsEventListener(identifier: 'myext/my-listener')]
final class MyListener
{
public function __invoke(SomeEvent $event): void
{
// Handle event
}
}// ❌ OLD (runtime TCA modification - forbidden in v14)
// ext_tables.php
$GLOBALS['TCA']['tt_content']['columns']['myfield'] = [...];
// ✅ NEW (static TCA files only)
// Configuration/TCA/Overrides/tt_content.php
$GLOBALS['TCA']['tt_content']['columns']['myfield'] = [...];// ❌ OLD (ext_tables.php registration)
ExtensionUtility::registerModule(...);
// ✅ NEW (Configuration/Backend/Modules.php)
return [
'web_mymodule' => [
'parent' => 'web',
'access' => 'user,group',
'iconIdentifier' => 'myext-module',
'labels' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang_mod.xlf',
'extensionName' => 'MyExt',
'controllerActions' => [
MyController::class => ['index', 'list'],
],
],
];| Removed/Changed | Replacement |
|---|---|
| |
| Constructor injection |
| Request attribute |
| Various hooks | PSR-14 events |
| Removed/Changed | Replacement |
|---|---|
| Runtime TCA changes | Static TCA only |
| Legacy backend modules | Modules.php |
| QueryBuilder |
# Clear Rector cache
rm -rf .rector_cache/
# Run with verbose output
vendor/bin/rector process --dry-run -vvv
# Skip problematic rules
# Add to rector.php:
->withSkip([
\Ssch\TYPO3Rector\SomeRule::class,
])# Generate baseline for existing issues
vendor/bin/phpstan analyse --generate-baseline
# Add to phpstan.neon:
includes:
- phpstan-baseline.neon# Regenerate autoload
ddev composer dump-autoload
# Clear all caches
ddev typo3 cache:flush
rm -rf var/cache/*
# Re-setup extension
ddev typo3 extension:setup# Check for schema differences
ddev typo3 database:updateschema --verbose
# Apply schema changes
ddev typo3 database:updateschema "*.add,*.change"rector --dry-runfractor --dry-runphpstan analysephp-cs-fixer --dry-run