typo3-update

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

TYPO3 Dual-Version Development: v13 & v14

TYPO3 双版本开发:v13 & v14

Strategy: Write code that works on both TYPO3 v13 and v14, with v14 as the preferred target. All patterns in this skill are designed for dual-version compatibility.
TYPO3 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.
策略: 编写可同时运行在TYPO3 v13和v14上的代码,优先以v14为目标版本。 本指南中的所有模式均为双版本兼容设计。
优先使用TYPO3原生API: 编写自定义实现之前,请始终优先使用TYPO3内置API、核心功能和既定规范,不要重复实现TYPO3已经提供的能力。请通过查阅TYPO3官方文档,确认你所使用的API和方法在目标版本(v13或v14)中存在且未被弃用。

1. Version Strategy

1. 版本策略

Target: TYPO3 v13 and v14

目标版本:TYPO3 v13 和 v14

VersionStatusPHPSupport Until
v12.4 LTSMaintenance8.1-8.3April 2026
v13.4 LTSActive LTS8.2-8.4~2028
v14.xLatest8.2-8.4~2029
Best Practice: Target both v13 and v14 for maximum compatibility. New projects should prefer v14.
版本状态PHP版本支持截止日期
v12.4 LTS维护期8.1-8.32026年4月
v13.4 LTS活跃LTS版本8.2-8.4~2028年
v14.x最新版本8.2-8.4~2029年
最佳实践: 同时适配v13和v14以获得最大兼容性,新项目优先选择v14。

Version Constraints

版本约束

php
<?php
// ext_emconf.php - Dual version support
$EM_CONF[$_EXTKEY] = [
    'title' => 'My Extension',
    'version' => '2.0.0',
    'state' => 'stable',
    'constraints' => [
        'depends' => [
            'typo3' => '13.0.0-14.99.99',
            'php' => '8.2.0-8.4.99',
        ],
        'conflicts' => [],
        'suggests' => [],
    ],
];
json
// composer.json - Dual version support
{
    "name": "vendor/my-extension",
    "type": "typo3-cms-extension",
    "require": {
        "php": "^8.2",
        "typo3/cms-core": "^13.0 || ^14.0"
    },
    "extra": {
        "typo3/cms": {
            "extension-key": "my_extension"
        }
    }
}
php
<?php
// ext_emconf.php - 双版本支持配置
$EM_CONF[$_EXTKEY] = [
    'title' => 'My Extension',
    'version' => '2.0.0',
    'state' => 'stable',
    'constraints' => [
        'depends' => [
            'typo3' => '13.0.0-14.99.99',
            'php' => '8.2.0-8.4.99',
        ],
        'conflicts' => [],
        'suggests' => [],
    ],
];
json
// composer.json - 双版本支持配置
{
    "name": "vendor/my-extension",
    "type": "typo3-cms-extension",
    "require": {
        "php": "^8.2",
        "typo3/cms-core": "^13.0 || ^14.0"
    },
    "extra": {
        "typo3/cms": {
            "extension-key": "my_extension"
        }
    }
}

2. PHP Requirements

2. PHP要求

Minimum PHP Version: 8.2

最低PHP版本:8.2

Both v13 and v14 require PHP 8.2+. Use modern PHP features:
php
<?php
declare(strict_types=1);

namespace Vendor\Extension\Service;

// ✅ Constructor property promotion (PHP 8.0+)
final class MyService
{
    public function __construct(
        private readonly SomeDependency $dependency,
        private readonly AnotherService $anotherService,
    ) {}
}

// ✅ Named arguments (PHP 8.0+)
$result = $this->doSomething(
    name: 'value',
    options: ['key' => 'value'],
);

// ✅ Match expressions (PHP 8.0+)
$type = match ($input) {
    'a' => 'Type A',
    'b' => 'Type B',
    default => 'Unknown',
};

// ✅ Enums (PHP 8.1+)
enum Status: string
{
    case Draft = 'draft';
    case Published = 'published';
}
v13和v14均要求PHP 8.2及以上版本,建议使用现代PHP特性:
php
<?php
declare(strict_types=1);

namespace Vendor\Extension\Service;

// ✅ 构造函数属性提升(PHP 8.0+)
final class MyService
{
    public function __construct(
        private readonly SomeDependency $dependency,
        private readonly AnotherService $anotherService,
    ) {}
}

