TYPO3 extension tea 

Extension key

tea

Package name

ttn/tea

Version

main

Language

en

Author

Oliver Klee & Contributors

License

This extension is published under the GNU General Public License v2.0.

Rendered

Wed, 08 Apr 2026 16:02:32 +0000


This TYPO3 extension, based on Extbase and Fluid, is an example of best practices in building an extension and securing its quality by automated code checks, unit/functional/acceptance testing and continuous integration (CI).


Table of Contents:

Introduction 

What does it do? 

This TYPO3 extension, based on Extbase and Fluid, is an example of best practices in building an extension and securing its quality by automated code checks, unit/functional/acceptance testing and continuous integration (CI).

Why is this extension called "tea"? 

This extension aims to cover all fundamental aspects of developing an Extbase extension. The fictional use case is the management of tea varieties. The required models, classes, controllers, and other components represent all typical parts of an Extbase extension. Additional examples demonstrate how to test these components.

Since well-chosen extension names should reflect their domain, this extension is named “tea”. While we could have added _example to indicate that it is a sample extension, this would contradict our goal of demonstrating best practices.

This is not related to the tea package manager.

Target group 

The target group for this extension is TYPO3 extension developers. Typical use cases include:

  • Gaining inspiration by exploring the code
  • Cloning the tea extension locally to access a working example of tests, which can then be adapted for your own extension

Presentation at the TYPO3 Online Days 2021 

At the TYPO3 Online Days 2021, Oliver Klee held a session presenting our approach to automate extension code quality. Have a look at the slides and the video of the presentation!

Slides
Video

Development environment 

You can run the code quality checks and automated tests locally (using a local PHP, Composer, and database) or using runTests.sh.

To kickstart the project, we suggest the usage of the TYPO3-testing-distribution by Oliver Klee as development environment. The distribution comes with a frontend, example data and predefined plugins.

The following commands expect that your SSH key is known by GitHub. If not, you need to use https://.

git clone git@github.com:oliverklee/TYPO3-testing-distribution.git
Copied!

Please checkout the branch for the TYPO3 version you need. e.g. 12.x. For more information about the TYPO3-testing-distribution (e.g. backend user credentials), use the documentation: https://github.com/oliverklee-de/TYPO3-testing-distribution/blob/13.x/README.md (TYPO3 13) Adjust link to your needed version.

git clone git@github.com:TYPO3BestPractices/tea.git
Copied!

You can organize the folder structure as you wish, but lets say your folder structure looks like this:

git\
    TYPO3-testing-distribution
    tea
Copied!

Inside the testing distribution there is a file docker-compose.extensions.yaml.template which mounts the used extensions. This file need to be renamed and adjusted.

cp .ddev/docker-compose.extensions.yaml.template .ddev/docker-compose.extensions.yaml
Copied!

The file needs to mount the tea extension into the testing distribution. Keep in mind that you use the correct paths here.

services:
  web:
    volumes:
      - "$HOME/git/tea:/var/www/html/src/extensions/tea:cached,ro"
Copied!

After that you can start the testing distribution using ddev.

ddev start
ddev composer install
ddev install-typo3
ddev db-import
Copied!

After that you should be able to access the frontend:

ddev launch
Copied!

Technical Background 

Detailed documentation of all subaspects of testing:

Our approach to PHP version support 

  1. For the TYPO3 versions we currently support, this extension will support all PHP versions officially supported by those TYPO3 versions.
  2. In addition, we are doing our best to support newer PHP versions as soon as they are available (even release candidates, alphas and betas).
  3. Before we mark a PHP version as supported via composer.json and ext_emconf.php, we ensure that all tests pass on that PHP version. (That also means that if you want to install the extension on that PHP version nonetheless, you will need to tweak Composer's platform settings to bypass this.)

Release and Branching Strategy 

When maintaining TYPO3 extensions, developers often need to support multiple TYPO3 Long-Term Support (LTS) versions simultaneously. This typically requires a clear release and branching strategy to manage development and maintenance across different TYPO3 versions.

There are two common strategies for managing branches when supporting multiple TYPO3 LTS versions.

Approach 1: One branch for multiple TYPO3 LTS versions 

In this approach, there is a single main branch that receives new features and updates, while supporting multiple TYPO3 LTS versions at the same time.

The downside of this approach is that it may require some version-dependent code switches, which can increase complexity. However, the major advantage is that there is only one branch to maintain, making it easier to implement new features and code changes across all supported TYPO3 versions.

This approach simplifies the maintenance of the extension and is preferred when minimizing maintenance overhead is the primary concern.

Approach 2: Separate branch per TYPO3 LTS version 

In this approach, there is one main branch for each TYPO3 LTS version. This means that each branch exclusively supports a single TYPO3 LTS version.

The advantage here is that version-specific code can be used without requiring version-dependent switches, reducing complexity in the codebase. However, this approach increases the maintenance burden, as any new features or updates must be applied to each branch individually.

This approach may be necessary when there are significant differences between TYPO3 LTS versions or when you want to avoid version-dependent code.

Conclusion 

The appropriate release and branching strategy depends on the specific requirements of the extension and the TYPO3 versions you are supporting. If reducing maintenance complexity is a priority, using a single branch for multiple versions is often the better choice. However, if you need to tailor your extension for each version, a separate branch for each version may be more suitable.

Dependency manager 

To keep things simple, most development tools, for example php-cs-fixer, are installed by Composer as development requirements and in some cases where installation via Composer is not possible, we use PHIVE.

PHIVE packages each tool with all its dependencies as a separate PHAR. This helps avoid dependency hell (which means that you cannot install or upgrade some tool as the tool's dependencies conflict with the dependencies on another library). It also allows running versions of tools that require a PHP version that is higher than the lowest allowed PHP version for this project.

Using PHIVE to install phpunit/phpcov 

To support php version 7.4 and 8.2 in the tea extension, we are using PHIVE to install phpunit/phpcov. We need phpunit/phpcov in version 10 to support php 8.2. Our minimum php version 7.4 would prevent the installation with composer.

Command Controller 

The "tea" extension comes with a CommandController that can be used for the automatic creation of test data. It also serves to illustrate how data can be created in the database using a command controller.

You must set a page id as argument. Therefore it's necessary to create an sysfolder before.

You can add option -d to delete already existing data.

vendor/bin/typo3 tea:create-test-data 3
Copied!

Running checks & tests 

Most code checks and tests can be run via Composer commands.

Composer scripts 

For most development-related tasks, this extension provides Composer scripts. If you are working locally (Composer needs to be installed on your local machine), you can run them using composer <scriptname>.

You can run composer or ./Build/Scripts/runTests.sh -s composer to display a list of all available Composer commands and scripts. For all custom Composer scripts there are descriptions in the script-description section of the composer.json.

Keep in mind that certain commands only work if you have run composer install or ./Build/Scripts/runTests.sh -s composer install before.

If you have problems with missing dependencies on your local machine, it is recommended to execute tests with the usage of the runTests.sh script.

./Build/Scripts/runTests.sh -s composer check:php:lint
Copied!

This makes your life easier because you don't have to worry about local dependencies.

Running code checks 

You can currently run these code checks on the command line:

./Build/Scripts/runTests.sh -s composer check:composer:normalize
Copied!

Checks the composer.json.

./Build/Scripts/runTests.sh -s composer check:json:lint
Copied!

Lints the JSON files.

./Build/Scripts/runTests.sh -s composer check:php
Copied!

Runs all static checks for the PHP files.

./Build/Scripts/runTests.sh -s composer check:php:cs-fixer
Copied!

Checks the code style with the PHP Coding Standards Fixer (PHP-CS-Fixer).

./Build/Scripts/runTests.sh -s composer check:php:lint
Copied!

Lints the PHP files for syntax errors.

./Build/Scripts/runTests.sh -s composer check:php:stan
Copied!

Checks the PHP types using PHPStan.

./Build/Scripts/runTests.sh -s composer check:static
Copied!

Runs all static code checks (syntax, style, types).

./Build/Scripts/runTests.sh -scomposer check:typoscript:lint
Copied!

Lints the TypoScript files.

./Build/Scripts/runTests.sh -s composer check:yaml:lint
Copied!

Lints the YAML files.

./Build/Scripts/runTests.sh -s composer fix
Copied!

Runs all fixers (except for the ones that need JavaScript).

./Build/Scripts/runTests.sh -s composer fix:php
Copied!

Runs all fixers for the PHP code.

./Build/Scripts/runTests.sh -s composer fix:php:cs-fixer
Copied!

Fixes the code style with PHP-CS-Fixer.

./Build/Scripts/runTests.sh -s composer phpstan:baseline
Copied!

Updates the PHPStan baseline file to match the code.

Running unit and functional tests 

./Build/Scripts/runTests.sh -s composer update
Copied!

You can currently run these tests on the command line:

./Build/Scripts/runTests.sh -s functional
Copied!

Runs the functional tests using a database populated from the CSV files in Tests/Functional/Controller/Fixtures/Database folder.

./Build/Scripts/runTests.sh -s unit
Copied!

Runs the unit tests.

Running unit and functional tests in PHPStorm 

General setup 

  • Open File > Settings > PHP > Test Frameworks.
  • (*) Use Composer autoloader.
  • Path to script: select .Build/vendor/autoload.php in your project folder.

In the Run configurations, edit the PHPUnit configuration and use these settings so this configuration can serve as a template:

  • Directory: use the Tests/Unit directory in your project.
  • (*) Use alternative configuration file.
  • Use .Build/vendor/typo3/testing-framework/Resources/Core/Build/UnitTests.xml in your project folder.
  • Add the following environment variables:

    • typo3DatabaseUsername
    • typo3DatabasePassword
    • typo3DatabaseHost
    • typo3DatabaseName

Unit tests configuration 

In the Run configurations, copy the PHPUnit configuration and use these settings:

  • Directory: use the Tests/Unit directory in your project

Functional tests configuration 

In the Run configurations, copy the PHPUnit configuration and use these settings:

  • Directory: use the Tests/Functional directory in your project.
  • (*) Use alternative configuration file.
  • Use .Build/vendor/typo3/testing-framework/Resources/Core/Build/FunctionalTests.xml.

Continuous integration 

As an example, this extension provides several ways to perform the code quality checks and tests in a CI pipeline. You can copy the appropriate configuration depending on which Git hosting service and CI platform you want to use.

Table of Contents:

GitHub Actions 

This extension has a code-checking workflow for GitHub Actions using local tools: This workflow uses the development tools installed via Composer and PHIVE and calls them using the provided Composer scripts. This allows running the code quality checks locally as well as in GitHub Actions.

Documentation rendering 

In order to render the documentation, please use the runTests.sh wrapper:

./Build/Scripts/runTests.sh -s docsGenerate
Copied!

You do not need to start or build the containers for this as this happens automatically.

Security 

Libraries and extensions do not need the security check as they should not have any restrictions concerning the other libraries they are installed alongside with (unless those would create breakage). Libraries and extension also should not have a version-controlled composer.lock (which usually is used for security checks).

Instead, the projects and distributions (i.e., for TYPO3 installations) need to have the security checks.

Our approach to assertions and type checks 

In general, we use assertions and type checks to catch bad input, to help debug problems, and to help static type analysis tools like PHPStan or Psalm to determine the type and range of a variable where it cannot detect it automatically.

In production code 

To catch bad input that actually is possible, we throw an exception. This allows developers and users to see what is wrong and to fix it.

if (!$model instanceof Tea) {
    throw new \RuntimeException('No model found.', 1687363745);
}
Copied!

To help PHPStan and Psalm with variables where we (as developers) know the type and range for sure, but the tool cannot determine it automatically, we use assertions.

This makes the type and range obvious both to PHPStan as well as the human reader.

/**
 * @return int<0, max>
 */
private function getUidOfLoggedInUser(): int
{
    $userUid = $this->context->getPropertyFromAspect('frontend.user', 'id');
    \assert(\is_int($userUid) && $userUid >= 0);

    return $userUid;
}
Copied!

In tests 

In tests, we use PHPUnit's assertions to check the expected outcome of a test, and also to test preconditions.

This allows us to have test failure messages that help debugging as well as allow PHPStan to determine types.

/**
 * @test
 */
public function findByUidForExistingRecordMapsAllScalarData(): void
{
    $this->importCSVDataSet(__DIR__ . '/Fixtures/TeaWithAllScalarData.csv');

    $model = $this->subject->findByUid(1);
    self::assertInstanceOf(Tea::class, $model);

    self::assertSame('Earl Grey', $model->getTitle());
    self::assertSame('Fresh and hot.', $model->getDescription());
    self::assertSame(2, $model->getOwnerUid());
}
Copied!

Divergences to TYPO3 Core 

A list of all divergences to the TYPO3 core and why we decided to diverge.

Attributes 

TYPO3 also extends the availability of registering services via attributes. We will prefer the attributes over Services files. But we won't use both at once and still need Services files as long as PHP 7.4 is supported. Attributes will be used in favor of Services files once we drop unsupported PHP versions.

Services Files 

We choose to use Services.php instead of Services.yaml. It still is completely fine to use YAML files over PHP files or even mix both. Some things are way shorter to write with the YAML syntax.

We prefer the PHP file over YAML for the following reasons:

  • Static Code Analysis

    Static code analysis tools, like PHPStan, can analyse the PHP source code base. They typically don't support other files like YAML. Those tools report issues for not found classes, e.g. due to typos.

  • Auto completion

    Modern tooling like IDEs and Language Servers provide auto completion for PHP source files out of the box. That way programmers can discover APIs and write more robust code faster.

  • Automatic code migration

    PHP Code can be auto migrated via tools like rector. E.g. renaming a class can be applied to PHP code, but no current tool for yaml exists.

Sitemap