// ✅ 命名参数(PHP 8.0+)
$result = $this->doSomething(
    name: 'value',
    options: ['key' => 'value'],
);

// ✅ Match表达式(PHP 8.0+)
$type = match ($input) {
    'a' => 'Type A',
    'b' => 'Type B',
    default => 'Unknown',
};

// ✅ 枚举(PHP 8.1+)
enum Status: string
{
    case Draft = 'draft';
    case Published = 'published';
}

3. Controller Patterns (v13/v14 Compatible)

3. 控制器模式(兼容v13/v14)

Extbase Action Controller

Extbase 动作控制器

php
<?php
declare(strict_types=1);

namespace Vendor\Extension\Controller;

use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use Vendor\Extension\Domain\Repository\ItemRepository;

final class ItemController extends ActionController
{
    public function __construct(
        private readonly ItemRepository $itemRepository,
    ) {}

    // ✅ Must return ResponseInterface (required since v13)
    public function listAction(): ResponseInterface
    {
        $items = $this->itemRepository->findAll();
        $this->view->assign('items', $items);
        return $this->htmlResponse();
    }

    public function showAction(int $item): ResponseInterface
    {
        $item = $this->itemRepository->findByUid($item);
        $this->view->assign('item', $item);
        return $this->htmlResponse();
    }

    // ✅ JSON response
    public function apiAction(): ResponseInterface
    {
        $data = ['success' => true, 'items' => []];
        return $this->jsonResponse(json_encode($data));
    }

    // ✅ Redirect
    public function createAction(): ResponseInterface
    {
        // Process creation...
        $this->addFlashMessage('Item created');
        return $this->redirect('list');
    }
}
php
<?php
declare(strict_types=1);

namespace Vendor\Extension\Controller;

use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use Vendor\Extension\Domain\Repository\ItemRepository;

final class ItemController extends ActionController
{
    public function __construct(
        private readonly ItemRepository $itemRepository,
    ) {}

    // ✅ 必须返回ResponseInterface(v13起强制要求)
    public function listAction(): ResponseInterface
    {
        $items = $this->itemRepository->findAll();
        $this->view->assign('items', $items);
        return $this->htmlResponse();
    }

    public function showAction(int $item): ResponseInterface
    {
        $item = $this->itemRepository->findByUid($item);
        $this->view->assign('item', $item);
        return $this->htmlResponse();
    }

    // ✅ JSON响应
    public function apiAction(): ResponseInterface
    {
        $data = ['success' => true, 'items' => []];
        return $this->jsonResponse(json_encode($data));
    }

    // ✅ 重定向
    public function createAction(): ResponseInterface
    {
        // 处理创建逻辑...
        $this->addFlashMessage('Item created');
        return $this->redirect('list');
    }
}

Backend Module Controller

后台模块控制器

php
<?php
declare(strict_types=1);

namespace Vendor\Extension\Controller;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Attribute\AsController;
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;

#[AsController]
final class BackendModuleController
{
    public function __construct(
        private readonly ModuleTemplateFactory $moduleTemplateFactory,
    ) {}

    public function indexAction(ServerRequestInterface $request): ResponseInterface
    {
        $moduleTemplate = $this->moduleTemplateFactory->create($request);
        $moduleTemplate->assign('items', []);
        
        return $moduleTemplate->renderResponse('Backend/Index');
    }
}
php
<?php
declare(strict_types=1);

namespace Vendor\Extension\Controller;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Attribute\AsController;
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;

#[AsController]
final class BackendModuleController
{
    public function __construct(
        private readonly ModuleTemplateFactory $moduleTemplateFactory,
    ) {}

    public function indexAction(ServerRequestInterface $request): ResponseInterface
    {
        $moduleTemplate = $this->moduleTemplateFactory->create($request);
        $moduleTemplate->assign('items', []);
        
        return $moduleTemplate->renderResponse('Backend/Index');
    }
}

4. View & Templating (v13/v14 Compatible)

4. 视图与模板(兼容v13/v14)

ViewFactory (Preferred Pattern)

ViewFactory(推荐模式)

php
<?php
declare(strict_types=1);

namespace Vendor\Extension\Service;

use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\View\ViewFactoryData;
use TYPO3\CMS\Core\View\ViewFactoryInterface;

final class RenderingService
{
    public function __construct(
        private readonly ViewFactoryInterface $viewFactory,
    ) {}

    public function renderEmail(ServerRequestInterface $request, array $data): string
    {
        $viewFactoryData = new ViewFactoryData(
            templateRootPaths: ['EXT:my_extension/Resources/Private/Templates/Email'],
            partialRootPaths: ['EXT:my_extension/Resources/Private/Partials'],
            layoutRootPaths: ['EXT:my_extension/Resources/Private/Layouts'],
            request: $request,
        );
        
        $view = $this->viewFactory->create($viewFactoryData);
        $view->assignMultiple($data);
        
        return $view->render('Notification');
    }
}
php
<?php
declare(strict_types=1);

namespace Vendor\Extension\Service;

use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\View\ViewFactoryData;
use TYPO3\CMS\Core\View\ViewFactoryInterface;

final class RenderingService
{
    public function __construct(
        private readonly ViewFactoryInterface $viewFactory,
    ) {}

    public function renderEmail(ServerRequestInterface $request, array $data): string
    {
        $viewFactoryData = new ViewFactoryData(
            templateRootPaths: ['EXT:my_extension/Resources/Private/Templates/Email'],
            partialRootPaths: ['EXT:my_extension/Resources/Private/Partials'],
            layoutRootPaths: ['EXT:my_extension/Resources/Private/Layouts'],
            request: $request,
        );
        
        $view = $this->viewFactory->create($viewFactoryData);
        $view->assignMultiple($data);
        
        return $view->render('Notification');
    }
}

Fluid Template Best Practices

Fluid 模板最佳实践

html
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
      xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
      data-namespace-typo3-fluid="true">

<f:layout name="Default" />

<f:section name="Main">
    <div class="content">
        <!-- ✅ Auto-escaped output -->
        <h1>{item.title}</h1>
        
        <!-- ✅ Explicit HTML (use with caution) -->
        <f:format.html>{item.bodytext}</f:format.html>
        
        <!-- ✅ Conditional rendering -->
        <f:if condition="{items}">
            <f:then>
                <f:for each="{items}" as="item">
                    <f:render partial="Item" arguments="{item: item}" />
                </f:for>
            </f:then>
            <f:else>
                <p>No items found.</p>
            </f:else>
        </f:if>
        
        <!-- ✅ Link building -->
        <f:link.action action="show" arguments="{item: item.uid}">
            View Details
        </f:link.action>
    </div>
</f:section>

</html>
html
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
      xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
      data-namespace-typo3-fluid="true">

<f:layout name="Default" />

<f:section name="Main">
    <div class="content">
        <!-- ✅ 自动转义输出 -->
        <h1>{item.title}</h1>
        
        <!-- ✅ 显式输出HTML(谨慎使用) -->
        <f:format.html>{item.bodytext}</f:format.html>
        
        <!-- ✅ 条件渲染 -->
        <f:if condition="{items}">
            <f:then>
                <f:for each="{items}" as="item">
                    <f:render partial="Item" arguments="{item: item}" />
                </f:for>
            </f:then>
            <f:else>
                <p>No items found.</p>
            </f:else>
        </f:if>
        
        <!-- ✅ 链接生成 -->
        <f:link.action action="show" arguments="{item: item.uid}">
            查看详情
        </f:link.action>
    </div>
</f:section>

</html>

5. Event System (v13/v14 Compatible)

5. 事件系统(兼容v13/v14)

PSR-14 Event Listeners

PSR-14 事件监听器

PSR-14 events are the standard in both v13 and v14. Always prefer events over hooks.
php
<?php
declare(strict_types=1);

namespace Vendor\Extension\EventListener;

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Frontend\Event\ModifyCacheLifetimeForPageEvent;

#[AsEventListener(identifier: 'vendor-extension/modify-cache-lifetime')]
final class ModifyCacheLifetimeListener
{
    public function __invoke(ModifyCacheLifetimeForPageEvent $event): void
    {
        // Reduce cache lifetime for certain pages
        if ($event->getPageId() === 123) {
            $event->setCacheLifetime(300); // 5 minutes
        }
    }
}
PSR-14事件是v13和v14通用的标准,优先使用事件而非钩子。
php
<?php
declare(strict_types=1);

namespace Vendor\Extension\EventListener;

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Frontend\Event\ModifyCacheLifetimeForPageEvent;

#[AsEventListener(identifier: 'vendor-extension/modify-cache-lifetime')]
final class ModifyCacheLifetimeListener
{
    public function __invoke(ModifyCacheLifetimeForPageEvent $event): void
    {
        // 缩短特定页面的缓存有效期
        if ($event->getPageId() === 123) {
            $event->setCacheLifetime(300); // 5分钟
        }
    }
}

Common Events (v13/v14)

常用事件(v13/v14通用)

EventPurpose
BeforeRecordOperationEvent
Before DataHandler operations
AfterRecordOperationEvent
After DataHandler operations
ModifyPageLinkConfigurationEvent
Modify link building
ModifyCacheLifetimeForPageEvent
Adjust page cache
BeforeStdWrapFunctionsInitializedEvent
Modify stdWrap
事件名称用途
BeforeRecordOperationEvent
DataHandler操作执行前触发
AfterRecordOperationEvent
DataHandler操作执行后触发
ModifyPageLinkConfigurationEvent
修改链接生成配置
ModifyCacheLifetimeForPageEvent
调整页面缓存有效期
BeforeStdWrapFunctionsInitializedEvent
修改stdWrap配置

Services.yaml Registration

Services.yaml 注册方式

yaml
undefined
yaml
undefined

Configuration/Services.yaml

Configuration/Services.yaml

services: _defaults: autowire: true autoconfigure: true public: false
Vendor\Extension: resource: '../Classes/' exclude: - '../Classes/Domain/Model/'

Event listener (alternative to #[AsEventListener] attribute)

Vendor\Extension\EventListener\MyListener: tags: - name: event.listener identifier: 'vendor-extension/my-listener'
undefined
services: _defaults: autowire: true autoconfigure: true public: false
Vendor\Extension: resource: '../Classes/' exclude: - '../Classes/Domain/Model/'

事件监听器(#[AsEventListener]注解的替代方案)

Vendor\Extension\EventListener\MyListener: tags: - name: event.listener identifier: 'vendor-extension/my-listener'
undefined

6. Backend Module Registration (v13/v14)

6. 后台模块注册(v13/v14通用)

Configuration/Backend/Modules.php

Configuration/Backend/Modules.php

php
<?php
// Configuration/Backend/Modules.php
return [
    'web_myextension' => [
        'parent' => 'web',
        'position' => ['after' => 'web_info'],
        'access' => 'user,group',
        'iconIdentifier' => 'myextension-module',
        'path' => '/module/web/myextension',
        'labels' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_mod.xlf',
        'extensionName' => 'MyExtension',
        'controllerActions' => [
            \Vendor\MyExtension\Controller\BackendController::class => [
                'index',
                'list',
                'show',
            ],
        ],
    ],
];
php
<?php
// Configuration/Backend/Modules.php
return [
    'web_myextension' => [
        'parent' => 'web',
        'position' => ['after' => 'web_info'],
        'access' => 'user,group',
        'iconIdentifier' => 'myextension-module',
        'path' => '/module/web/myextension',
        'labels' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_mod.xlf',
        'extensionName' => 'MyExtension',
        'controllerActions' => [
            \Vendor\MyExtension\Controller\BackendController::class => [
                'index',
                'list',
                'show',
            ],
        ],
    ],
];

Icon Registration

图标注册

php
<?php
// Configuration/Icons.php
return [
    'myextension-module' => [
        'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
        'source' => 'EXT:my_extension/Resources/Public/Icons/module.svg',
    ],
];
php
<?php
// Configuration/Icons.php
return [
    'myextension-module' => [
        'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
        'source' => 'EXT:my_extension/Resources/Public/Icons/module.svg',
    ],
];

7. TCA Configuration (v13/v14 Compatible)

7. TCA配置(兼容v13/v14)

Static TCA Only

仅使用静态TCA

In v14, runtime TCA modifications are forbidden. Always use static files:
php
<?php
// Configuration/TCA/tx_myext_domain_model_item.php
return [
    'ctrl' => [
        'title' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_item',
        'label' => 'title',
        'tstamp' => 'tstamp',
        'crdate' => 'crdate',
        'delete' => 'deleted',
        'enablecolumns' => [
            'disabled' => 'hidden',
            'starttime' => 'starttime',
            'endtime' => 'endtime',
        ],
        'searchFields' => 'title,description',
        'iconIdentifier' => 'myextension-item',
    ],
    'palettes' => [
        'visibility' => [
            'showitem' => 'hidden',
        ],
        'access' => [
            'showitem' => 'starttime, endtime',
        ],
    ],
    'types' => [
        '1' => [
            'showitem' => '
                --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
                    title, description,
                --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
                    --palette--;;visibility,
                    --palette--;;access,
            ',
        ],
    ],
    'columns' => [
        'title' => [
            'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_item.title',
            'config' => [
                'type' => 'input',
                'size' => 50,
                'max' => 255,
                'required' => true,
            ],
        ],
        'description' => [
            'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_item.description',
            'config' => [
                'type' => 'text',
                'cols' => 40,
                'rows' => 5,
                'enableRichtext' => true,
            ],
        ],
    ],
];
v14中禁止运行时修改TCA,请始终使用静态文件配置:
php
<?php
// Configuration/TCA/tx_myext_domain_model_item.php
return [
    'ctrl' => [
        'title' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_item',
        'label' => 'title',
        'tstamp' => 'tstamp',
        'crdate' => 'crdate',
        'delete' => 'deleted',
        'enablecolumns' => [
            'disabled' => 'hidden',
            'starttime' => 'starttime',
            'endtime' => 'endtime',
        ],
        'searchFields' => 'title,description',
        'iconIdentifier' => 'myextension-item',
    ],
    'palettes' => [
        'visibility' => [
            'showitem' => 'hidden',
        ],
        'access' => [
            'showitem' => 'starttime, endtime',
        ],
    ],
    'types' => [
        '1' => [
            'showitem' => '
                --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
                    title, description,
                --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
                    --palette--;;visibility,
                    --palette--;;access,
            ',
        ],
    ],
    'columns' => [
        'title' => [
            'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_item.title',
            'config' => [
                'type' => 'input',
                'size' => 50,
                'max' => 255,
                'required' => true,
            ],
        ],
        'description' => [
            'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_item.description',
            'config' => [
                'type' => 'text',
                'cols' => 40,
                'rows' => 5,
                'enableRichtext' => true,
            ],
        ],
    ],
];

Auto-Created Columns from ctrl (v13.3+)

ctrl字段自动生成(v13.3+)

Since TYPO3 v13.3, column definitions for fields referenced in
ctrl
are auto-created by the Core. You no longer need explicit
'columns'
entries for:
  • enablecolumns
    fields:
    hidden
    /
    disabled
    ,
    starttime
    ,
    endtime
    ,
    fe_group
  • Language fields:
    sys_language_uid
    ,
    l10n_parent
    ,
    l10n_diffsource
  • editlock
    ,
    description
    (if set as
    descriptionColumn
    )
Convention fields don't need
ext_tables.sql
-- all fields referenced by
ctrl
properties (
deleted
,
hidden
,
starttime
,
endtime
,
fe_group
,
sorting
,
tstamp
,
crdate
,
sys_language_uid
,
l10n_parent
,
l10n_diffsource
) are added to the database automatically. Defining them in
ext_tables.sql
may cause problems.
Fields that never need
columns
definitions:
  • deleted
    -- handled by DataHandler, not shown in forms
  • tstamp
    ,
    crdate
    ,
    t3_origuid
    -- managed by DataHandler, never displayed
  • sorting
    -- managed by DataHandler, should NOT be in
    columns
Still required:
  • The
    ctrl
    properties themselves (
    tstamp
    ,
    crdate
    ,
    delete
    ,
    enablecolumns
    , etc.)
  • References in
    showitem
    / palettes (auto-created columns must still be added to types manually)
  • Use dedicated palettes:
    visibility
    for
    hidden
    ,
    access
    for
    starttime, endtime
  • Your own custom column definitions
Override auto-created columns in
Configuration/TCA/Overrides/
if needed:
php
<?php
// Configuration/TCA/Overrides/pages.php
// New pages are disabled by default
$GLOBALS['TCA']['pages']['columns']['disabled']['config']['default'] = 1;
Important: Auto-creation only works for
ctrl
properties in base
Configuration/TCA/
files, NOT in
Configuration/TCA/Overrides/
.
从TYPO3 v13.3开始,
ctrl
中引用的字段的列定义会由核心自动生成,你无需为以下字段显式编写
'columns'
配置:
  • enablecolumns
    字段:
    hidden
    /
    disabled
    starttime
    endtime
    fe_group
  • 多语言字段:
    sys_language_uid
    l10n_parent
    l10n_diffsource
  • editlock
    description
    (如果设置为
    descriptionColumn
约定字段不需要在
ext_tables.sql
中定义
——所有
ctrl
属性引用的字段(
deleted
hidden
starttime
endtime
fe_group
sorting
tstamp
crdate
sys_language_uid
l10n_parent
l10n_diffsource
)都会自动添加到数据库,在
ext_tables.sql
中定义这些字段反而可能引发问题。
完全不需要
columns
定义的字段:
  • deleted
    ——由DataHandler处理,不在表单中展示
  • tstamp
    crdate
    t3_origuid
    ——由DataHandler管理,永远不会展示
  • sorting
    ——由DataHandler管理,不应该出现在
    columns
仍需手动配置的内容:
  • ctrl
    属性本身(
    tstamp
    crdate
    delete
    enablecolumns
    等)
  • showitem
    /调色板中的引用(自动生成的列仍需手动添加到types配置中)
  • 使用专用调色板:
    visibility
    用于
    hidden
    access
    用于
    starttime, endtime
  • 自定义字段的列定义
如果需要,可在
Configuration/TCA/Overrides/
中覆盖自动生成的列:
php
<?php
// Configuration/TCA/Overrides/pages.php
// 新建页面默认禁用
$GLOBALS['TCA']['pages']['columns']['disabled']['config']['default'] = 1;
重要: 自动生成仅对
Configuration/TCA/
基础文件中的
ctrl
属性生效,
Configuration/TCA/Overrides/
中的配置不支持自动生成。

TCA Overrides

TCA 覆盖配置

php
<?php
// Configuration/TCA/Overrides/tt_content.php
defined('TYPO3') or die();

\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTcaSelectItem(
    'tt_content',
    'CType',
    [
        'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:ctype.title',
        'value' => 'myextension_element',
        'icon' => 'content-text',
        'group' => 'default',
    ]
);

$GLOBALS['TCA']['tt_content']['types']['myextension_element'] = [
    'showitem' => '
        --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
            --palette--;;general,
            header,
            bodytext,
        --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
            --palette--;;hidden,
    ',
];
php
<?php
// Configuration/TCA/Overrides/tt_content.php
defined('TYPO3') or die();

\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTcaSelectItem(
    'tt_content',
    'CType',
    [
        'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:ctype.title',
        'value' => 'myextension_element',
        'icon' => 'content-text',
        'group' => 'default',
    ]
);

$GLOBALS['TCA']['tt_content']['types']['myextension_element'] = [
    'showitem' => '
        --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
            --palette--;;general,
            header,
            bodytext,
        --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
            --palette--;;hidden,
    ',
];

8. Database Operations (v13/v14 Compatible)

8. 数据库操作(兼容v13/v14)

QueryBuilder

QueryBuilder

php
<?php
declare(strict_types=1);

namespace Vendor\Extension\Repository;

use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;

final class CustomRepository
{
    public function __construct(
        private readonly ConnectionPool $connectionPool,
    ) {}

    public function findByStatus(string $status): array
    {
        $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_myext_items');
        
        return $queryBuilder
            ->select('*')
            ->from('tx_myext_items')
            ->where(
                $queryBuilder->expr()->eq(
                    'status',
                    $queryBuilder->createNamedParameter($status)
                )
            )
            ->orderBy('title', 'ASC')
            ->executeQuery()
            ->fetchAllAssociative();
    }

    public function countByPid(int $pid): int
    {
        $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_myext_items');
        
        return (int)$queryBuilder
            ->count('uid')
            ->from('tx_myext_items')
            ->where(
                $queryBuilder->expr()->eq(
                    'pid',
                    $queryBuilder->createNamedParameter($pid, Connection::PARAM_INT)
                )
            )
            ->executeQuery()
            ->fetchOne();
    }
}
php
<?php
declare(strict_types=1);

namespace Vendor\Extension\Repository;

use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;

final class CustomRepository
{
    public function __construct(
        private readonly ConnectionPool $connectionPool,
    ) {}

    public function findByStatus(string $status): array
    {
        $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_myext_items');
        
        return $queryBuilder
            ->select('*')
            ->from('tx_myext_items')
            ->where(
                $queryBuilder->expr()->eq(
                    'status',
                    $queryBuilder->createNamedParameter($status)
                )
            )
            ->orderBy('title', 'ASC')
            ->executeQuery()
            ->fetchAllAssociative();
    }

    public function countByPid(int $pid): int
    {
        $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_myext_items');
        
        return (int)$queryBuilder
            ->count('uid')
            ->from('tx_myext_items')
            ->where(
                $queryBuilder->expr()->eq(
                    'pid',
                    $queryBuilder->createNamedParameter($pid, Connection::PARAM_INT)
                )
            )
            ->executeQuery()
            ->fetchOne();
    }
}

Extbase Repository

Extbase 仓库

php
<?php
declare(strict_types=1);

namespace Vendor\Extension\Domain\Repository;

use TYPO3\CMS\Extbase\Persistence\QueryInterface;
use TYPO3\CMS\Extbase\Persistence\Repository;

final class ItemRepository extends Repository
{
    protected $defaultOrderings = [
        'sorting' => QueryInterface::ORDER_ASCENDING,
    ];

    public function findPublished(): array
    {
        $query = $this->createQuery();
        $query->matching(
            $query->logicalAnd(
                $query->equals('hidden', false),
                $query->lessThanOrEqual('starttime', time()),
                $query->logicalOr(
                    $query->equals('endtime', 0),
                    $query->greaterThan('endtime', time())
                )
            )
        );
        
        return $query->execute()->toArray();
    }
}
php
<?php
declare(strict_types=1);

namespace Vendor\Extension\Domain\Repository;

use TYPO3\CMS\Extbase\Persistence\QueryInterface;
use TYPO3\CMS\Extbase\Persistence\Repository;

final class ItemRepository extends Repository
{
    protected $defaultOrderings = [
        'sorting' => QueryInterface::ORDER_ASCENDING,
    ];

    public function findPublished(): array
    {
        $query = $this->createQuery();
        $query->matching(
            $query->logicalAnd(
                $query->equals('hidden', false),
                $query->lessThanOrEqual('starttime', time()),
                $query->logicalOr(
                    $query->equals('endtime', 0),
                    $query->greaterThan('endtime', time())
                )
            )
        );
        
        return $query->execute()->toArray();
    }
}

9. CLI Commands (v13/v14 Compatible)

9. CLI命令(兼容v13/v14)

php
<?php
declare(strict_types=1);

namespace Vendor\Extension\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use TYPO3\CMS\Core\Core\Bootstrap;

#[AsCommand(
    name: 'myext:process',
    description: 'Process items in the extension',
)]
final class ProcessCommand extends Command
{
    protected function configure(): void
    {
        $this
            ->addArgument('type', InputArgument::REQUIRED, 'The type to process')
            ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force processing');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        
        // Initialize backend for DataHandler operations
        Bootstrap::initializeBackendAuthentication();
        
        $type = $input->getArgument('type');
        $force = $input->getOption('force');
        
        $io->title('Processing: ' . $type);
        
        // Your logic here...
        
        $io->success('Processing completed successfully');
        
        return Command::SUCCESS;
    }
}
php
<?php
declare(strict_types=1);

namespace Vendor\Extension\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use TYPO3\CMS\Core\Core\Bootstrap;

#[AsCommand(
    name: 'myext:process',
    description: '处理扩展中的条目',
)]
final class ProcessCommand extends Command
{
    protected function configure(): void
    {
        $this
            ->addArgument('type', InputArgument::REQUIRED, '要处理的类型')
            ->addOption('force', 'f', InputOption::VALUE_NONE, '强制处理');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        
        // 为DataHandler操作初始化后台权限
        Bootstrap::initializeBackendAuthentication();
        
        $type = $input->getArgument('type');
        $force = $input->getOption('force');
        
        $io->title('处理中:' . $type);
        
        // 你的业务逻辑...
        
        $io->success('处理完成');
        
        return Command::SUCCESS;
    }
}

Command Registration

命令注册

yaml
undefined
yaml
undefined

Configuration/Services.yaml

Configuration/Services.yaml

services: Vendor\Extension\Command\ProcessCommand: tags: - name: console.command
undefined
services: Vendor\Extension\Command\ProcessCommand: tags: - name: console.command
undefined

10. Testing for Dual-Version Compatibility

10. 双版本兼容性测试

PHPUnit Setup

PHPUnit 配置

xml
<!-- phpunit.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/typo3/testing-framework/Resources/Core/Build/UnitTestsBootstrap.php"
         colors="true">
    <testsuites>
        <testsuite name="Unit">
            <directory>Tests/Unit</directory>
        </testsuite>
        <testsuite name="Functional">
            <directory>Tests/Functional</directory>
        </testsuite>
    </testsuites>
</phpunit>
xml
<!-- phpunit.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/typo3/testing-framework/Resources/Core/Build/UnitTestsBootstrap.php"
         colors="true">
    <testsuites>
        <testsuite name="单元测试">
            <directory>Tests/Unit</directory>
        </testsuite>
        <testsuite name="功能测试">
            <directory>Tests/Functional</directory>
        </testsuite>
    </testsuites>
</phpunit>

Test Both Versions in CI

CI中测试两个版本

yaml
undefined
yaml
undefined

.github/workflows/ci.yaml

.github/workflows/ci.yaml

name: CI
on: [push, pull_request]
jobs: test: runs-on: ubuntu-latest strategy: matrix: typo3: ['^13.0', '^14.0'] php: ['8.2', '8.3']
steps:
  - uses: actions/checkout@v4
  
  - name: Setup PHP
    uses: shivammathur/setup-php@v2
    with:
      php-version: ${{ matrix.php }}
      extensions: intl, pdo_mysql
  
  - name: Install dependencies
    run: |
      composer require typo3/cms-core:${{ matrix.typo3 }} --no-update
      composer install --prefer-dist --no-progress
  
  - name: Run tests
    run: vendor/bin/phpunit
undefined
name: CI
on: [push, pull_request]
jobs: test: runs-on: ubuntu-latest strategy: matrix: typo3: ['^13.0', '^14.0'] php: ['8.2', '8.3']
steps:
  - uses: actions/checkout@v4
  
  - name: 配置PHP环境
    uses: shivammathur/setup-php@v2
    with:
      php-version: ${{ matrix.php }}
      extensions: intl, pdo_mysql
  
  - name: 安装依赖
    run: |
      composer require typo3/cms-core:${{ matrix.typo3 }} --no-update
      composer install --prefer-dist --no-progress
  
  - name: 运行测试
    run: vendor/bin/phpunit
undefined

11. Upgrade Process

11. 升级流程

From v12 to v13/v14

从v12升级到v13/v14

bash
undefined
bash
undefined

1. Create backup

1. 创建备份

ddev snapshot --name=before-upgrade
ddev snapshot --name=before-upgrade

2. Update composer constraints

2. 更新composer约束

ddev composer require "typo3/cms-core:^13.0 || ^14.0" --no-update ddev composer update "typo3/*" --with-all-dependencies
ddev composer require "typo3/cms-core:^13.0 || ^14.0" --no-update ddev composer update "typo3/*" --with-all-dependencies

3. Run upgrade wizards

3. 运行升级向导

ddev typo3 upgrade:list ddev typo3 upgrade:run
ddev typo3 upgrade:list ddev typo3 upgrade:run

4. Clear caches

4. 清除缓存

ddev typo3 cache:flush
ddev typo3 cache:flush

5. Update database schema

5. 更新数据库结构

ddev typo3 database:updateschema
ddev typo3 database:updateschema

6. Test thoroughly

6. 全面测试

undefined
undefined

12. Resources

12. 参考资源

Credits & Attribution

致谢

Thanks to Netresearch DTT GmbH for their contributions to the TYPO3 community.
感谢Netresearch DTT GmbH为TYPO3社区做出的贡献。