Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:43:22 +08:00
commit aab6ef2415
31 changed files with 12720 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,917 @@
# TYPO3 Extension Best Practices
**Source:** TYPO3 Best Practices (Tea Extension) and Core API Standards
**Purpose:** Real-world patterns and organizational best practices for TYPO3 extensions
## Project Structure
### Complete Extension Layout
```
my_extension/
├── .ddev/ # DDEV configuration
│ └── config.yaml
├── .github/ # GitHub Actions CI/CD
│ └── workflows/
│ └── tests.yml
├── Build/ # Build tools and configs
│ ├── phpunit/
│ │ ├── UnitTests.xml
│ │ └── FunctionalTests.xml
│ └── Scripts/
│ └── runTests.sh
├── Classes/ # PHP source code
│ ├── Controller/
│ ├── Domain/
│ │ ├── Model/
│ │ └── Repository/
│ ├── Service/
│ ├── Utility/
│ ├── EventListener/
│ └── ViewHelper/
├── Configuration/ # TYPO3 configuration
│ ├── Backend/
│ │ └── Modules.php
│ ├── Services.yaml
│ ├── TCA/
│ ├── TypoScript/
│ │ ├── setup.typoscript
│ │ └── constants.typoscript
│ └── Sets/ # TYPO3 v13+
│ └── MySet/
│ └── config.yaml
├── Documentation/ # RST documentation
│ ├── Index.rst
│ ├── Settings.cfg
│ ├── Introduction/
│ ├── Installation/
│ ├── Configuration/
│ ├── Developer/
│ └── Editor/
├── Resources/
│ ├── Private/
│ │ ├── Language/
│ │ │ ├── locallang.xlf
│ │ │ └── de.locallang.xlf
│ │ ├── Layouts/
│ │ ├── Partials/
│ │ └── Templates/
│ └── Public/
│ ├── Css/
│ ├── Icons/
│ ├── Images/
│ └── JavaScript/
├── Tests/
│ ├── Unit/
│ ├── Functional/
│ │ └── Fixtures/
│ └── Acceptance/
│ ├── Support/
│ └── codeception.yml
├── .editorconfig # Editor configuration
├── .gitattributes # Git attributes
├── .gitignore # Git ignore rules
├── .php-cs-fixer.dist.php # PHP CS Fixer config
├── composer.json # Composer configuration
├── composer.lock # Locked dependencies
├── ext_emconf.php # Extension metadata
├── ext_localconf.php # Global configuration
├── LICENSE # License file
├── phpstan.neon # PHPStan configuration
└── README.md # Project README
```
## Best Practices by Category
### 1. Dependency Management
**composer.json Best Practices:**
```json
{
"name": "vendor/my-extension",
"type": "typo3-cms-extension",
"description": "Clear, concise extension description",
"license": "GPL-2.0-or-later",
"authors": [
{
"name": "Author Name",
"email": "author@example.com",
"role": "Developer"
}
],
"require": {
"php": "^8.1",
"typo3/cms-core": "^12.4 || ^13.0",
"typo3/cms-backend": "^12.4 || ^13.0",
"typo3/cms-extbase": "^12.4 || ^13.0",
"typo3/cms-fluid": "^12.4 || ^13.0"
},
"require-dev": {
"typo3/coding-standards": "^0.7",
"typo3/testing-framework": "^8.0",
"phpunit/phpunit": "^10.5",
"phpstan/phpstan": "^1.10",
"friendsofphp/php-cs-fixer": "^3.0"
},
"autoload": {
"psr-4": {
"Vendor\\MyExtension\\": "Classes/"
}
},
"autoload-dev": {
"psr-4": {
"Vendor\\MyExtension\\Tests\\": "Tests/"
}
},
"config": {
"vendor-dir": ".Build/vendor",
"bin-dir": ".Build/bin",
"sort-packages": true,
"allow-plugins": {
"typo3/class-alias-loader": true,
"typo3/cms-composer-installers": true
}
},
"extra": {
"typo3/cms": {
"extension-key": "my_extension",
"web-dir": ".Build/Web"
}
}
}
```
### 2. Code Quality Tools
**.php-cs-fixer.dist.php:**
```php
<?php
declare(strict_types=1);
$config = \TYPO3\CodingStandards\CsFixerConfig::create();
$config->getFinder()
->in(__DIR__ . '/Classes')
->in(__DIR__ . '/Configuration')
->in(__DIR__ . '/Tests');
return $config;
```
**phpstan.neon:**
```neon
includes:
- .Build/vendor/phpstan/phpstan/conf/bleedingEdge.neon
parameters:
level: 9
paths:
- Classes
- Configuration
- Tests
excludePaths:
- .Build
- vendor
```
#### PHPStan Level 10 Best Practices for TYPO3
**Handling $GLOBALS['TCA'] in Tests:**
PHPStan cannot infer types for runtime-configured `$GLOBALS` arrays. Use ignore annotations:
```php
// ✅ Right: Suppress offsetAccess warnings for $GLOBALS['TCA']
/** @var array<string, mixed> $tcaConfig */
$tcaConfig = [
'type' => 'text',
'enableRichtext' => true,
];
// @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible
$GLOBALS['TCA']['tt_content']['columns']['bodytext']['config'] = $tcaConfig;
// ❌ Wrong: No type annotation or suppression
$GLOBALS['TCA']['tt_content']['columns']['bodytext']['config'] = [
'type' => 'text',
]; // PHPStan error: offsetAccess.nonOffsetAccessible
```
**Factory Methods vs Property Initialization:**
Avoid uninitialized property errors in test classes:
```php
// ❌ Wrong: PHPStan warns about uninitialized property
final class MyServiceTest extends UnitTestCase
{
private MyService $subject; // Uninitialized property
protected function setUp(): void
{
parent::setUp();
$this->subject = new MyService();
}
}
// ✅ Right: Use factory method
final class MyServiceTest extends UnitTestCase
{
private function createSubject(): MyService
{
return new MyService();
}
#[Test]
public function testSomething(): void
{
$subject = $this->createSubject();
// Use $subject
}
}
```
**Type Assertions for Dynamic Arrays:**
When testing arrays modified by reference:
```php
// ❌ Wrong: PHPStan cannot verify type after modification
public function testFieldProcessing(): void
{
$fieldArray = ['bodytext' => '<p>Test</p>'];
$this->subject->processFields($fieldArray);
// PHPStan error: Cannot access offset on mixed
self::assertStringContainsString('Test', $fieldArray['bodytext']);
}
// ✅ Right: Add type assertions
public function testFieldProcessing(): void
{
$fieldArray = ['bodytext' => '<p>Test</p>'];
$this->subject->processFields($fieldArray);
self::assertArrayHasKey('bodytext', $fieldArray);
self::assertIsString($fieldArray['bodytext']);
self::assertStringContainsString('Test', $fieldArray['bodytext']);
}
```
**Intersection Types for Mocks:**
Use intersection types for proper PHPStan analysis of mocks:
```php
// ✅ Right: Intersection type for mock
/** @var ResourceFactory&MockObject $resourceFactoryMock */
$resourceFactoryMock = $this->createMock(ResourceFactory::class);
// Alternative: @phpstan-var annotation
$resourceFactoryMock = $this->createMock(ResourceFactory::class);
/** @phpstan-var ResourceFactory&MockObject $resourceFactoryMock */
```
**Common PHPStan Suppressions for TYPO3:**
```php
// Suppress $GLOBALS['TCA'] access
// @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible
$GLOBALS['TCA']['table']['columns']['field'] = $config;
// Suppress $GLOBALS['TYPO3_CONF_VARS'] access
// @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['key'] = MyClass::class;
// Suppress mixed type from legacy code
// @phpstan-ignore-next-line argument.type
$this->view->assign('data', $legacyArray);
```
**Type Hints for Service Container Retrieval:**
```php
// ✅ Right: Type hint service retrieval
/** @var DataHandler $dataHandler */
$dataHandler = $this->get(DataHandler::class);
/** @var ResourceFactory $resourceFactory */
$resourceFactory = $this->get(ResourceFactory::class);
```
### 3. Service Configuration
**Configuration/Services.yaml:**
```yaml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
# Auto-register all classes
Vendor\MyExtension\:
resource: '../Classes/*'
# Exclude specific directories
Vendor\MyExtension\Domain\Model\:
resource: '../Classes/Domain/Model/*'
autoconfigure: false
# Explicit service configuration example
Vendor\MyExtension\Service\EmailService:
arguments:
$fromEmail: '%env(DEFAULT_FROM_EMAIL)%'
$fromName: 'TYPO3 Extension'
# Tag configuration example
Vendor\MyExtension\Command\ImportCommand:
tags:
- name: 'console.command'
command: 'myext:import'
description: 'Import data from external source'
```
### 4. Backend Module Configuration
**Configuration/Backend/Modules.php:**
```php
<?php
return [
'web_myext' => [
'parent' => 'web',
'position' => ['after' => 'web_info'],
'access' => 'user',
'workspaces' => 'live',
'path' => '/module/web/myext',
'labels' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_mod.xlf',
'extensionName' => 'MyExtension',
'controllerActions' => [
\Vendor\MyExtension\Controller\BackendController::class => [
'list',
'show',
'edit',
'update',
],
],
],
];
```
### 5. Testing Infrastructure
**Build/Scripts/runTests.sh:**
```bash
#!/usr/bin/env bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
# Run unit tests
if [ "$1" = "unit" ]; then
php vendor/bin/phpunit -c Build/phpunit/UnitTests.xml
fi
# Run functional tests
if [ "$1" = "functional" ]; then
typo3DatabaseDriver=pdo_sqlite \
php vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml
fi
# Run all tests
if [ "$1" = "all" ]; then
php vendor/bin/phpunit -c Build/phpunit/UnitTests.xml
typo3DatabaseDriver=pdo_sqlite \
php vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml
fi
```
### 6. CI/CD Configuration
**.github/workflows/tests.yml:**
```yaml
name: Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
tests:
name: Tests (PHP ${{ matrix.php }}, TYPO3 ${{ matrix.typo3 }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.1', '8.2', '8.3']
typo3: ['12.4', '13.0']
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: mbstring, xml, json, zip, curl
coverage: none
- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache Composer dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Lint PHP
run: find . -name \*.php ! -path "./vendor/*" ! -path "./.Build/*" -exec php -l {} \;
- name: PHP CS Fixer
run: .Build/bin/php-cs-fixer fix --dry-run --diff
- name: PHPStan
run: .Build/bin/phpstan analyze
- name: Unit Tests
run: .Build/bin/phpunit -c Build/phpunit/UnitTests.xml
- name: Functional Tests
run: |
typo3DatabaseDriver=pdo_sqlite \
.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml
```
### 7. Documentation Standards
**Documentation/Index.rst:**
```rst
.. include:: /Includes.rst.txt
==============
My Extension
==============
:Extension key:
my_extension
:Package name:
vendor/my-extension
:Version:
|release|
:Language:
en
:Author:
Author Name
:License:
This document is published under the
`Creative Commons BY 4.0 <https://creativecommons.org/licenses/by/4.0/>`__
license.
:Rendered:
|today|
----
Clear and concise extension description explaining the purpose and main features.
----
**Table of Contents:**
.. toctree::
:maxdepth: 2
:titlesonly:
Introduction/Index
Installation/Index
Configuration/Index
Editor/Index
Developer/Index
Sitemap
```
**Page Size Guidelines:**
Follow TYPO3 documentation best practices for page organization and sizing:
**Index.rst (Landing Page):**
- **Target:** 80-150 lines
- **Maximum:** 200 lines
- **Purpose:** Entry point with metadata, brief description, and navigation only
- **Contains:** Extension metadata, brief description, card-grid (optional), toctree, license
- **Anti-pattern:** ❌ Embedding all content (introduction, requirements, contributing, credits, etc.)
**Content Pages:**
- **Target:** 100-300 lines per file
- **Optimal:** 150-200 lines
- **Maximum:** 400 lines (split if larger)
- **Structure:** Focused on single topic or logically related concepts
- **Split Strategy:** Create subdirectories for complex topics with multiple aspects
**Red Flags:**
- ❌ Index.rst >200 lines → Extract content to Introduction/, Contributing/, etc.
- ❌ Single file >400 lines → Split into multiple focused pages
- ❌ All content in Index.rst → Create proper section directories
- ❌ Navigation by scrolling → Use card-grid + toctree structure
**Proper Structure Example:**
```
Documentation/
├── Index.rst # Landing page (80-150 lines)
├── Introduction/ # Getting started
│ └── Index.rst # Features, requirements, quick start
├── Installation/ # Setup instructions
│ └── Index.rst
├── Configuration/ # Configuration guides
│ ├── Index.rst
│ ├── Basic.rst
│ └── Advanced.rst
├── Contributing/ # Contribution guidelines
│ └── Index.rst # Code, translations, credits, resources
├── Examples/ # Usage examples
├── Troubleshooting/ # Problem solving
└── API/ # Developer reference
```
**Benefits:**
- ✅ Better user experience (focused, scannable pages)
- ✅ Easier maintenance (smaller, manageable files)
- ✅ Improved search results (specific pages rank better)
- ✅ Clear information architecture
- ✅ Follows TYPO3 documentation standards
- ✅ Mobile-friendly navigation
**Reference:** [TYPO3 tea extension](https://github.com/TYPO3BestPractices/tea) - exemplary documentation structure
### 8. Version Control Best Practices
#### Default Branch Naming
**✅ Use `main` as the default branch instead of `master`**
**Rationale:**
- **Industry Standard**: GitHub, GitLab, and Bitbucket all default to `main` for new repositories
- **Modern Convention**: Aligns with current version control ecosystem standards
- **Inclusive Language**: Part of broader industry shift toward inclusive terminology
- **Consistency**: Matches TYPO3 Core and most modern TYPO3 extensions
**Migration from `master` to `main`:**
If your extension currently uses `master`, migrate to `main`:
```bash
# 1. Create main branch from master
git checkout master
git pull origin master
git checkout -b main
git push -u origin main
# 2. Change default branch on GitHub
gh repo edit --default-branch main
# 3. Update all branch references in codebase
# - CI/CD workflows (.github/workflows/*.yml)
# - Documentation (guides.xml, *.rst files)
# - URLs in CONTRIBUTING.md, README.md
# 4. Delete old master branch
git branch -d master
git push origin --delete master
```
**Example CI/CD workflow update:**
```yaml
# .github/workflows/tests.yml
on:
push:
branches: [main, develop] # Changed from: master
pull_request:
branches: [main] # Changed from: master
```
**Example documentation update:**
```xml
<!-- Documentation/guides.xml -->
<extension edit-on-github-branch="main" /> <!-- Changed from: master -->
```
#### Branch Protection Enforcement
**Prevent accidental `master` branch recreation** and **protect `main` branch** using GitHub Repository Rulesets.
**Block master branch - prevents creation and pushes:**
Create `ruleset-block-master.json`:
```json
{
"name": "Block master branch",
"target": "branch",
"enforcement": "active",
"conditions": {
"ref_name": {
"include": ["refs/heads/master"],
"exclude": []
}
},
"rules": [
{
"type": "creation"
},
{
"type": "update"
},
{
"type": "deletion"
}
],
"bypass_actors": []
}
```
Apply the ruleset:
```bash
gh api -X POST repos/OWNER/REPO/rulesets \
--input ruleset-block-master.json
```
**Protect main branch - requires CI and prevents force pushes:**
Create `ruleset-protect-main.json`:
```json
{
"name": "Protect main branch",
"target": "branch",
"enforcement": "active",
"conditions": {
"ref_name": {
"include": ["refs/heads/main"],
"exclude": []
}
},
"rules": [
{
"type": "required_status_checks",
"parameters": {
"required_status_checks": [
{
"context": "build"
}
],
"strict_required_status_checks_policy": false
}
},
{
"type": "non_fast_forward"
}
],
"bypass_actors": [
{
"actor_id": 5,
"actor_type": "RepositoryRole",
"bypass_mode": "always"
}
]
}
```
Apply the ruleset:
```bash
gh api -X POST repos/OWNER/REPO/rulesets \
--input ruleset-protect-main.json
```
**Verify rulesets are active:**
```bash
# List all rulesets
gh api repos/OWNER/REPO/rulesets
# Test master branch is blocked (should fail)
git push origin test-branch:master
# Expected: remote: error: GH013: Repository rule violations found
```
**Benefits of Repository Rulesets:**
- ✅ Prevents accidental `master` branch recreation
- ✅ Enforces CI status checks before merging to `main`
- ✅ Prevents force pushes to protected branches
- ✅ Allows admin bypass for emergency situations
- ✅ More flexible than legacy branch protection rules
- ✅ Supports complex conditions and multiple rule types
### 9. Language File Organization
**Resources/Private/Language/locallang.xlf:**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext"
original="EXT:my_extension/Resources/Private/Language/locallang.xlf"
date="2024-01-01T12:00:00Z"
product-name="my_extension">
<header/>
<body>
<trans-unit id="plugin.title" resname="plugin.title">
<source>My Extension Plugin</source>
</trans-unit>
<trans-unit id="plugin.description" resname="plugin.description">
<source>Displays product list with filters</source>
</trans-unit>
</body>
</file>
</xliff>
```
### 10. TCA Best Practices
**Configuration/TCA/tx_myext_domain_model_product.php:**
```php
<?php
return [
'ctrl' => [
'title' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_product',
'label' => 'title',
'tstamp' => 'tstamp',
'crdate' => 'crdate',
'delete' => 'deleted',
'sortby' => 'sorting',
'versioningWS' => true,
'origUid' => 't3_origuid',
'languageField' => 'sys_language_uid',
'transOrigPointerField' => 'l10n_parent',
'transOrigDiffSourceField' => 'l10n_diffsource',
'translationSource' => 'l10n_source',
'enablecolumns' => [
'disabled' => 'hidden',
'starttime' => 'starttime',
'endtime' => 'endtime',
],
'searchFields' => 'title,description',
'iconfile' => 'EXT:my_extension/Resources/Public/Icons/product.svg',
],
'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,
hidden, starttime, endtime
',
],
],
'columns' => [
'title' => [
'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_product.title',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim,required',
'max' => 255,
],
],
'description' => [
'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_product.description',
'config' => [
'type' => 'text',
'enableRichtext' => true,
'richtextConfiguration' => 'default',
],
],
],
];
```
### 11. Security Best Practices
**✅ Input Validation:**
```php
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
// Validate integer input
if (!MathUtility::canBeInterpretedAsInteger($input)) {
throw new \InvalidArgumentException('Invalid integer value');
}
// Sanitize email
$email = GeneralUtility::validEmail($input) ? $input : '';
// Escape output in templates
{product.title -> f:format.htmlspecialchars()}
```
**✅ SQL Injection Prevention:**
```php
// Use QueryBuilder with bound parameters
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_myext_domain_model_product');
$products = $queryBuilder
->select('*')
->from('tx_myext_domain_model_product')
->where(
$queryBuilder->expr()->eq(
'uid',
$queryBuilder->createNamedParameter($uid, Connection::PARAM_INT)
)
)
->executeQuery()
->fetchAllAssociative();
```
**✅ CSRF Protection:**
```html
<!-- Always include form protection token -->
<f:form.hidden property="__trustedProperties" value="{formProtection}" />
```
## Common Anti-Patterns to Avoid
### ❌ Don't: Use GeneralUtility::makeInstance() for Services
```php
// Old way (deprecated)
$repository = GeneralUtility::makeInstance(ProductRepository::class);
```
### ✅ Do: Use Dependency Injection
```php
// Modern way
public function __construct(
private readonly ProductRepository $repository
) {}
```
### ❌ Don't: Access $GLOBALS directly
```php
// Avoid global state
$user = $GLOBALS['BE_USER'];
$tsfe = $GLOBALS['TSFE'];
```
### ✅ Do: Inject Context and Services
```php
public function __construct(
private readonly Context $context,
private readonly TypoScriptService $typoScriptService
) {}
```
### ❌ Don't: Use ext_tables.php for configuration
```php
// ext_tables.php (deprecated for most uses)
```
### ✅ Do: Use dedicated configuration files
```php
// Configuration/Backend/Modules.php
// Configuration/TCA/
// Configuration/Services.yaml
```
## Conformance Checklist
- [ ] Complete directory structure following best practices
- [ ] composer.json with proper PSR-4 autoloading
- [ ] Quality tools configured (php-cs-fixer, phpstan)
- [ ] CI/CD pipeline (GitHub Actions or GitLab CI)
- [ ] Comprehensive test coverage (unit, functional, acceptance)
- [ ] Complete documentation in RST format
- [ ] Service configuration in Services.yaml
- [ ] Backend modules in Configuration/Backend/
- [ ] TCA files in Configuration/TCA/
- [ ] Language files in XLIFF format
- [ ] Dependency injection throughout
- [ ] No global state access
- [ ] Security best practices followed
- [ ] .editorconfig for consistent formatting
- [ ] README.md with clear instructions
- [ ] LICENSE file present

View File

@@ -0,0 +1,610 @@
# TYPO3 Coding Guidelines
**Source:** TYPO3 Core API Reference - Coding Guidelines
**Purpose:** PHP code style, formatting standards, and PSR-12 compliance for TYPO3 extensions
## PSR-12 Compliance
TYPO3 follows **PSR-12: Extended Coding Style** as the foundation for PHP code style.
**Key PSR-12 Requirements:**
- 4 spaces for indentation (NO tabs)
- Unix line endings (LF)
- Maximum line length: 120 characters (soft limit), 80 recommended
- Opening braces for classes/methods on same line
- One statement per line
- Visibility MUST be declared on all properties and methods
## Identifier Naming Conventions
### Variables and Methods: camelCase
```php
// ✅ Right
$userName = 'John';
$totalPrice = 100;
public function calculateTotal() {}
public function getUserData() {}
// ❌ Wrong
$user_name = 'John'; // snake_case
$UserName = 'John'; // PascalCase
public function CalculateTotal() {} // PascalCase
public function get_user_data() {} // snake_case
```
### Classes: UpperCamelCase (PascalCase)
```php
// ✅ Right
class UserController {}
class PaymentService {}
class ProductRepository {}
// ❌ Wrong
class userController {} // camelCase
class payment_service {} // snake_case
class productRepository {} // camelCase
```
### Constants: SCREAMING_SNAKE_CASE
```php
// ✅ Right
const MAX_UPLOAD_SIZE = 1024;
const API_ENDPOINT = 'https://api.example.com';
private const DEFAULT_TIMEOUT = 30;
// ❌ Wrong
const maxUploadSize = 1024; // camelCase
const ApiEndpoint = '...'; // PascalCase
```
### Namespaces: UpperCamelCase
```php
// ✅ Right
namespace Vendor\ExtensionKey\Domain\Model;
namespace Vendor\ExtensionKey\Controller;
// ❌ Wrong
namespace vendor\extension_key\domain\model;
namespace Vendor\extension_key\Controller;
```
## Function and Method Naming
### Descriptive Names with Verbs
```php
// ✅ Right: Verb + noun, descriptive
public function getUserById(int $id): ?User {}
public function calculateTotalPrice(array $items): float {}
public function isValidEmail(string $email): bool {}
public function hasPermission(string $action): bool {}
// ❌ Wrong: No verb, ambiguous
public function user(int $id) {}
public function price(array $items) {}
public function email(string $email) {}
public function permission(string $action) {}
```
### Boolean Methods: is/has/can/should
```php
// ✅ Right
public function isActive(): bool {}
public function hasAccess(): bool {}
public function canEdit(): bool {}
public function shouldRender(): bool {}
// ❌ Wrong
public function active(): bool {}
public function access(): bool {}
public function checkEdit(): bool {}
```
## Array Formatting
### Short Syntax Only
```php
// ✅ Right: Short array syntax
$items = [];
$config = ['foo' => 'bar'];
$users = [
['name' => 'John', 'age' => 30],
['name' => 'Jane', 'age' => 25],
];
// ❌ Wrong: Long array syntax (deprecated)
$items = array();
$config = array('foo' => 'bar');
```
### Multi-line Array Formatting
```php
// ✅ Right: Proper indentation and trailing comma
$configuration = [
'key1' => 'value1',
'key2' => 'value2',
'nested' => [
'subkey1' => 'subvalue1',
'subkey2' => 'subvalue2',
], // Trailing comma
];
// ❌ Wrong: No trailing comma, inconsistent indentation
$configuration = [
'key1' => 'value1',
'key2' => 'value2',
'nested' => [
'subkey1' => 'subvalue1',
'subkey2' => 'subvalue2'
]
];
```
## Conditional Statement Layout
### If/ElseIf/Else
```php
// ✅ Right: Proper spacing and braces
if ($condition) {
doSomething();
} elseif ($otherCondition) {
doSomethingElse();
} else {
doDefault();
}
// ❌ Wrong: Missing spaces, wrong brace placement
if($condition){
doSomething();
}
else if ($otherCondition) {
doSomethingElse();
}
else {
doDefault();
}
```
### Switch Statements
```php
// ✅ Right
switch ($status) {
case 'active':
processActive();
break;
case 'pending':
processPending();
break;
default:
processDefault();
}
// ❌ Wrong: Inconsistent indentation
switch ($status) {
case 'active':
processActive();
break;
case 'pending':
processPending();
break;
default:
processDefault();
}
```
## String Handling
### Single Quotes Default
```php
// ✅ Right: Single quotes for simple strings
$message = 'Hello, World!';
$path = 'path/to/file.php';
// ❌ Wrong: Unnecessary double quotes
$message = "Hello, World!"; // No variable interpolation
$path = "path/to/file.php";
```
### Double Quotes for Interpolation
```php
// ✅ Right: Double quotes when interpolating
$name = 'John';
$message = "Hello, {$name}!";
// ❌ Wrong: Concatenation instead of interpolation
$message = 'Hello, ' . $name . '!'; // Less readable
```
### String Concatenation
```php
// ✅ Right: Spaces around concatenation operator
$fullPath = $basePath . '/' . $filename;
$message = 'Hello ' . $name . ', welcome!';
// ❌ Wrong: No spaces around operator
$fullPath = $basePath.'/'.$filename;
$message = 'Hello '.$name.', welcome!';
```
## PHPDoc Comment Standards
### Class Documentation
```php
// ✅ Right: Complete class documentation
/**
* Service for calculating product prices with tax and discounts
*
* This service handles complex price calculations including:
* - Tax rates based on country
* - Quantity discounts
* - Promotional codes
*
* @author John Doe <john@example.com>
* @license GPL-2.0-or-later
*/
final class PriceCalculationService
{
// ...
}
```
### Method Documentation
```php
// ✅ Right: Complete method documentation
/**
* Calculate total price with tax for given items
*
* @param array<int, array{product: Product, quantity: int}> $items
* @param string $countryCode ISO 3166-1 alpha-2 country code
* @param float $discountPercent Discount percentage (0-100)
* @return float Total price including tax
* @throws \InvalidArgumentException If country code is invalid
*/
public function calculateTotal(
array $items,
string $countryCode,
float $discountPercent = 0.0
): float {
// ...
}
// ❌ Wrong: Missing or incomplete documentation
/**
* Calculates total
*/
public function calculateTotal($items, $countryCode, $discountPercent = 0.0) {
// Missing param types, descriptions, return type
}
```
### Property Documentation
```php
// ✅ Right
/**
* @var UserRepository User data repository
*/
private readonly UserRepository $userRepository;
/**
* @var array<string, mixed> Configuration options
*/
private array $config = [];
// ❌ Wrong: No type hint or description
/**
* @var mixed
*/
private $userRepository;
```
## Curly Brace Placement
### Classes and Methods: Same Line
```php
// ✅ Right: Opening brace on same line
class MyController
{
public function indexAction(): ResponseInterface
{
// ...
}
}
// ❌ Wrong: Opening brace on new line (K&R style)
class MyController {
public function indexAction(): ResponseInterface {
// ...
}
}
```
### Control Structures: Same Line
```php
// ✅ Right
if ($condition) {
doSomething();
}
foreach ($items as $item) {
processItem($item);
}
// ❌ Wrong: Opening brace on new line
if ($condition)
{
doSomething();
}
```
## Namespace and Use Statements
### Namespace Structure
```php
// ✅ Right: Proper namespace declaration
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
class Product extends AbstractEntity
{
// ...
}
```
### Use Statements Organization
```php
// ✅ Right: Grouped and sorted
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use Vendor\ExtensionKey\Domain\Repository\ProductRepository;
// ❌ Wrong: Unsorted, mixed
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use Vendor\ExtensionKey\Domain\Repository\ProductRepository;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Imaging\IconFactory;
```
## Type Declarations
### Strict Types
```php
// ✅ Right: declare(strict_types=1) at the top
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Service;
class MyService
{
public function calculate(int $value): float
{
return $value * 1.19;
}
}
// ❌ Wrong: No strict types declaration
<?php
namespace Vendor\ExtensionKey\Service;
class MyService
{
public function calculate($value) // No type hints
{
return $value * 1.19;
}
}
```
### Property Type Declarations (PHP 7.4+)
```php
// ✅ Right: Typed properties
class User
{
private string $username;
private int $id;
private ?string $email = null;
private array $roles = [];
}
// ❌ Wrong: No type declarations
class User
{
private $username;
private $id;
private $email;
private $roles;
}
```
## File Structure
### Standard File Template
```php
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace Vendor\ExtensionKey\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
/**
* Product model
*/
class Product extends AbstractEntity
{
/**
* @var string Product title
*/
private string $title = '';
public function getTitle(): string
{
return $this->title;
}
public function setTitle(string $title): void
{
$this->title = $title;
}
}
```
## PHPStan and Static Analysis
TYPO3 extensions should use **PHPStan level 10** (strictest) for maximum type safety and code quality.
### PHPStan Baseline Hygiene
**Critical Rule:** New code must NEVER add errors to `phpstan-baseline.neon`.
The baseline file exists only for legacy code that hasn't been refactored yet. All new code must pass PHPStan level 10 without baseline suppression.
**Validation:**
```bash
# Check if your changes added to baseline
git diff HEAD~1 Build/phpstan-baseline.neon
# If count increased, you MUST fix the underlying issues
# Example: count: 8 → count: 9 means you added 1 new error
```
### Type-Safe Mixed Value Handling
**Common PHPStan Error:** "Cannot cast mixed to int/string/bool"
**Occurs with:** TypoScript configuration, user input, API responses
**❌ Wrong (adds to baseline):**
```php
// PHPStan: Cannot cast mixed to int
$maxSize = (int) ($conf['maxSize'] ?? 0);
```
**✅ Right (passes level 10):**
```php
// Type-guard before casting
$value = $conf['maxSize'] ?? 0;
if (is_numeric($value)) {
$maxSize = (int) $value;
} else {
$maxSize = 0;
}
```
### Common Mixed Type Patterns
**Arrays from configuration:**
```php
// ❌ Wrong
$items = (array) $conf['items'];
// ✅ Right
$items = [];
if (isset($conf['items']) && is_array($conf['items'])) {
$items = $conf['items'];
}
```
**Strings from user input:**
```php
// ❌ Wrong
$name = (string) $_POST['name'];
// ✅ Right
$name = '';
if (isset($_POST['name']) && is_string($_POST['name'])) {
$name = $_POST['name'];
}
```
**Boolean from configuration:**
```php
// ❌ Wrong
$enabled = (bool) $conf['enabled'];
// ✅ Right
$enabled = isset($conf['enabled']) && (bool) $conf['enabled'];
```
### Pre-Commit PHPStan Check
Always run PHPStan before committing:
```bash
# Run PHPStan
composer ci:php:stan
# Verify no new baseline entries
git diff Build/phpstan-baseline.neon
# If baseline changed, fix the issues instead of committing the baseline
```
## Conformance Checklist
- [ ] All PHP files use 4 spaces for indentation (NO tabs)
- [ ] Variables and methods use camelCase
- [ ] Classes use UpperCamelCase
- [ ] Constants use SCREAMING_SNAKE_CASE
- [ ] Array short syntax [] used (not array())
- [ ] Multi-line arrays have trailing commas
- [ ] Strings use single quotes by default
- [ ] String concatenation has spaces around `.` operator
- [ ] All classes have PHPDoc comments
- [ ] All public methods have PHPDoc with @param and @return
- [ ] Opening braces on same line for classes/methods
- [ ] declare(strict_types=1) at top of all PHP files
- [ ] Proper namespace structure matching directory
- [ ] Use statements grouped and sorted
- [ ] Type declarations on all properties and method parameters
- [ ] Maximum line length 120 characters
- [ ] Unix line endings (LF)
- [ ] PHPStan level 10 passes with zero errors
- [ ] No new errors added to phpstan-baseline.neon
- [ ] Type-guards before casting mixed values (is_numeric, is_string, is_array)

View File

@@ -0,0 +1,468 @@
# Composer.json Validation Standards (TYPO3 v13)
**Source:** TYPO3 Core API Reference v13.4 - FileStructure/ComposerJson.html
**Purpose:** Complete validation rules for composer.json in TYPO3 extensions
## Mandatory Fields
### name
**Format:** `<vendor>/<dashed-extension-key>`
**Examples:**
```json
"name": "vendor-name/my-extension"
"name": "johndoe/some-extension"
```
**Validation:**
```bash
jq -r '.name' composer.json | grep -E '^[a-z0-9-]+/[a-z0-9-]+$' && echo "✅ Valid" || echo "❌ Invalid format"
```
### type
**Required Value:** `typo3-cms-extension` (for third-party extensions)
**Validation:**
```bash
jq -r '.type' composer.json | grep -q "typo3-cms-extension" && echo "✅ Correct type" || echo "❌ Wrong type"
```
### description
**Format:** Single-line summary describing what the extension does
**Requirements:**
- Clear, concise description of extension functionality
- Should identify the vendor/company for professional extensions
- Avoid vague descriptions like "An extension" or "Utility tools"
**Good Examples:**
```json
"description": "Adds image support to CKEditor5 RTE - by Netresearch"
"description": "TYPO3 extension for advanced content management by Vendor GmbH"
"description": "Provides custom form elements for newsletter subscription"
```
**Bad Examples:**
```json
"description": "Extension" // Too vague
"description": "Some tools" // Meaningless
"description": "" // Empty
```
**Validation:**
```bash
# Check description exists and is not empty
jq -r '.description' composer.json | grep -q . && echo "✅ Has description" || echo "❌ Missing description"
# Check description length (should be meaningful, >20 chars)
DESC_LEN=$(jq -r '.description | length' composer.json)
[[ $DESC_LEN -gt 20 ]] && echo "✅ Description is meaningful" || echo "⚠️ Description too short"
```
### license
**Recommended:** `GPL-2.0-only` or `GPL-2.0-or-later`
**Validation:**
```bash
jq -r '.license' composer.json | grep -qE "GPL-2.0-(only|or-later)" && echo "✅ GPL license" || echo "⚠️ Check license"
```
### require
**Minimum:** Must specify `typo3/cms-core` with version constraints
**Version Constraint Format:**
- `^12.4 || ^13.4` - Multiple major versions (recommended for v12/v13 compat)
- `^12.4` - Single major version
- `>=12.4` ❌ - NO upper bound (not recommended)
**Validation:**
```bash
# Check typo3/cms-core present
jq -r '.require["typo3/cms-core"]' composer.json | grep -q . && echo "✅ TYPO3 core required" || echo "❌ Missing typo3/cms-core"
# Check for upper bound (^ or specific upper version)
jq -r '.require["typo3/cms-core"]' composer.json | grep -qE '(\^|[0-9]+\.[0-9]+\.[0-9]+-[0-9]+\.[0-9]+\.[0-9]+)' && echo "✅ Has upper bound" || echo "⚠️ Missing upper bound"
```
### autoload
**Format:** PSR-4 mapping to Classes/ directory
**Example:**
```json
"autoload": {
"psr-4": {
"Vendor\\ExtensionName\\": "Classes/"
}
}
```
**Validation:**
```bash
jq -r '.autoload["psr-4"]' composer.json | grep -q "Classes" && echo "✅ PSR-4 autoload configured" || echo "❌ Missing autoload"
```
### extra.typo3/cms.extension-key
**Required:** Maps to underscored extension key
**Example:**
```json
"extra": {
"typo3/cms": {
"extension-key": "my_extension"
}
}
```
**Validation:**
```bash
jq -r '.extra."typo3/cms"."extension-key"' composer.json | grep -q . && echo "✅ Extension key defined" || echo "❌ Missing extension-key"
```
---
## Recommended Fields (Professional Extensions)
### authors
**Format:** Array of author objects with name, email, role, homepage
**Example:**
```json
"authors": [
{
"name": "Developer Name",
"email": "developer@company.com",
"role": "Developer",
"homepage": "https://www.company.com/"
}
]
```
**Required Sub-Fields:**
| Field | Format | Purpose |
|-------|--------|---------|
| `name` | String | Developer's full name |
| `email` | Email address | Contact email |
| `role` | String | `Developer`, `Maintainer`, `Lead Developer` |
| `homepage` | URL | Company or personal website |
**Validation:**
```bash
# Check authors array exists
jq -r '.authors' composer.json | grep -q "name" && echo "✅ Has authors" || echo "⚠️ Missing authors"
# Check authors have email
jq -r '.authors[].email' composer.json | grep -q "@" && echo "✅ Has author emails" || echo "⚠️ Missing author emails"
# Check authors have homepage
jq -r '.authors[].homepage' composer.json | grep -q "http" && echo "✅ Has author homepage" || echo "⚠️ Missing author homepage"
```
### homepage
**Format:** URL to project repository or documentation
**Example:**
```json
"homepage": "https://github.com/vendor/extension-name"
```
**Validation:**
```bash
jq -r '.homepage' composer.json | grep -qE "^https?://" && echo "✅ Has homepage" || echo "⚠️ Missing homepage"
```
### support
**Format:** Object with support channels
**Example:**
```json
"support": {
"issues": "https://github.com/vendor/extension/issues",
"source": "https://github.com/vendor/extension"
}
```
**Validation:**
```bash
jq -r '.support.issues' composer.json | grep -q "http" && echo "✅ Has issues URL" || echo "⚠️ Missing issues URL"
```
### keywords
**Format:** Array of relevant keywords for discoverability
**Example:**
```json
"keywords": [
"TYPO3",
"extension",
"content",
"management"
]
```
**Validation:**
```bash
jq -r '.keywords | length' composer.json | grep -qE '^[1-9]' && echo "✅ Has keywords" || echo "⚠️ Missing keywords"
```
---
## Complete Required Fields Checklist
**Mandatory (MUST have):**
- [ ] `name` - vendor/package format
- [ ] `type` - must be `typo3-cms-extension`
- [ ] `description` - clear, concise description
- [ ] `license` - SPDX identifier (GPL-2.0-or-later, AGPL-3.0-or-later)
- [ ] `require.typo3/cms-core` - with upper bound constraint
- [ ] `require.php` - PHP version constraint
- [ ] `autoload.psr-4` - mapping to Classes/
- [ ] `extra.typo3/cms.extension-key` - underscored extension key
**Recommended (SHOULD have):**
- [ ] `authors` - with name, email, role, homepage
- [ ] `homepage` - project repository URL
- [ ] `support.issues` - issue tracker URL
- [ ] `keywords` - for discoverability
---
## Deprecated Properties
### replace with typo3-ter vendor
**Status:** DEPRECATED - Legacy TER integration approach
**Detection:**
```bash
jq -r '.replace' composer.json | grep -q "typo3-ter" && echo "⚠️ Deprecated: typo3-ter in replace" || echo "✅ No deprecated replace"
```
### replace with "ext_key": "self.version"
**Status:** DEPRECATED - Legacy dependency specification
**Detection:**
```bash
jq -r '.replace' composer.json | grep -qE '"[a-z_]+": "self.version"' && echo "⚠️ Deprecated: self.version replace" || echo "✅ No self.version"
```
---
## TYPO3 v12-v13 Version Constraints
### Recommended Format
```json
"require": {
"typo3/cms-core": "^12.4 || ^13.4",
"php": "^8.1"
}
```
### PHP Version Constraints
```json
"require": {
"php": "^8.1" // TYPO3 v12: PHP 8.1-8.4
}
```
**Validation:**
```bash
# Check PHP constraint
jq -r '.require.php' composer.json | grep -qE '\^8\.[1-4]' && echo "✅ Valid PHP constraint" || echo "⚠️ Check PHP version"
```
---
## Synchronization with ext_emconf.php
**Critical:** `composer.json` and `ext_emconf.php` must have matching dependency constraints.
**Mapping:**
| composer.json | ext_emconf.php |
|--------------|----------------|
| `require.typo3/cms-core` | `constraints.depends.typo3` |
| `require.php` | `constraints.depends.php` |
| `require.*` | `constraints.depends.*` |
**Example Synchronization:**
composer.json:
```json
"require": {
"typo3/cms-core": "^12.4 || ^13.4",
"php": "^8.1",
"typo3/cms-fluid": "^12.4 || ^13.4"
}
```
ext_emconf.php:
```php
'constraints' => [
'depends' => [
'typo3' => '12.4.0-13.4.99',
'php' => '8.1.0-8.4.99',
'fluid' => '12.4.0-13.4.99',
],
],
```
---
## Complete Validation Script
```bash
#!/bin/bash
# validate-composer.sh
ERRORS=0
echo "=== Composer.json Validation ===="
# Check mandatory fields
jq -r '.name' composer.json > /dev/null 2>&1 || { echo "❌ Missing 'name'"; ((ERRORS++)); }
jq -r '.type' composer.json | grep -q "typo3-cms-extension" || { echo "❌ Wrong or missing 'type'"; ((ERRORS++)); }
jq -r '.description' composer.json | grep -q . || { echo "❌ Missing 'description'"; ((ERRORS++)); }
# Check description is meaningful (>20 chars)
DESC_LEN=$(jq -r '.description | length' composer.json 2>/dev/null)
[[ $DESC_LEN -lt 20 ]] && { echo "⚠️ Description too short (should be >20 chars)"; ((WARNINGS++)); }
# Check typo3/cms-core
jq -r '.require["typo3/cms-core"]' composer.json | grep -q . || { echo "❌ Missing typo3/cms-core"; ((ERRORS++)); }
# Check version constraints have upper bounds
jq -r '.require["typo3/cms-core"]' composer.json | grep -qE '(\^|[0-9]+\.[0-9]+\.[0-9]+-[0-9]+\.[0-9]+\.[0-9]+)' || { echo "⚠️ TYPO3 constraint missing upper bound"; ((ERRORS++)); }
# Check autoload
jq -r '.autoload["psr-4"]' composer.json | grep -q "Classes" || { echo "❌ Missing PSR-4 autoload"; ((ERRORS++)); }
# Check extension-key
jq -r '.extra."typo3/cms"."extension-key"' composer.json | grep -q . || { echo "❌ Missing extension-key"; ((ERRORS++)); }
# Check for deprecated replace
jq -r '.replace' composer.json 2>/dev/null | grep -q "typo3-ter\|self.version" && echo "⚠️ Deprecated replace property found"
echo ""
echo "Validation complete: $ERRORS critical errors"
exit $ERRORS
```
---
## Optional but Recommended Fields
### require-dev
**Purpose:** Development dependencies not needed in production
**Example:**
```json
"require-dev": {
"typo3/coding-standards": "^0.7",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.0"
}
```
### suggest
**Purpose:** Optional packages that enhance functionality
**Example:**
```json
"suggest": {
"typo3/cms-filelist": "For file browser functionality",
"typo3/cms-reactions": "For webhook support"
}
```
---
## Best Practices
1. **Packagist Publication:** Publishing to Packagist makes extensions available in TYPO3 Extension Repository automatically
2. **Documentation Rendering:** `composer.json` is **REQUIRED** for extensions with documentation on docs.typo3.org
3. **Version Constraint Strategy:**
- Use `^` for flexible upper bounds
- Specify both major version ranges for v12/v13 compatibility
- Always include upper bounds (avoid `>=` without upper limit)
4. **Namespace Alignment:** PSR-4 namespace should match vendor/extension structure
5. **Composer Priority:** Composer-based installations prioritize `composer.json` over `ext_emconf.php` for dependency resolution
---
## Common Violations and Fixes
### Missing extra.typo3/cms.extension-key
**Before:**
```json
{
"name": "vendor/my-extension",
"type": "typo3-cms-extension"
}
```
**After:**
```json
{
"name": "vendor/my-extension",
"type": "typo3-cms-extension",
"extra": {
"typo3/cms": {
"extension-key": "my_extension"
}
}
}
```
### Version Constraint Without Upper Bound
**Before:**
```json
"require": {
"typo3/cms-core": ">=12.4"
}
```
**After:**
```json
"require": {
"typo3/cms-core": "^12.4 || ^13.4"
}
```
### Deprecated replace Property
**Before:**
```json
"replace": {
"typo3-ter/my-extension": "self.version"
}
```
**After:**
```json
// Remove replace property entirely
```
---
## Additional Validation Commands
### Check all required dependencies have upper bounds
```bash
jq -r '.require | to_entries[] | select(.value | test(">=") and (test("\\^") | not)) | .key' composer.json
```
### Verify package type
```bash
jq -r '.type' composer.json | grep -q "typo3-cms-extension" && echo "✅" || echo "❌ Wrong package type"
```
### Check PSR-4 namespace format
```bash
jq -r '.autoload["psr-4"] | keys[]' composer.json | grep -E '^[A-Z][a-zA-Z0-9]*\\\\[A-Z][a-zA-Z0-9]*\\\\$' && echo "✅ Valid namespace" || echo "⚠️ Check namespace format"
```
### Validate JSON syntax
```bash
jq . composer.json > /dev/null && echo "✅ Valid JSON" || echo "❌ JSON syntax error"
```

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,524 @@
# Development Environment Standards
**Purpose:** Validate development environment setup for consistent, reproducible TYPO3 extension development
## Why Development Environment Matters
A properly configured development environment ensures:
-**Consistency** - All developers work with identical PHP/TYPO3/database versions
-**Onboarding** - New contributors can start immediately without complex setup
-**CI/CD Parity** - Local environment matches production/staging
-**Reproducibility** - Bugs are reproducible across all environments
-**Cross-Platform** - Works on macOS, Linux, Windows (WSL)
Without standardized dev environment:
- ❌ "Works on my machine" syndrome
- ❌ Inconsistent PHP/database versions causing bugs
- ❌ Complex setup discourages contributions
- ❌ CI failures that don't reproduce locally
## TYPO3 Community Standards
### DDEV - Primary Recommendation
**DDEV** is the **de facto standard** for TYPO3 development:
- ✅ Official TYPO3 core development uses DDEV
- ✅ TYPO3 Best Practices (Tea extension) uses DDEV
- ✅ TYPO3 documentation recommends DDEV
- ✅ Cross-platform support (Docker-based)
- ✅ Preconfigured for TYPO3 (`ddev config --project-type=typo3`)
**Alternative:** Docker Compose (acceptable, more manual configuration)
## Validation Checklist
### 1. DDEV Configuration
**Check for `.ddev/` directory:**
```bash
ls -la .ddev/
```
**Required files:**
- `.ddev/config.yaml` - Core DDEV configuration
- `.ddev/.gitignore` - Excludes dynamic files (import-db, .ddev-docker-compose-*.yaml)
**Optional but recommended:**
- `.ddev/config.typo3.yaml` - TYPO3-specific settings
- `.ddev/commands/` - Custom DDEV commands
- `.ddev/docker-compose.*.yaml` - Additional services
**Severity if missing:** 🟡 **Medium** - Indicates no standardized dev environment
### 2. DDEV config.yaml Structure
**Minimum DDEV Configuration:**
```yaml
name: extension-name
type: typo3
docroot: .Build/public
php_version: "8.2" # Match composer.json minimum
webserver_type: nginx-fpm
router_http_port: "80"
router_https_port: "443"
xdebug_enabled: false
additional_hostnames: []
additional_fqdns: []
database:
type: mariadb
version: "10.11"
omit_containers: [ddev-ssh-agent]
```
**Validation Rules:**
| Field | Validation | Example | Severity |
|-------|-----------|---------|----------|
| `name` | Should match extension key or composer name | `rte-ckeditor-image` | Low |
| `type` | Must be `typo3` | `typo3` | High |
| `docroot` | Should match composer.json web-dir | `.Build/public` | High |
| `php_version` | Should match composer.json minimum PHP | `"8.2"` | High |
| `database.type` | Should be `mariadb` (TYPO3 standard) | `mariadb` | Medium |
| `database.version` | Should be LTS version (10.11 or 11.x) | `"10.11"` | Medium |
**Example Check:**
```bash
# Extension composer.json
"require": {
"php": "^8.2 || ^8.3 || ^8.4",
"typo3/cms-core": "^13.4"
}
"extra": {
"typo3/cms": {
"web-dir": ".Build/public"
}
}
# DDEV config.yaml SHOULD have:
php_version: "8.2" # ✅ Matches minimum
docroot: .Build/public # ✅ Matches web-dir
type: typo3 # ✅ Correct type
# DDEV config.yaml SHOULD NOT have:
php_version: "7.4" # ❌ Below minimum
docroot: public # ❌ Doesn't match web-dir
type: php # ❌ Wrong type
```
### 3. Docker Compose (Alternative)
If DDEV not present, check for `docker-compose.yml`:
**Minimum Docker Compose Configuration:**
```yaml
version: '3.8'
services:
web:
image: ghcr.io/typo3/core-testing-php82:latest
volumes:
- .:/var/www/html
working_dir: /var/www/html
ports:
- "8000:80"
environment:
TYPO3_CONTEXT: Development
db:
image: mariadb:10.11
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: typo3
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:
```
**Validation Rules:**
| Service | Validation | Severity |
|---------|-----------|----------|
| `web` service exists | Required | High |
| PHP version matches composer.json | Required | High |
| `db` service exists | Required | Medium |
| Database type is MariaDB/MySQL | Recommended | Low |
| Volumes preserve database data | Required | High |
**Severity if missing:** 🟡 **Medium** - Harder to onboard, but not critical
### 4. DevContainer (VS Code Remote Containers)
Check for `.devcontainer/devcontainer.json`:
**Example DevContainer Configuration:**
```json
{
"name": "TYPO3 Extension Development",
"dockerComposeFile": ["../docker-compose.yml"],
"service": "web",
"workspaceFolder": "/var/www/html",
"customizations": {
"vscode": {
"extensions": [
"bmewburn.vscode-intelephense-client",
"xdebug.php-debug",
"EditorConfig.EditorConfig"
],
"settings": {
"php.validate.executablePath": "/usr/local/bin/php"
}
}
},
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {},
"ghcr.io/devcontainers/features/node:1": {}
}
}
```
**Validation:**
- File exists: ✅ Good (VS Code support)
- References docker-compose.yml or DDEV: ✅ Integrated approach
- Empty directory: ⚠️ Incomplete setup
**Severity if missing:** 🟢 **Low** - Nice to have, not required
## DDEV-Specific Best Practices
### TYPO3-Optimized Settings
**`.ddev/config.typo3.yaml`:**
```yaml
# TYPO3-specific DDEV configuration
override_config: false
web_extra_daemons:
- name: "typo3-backend-lock-handler"
command: "/var/www/html/.Build/bin/typo3 scheduler:run"
directory: /var/www/html
hooks:
post-start:
- exec: composer install
- exec: .Build/bin/typo3 cache:flush
# Additional PHP settings for TYPO3
php_ini:
memory_limit: 512M
max_execution_time: 240
upload_max_filesize: 32M
post_max_size: 32M
```
### Custom DDEV Commands
**`.ddev/commands/web/typo3`:**
```bash
#!/bin/bash
## Description: Run TYPO3 CLI commands
## Usage: typo3 [args]
## Example: "ddev typo3 cache:flush"
.Build/bin/typo3 "$@"
```
**`.ddev/commands/web/test-unit`:**
```bash
#!/bin/bash
## Description: Run unit tests
## Usage: test-unit [args]
.Build/bin/phpunit -c Build/phpunit/UnitTests.xml "$@"
```
**`.ddev/commands/web/test-functional`:**
```bash
#!/bin/bash
## Description: Run functional tests
## Usage: test-functional [args]
.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml "$@"
```
## Conformance Evaluation Workflow
### Step 1: Detect Development Environment Type
```bash
# Check for DDEV
if [ -f ".ddev/config.yaml" ]; then
DEV_ENV="ddev"
SCORE=20 # Full points for DDEV
# Check for Docker Compose
elif [ -f "docker-compose.yml" ]; then
DEV_ENV="docker-compose"
SCORE=15 # Good, but manual
# Check for DevContainer only
elif [ -f ".devcontainer/devcontainer.json" ]; then
DEV_ENV="devcontainer"
SCORE=10 # VS Code specific
# No dev environment
else
DEV_ENV="none"
SCORE=0
fi
```
### Step 2: Validate Configuration Against Extension
**For DDEV:**
```bash
# Extract extension requirements
MIN_PHP=$(jq -r '.require.php' composer.json | grep -oE '[0-9]+\.[0-9]+' | head -1)
WEB_DIR=$(jq -r '.extra.typo3.cms."web-dir"' composer.json)
# Validate DDEV config
DDEV_PHP=$(grep 'php_version:' .ddev/config.yaml | awk '{print $2}' | tr -d '"')
DDEV_DOCROOT=$(grep 'docroot:' .ddev/config.yaml | awk '{print $2}')
DDEV_TYPE=$(grep 'type:' .ddev/config.yaml | awk '{print $2}')
# Compare
if [ "${DDEV_PHP}" != "${MIN_PHP}" ]; then
echo "⚠️ PHP version mismatch: DDEV ${DDEV_PHP} vs required ${MIN_PHP}"
fi
if [ "${DDEV_DOCROOT}" != "${WEB_DIR}" ]; then
echo "⚠️ Docroot mismatch: DDEV ${DDEV_DOCROOT} vs composer ${WEB_DIR}"
fi
if [ "${DDEV_TYPE}" != "typo3" ]; then
echo "❌ DDEV type should be 'typo3', found '${DDEV_TYPE}'"
fi
```
### Step 3: Check for Recommended Enhancements
```bash
# DDEV commands
if [ -d ".ddev/commands/web" ]; then
COMMANDS=$(ls .ddev/commands/web/ 2>/dev/null | wc -l)
echo "✅ DDEV has ${COMMANDS} custom commands"
else
echo " No custom DDEV commands (consider adding typo3, test-unit, test-functional)"
fi
# TYPO3-specific config
if [ -f ".ddev/config.typo3.yaml" ]; then
echo "✅ TYPO3-specific DDEV configuration present"
else
echo " No TYPO3-specific config (optional)"
fi
```
## Conformance Report Integration
### When Evaluating Development Environment:
**In "Best Practices" Section:**
```markdown
### Development Environment
**Configuration:**
- ✅ DDEV configured (.ddev/config.yaml present)
- ✅ PHP version matches composer.json minimum (8.2)
- ✅ Docroot matches composer.json web-dir (.Build/public)
- ✅ Type set to 'typo3' for TYPO3-optimized setup
- ✅ MariaDB 10.11 (LTS) configured
- ✅ Custom DDEV commands for testing (test-unit, test-functional)
- Optional: TYPO3-specific config (.ddev/config.typo3.yaml) could enhance setup
**Or with issues:**
- ❌ No development environment configuration
- Missing: .ddev/config.yaml, docker-compose.yml
- Impact: Inconsistent development environments, difficult onboarding
- Severity: Medium
- Recommendation: Add DDEV configuration from Tea extension pattern
- Reference: https://github.com/TYPO3BestPractices/tea/tree/main/.ddev
- ⚠️ DDEV PHP version mismatch
- File: .ddev/config.yaml
- Current: php_version: "7.4"
- Expected: php_version: "8.2" (from composer.json)
- Severity: High
- Fix: Update php_version to match minimum requirement
- ⚠️ DDEV docroot mismatch
- File: .ddev/config.yaml
- Current: docroot: public
- Expected: docroot: .Build/public (from composer.json extra.typo3.cms.web-dir)
- Severity: High
- Fix: Update docroot to match web-dir
```
## Scoring Impact
**Best Practices Score Components (out of 20):**
| Component | Max Points | DDEV | Docker Compose | None |
|-----------|-----------|------|----------------|------|
| **Dev Environment Exists** | 6 | 6 | 4 | 0 |
| **Configuration Correct** | 4 | 4 | 3 | 0 |
| **Version Matching** | 3 | 3 | 2 | 0 |
| **Documentation** | 2 | 2 | 1 | 0 |
| **Custom Commands/Enhancements** | 2 | 2 | 0 | 0 |
| **Other Best Practices** | 3 | 3 | 3 | 3 |
| **Total** | 20 | 20 | 13 | 3 |
**Deductions:**
| Issue | Severity | Score Impact |
|-------|----------|--------------|
| No dev environment at all | High | -6 points |
| PHP version mismatch | High | -3 points |
| Docroot mismatch | High | -3 points |
| Wrong type (not 'typo3') | Medium | -2 points |
| Missing custom commands | Low | -1 point |
| No documentation | Low | -1 point |
## Tea Extension Reference
**Source:** https://github.com/TYPO3BestPractices/tea/tree/main/.ddev
**Tea DDEV Structure:**
```
.ddev/
├── .gitignore
├── config.yaml # Main configuration
├── config.typo3.yaml # TYPO3-specific settings
└── commands/
└── web/
├── typo3 # TYPO3 CLI wrapper
├── test-unit # Run unit tests
└── test-functional # Run functional tests
```
**Tea config.yaml (simplified):**
```yaml
name: tea
type: typo3
docroot: .Build/public
php_version: "8.2"
webserver_type: nginx-fpm
database:
type: mariadb
version: "10.11"
xdebug_enabled: false
```
**Usage Examples:**
```bash
# Start DDEV
ddev start
# Install dependencies
ddev composer install
# Run TYPO3 CLI
ddev typo3 cache:flush
# Run unit tests
ddev test-unit
# Run functional tests
ddev test-functional
# Access database
ddev mysql
# SSH into container
ddev ssh
```
## Quick Reference Checklist
**When evaluating development environment:**
```
□ .ddev/config.yaml exists (preferred)
□ OR docker-compose.yml exists (acceptable)
□ OR .devcontainer/devcontainer.json exists (VS Code only)
□ Configuration type is 'typo3' (DDEV) or uses TYPO3 image (Docker Compose)
□ PHP version matches composer.json minimum
□ Docroot matches composer.json web-dir
□ Database is MariaDB 10.11+ or MySQL 8.0+
□ Custom commands for common tasks (DDEV)
□ Documentation exists (README.md mentions DDEV/Docker setup)
□ .ddev/.gitignore present (excludes dynamic files)
□ Post-start hooks run composer install (optional but nice)
```
## Common Issues
### Issue: Empty .devcontainer/
**Diagnosis:**
```bash
ls -la .devcontainer/
# total 8
# drwxr-sr-x 2 user user 4096 Oct 20 20:05 .
```
**Severity:** 🟢 Low (incomplete setup, doesn't help or hurt)
**Fix:** Either populate with devcontainer.json or remove directory
### Issue: DDEV but no .gitignore
**Diagnosis:**
```bash
ls -la .ddev/.gitignore
# No such file or directory
```
**Problem:** DDEV generates dynamic files that shouldn't be committed
**Fix:** Create `.ddev/.gitignore`:
```
/*.yaml
.ddev-docker-compose-*.yaml
.homeadditions
.sshimagename
commands/web/.ddev-docker-compose-*.yaml
import-db/
```
### Issue: Wrong DDEV project type
**Diagnosis:**
```yaml
# .ddev/config.yaml
type: php # ❌ Wrong
```
**Problem:** Misses TYPO3-specific optimizations (URL structure, etc.)
**Fix:** Change to `type: typo3`
## Resources
- **DDEV Documentation:** https://ddev.readthedocs.io/
- **DDEV TYPO3 Quickstart:** https://ddev.readthedocs.io/en/stable/users/quickstart/#typo3
- **Tea Extension DDEV Setup:** https://github.com/TYPO3BestPractices/tea/tree/main/.ddev
- **TYPO3 Docker Documentation:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/LocalDevelopment/

View File

@@ -0,0 +1,621 @@
# Directory Structure Standards
**Purpose:** Validate proper separation of committed configuration vs generated/temporary files in TYPO3 extensions
## Why Directory Structure Matters
Proper directory organization prevents common issues:
-**Version Control Hygiene** - Only configuration committed, not generated files
-**Build Reproducibility** - Clean installs work without artifacts
-**CI/CD Clarity** - Clear separation of what's tracked vs generated
-**Onboarding Efficiency** - New contributors understand structure instantly
-**Maintenance Simplicity** - Updates don't conflict with local artifacts
Without proper structure:
- ❌ Bloated repositories with vendor/, cache files committed
- ❌ Git conflicts in generated files
- ❌ CI failures from missing .gitignore patterns
- ❌ Confusion about what files are source vs generated
## TYPO3 Standard Directory Pattern
### Build/ - Committed Configuration
**Purpose:** Project-specific configuration files that define how to build, test, and validate code
**Characteristics:**
- ✅ Committed to git
- ✅ Shared across all developers
- ✅ Version controlled
- ✅ Defines project standards
**Standard Contents:**
```
Build/
├── phpstan.neon # Static analysis config
├── phpstan-baseline.neon # Known issues baseline (optional)
├── php-cs-fixer.php # Code style config
├── rector.php # Refactoring rules (optional, can be in Build/rector/)
├── phpunit/
│ ├── UnitTests.xml # Unit test configuration
│ ├── FunctionalTests.xml # Functional test configuration
│ └── bootstrap.php # Test bootstrap (if needed)
└── Scripts/
└── runTests.sh # Test orchestration script
```
**Alternative Rector Location:**
```
Build/
└── rector/
└── rector.php # Rector config in subdirectory
```
**Git Status:** All files tracked (not in .gitignore)
### .Build/ - Generated/Temporary Files
**Purpose:** Composer-generated files, caches, runtime artifacts, test outputs
**Characteristics:**
- ❌ NOT committed to git (gitignored)
- ✅ Generated by composer/build tools
- ✅ Recreatable from configuration
- ✅ Developer-specific caches allowed
**Standard Contents:**
```
.Build/
├── bin/ # Composer bin-dir (phpunit, phpstan, etc.)
├── public/ # Web root (TYPO3 web-dir)
│ ├── index.php
│ ├── typo3/
│ └── typo3conf/
├── vendor/ # Composer dependencies
├── .php-cs-fixer.cache # php-cs-fixer cache file
├── .phpunit.result.cache # PHPUnit cache
└── var/ # Runtime cache/logs (optional)
```
**Git Status:** Entire directory in .gitignore
**Standard .gitignore Entry:**
```gitignore
# Composer generated files
.Build/
```
## Validation Checklist
### 1. Build/ Directory Structure
**Check for committed configuration files:**
```bash
ls -la Build/
```
**Required files for comprehensive quality:**
- `Build/phpstan.neon` - Static analysis configuration
- `Build/php-cs-fixer.php` - Code style configuration
- `Build/phpunit/UnitTests.xml` - Unit test configuration
- `Build/Scripts/runTests.sh` - Test orchestration
**Optional but recommended:**
- `Build/phpunit/FunctionalTests.xml` - Functional test configuration
- `Build/phpstan-baseline.neon` - Known issues baseline
- `Build/rector.php` or `Build/rector/rector.php` - Refactoring rules
**Severity if missing:** 🟡 **Medium** - Quality tools not standardized
### 2. .Build/ Directory Gitignore
**Check .gitignore for .Build/ exclusion:**
```bash
grep -E '^\\.Build/' .gitignore
```
**Expected pattern:**
```gitignore
.Build/
```
**Alternative patterns (also valid):**
```gitignore
/.Build/
.Build/*
```
**Check for accidental commits:**
```bash
git ls-files .Build/
# Should return empty - no files from .Build/ should be tracked
```
**Severity if misconfigured:** 🔴 **High** - Can lead to repository bloat
### 3. Composer Configuration Alignment
**Validate composer.json paths match directory structure:**
```bash
# Extract composer paths
cat composer.json | jq -r '.config."bin-dir"' # Should be .Build/bin
cat composer.json | jq -r '.config."vendor-dir"' # Should be .Build/vendor
cat composer.json | jq -r '.extra.typo3.cms."web-dir"' # Should be .Build/public
```
**Expected composer.json:**
```json
{
"config": {
"vendor-dir": ".Build/vendor",
"bin-dir": ".Build/bin"
},
"extra": {
"typo3/cms": {
"web-dir": ".Build/public"
}
}
}
```
**Severity if mismatched:** 🔴 **High** - Build will fail or create wrong structure
### 4. Quality Tool Configuration Paths
**Validate tool configs reference correct cache locations:**
```bash
# Check PHPUnit cache location
grep -r "\.phpunit\.result\.cache" Build/phpunit/*.xml
# Should reference .Build/.phpunit.result.cache or no cache directive
# Check php-cs-fixer cache
grep -r "cache-file" composer.json Build/php-cs-fixer.php
# Should reference .Build/.php-cs-fixer.cache if specified
# Check PHPStan cache (usually auto-managed)
grep -r "tmpDir" Build/phpstan.neon
# Should reference .Build/phpstan if specified, or auto temp
```
**Example CORRECT patterns:**
```json
// composer.json
{
"scripts": {
"ci:cgl": [
"php-cs-fixer fix --cache-file .Build/.php-cs-fixer.cache --dry-run --diff"
]
}
}
```
```php
// Build/php-cs-fixer.php
return (new PhpCsFixer\Config())
->setCacheFile('.Build/.php-cs-fixer.cache')
->setRules([...]);
```
```neon
# Build/phpstan.neon
parameters:
tmpDir: .Build/phpstan
```
**Severity if wrong:** 🟡 **Medium** - Works but creates clutter in Build/
### 5. Tea Extension Reference
**Source:** https://github.com/TYPO3BestPractices/tea
**Tea Directory Structure:**
```
tea/
├── .gitignore # Excludes .Build/
├── Build/
│ ├── phpstan.neon
│ ├── phpstan-baseline.neon
│ ├── php-cs-fixer.php
│ ├── phpunit/
│ │ ├── FunctionalTests.xml
│ │ └── UnitTests.xml
│ └── Scripts/
│ └── runTests.sh
├── .Build/ # Gitignored, generated by composer
│ ├── bin/
│ ├── public/
│ └── vendor/
└── composer.json # Defines .Build/ paths
```
**Tea .gitignore (relevant excerpt):**
```gitignore
# Composer-generated files
.Build/
composer.lock
```
**Tea composer.json (relevant excerpt):**
```json
{
"config": {
"vendor-dir": ".Build/vendor",
"bin-dir": ".Build/bin"
},
"extra": {
"typo3/cms": {
"web-dir": ".Build/public"
}
}
}
```
## Common Issues and Fixes
### Issue 1: Cache Files in Build/ Directory
**Diagnosis:**
```bash
ls -la Build/.php-cs-fixer.cache Build/.phpunit.result.cache
# If these files exist, they're in the WRONG location
```
**Problem:** Cache files committed or in wrong directory
**Fix:**
```bash
# Remove from wrong location
rm Build/.php-cs-fixer.cache Build/.phpunit.result.cache
# Update configuration to use .Build/
```
**Update php-cs-fixer config:**
```php
// Build/php-cs-fixer.php
return (new PhpCsFixer\Config())
->setCacheFile('.Build/.php-cs-fixer.cache') // ✅ Correct location
// ...
```
**Update composer scripts:**
```json
{
"scripts": {
"ci:cgl": [
"php-cs-fixer fix --cache-file .Build/.php-cs-fixer.cache --dry-run --diff"
]
}
}
```
### Issue 2: .Build/ Files Committed to Git
**Diagnosis:**
```bash
git ls-files .Build/
# Should be empty
```
**Problem:** Generated files tracked in git (vendor/, bin/, etc.)
**Fix:**
```bash
# Remove from git tracking
git rm -r --cached .Build/
# Ensure .gitignore has entry
echo ".Build/" >> .gitignore
# Commit the cleanup
git add .gitignore
git commit -m "fix: remove .Build/ from git tracking, add to .gitignore"
```
### Issue 3: Missing Build/ Directory
**Diagnosis:**
```bash
ls -la Build/
# Directory doesn't exist
```
**Problem:** No standardized quality tool configuration
**Fix:**
```bash
# Create Build/ directory structure
mkdir -p Build/{phpunit,Scripts}
# Add quality tool configs (see templates below)
# Then commit
git add Build/
git commit -m "feat: add Build/ directory with quality tool configurations"
```
### Issue 4: Rector in Wrong Location
**Diagnosis:**
```bash
# Check for rector.php in project root
ls -la rector.php
# Should be in Build/ or Build/rector/ instead
```
**Problem:** Configuration file in project root instead of Build/
**Fix:**
```bash
# Option 1: Move to Build/
mv rector.php Build/rector.php
# Option 2: Move to Build/rector/ (preferred for complex configs)
mkdir -p Build/rector
mv rector.php Build/rector/rector.php
# Update paths in rector.php
# Then commit
git add Build/
git rm rector.php
git commit -m "refactor: move rector config to Build/ directory"
```
## Conformance Report Integration
### When Evaluating Directory Structure:
**In "Best Practices" Section:**
```markdown
### Directory Structure
**Analysis:**
- ✅ Build/ directory present with committed configurations
- ✅ Build/phpstan.neon, Build/php-cs-fixer.php present
- ✅ Build/phpunit/ directory with test configs
- ✅ Build/Scripts/runTests.sh present
- ✅ .Build/ properly gitignored (entire directory)
- ✅ Composer paths correctly reference .Build/
- ✅ Cache files located in .Build/, not Build/
- ✅ No .Build/ files committed to git
**Or with issues:**
- ❌ Cache files in wrong location
- Files: Build/.php-cs-fixer.cache, Build/.phpunit.result.cache
- Expected: .Build/.php-cs-fixer.cache, .Build/.phpunit.result.cache
- Severity: Medium
- Fix: Move cache files to .Build/ and update configs
- ❌ .Build/ files committed to git
- Files: .Build/vendor/, .Build/bin/
- Command: `git ls-files .Build/` shows tracked files
- Severity: High
- Fix: `git rm -r --cached .Build/` and ensure .gitignore has `.Build/`
- ⚠️ Missing Build/ directory
- Impact: No standardized quality tool configuration
- Severity: Medium
- Recommendation: Create Build/ with phpstan.neon, php-cs-fixer.php, phpunit configs
```
## Scoring Impact
**Best Practices Score Deductions:**
| Issue | Severity | Score Impact |
|-------|----------|--------------|
| .Build/ files committed | High | -4 points |
| Cache files in Build/ | Medium | -2 points |
| Missing .gitignore for .Build/ | High | -3 points |
| Composer paths don't match structure | High | -3 points |
| Missing Build/ directory | Medium | -2 points |
| Rector/configs in project root | Low | -1 point |
**Maximum deduction for directory issues:** -6 points (out of 20 for Best Practices)
## Automated Validation Script
Create `scripts/validate-directory-structure.sh`:
```bash
#!/bin/bash
set -e
echo "🔍 Validating directory structure..."
ISSUES=0
# Check 1: .Build/ should be gitignored
if ! grep -qE '^\\.Build/' .gitignore; then
echo "❌ .gitignore missing '.Build/' entry"
ISSUES=$((ISSUES + 1))
else
echo "✅ .Build/ properly gitignored"
fi
# Check 2: No .Build/ files should be tracked
TRACKED_BUILD=$(git ls-files .Build/ 2>/dev/null | wc -l)
if [ "${TRACKED_BUILD}" -gt 0 ]; then
echo "❌ .Build/ files are committed to git:"
git ls-files .Build/
ISSUES=$((ISSUES + 1))
else
echo "✅ No .Build/ files tracked in git"
fi
# Check 3: Build/ directory should exist
if [ ! -d "Build" ]; then
echo "⚠️ Build/ directory missing"
ISSUES=$((ISSUES + 1))
else
echo "✅ Build/ directory exists"
# Check for standard files
[ -f "Build/phpstan.neon" ] && echo " ✅ phpstan.neon" || echo " ⚠️ phpstan.neon missing"
[ -f "Build/php-cs-fixer.php" ] && echo " ✅ php-cs-fixer.php" || echo " ⚠️ php-cs-fixer.php missing"
[ -d "Build/phpunit" ] && echo " ✅ phpunit/" || echo " ⚠️ phpunit/ missing"
[ -f "Build/Scripts/runTests.sh" ] && echo " ✅ runTests.sh" || echo " ⚠️ runTests.sh missing"
fi
# Check 4: Cache files should NOT be in Build/
if [ -f "Build/.php-cs-fixer.cache" ] || [ -f "Build/.phpunit.result.cache" ]; then
echo "❌ Cache files in wrong location (Build/ instead of .Build/):"
ls -la Build/.*.cache 2>/dev/null || true
ISSUES=$((ISSUES + 1))
else
echo "✅ No cache files in Build/"
fi
# Check 5: Composer paths should reference .Build/
BIN_DIR=$(jq -r '.config."bin-dir" // ".Build/bin"' composer.json)
VENDOR_DIR=$(jq -r '.config."vendor-dir" // ".Build/vendor"' composer.json)
WEB_DIR=$(jq -r '.extra.typo3.cms."web-dir" // ".Build/public"' composer.json)
if [ "${BIN_DIR}" = ".Build/bin" ]; then
echo "✅ Composer bin-dir: ${BIN_DIR}"
else
echo "⚠️ Composer bin-dir: ${BIN_DIR} (expected .Build/bin)"
ISSUES=$((ISSUES + 1))
fi
if [ "${VENDOR_DIR}" = ".Build/vendor" ]; then
echo "✅ Composer vendor-dir: ${VENDOR_DIR}"
else
echo "⚠️ Composer vendor-dir: ${VENDOR_DIR} (expected .Build/vendor)"
ISSUES=$((ISSUES + 1))
fi
if [ "${WEB_DIR}" = ".Build/public" ]; then
echo "✅ TYPO3 web-dir: ${WEB_DIR}"
else
echo "⚠️ TYPO3 web-dir: ${WEB_DIR} (expected .Build/public)"
ISSUES=$((ISSUES + 1))
fi
echo ""
if [ ${ISSUES} -eq 0 ]; then
echo "✅ Directory structure validation complete - no issues found"
exit 0
else
echo "❌ Directory structure validation found ${ISSUES} issue(s)"
exit 1
fi
```
## Quick Reference Checklist
**When evaluating directory structure:**
```
□ .gitignore contains .Build/ entry
□ Build/ directory exists and contains configs
□ Build/phpstan.neon exists
□ Build/php-cs-fixer.php exists
□ Build/phpunit/ directory exists with XML configs
□ Build/Scripts/runTests.sh exists
□ .Build/ is NOT tracked in git (git ls-files .Build/ is empty)
□ Cache files are in .Build/, not Build/
□ Composer bin-dir = .Build/bin
□ Composer vendor-dir = .Build/vendor
□ TYPO3 web-dir = .Build/public
□ No configuration files in project root (rector.php, phpstan.neon, etc.)
```
## Configuration File Templates
### Build/phpstan.neon
```neon
includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon
- vendor/saschaegerer/phpstan-typo3/extension.neon
parameters:
level: max
paths:
- Classes
- Tests
tmpDir: .Build/phpstan
reportUnmatchedIgnoredErrors: true
```
### Build/php-cs-fixer.php
```php
<?php
declare(strict_types=1);
$finder = (new PhpCsFixer\Finder())
->in(__DIR__ . '/../Classes')
->in(__DIR__ . '/../Tests');
return (new PhpCsFixer\Config())
->setRules([
'@PSR12' => true,
'@PhpCsFixer' => true,
'declare_strict_types' => true,
])
->setCacheFile('.Build/.php-cs-fixer.cache')
->setRiskyAllowed(true)
->setFinder($finder);
```
### Build/rector/rector.php
```php
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\ValueObject\PhpVersion;
use Ssch\TYPO3Rector\Set\Typo3LevelSetList;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/../../Classes/',
__DIR__ . '/../../Tests/',
])
->withPhpVersion(PhpVersion::PHP_82)
->withPhpSets(true)
->withSets([
Typo3LevelSetList::UP_TO_TYPO3_13,
]);
```
## Resources
- **Tea Extension Structure:** https://github.com/TYPO3BestPractices/tea
- **Composer Documentation:** https://getcomposer.org/doc/06-config.md
- **TYPO3 Extension Structure:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/FileStructure/

View File

@@ -0,0 +1,712 @@
# Excellence Indicators Reference
**Purpose:** Document optional features that indicate exceptional TYPO3 extension quality beyond basic conformance
## Overview
Excellence Indicators are **optional** features that demonstrate exceptional project quality, community engagement, and professional development practices. Extensions are **not penalized** for missing these features, but **earn bonus points** when present.
**Key Principle:** Base conformance (0-100 points) measures adherence to TYPO3 standards. Excellence indicators (0-20 bonus points) reward exceptional quality.
---
## Scoring System
**Total Excellence Points: 0-20 (bonus)**
| Category | Max Points | Purpose |
|----------|-----------|---------|
| Community & Internationalization | 6 | Engagement, accessibility, distribution |
| Advanced Quality Tooling | 7 | Automation, code quality, maintenance |
| Documentation Excellence | 4 | Comprehensive docs, modern tooling |
| Extension Configuration | 3 | Professional setup, flexibility |
---
## Category 1: Community & Internationalization (0-6 points)
### 1.1 Crowdin Integration (+2 points)
**File:** `crowdin.yml`
**Purpose:** Community-driven translation management platform integration
**Example (georgringer/news):**
```yaml
files:
- source: /Resources/Private/Language/locallang*.xlf
translation: /Resources/Private/Language/%two_letters_code%.%original_file_name%
```
**Benefits:**
- Enables community translators to contribute
- Automated translation synchronization
- Professional multilingual support
- Reduces maintenance burden for translations
**Validation:**
```bash
[ -f "crowdin.yml" ] && echo "✅ Crowdin integration (+2)"
```
**Reference:** [Crowdin TYPO3 Integration](https://crowdin.com/)
---
### 1.2 GitHub Issue Templates (+1 point)
**Files:** `.github/ISSUE_TEMPLATE/`
- `Bug_report.md`
- `Feature_request.md`
- `Support_question.md`
**Purpose:** Structured community contribution and issue reporting
**Example (georgringer/news):**
```markdown
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'bug'
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
...
```
**Benefits:**
- Ensures complete bug reports
- Reduces back-and-forth communication
- Categorizes issues automatically
- Professional project impression
**Validation:**
```bash
ls -1 .github/ISSUE_TEMPLATE/*.md 2>/dev/null | wc -l
# 3 files = +1 point
```
---
### 1.3 .gitattributes Export Optimization (+1 point)
**File:** `.gitattributes`
**Purpose:** Reduce TER (TYPO3 Extension Repository) package size by excluding development files
**Example (georgringer/news):**
```gitattributes
/.github/ export-ignore
/Build/ export-ignore
/Tests/ export-ignore
/.editorconfig export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.styleci.yml export-ignore
/Makefile export-ignore
```
**Benefits:**
- Smaller download size for production installations
- Faster `composer install` in production
- Professional package distribution
- Security (doesn't ship development files)
**Validation:**
```bash
grep -q "export-ignore" .gitattributes && echo "✅ Export optimization (+1)"
```
**Impact Example:**
- Repository size: 15 MB (with tests, CI configs)
- TER package size: 2 MB (production files only)
- **Reduction:** ~87%
---
### 1.4 Professional README with Badges (+2 points)
**File:** `README.md`
**Purpose:** Comprehensive project overview with status indicators
**Required Elements (all 4 required for points):**
1. Stability badge (Packagist or TER)
2. CI/Build status badge (GitHub Actions, GitLab CI)
3. Download stats (Packagist downloads)
4. Compatibility matrix table
**Example (georgringer/news):**
```markdown
[![Latest Stable Version](https://poser.pugx.org/georgringer/news/v/stable)](https://extensions.typo3.org/extension/news/)
[![TYPO3 12](https://img.shields.io/badge/TYPO3-12-orange.svg)](https://get.typo3.org/version/12)
[![TYPO3 13](https://img.shields.io/badge/TYPO3-13-orange.svg)](https://get.typo3.org/version/13)
[![Total Downloads](https://poser.pugx.org/georgringer/news/d/total)](https://packagist.org/packages/georgringer/news)
![Build v12](https://github.com/georgringer/news/actions/workflows/core12.yml/badge.svg)
[![Crowdin](https://badges.crowdin.net/typo3-extension-news/localized.svg)](https://crowdin.com/project/typo3-extension-news)
## Compatibility
| News | TYPO3 | PHP | Support / Development |
|------|-----------|-----------|--------------------------------------|
| 12 | 12 - 13 | 8.1 - 8.3 | features, bugfixes, security updates |
| 11 | 11 - 12 | 7.4 - 8.3 | security updates |
```
**Validation:**
```bash
# Check for at least 3 badges and a compatibility table
grep -c "!\[" README.md # Badge count
grep -c "^|" README.md # Table rows
```
---
## Category 2: Advanced Quality Tooling (0-7 points)
### 2.1 Fractor Configuration (+2 points)
**File:** `Build/fractor/fractor.php`
**Purpose:** Automated refactoring for TypoScript and XML configuration files
**What is Fractor?**
- Rector handles PHP code refactoring
- **Fractor handles TypoScript and XML** file refactoring
- Automates TYPO3 configuration migrations
**Example (georgringer/news):**
```php
<?php
declare(strict_types=1);
use a9f\Fractor\Configuration\FractorConfiguration;
use a9f\FractorTypoScript\Configuration\TypoScriptProcessorOption;
use a9f\FractorXml\Configuration\XmlProcessorOption;
use a9f\Typo3Fractor\Set\Typo3LevelSetList;
return FractorConfiguration::configure()
->withPaths([
__DIR__ . '/../../Classes',
__DIR__ . '/../../Configuration/',
__DIR__ . '/../../Resources',
])
->withSets([
Typo3LevelSetList::UP_TO_TYPO3_12,
])
->withOptions([
TypoScriptProcessorOption::INDENT_CHARACTER => 'auto',
XmlProcessorOption::INDENT_CHARACTER => Indent::STYLE_TAB,
]);
```
**Benefits:**
- Automates TypoScript configuration migrations
- Modernizes FlexForm XML structures
- Reduces manual refactoring effort
- Catches TYPO3 API changes in configuration
**Required Packages:**
```json
{
"require-dev": {
"a9f/fractor": "^1.0",
"a9f/typo3-fractor": "^1.0"
}
}
```
**Validation:**
```bash
[ -f "Build/fractor/fractor.php" ] && echo "✅ Fractor configuration (+2)"
```
---
### 2.2 TYPO3 CodingStandards Package (+1 point)
**File:** `Build/php-cs-fixer/php-cs-fixer.php`
**Purpose:** Official TYPO3 community coding standards package (not custom config)
**Example (georgringer/news):**
```php
<?php
use PhpCsFixer\Finder;
use TYPO3\CodingStandards\CsFixerConfig;
$config = CsFixerConfig::create();
$config->setHeader(
'This file is part of the "news" Extension for TYPO3 CMS.
For the full copyright and license information, please read the
LICENSE.txt file that was distributed with this source code.',
true
);
```
**Benefits:**
- Official TYPO3 community standards
- Automatic copyright header injection
- PER Coding Style (PSR-12 successor)
- Consistent with TYPO3 core
**Required Package:**
```json
{
"require-dev": {
"typo3/coding-standards": "^0.5"
}
}
```
**Validation:**
```bash
grep -q "TYPO3\\\\CodingStandards" Build/php-cs-fixer/php-cs-fixer.php && echo "✅ TYPO3 CodingStandards (+1)"
```
**Alternative (no points):** Custom php-cs-fixer config (still good, but not official package)
---
### 2.3 StyleCI Integration (+1 point)
**File:** `.styleci.yml`
**Purpose:** Cloud-based automatic code style checking on pull requests
**Example (georgringer/news):**
```yaml
preset: psr12
enabled:
- no_unused_imports
- ordered_imports
- single_quote
- short_array_syntax
- hash_to_slash_comment
- native_function_casing
finder:
name:
- "*.php"
not-path:
- ".Build"
- "Build/php-cs-fixer"
- "Documentation"
```
**Benefits:**
- Automatic PR code style checks (no local setup needed)
- Visual code review integration
- Reduces reviewer burden
- Enforces consistency across contributors
**Validation:**
```bash
[ -f ".styleci.yml" ] && echo "✅ StyleCI integration (+1)"
```
**Note:** Alternative to local php-cs-fixer CI checks, not replacement
---
### 2.4 Makefile Task Automation (+1 point)
**File:** `Makefile`
**Purpose:** Self-documenting task automation and workflow management
**Example (georgringer/news):**
```makefile
.PHONY: help
help: ## Displays this list of targets with descriptions
@echo "The following commands are available:\n"
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}'
.PHONY: docs
docs: ## Generate projects docs (from "Documentation" directory)
mkdir -p Documentation-GENERATED-temp
docker run --rm --pull always -v "$(shell pwd)":/project -t ghcr.io/typo3-documentation/render-guides:latest --config=Documentation
```
**Benefits:**
- Discoverable commands (`make help`)
- Consistent workflow across contributors
- Reduces documentation for common tasks
- Docker-based documentation rendering
**Validation:**
```bash
[ -f "Makefile" ] && grep -q "^help:.*##" Makefile && echo "✅ Makefile automation (+1)"
```
---
### 2.5 Comprehensive CI Matrix (+2 points)
**Files:** `.github/workflows/*.yml` or `.gitlab-ci.yml`
**Purpose:** Test across multiple PHP versions and dependency scenarios
**Required for +2 points:**
- At least 3 PHP versions tested
- Both `composerInstallLowest` and `composerInstallHighest` strategies
- Multiple TYPO3 versions if extension supports multiple
**Example (georgringer/news):**
```yaml
name: core 12
on: [ push, pull_request ]
jobs:
tests:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
php: [ '8.1', '8.2', '8.3', '8.4' ] # 4 PHP versions
composerInstall: [ 'composerInstallLowest', 'composerInstallHighest' ]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install testing system
run: Build/Scripts/runTests.sh -t 12 -p ${{ matrix.php }} -s ${{ matrix.composerInstall }}
- name: Lint PHP
run: Build/Scripts/runTests.sh -t 12 -p ${{ matrix.php }} -s lint
```
**Benefits:**
- Catches dependency conflicts early
- Ensures compatibility across PHP versions
- Tests minimum and maximum dependency versions
- Professional CI/CD setup
**Validation:**
```bash
# Check for matrix with multiple PHP versions and composerInstall strategies
grep -A 5 "matrix:" .github/workflows/*.yml | grep -c "composerInstall"
```
---
## Category 3: Documentation Excellence (0-4 points)
### 3.1 Extensive RST Documentation (100+ files) (+3 points)
**Directory:** `Documentation/`
**Purpose:** Comprehensive, structured documentation covering all aspects
**Example (georgringer/news: 183 RST files):**
```
Documentation/
├── Addons/ # Extension integrations
├── Administration/ # Backend administration
├── Introduction/ # Getting started
├── QuickStart/ # Fast setup guide
├── Reference/ # API reference
├── Tutorials/ # Step-by-step guides
├── UsersManual/ # End-user documentation
└── Images/ # Visual assets
```
**Scoring:**
- 50-99 RST files: +1 point
- 100-149 RST files: +2 points
- 150+ RST files: +3 points
**Validation:**
```bash
RST_COUNT=$(find Documentation -name "*.rst" | wc -l)
if [ $RST_COUNT -ge 150 ]; then
echo "✅ Extensive documentation 150+ RST (+3)"
elif [ $RST_COUNT -ge 100 ]; then
echo "✅ Comprehensive documentation 100+ RST (+2)"
elif [ $RST_COUNT -ge 50 ]; then
echo "✅ Good documentation 50+ RST (+1)"
fi
```
**Benefits:**
- Reduces support burden
- Improves onboarding
- Professional project impression
- Better community adoption
---
### 3.2 Modern Documentation Tooling (+1 point)
**Files:**
- `Documentation/guides.xml`
- `Documentation/screenshots.json`
**Purpose:** Modern TYPO3 documentation rendering and screenshot management
**Example (georgringer/news):**
```xml
<!-- guides.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<guides xmlns="https://guides.typo3.org/ns/1.0">
<project>
<title>News System</title>
<release>12.0</release>
<vendor>georgringer</vendor>
</project>
</guides>
```
```json
// screenshots.json
{
"screenshots": [
{
"file": "Images/Administration/BackendModule.png",
"caption": "News administration module"
}
]
}
```
**Benefits:**
- Automated documentation rendering
- Screenshot management and regeneration
- Consistent with TYPO3 documentation standards
- Future-proof documentation setup
**Validation:**
```bash
[ -f "Documentation/guides.xml" ] && echo "✅ Modern documentation tooling (+1)"
```
---
## Category 4: Extension Configuration (0-3 points)
### 4.1 Extension Configuration Template (+1 point)
**File:** `ext_conf_template.txt`
**Purpose:** Backend extension configuration interface with categorized settings
**Example (georgringer/news):**
```
# Records
###########################
# cat=records/enable/103; type=boolean; label=LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:extmng.prependAtCopy
prependAtCopy = 1
# cat=records/enable/101; type=string; label=LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:extmng.tagPid
tagPid = 1
# cat=records/enable/26; type=boolean; label=LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:extmng.rteForTeaser
rteForTeaser = 0
# Backend module
# cat=backend module/enable/10; type=boolean; label=LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:extmng.showAdministrationModule
showAdministrationModule = 1
```
**Benefits:**
- User-friendly backend configuration
- Categorized settings for clarity
- Localized labels
- No PHP knowledge required for configuration
**Validation:**
```bash
[ -f "ext_conf_template.txt" ] && echo "✅ Extension configuration template (+1)"
```
**Note:** Not required for modern TYPO3 extensions using Site Sets, but still valuable for global extension settings
---
### 4.2 Composer Documentation Scripts (+1 point)
**File:** `composer.json`
**Purpose:** Automated documentation rendering and watching
**Required Scripts (at least 2 of 3):**
- `doc-init` - Initialize documentation rendering
- `doc-make` - Render documentation
- `doc-watch` - Watch and auto-render documentation
**Example (georgringer/news):**
```json
{
"scripts": {
"doc-init": "docker run --rm --pull always -v $(pwd):/project -it ghcr.io/typo3-documentation/render-guides:latest --config=Documentation",
"doc-make": "make docs",
"doc-watch": "docker run --rm -it --pull always -v \"./Documentation:/project/Documentation\" -v \"./Documentation-GENERATED-temp:/project/Documentation-GENERATED-temp\" -p 5173:5173 ghcr.io/garvinhicking/typo3-documentation-browsersync:latest"
},
"scripts-descriptions": {
"doc-init": "Initialize documentation rendering",
"doc-make": "Render documentation",
"doc-watch": "Render documentation including a watcher"
}
}
```
**Benefits:**
- Easy documentation development
- Live preview during writing
- Docker-based (no local dependencies)
- Consistent with TYPO3 documentation workflow
**Validation:**
```bash
grep -q "doc-init.*doc-make" composer.json && echo "✅ Composer doc scripts (+1)"
```
---
### 4.3 Multiple Configuration Sets (TYPO3 13) (+1 point)
**Directory:** `Configuration/Sets/`
**Purpose:** Multiple configuration presets for different use cases
**Required:** At least 2 different Sets (not just one default)
**Example (georgringer/news has 5 Sets):**
```
Configuration/Sets/
├── News/ # Base news functionality
├── RecordLinks/ # Record link handling
├── Sitemap/ # Sitemap generation
├── Twb4/ # Twitter Bootstrap 4 templates
└── Twb5/ # Twitter Bootstrap 5 templates
```
**Benefits:**
- Quick setup for different scenarios
- Reusable configuration patterns
- Modern TYPO3 13 architecture
- Flexible deployment
**Validation:**
```bash
SET_COUNT=$(find Configuration/Sets -mindepth 1 -maxdepth 1 -type d | wc -l)
[ $SET_COUNT -ge 2 ] && echo "✅ Multiple configuration Sets (+1)"
```
---
## Excellence Indicators Conformance Report Format
### Example Report Section
```markdown
## Excellence Indicators (Bonus Score: 12/20)
### ✅ Community & Internationalization (4/6)
- ✅ Crowdin integration (crowdin.yml): +2 points
- ✅ GitHub issue templates (3 templates): +1 point
- ❌ .gitattributes export optimization: 0 points
- ✅ Professional README with badges: +2 points
- Stability badge: ✅
- CI status badge: ✅
- Download stats: ✅
- Compatibility matrix: ✅
### ✅ Advanced Quality Tooling (5/7)
- ✅ Fractor configuration (Build/fractor/fractor.php): +2 points
- ❌ TYPO3 CodingStandards package: 0 points (uses custom config)
- ✅ StyleCI integration (.styleci.yml): +1 point
- ❌ Makefile automation: 0 points
- ✅ Comprehensive CI matrix (4 PHP versions, composerInstallLowest/Highest): +2 points
### ✅ Documentation Excellence (3/4)
- ✅ Extensive documentation (183 RST files): +3 points
- ❌ Modern documentation tooling (guides.xml): 0 points
### ❌ Extension Configuration (0/3)
- ❌ ext_conf_template.txt: 0 points
- ❌ Composer documentation scripts: 0 points
- ❌ Multiple Configuration Sets: 0 points (only 1 Set present)
### Summary
This extension demonstrates exceptional quality in documentation and CI/CD practices. Consider adding:
- .gitattributes with export-ignore for smaller TER packages
- TYPO3 CodingStandards package for official community standards
- Makefile for task automation
- Modern documentation tooling (guides.xml, screenshots.json)
- Extension configuration template for backend settings
```
---
## Quick Reference Validation Checklist
**When evaluating excellence indicators:**
```
Community & Internationalization (0-6):
□ crowdin.yml present (+2)
□ 3 GitHub issue templates (+1)
□ .gitattributes with export-ignore (+1)
□ README with 4+ badges + compatibility table (+2)
Advanced Quality Tooling (0-7):
□ Build/fractor/fractor.php present (+2)
□ TYPO3\CodingStandards in php-cs-fixer config (+1)
□ .styleci.yml present (+1)
□ Makefile with help target (+1)
□ CI matrix: 3+ PHP versions + composerInstall variants (+2)
Documentation Excellence (0-4):
□ 50-99 RST files (+1) / 100-149 (+2) / 150+ (+3)
□ guides.xml + screenshots.json (+1)
Extension Configuration (0-3):
□ ext_conf_template.txt present (+1)
□ Composer doc scripts (doc-init, doc-make, doc-watch) (+1)
□ 2+ Configuration Sets in Configuration/Sets/ (+1)
```
---
## Implementation Notes
**For Conformance Skill:**
1. **Never penalize** missing excellence indicators
2. **Always report** excellence indicators separately from base conformance
3. **Score format:** `Base: 94/100 | Excellence: 12/20 | Total: 106/120`
4. **Optional evaluation:** Can be disabled with flag if user only wants base conformance
**Example CLI:**
```bash
# Full evaluation (base + excellence)
check-conformance --with-excellence
# Base conformance only
check-conformance
# Excellence only (for established extensions)
check-conformance --excellence-only
```
---
## Resources
- **georgringer/news:** https://github.com/georgringer/news (primary reference for excellence patterns)
- **TYPO3 Best Practices (Tea):** https://github.com/TYPO3BestPractices/tea (primary reference for base conformance)
- **Fractor:** https://github.com/andreaswolf/fractor
- **TYPO3 CodingStandards:** https://github.com/TYPO3/coding-standards
- **StyleCI:** https://styleci.io/
- **Crowdin:** https://crowdin.com/

View File

@@ -0,0 +1,610 @@
# ext_emconf.php Validation Standards (TYPO3 v13)
**Source:** TYPO3 Core API Reference v13.4 - FileStructure/ExtEmconf.html
**Purpose:** Complete validation rules for ext_emconf.php including critical TER restrictions
## CRITICAL RESTRICTIONS
### ❌ MUST NOT use declare(strict_types=1)
**CRITICAL:** The TYPO3 Extension Repository (TER) upload **WILL FAIL** if `declare(strict_types=1)` is present in ext_emconf.php.
**WRONG:**
```php
<?php
declare(strict_types=1);
$EM_CONF[$_EXTKEY] = [
// configuration
];
```
**CORRECT:**
```php
<?php
$EM_CONF[$_EXTKEY] = [
// configuration
];
```
**Detection:**
```bash
grep "declare(strict_types" ext_emconf.php && echo "❌ CRITICAL: TER upload will FAIL" || echo "✅ No strict_types"
```
### ✅ MUST use $_EXTKEY variable
Extensions must reference the global `$_EXTKEY` variable, not hardcode the extension key.
**WRONG:**
```php
$EM_CONF['my_extension'] = [
// configuration
];
```
**CORRECT:**
```php
$EM_CONF[$_EXTKEY] = [
// configuration
];
```
**Detection:**
```bash
grep '\$EM_CONF\[$_EXTKEY\]' ext_emconf.php && echo "✅ Uses $_EXTKEY" || echo "❌ Hardcoded key"
```
### ❌ MUST NOT contain custom code
The ext_emconf.php file must only contain the `$EM_CONF` array assignment. No additional functions, classes, or executable code is allowed.
**WRONG:**
```php
<?php
function getVersion() { return '1.0.0'; }
$EM_CONF[$_EXTKEY] = [
'version' => getVersion(),
];
```
**WRONG:**
```php
<?php
$EM_CONF[$_EXTKEY] = [
'title' => 'My Extension',
];
// Additional initialization code
require_once 'setup.php';
```
**CORRECT:**
```php
<?php
$EM_CONF[$_EXTKEY] = [
'title' => 'My Extension',
'version' => '1.0.0',
];
```
**Detection:**
```bash
# Check for function/class definitions
grep -E '^(function|class|interface|trait|require|include)' ext_emconf.php && echo "❌ Contains custom code" || echo "✅ No custom code"
```
---
## Mandatory Fields
### title
**Format:** English extension name
**Example:**
```php
'title' => 'My Extension',
```
**Validation:**
```bash
grep "'title' =>" ext_emconf.php && echo "✅ Has title" || echo "❌ Missing title"
```
### description
**Format:** Short, precise English description of extension functionality
**Requirements:**
- Clear explanation of what the extension does
- Should identify the vendor/company for professional extensions
- Must be meaningful (not just "Extension" or empty)
- Keep concise but informative (typically 50-150 characters)
**Good Examples:**
```php
'description' => 'Adds image support to CKEditor5 RTE - by Netresearch',
'description' => 'Provides advanced content management features for TYPO3 editors',
'description' => 'Custom form elements for newsletter subscription by Vendor GmbH',
```
**Bad Examples:**
```php
'description' => '', // Empty
'description' => 'Extension', // Too vague
'description' => 'Some tools', // Meaningless
```
**Validation:**
```bash
# Check description exists
grep "'description' =>" ext_emconf.php && echo "✅ Has description" || echo "❌ Missing description"
# Check description is not empty or trivial
DESC=$(grep -oP "'description' => '\K[^']+(?=')" ext_emconf.php)
[[ ${#DESC} -gt 20 ]] && echo "✅ Description is meaningful" || echo "⚠️ Description too short or vague"
```
### version
**Format:** `[int].[int].[int]` (semantic versioning)
**Examples:**
- `1.0.0`
- `2.5.12`
- `v1.0.0` ❌ (no 'v' prefix)
- `1.0` ❌ (must have three parts)
**Validation:**
```bash
grep -oP "'version' => '\K[0-9]+\.[0-9]+\.[0-9]+" ext_emconf.php && echo "✅ Valid version format" || echo "❌ Invalid version"
```
### author
**Format:** Developer name(s), comma-separated for multiple
**Example:**
```php
'author' => 'Sebastian Koschel, Sebastian Mendel, Rico Sonntag',
```
**Validation:**
```bash
grep "'author' =>" ext_emconf.php && echo "✅ Has author" || echo "❌ Missing author"
```
### author_email
**Format:** Email address(es), comma-separated for multiple
**Example:**
```php
'author_email' => 'developer@company.com, other@company.com',
```
**Validation:**
```bash
grep "'author_email' =>" ext_emconf.php | grep -q "@" && echo "✅ Has author_email" || echo "❌ Missing author_email"
```
### author_company
**Format:** Company name
**Example:**
```php
'author_company' => 'Company Name GmbH',
```
**Validation:**
```bash
grep "'author_company' =>" ext_emconf.php && echo "✅ Has author_company" || echo "⚠️ Missing author_company"
```
---
## Category Options
**Valid categories:**
| Category | Purpose |
|----------|---------|
| `be` | Backend-oriented functionality |
| `module` | Backend modules |
| `fe` | Frontend-oriented functionality |
| `plugin` | Frontend plugins |
| `misc` | Miscellaneous utilities |
| `services` | TYPO3 services |
| `templates` | Website templates |
| `example` | Example/demonstration extensions |
| `doc` | Documentation |
| `distribution` | Full site distributions/kickstarters |
**Example:**
```php
'category' => 'fe',
```
**Validation:**
```bash
grep -oP "'category' => '\K[a-z]+(?=')" ext_emconf.php | grep -qE '^(be|module|fe|plugin|misc|services|templates|example|doc|distribution)$' && echo "✅ Valid category" || echo "❌ Invalid category"
```
---
## State Values
**Valid states:**
| State | Meaning |
|-------|---------|
| `alpha` | Initial development phase, unstable |
| `beta` | Functional but incomplete or under active development |
| `stable` | Production-ready (author commits to maintenance) |
| `experimental` | Exploratory work, may be abandoned |
| `test` | Demonstration or testing purposes only |
| `obsolete` | Deprecated or unmaintained |
| `excludeFromUpdates` | Prevents Extension Manager from updating |
**Example:**
```php
'state' => 'stable',
```
**Validation:**
```bash
grep -oP "'state' => '\K[a-z]+(?=')" ext_emconf.php | grep -qE '^(alpha|beta|stable|experimental|test|obsolete|excludeFromUpdates)$' && echo "✅ Valid state" || echo "❌ Invalid state"
```
---
## Constraints Structure
### Format
```php
'constraints' => [
'depends' => [
'typo3' => '13.4.0-13.4.99',
'php' => '8.2.0-8.4.99',
],
'conflicts' => [
'incompatible_ext' => '',
],
'suggests' => [
'recommended_ext' => '1.0.0-2.99.99',
],
],
```
### depends
**Purpose:** Required dependencies loaded before this extension
**Mandatory entries:**
- `typo3` - TYPO3 version range
- `php` - PHP version range
**Example:**
```php
'depends' => [
'typo3' => '12.4.0-13.4.99',
'php' => '8.1.0-8.4.99',
'fluid' => '12.4.0-13.4.99',
],
```
**Validation:**
```bash
grep -A 5 "'depends' =>" ext_emconf.php | grep -q "'typo3'" && echo "✅ TYPO3 dependency" || echo "❌ Missing TYPO3 dep"
grep -A 5 "'depends' =>" ext_emconf.php | grep -q "'php'" && echo "✅ PHP dependency" || echo "❌ Missing PHP dep"
```
### conflicts
**Purpose:** Extensions incompatible with this one
**Example:**
```php
'conflicts' => [
'old_extension' => '',
],
```
### suggests
**Purpose:** Recommended companion extensions (loaded before current extension)
**Example:**
```php
'suggests' => [
'news' => '12.1.0-12.99.99',
],
```
---
## Version Constraint Format
### TYPO3 Version
**Format:** `major.minor.patch-major.minor.patch`
**Examples:**
- `12.4.0-12.4.99` - TYPO3 12 LTS only
- `13.4.0-13.4.99` - TYPO3 13 LTS only
- `12.4.0-13.4.99` - Both v12 and v13 (recommended for compatibility)
### PHP Version
**Format:** `major.minor.patch-major.minor.patch`
**TYPO3 Compatibility:**
- TYPO3 12 LTS: PHP 8.1-8.4
- TYPO3 13 LTS: PHP 8.2-8.4
**Example:**
```php
'php' => '8.1.0-8.4.99', // For v12/v13 compatibility
'php' => '8.2.0-8.4.99', // For v13 only
```
---
## Synchronization with composer.json
**Critical:** ext_emconf.php and composer.json must have matching constraints.
### Mapping Table
| composer.json | ext_emconf.php | Example |
|--------------|----------------|---------|
| `"typo3/cms-core": "^12.4 \|\| ^13.4"` | `'typo3' => '12.4.0-13.4.99'` | TYPO3 version |
| `"php": "^8.1"` | `'php' => '8.1.0-8.4.99'` | PHP version |
| `"typo3/cms-fluid": "^12.4"` | `'fluid' => '12.4.0-12.4.99'` | Extension dependency |
### Validation Strategy
```bash
# Compare TYPO3 versions
COMPOSER_TYPO3=$(jq -r '.require."typo3/cms-core"' composer.json)
EMCONF_TYPO3=$(grep -oP "'typo3' => '\K[0-9.-]+" ext_emconf.php)
echo "Composer: $COMPOSER_TYPO3"
echo "ext_emconf: $EMCONF_TYPO3"
# Manual comparison required for ^x.y vs x.y.z-x.y.z format
```
---
## Complete Required Fields Checklist
**Mandatory (MUST have):**
- [ ] `title` - Extension name in English
- [ ] `description` - Short, precise description
- [ ] `version` - Semantic version (x.y.z format)
- [ ] `category` - Valid category (be, fe, plugin, misc, etc.)
- [ ] `state` - Valid state (stable, beta, alpha, etc.)
- [ ] `constraints.depends.typo3` - TYPO3 version range
- [ ] `constraints.depends.php` - PHP version range
**Recommended (SHOULD have):**
- [ ] `author` - Developer name(s)
- [ ] `author_email` - Contact email(s)
- [ ] `author_company` - Company name
- [ ] `constraints.conflicts` - Conflicting extensions (even if empty array)
- [ ] `constraints.suggests` - Suggested companion extensions
**Complete Example:**
```php
<?php
$EM_CONF[$_EXTKEY] = [
'title' => 'My Extension Title',
'description' => 'Provides specific functionality for TYPO3.',
'category' => 'fe',
'author' => 'Developer Name',
'author_email' => 'developer@company.com',
'author_company' => 'Company Name GmbH',
'state' => 'stable',
'version' => '1.0.0',
'constraints' => [
'depends' => [
'typo3' => '12.4.0-13.4.99',
'php' => '8.1.0-8.4.99',
],
'conflicts' => [],
'suggests' => [],
],
];
```
---
## Complete Validation Script
```bash
#!/bin/bash
# validate-ext-emconf.sh
ERRORS=0
WARNINGS=0
echo "=== ext_emconf.php Validation ===="
echo ""
# CRITICAL: Check for strict_types
if grep -q "declare(strict_types" ext_emconf.php 2>/dev/null; then
echo "❌ CRITICAL: ext_emconf.php has declare(strict_types=1)"
echo " TER upload will FAIL!"
((ERRORS++))
fi
# CRITICAL: Check for $_EXTKEY usage
if ! grep -q '\$EM_CONF\[$_EXTKEY\]' ext_emconf.php 2>/dev/null; then
echo "❌ CRITICAL: Must use \$EM_CONF[\$_EXTKEY], not hardcoded key"
((ERRORS++))
fi
# CRITICAL: Check for custom code
if grep -E '^(function|class|interface|trait|require|include)' ext_emconf.php 2>/dev/null; then
echo "❌ CRITICAL: ext_emconf.php contains custom code (functions/classes/requires)"
((ERRORS++))
fi
# Check mandatory fields
grep -q "'title' =>" ext_emconf.php || { echo "❌ Missing title"; ((ERRORS++)); }
grep -q "'description' =>" ext_emconf.php || { echo "❌ Missing description"; ((ERRORS++)); }
grep -qP "'version' => '[0-9]+\.[0-9]+\.[0-9]+" ext_emconf.php || { echo "❌ Missing or invalid version"; ((ERRORS++)); }
# Check description is meaningful (>20 chars)
DESC=$(grep -oP "'description' => '\K[^']+(?=')" ext_emconf.php)
[[ ${#DESC} -lt 20 ]] && { echo "⚠️ Description too short (should be >20 chars)"; ((WARNINGS++)); }
# Check category
CATEGORY=$(grep -oP "'category' => '\K[a-z]+(?=')" ext_emconf.php)
if [[ ! "$CATEGORY" =~ ^(be|module|fe|plugin|misc|services|templates|example|doc|distribution)$ ]]; then
echo "❌ Invalid category: $CATEGORY"
((ERRORS++))
fi
# Check state
STATE=$(grep -oP "'state' => '\K[a-z]+(?=')" ext_emconf.php)
if [[ ! "$STATE" =~ ^(alpha|beta|stable|experimental|test|obsolete|excludeFromUpdates)$ ]]; then
echo "❌ Invalid state: $STATE"
((ERRORS++))
fi
# Check constraints
grep -A 5 "'depends' =>" ext_emconf.php | grep -q "'typo3'" || { echo "❌ Missing TYPO3 dependency"; ((ERRORS++)); }
grep -A 5 "'depends' =>" ext_emconf.php | grep -q "'php'" || { echo "❌ Missing PHP dependency"; ((ERRORS++)); }
# Check recommended author fields
grep -q "'author' =>" ext_emconf.php || { echo "⚠️ Missing author"; ((WARNINGS++)); }
grep "'author_email' =>" ext_emconf.php | grep -q "@" || { echo "⚠️ Missing or invalid author_email"; ((WARNINGS++)); }
grep -q "'author_company' =>" ext_emconf.php || { echo "⚠️ Missing author_company"; ((WARNINGS++)); }
echo ""
echo "Validation complete: $ERRORS errors, $WARNINGS warnings"
exit $ERRORS
```
---
## Common Violations and Fixes
### 1. Using declare(strict_types=1)
**WRONG - TER upload FAILS:**
```php
<?php
declare(strict_types=1);
$EM_CONF[$_EXTKEY] = [
'title' => 'My Extension',
];
```
**CORRECT:**
```php
<?php
$EM_CONF[$_EXTKEY] = [
'title' => 'My Extension',
];
```
### 2. Hardcoded Extension Key
**WRONG:**
```php
$EM_CONF['my_extension'] = [
'title' => 'My Extension',
];
```
**CORRECT:**
```php
$EM_CONF[$_EXTKEY] = [
'title' => 'My Extension',
];
```
### 3. Invalid Category
**WRONG:**
```php
'category' => 'utility', // Not a valid category
```
**CORRECT:**
```php
'category' => 'misc', // Use 'misc' for utilities
```
### 4. Invalid Version Format
**WRONG:**
```php
'version' => 'v1.0.0', // No 'v' prefix
'version' => '1.0', // Must have 3 parts
```
**CORRECT:**
```php
'version' => '1.0.0',
```
### 5. Missing PHP/TYPO3 Constraints
**WRONG:**
```php
'constraints' => [
'depends' => [
'extbase' => '12.4.0-12.4.99',
],
],
```
**CORRECT:**
```php
'constraints' => [
'depends' => [
'typo3' => '12.4.0-13.4.99',
'php' => '8.1.0-8.4.99',
'extbase' => '12.4.0-12.4.99',
],
],
```
### 6. Mismatched composer.json Constraints
**WRONG:**
composer.json:
```json
"require": {
"typo3/cms-core": "^13.4"
}
```
ext_emconf.php:
```php
'typo3' => '12.4.0-12.4.99', // Mismatch!
```
**CORRECT:**
composer.json:
```json
"require": {
"typo3/cms-core": "^13.4"
}
```
ext_emconf.php:
```php
'typo3' => '13.4.0-13.4.99', // Matches!
```
---
## Quick Reference
### Critical Checks
```bash
# Will TER upload fail?
grep "declare(strict_types" ext_emconf.php && echo "❌ TER FAIL"
# Uses $_EXTKEY?
grep '\$EM_CONF\[$_EXTKEY\]' ext_emconf.php && echo "✅ OK"
# Valid category?
grep -oP "'category' => '\K[a-z]+(?=')" ext_emconf.php | grep -qE '^(be|module|fe|plugin|misc|services|templates|example|doc|distribution)$' && echo "✅ OK"
# Valid state?
grep -oP "'state' => '\K[a-z]+(?=')" ext_emconf.php | grep -qE '^(alpha|beta|stable|experimental|test|obsolete|excludeFromUpdates)$' && echo "✅ OK"
```

View File

@@ -0,0 +1,413 @@
# Extension Files Validation Standards (TYPO3 v13)
**Sources:** TYPO3 Core API Reference v13.4
**Purpose:** Validation rules for ext_localconf.php, ext_tables.php, ext_tables.sql, ext_tables_static+adt.sql, ext_conf_template.txt
## ext_localconf.php
### Purpose
Global configuration file loaded during TYPO3 bootstrap in frontend, backend, and CLI contexts.
### Required Structure
```php
<?php
declare(strict_types=1);
defined('TYPO3') or die();
// Configuration code here
```
### What SHOULD Be Included
✅ Registering hooks, XCLASSes, array assignments to `$GLOBALS['TYPO3_CONF_VARS']`
✅ Registering Request Handlers
✅ Adding default TypoScript via ExtensionManagementUtility APIs
✅ Registering Scheduler Tasks
✅ Adding reports to reports module
✅ Registering Services via Service API
### What Should NOT Be Included
❌ Function and class definitions (use services/utility classes)
❌ Class loader or package manager configuration
❌ Cache/config manager settings
❌ Log manager configuration
❌ Time zone, memory limit, locale settings
❌ Icon registration (use `Icons.php` instead)
### TYPO3 v13 Deprecations
**❌ DEPRECATED:** `\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addUserTSConfig()`
- **Removal:** TYPO3 v14.0
- **Alternative:** Use `Configuration/user.tsconfig` file instead
**❌ DEPRECATED (since v12):** Page TSconfig in ext_localconf.php
- **Alternative:** Use `Configuration/page.tsconfig` file instead
### Validation Commands
```bash
# Check required structure
head -5 ext_localconf.php | grep "declare(strict_types=1)" && echo "✅ Has strict_types"
head -5 ext_localconf.php | grep "defined('TYPO3')" && echo "✅ Has TYPO3 guard"
# Check for deprecated addUserTSConfig
grep "addUserTSConfig" ext_localconf.php && echo "⚠️ DEPRECATED: Use Configuration/user.tsconfig"
```
---
## ext_tables.php
### Deprecation Status
**PHASING OUT:** Increasingly replaced by modern configuration approaches.
### What Should NOT Be in ext_tables.php (v13)
**TCA configurations** → Use `Configuration/TCA/tablename.php`
**TCA overrides** → Use `Configuration/TCA/Overrides/somefile.php`
**Insert records** → Move to TCA Overrides files
**Static files** → Move to `Configuration/TCA/Overrides/sys_template.php`
**Backend modules** → Moved to `Configuration/Backend/` in v13.0
### Appropriate Uses (Remaining)
✅ Registering scheduler tasks with localization labels
✅ Registering custom page types
✅ Extending backend user settings
### v13 Migration
**Backend Module Registration:**
```php
OLD (ext_tables.php):
ExtensionUtility::registerModule(...);
NEW (Configuration/Backend/Modules.php):
return [
'web_myext' => [
'parent' => 'web',
'position' => ['after' => 'web_list'],
// ...
],
];
```
### Validation Commands
```bash
# Check for TCA modifications (should be in TCA/Overrides/)
grep -E "addTCAcolumns|addToAllTCAtypes" ext_tables.php && echo "⚠️ WARNING: Move to TCA/Overrides/"
# Check for backend module registration (should be in Configuration/Backend/)
grep "registerModule" ext_tables.php && echo "⚠️ WARNING: Move to Configuration/Backend/Modules.php"
```
---
## ext_tables.sql
### Purpose
Defines database tables and columns for extensions. Parsed when extensions are enabled.
### SQL Syntax Requirements
**Format:** Follow `mysqldump` utility output style
- TYPO3 parses and converts to target DBMS (MySQL, MariaDB, PostgreSQL, SQLite)
- Partial definitions allowed when extending existing tables
### Table Naming Conventions
```sql
-- Extension tables with prefix
CREATE TABLE tx_myextension_domain_model_table (
field_name varchar(255) DEFAULT '' NOT NULL,
);
-- Extending core tables
CREATE TABLE pages (
tx_myextension_field int(11) DEFAULT '0' NOT NULL,
);
```
### Auto-Generated Columns
If TCA exists, TYPO3 automatically creates:
- `uid` with PRIMARY KEY
- `pid` (unsigned) with default index `parent`
- System fields based on TCA `ctrl` properties
### New in v13: Empty Table Definitions
```sql
-- Valid when TCA enriches fields
CREATE TABLE tx_myextension_table (
);
```
### v13.4 CHAR/BINARY Handling
**WARNING:** Fixed-length types now properly flagged
- Use only with ensured fixed-length values (hash identifiers)
- **Avoid with Extbase ORM** (cannot ensure fixed-length in queries)
- Test extensively across database platforms
**Best Practice:**
```sql
VARCHAR(255) -- Variable length (preferred)
CHAR(32) -- Fixed length (use cautiously)
VARBINARY(255) -- Variable binary (preferred)
BINARY(16) -- Fixed binary (use cautiously)
```
### Validation Commands
```bash
# Check table naming
grep "CREATE TABLE" ext_tables.sql | grep -E "tx_[a-z_]+" && echo "✅ Proper naming"
# Check for CHAR usage (potential issue)
grep -E "CHAR\([0-9]+\)" ext_tables.sql && echo "⚠️ WARNING: CHAR type found - verify fixed-length"
# Validate syntax
php -r "file_get_contents('ext_tables.sql');" && echo "✅ File readable"
```
---
## ext_tables_static+adt.sql
### Purpose
Stores static SQL INSERT statements for pre-populated data.
### Critical Restrictions
**❌ ONLY INSERT statements allowed**
- No CREATE TABLE
- No ALTER TABLE
- No UPDATE/DELETE
**⚠️ Warning:** "Static data is not meant to be extended by other extensions. On re-import all extended fields and data is lost."
### When to Use
- Initial data required during installation
- Lookup tables, predefined categories
- Default configuration data
### Re-import Behavior
- Data truncated and reimported when file contents change
- Executed via:
- `bin/typo3 extension:setup`
- Admin Tools > Extensions reload
### Generation Command
```bash
mysqldump --user=[user] --password [database] [tablename] > ./ext_tables_static+adt.sql
```
### Validation Commands
```bash
# Check file exists
[ -f "ext_tables_static+adt.sql" ] && echo "✅ Static data file present"
# Verify only INSERT statements
grep -v "^INSERT" ext_tables_static+adt.sql | grep -E "^(CREATE|ALTER|UPDATE|DELETE)" && echo "❌ CRITICAL: Only INSERT allowed"
# Check corresponding table definition exists
grep "CREATE TABLE" ext_tables.sql && echo "✅ Table definitions present"
```
---
## ext_conf_template.txt
### Purpose
Defines extension configuration options in Admin Tools > Settings module.
### Syntax Format
```
# cat=Category; type=fieldtype; label=LLL:EXT:key/path.xlf:label
optionName = defaultValue
```
### Field Types
| Type | Purpose | Example |
|------|---------|---------
| `boolean` | Checkbox | `type=boolean` |
| `string` | Text field | `type=string` |
| `int` / `integer` | Whole number | `type=int` |
| `int+` | Positive integers | `type=int+` |
| `color` | Color picker | `type=color` |
| `options` | Select dropdown | `type=options[Val1=1,Val2=2]` |
| `user` | Custom function | `type=user[Vendor\Class->method]` |
| `small` | Compact text field | `type=small` |
| `wrap` | Wrapper field | `type=wrap` |
| `offset` | Offset value | `type=offset` |
### Options Syntax
```
# cat=basic; type=options[Option 1=value1,Option 2=value2]; label=Select Option
variable = value1
```
### User Function Syntax
```
# cat=advanced; type=user[Vendor\Extension\Class->methodName]; label=Custom Field
variable = 1
```
### Nested Structure
```
directories {
# cat=paths; type=string; label=Temp directory
tmp = /tmp
# cat=paths; type=string; label=Upload directory
uploads = /uploads
}
```
**Access:** `$config['directories']['tmp']`
### Localization
```
# Use LLL references for multi-language support
# cat=basic; type=string; label=LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:config.title
title = Default Title
```
### Validation Commands
```bash
# Check file exists
[ -f "ext_conf_template.txt" ] && echo "✅ Configuration template present"
# Check syntax format
grep -E "^#.*cat=.*type=.*label=" ext_conf_template.txt && echo "✅ Valid syntax found"
# Check for localization
grep "LLL:EXT:" ext_conf_template.txt && echo "✅ Uses localized labels"
# Validate field types
grep -E "type=(boolean|string|int|int\+|color|options|user|small|wrap|offset)" ext_conf_template.txt && echo "✅ Valid field types"
```
### Accessing Configuration in Code
```php
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
public function __construct(
private readonly ExtensionConfiguration $extensionConfiguration
) {}
// Get all configuration
$config = $this->extensionConfiguration->get('extension_key');
// Get specific value
$value = $this->extensionConfiguration->get('extension_key', 'optionName');
```
---
## Validation Checklist
### ext_localconf.php
- [ ] Has `declare(strict_types=1)` at top
- [ ] Has `defined('TYPO3') or die();` guard
- [ ] No function/class definitions
- [ ] **NOT** using deprecated `addUserTSConfig()`
- [ ] **NOT** adding page TSconfig (use Configuration/page.tsconfig)
### ext_tables.php
- [ ] No TCA definitions (use Configuration/TCA/)
- [ ] No TCA overrides (use Configuration/TCA/Overrides/)
- [ ] No backend module registration (use Configuration/Backend/)
- [ ] Only contains appropriate v13 use cases
### ext_tables.sql
- [ ] Follows mysqldump syntax
- [ ] Tables prefixed with `tx_<extensionkey>_`
- [ ] Uses VARCHAR/VARBINARY (not CHAR/BINARY unless necessary)
- [ ] Empty table definitions if TCA provides fields
### ext_tables_static+adt.sql (if present)
- [ ] **ONLY** INSERT statements (no CREATE/ALTER)
- [ ] Corresponding table structure in ext_tables.sql
- [ ] Static data is truly static (not extended by other extensions)
### ext_conf_template.txt (if present)
- [ ] Syntax: `# cat=; type=; label=`
- [ ] Valid field types used
- [ ] Localized labels with LLL: references
- [ ] Proper categorization
- [ ] Sensible default values
---
## Common Violations and Fixes
### ext_localconf.php: Using Deprecated addUserTSConfig
❌ Before:
```php
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addUserTSConfig('
options.pageTree.showPageIdWithTitle = 1
');
```
✅ After:
```
// Create Configuration/user.tsconfig
options.pageTree.showPageIdWithTitle = 1
```
### ext_tables.php: Backend Module in ext_tables.php
❌ Before (ext_tables.php):
```php
ExtensionUtility::registerModule('MyExt', 'web', 'mymodule', ...);
```
✅ After (Configuration/Backend/Modules.php):
```php
return [
'web_myext_mymodule' => [
'parent' => 'web',
'position' => ['after' => 'web_list'],
'access' => 'user',
'path' => '/module/web/myext',
'labels' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang_mod.xlf',
'extensionName' => 'MyExt',
'controllerActions' => [
\Vendor\MyExt\Controller\ModuleController::class => ['list', 'detail'],
],
],
];
```
### ext_tables.sql: Using CHAR Inappropriately
❌ Before:
```sql
CREATE TABLE tx_myext_table (
name CHAR(255) DEFAULT '' NOT NULL, -- Variable content!
);
```
✅ After:
```sql
CREATE TABLE tx_myext_table (
name VARCHAR(255) DEFAULT '' NOT NULL, -- Use VARCHAR
);
```
### ext_tables_static+adt.sql: Including CREATE Statements
❌ Before:
```sql
CREATE TABLE tx_myext_categories (
uid int(11) NOT NULL auto_increment,
title varchar(255) DEFAULT '' NOT NULL,
PRIMARY KEY (uid)
);
INSERT INTO tx_myext_categories VALUES (1, 'Category 1');
```
✅ After:
```sql
-- Move CREATE to ext_tables.sql
-- Only INSERT in ext_tables_static+adt.sql
INSERT INTO tx_myext_categories (uid, title) VALUES (1, 'Category 1');
INSERT INTO tx_myext_categories (uid, title) VALUES (2, 'Category 2');
```

View File

@@ -0,0 +1,273 @@
# TYPO3 Extension Architecture Standards
**Source:** TYPO3 Core API Reference - Extension Architecture
**Purpose:** File structure, directory hierarchy, and required files for TYPO3 extensions
## Required Files
### Essential Files
**composer.json**
- REQUIRED for all extensions
- Defines package metadata, dependencies, PSR-4 autoloading
- Example:
```json
{
"name": "vendor/extension-key",
"type": "typo3-cms-extension",
"require": {
"typo3/cms-core": "^12.4 || ^13.0"
},
"autoload": {
"psr-4": {
"Vendor\\ExtensionKey\\": "Classes/"
}
}
}
```
**ext_emconf.php**
- REQUIRED for TER (TYPO3 Extension Repository) publication
- Contains extension metadata
- Example:
```php
<?php
$EM_CONF[$_EXTKEY] = [
'title' => 'Extension Title',
'description' => 'Extension description',
'category' => 'fe',
'author' => 'Author Name',
'author_email' => 'author@example.com',
'state' => 'stable',
'version' => '1.0.0',
'constraints' => [
'depends' => [
'typo3' => '12.4.0-13.9.99',
],
],
];
```
**Documentation/Index.rst**
- REQUIRED for docs.typo3.org publication
- Main documentation entry point
- Must follow reStructuredText format
**Documentation/Settings.cfg**
- REQUIRED for docs.typo3.org publication
- Contains documentation project settings
- Example:
```ini
[general]
project = Extension Name
release = 1.0.0
copyright = 2024
[html_theme_options]
project_home = https://github.com/vendor/extension
```
## Directory Structure
### Core Directories
**Classes/**
- Contains all PHP classes
- MUST follow PSR-4 autoloading structure
- Namespace: `\VendorName\ExtensionKey\`
- Common subdirectories:
- `Classes/Controller/` - Extbase/backend controllers
- `Classes/Domain/Model/` - Domain models
- `Classes/Domain/Repository/` - Repositories
- `Classes/Service/` - Service classes
- `Classes/Utility/` - Utility classes
- `Classes/ViewHelper/` - Fluid ViewHelpers
- `Classes/EventListener/` - PSR-14 event listeners
**Configuration/**
- Contains all configuration files
- Required subdirectories:
- `Configuration/TCA/` - Table Configuration Array definitions
- `Configuration/Backend/` - Backend module configuration
- `Configuration/TypoScript/` - TypoScript configuration
- `Configuration/Sets/` - Configuration sets (TYPO3 v13+)
- Optional files:
- `Configuration/Services.yaml` - Dependency injection configuration
- `Configuration/TsConfig/` - Page/User TSconfig
- `Configuration/RequestMiddlewares.php` - PSR-15 middlewares
**Resources/**
- Contains all frontend/backend resources
- Structure:
- `Resources/Private/` - Non-public files
- `Resources/Private/Templates/` - Fluid templates
- `Resources/Private/Partials/` - Fluid partials
- `Resources/Private/Layouts/` - Fluid layouts
- `Resources/Private/Language/` - Translation files (XLIFF)
- `Resources/Public/` - Publicly accessible files
- `Resources/Public/Css/` - Stylesheets
- `Resources/Public/JavaScript/` - JavaScript files
- `Resources/Public/Icons/` - Extension icons
- `Resources/Public/Images/` - Images
**Tests/**
- Contains all test files
- Structure:
- `Tests/Unit/` - PHPUnit unit tests
- `Tests/Functional/` - PHPUnit functional tests
- `Tests/Acceptance/` - Codeception acceptance tests
- MUST mirror `Classes/` structure
**Documentation/**
- Contains RST documentation
- MUST include `Index.rst` and `Settings.cfg`
- Common structure:
- `Documentation/Introduction/`
- `Documentation/Installation/`
- `Documentation/Configuration/`
- `Documentation/Developer/`
- `Documentation/Editor/`
## Reserved File Prefixes
Files with the `ext_*` prefix are reserved for special purposes:
**ext_emconf.php**
- Extension metadata (REQUIRED for TER)
**ext_localconf.php**
- Global configuration executed in both frontend and backend
- Register hooks, event listeners, XCLASSes
- Add plugins, content elements
- Register services
**ext_tables.php**
- Backend-specific configuration
- Register backend modules
- Add TCA modifications
- DEPRECATED in favor of dedicated configuration files
**ext_tables.sql**
- Database table definitions
- Executed during extension installation
- Contains CREATE TABLE and ALTER TABLE statements
**ext_conf_template.txt**
- Extension configuration template
- Defines settings available in Extension Configuration
- TypoScript-like syntax
## File Naming Conventions
### PHP Classes
- File name MUST match class name exactly
- PSR-4 compliant
- Example: `Classes/Controller/MyController.php``class MyController`
### Database Tables
- Pattern: `tx_<extensionkeyprefix>_<tablename>`
- Example: `tx_myext_domain_model_product`
- Extension key must be converted to lowercase, underscores allowed
### TCA Files
- Pattern: `Configuration/TCA/<tablename>.php`
- Returns TCA array
- Example: `Configuration/TCA/tx_myext_domain_model_product.php`
### Language Files
- Pattern: `Resources/Private/Language/<context>.xlf`
- XLIFF 1.2 format
- Example: `Resources/Private/Language/locallang.xlf`
## Architecture Best Practices
### PSR-4 Autoloading
- All classes in `Classes/` directory
- Namespace structure MUST match directory structure
- Example:
- Class: `Vendor\ExtensionKey\Domain\Model\Product`
- File: `Classes/Domain/Model/Product.php`
### Dependency Injection
- Use constructor injection for dependencies
- Register services in `Configuration/Services.yaml`
- Example:
```yaml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
Vendor\ExtensionKey\:
resource: '../Classes/*'
```
### Configuration Files
- Separate concerns into dedicated configuration files
- Use `Configuration/Backend/` for backend modules (not ext_tables.php)
- Use `Configuration/TCA/` for table definitions
- Use `Configuration/TypoScript/` for TypoScript
### Testing Structure
- Mirror `Classes/` structure in `Tests/Unit/` and `Tests/Functional/`
- Example:
- Class: `Classes/Service/CalculationService.php`
- Unit Test: `Tests/Unit/Service/CalculationServiceTest.php`
- Functional Test: `Tests/Functional/Service/CalculationServiceTest.php`
## Common Issues
### ❌ Wrong: Mixed file types in root
```
my_extension/
├── MyController.php # WRONG: PHP in root
├── config.yaml # WRONG: Config in root
└── styles.css # WRONG: CSS in root
```
### ✅ Right: Proper directory structure
```
my_extension/
├── Classes/Controller/MyController.php
├── Configuration/Services.yaml
└── Resources/Public/Css/styles.css
```
### ❌ Wrong: Non-standard directory names
```
Classes/
├── Controllers/ # WRONG: Plural
├── Services/ # WRONG: Should be Service
└── Helpers/ # WRONG: Use Utility
```
### ✅ Right: Standard TYPO3 directory names
```
Classes/
├── Controller/ # Singular
├── Service/ # Singular
└── Utility/ # Standard naming
```
## Extension Key Naming
- Lowercase letters and underscores only
- Must start with a letter
- 3-30 characters
- Cannot start with `tx_`, `user_`, `pages`, `tt_`, `sys_`
- Example: `my_extension`, `blog_example`, `news`
## Conformance Checklist
- [ ] composer.json present with correct structure
- [ ] ext_emconf.php present with complete metadata
- [ ] Documentation/Index.rst and Documentation/Settings.cfg present
- [ ] Classes/ directory follows PSR-4 structure
- [ ] Configuration/ subdirectories properly organized
- [ ] Resources/ separated into Private/ and Public/
- [ ] Tests/ mirror Classes/ structure
- [ ] No PHP files in extension root (except ext_* files)
- [ ] File naming follows conventions
- [ ] Database table names use tx_<extensionkey>_ prefix
- [ ] Extension key follows naming rules

View File

@@ -0,0 +1,297 @@
# TYPO3 Hooks and PSR-14 Events
**Source:** TYPO3 Core API Reference - Hooks, Events, and Signals
**Purpose:** Understanding TYPO3 hook system, PSR-14 events, and migration strategies
## SC_OPTIONS Hooks Status in TYPO3 13
### ⚠️ Common Misconception
**INCORRECT:** "SC_OPTIONS hooks are deprecated in TYPO3 13"
**CORRECT:** SC_OPTIONS hooks are **NOT deprecated** in TYPO3 13. They remain the **official pattern** for specific use cases.
### SC_OPTIONS Hooks That Are Still Official
The following SC_OPTIONS hooks remain the official TYPO3 13 pattern:
#### DataHandler Hooks (Still Official)
```php
// Configuration/Services.yaml
Vendor\Extension\Database\MyDataHandlerHook:
public: true
tags:
- name: event.listener
identifier: 'my-extension/datahandler-hook'
method: 'processDatamap_postProcessFieldArray'
```
**Still Official in ext_localconf.php:**
```php
<?php
// TYPO3 13+ DataHandler hooks remain official pattern
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] =
\Vendor\Extension\Database\MyDataHandlerHook::class;
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'][] =
\Vendor\Extension\Database\MyDataHandlerHook::class;
```
**Key DataHandler Hook Methods (TYPO3 13+):**
- `processDatamap_preProcessFieldArray()` - Before field processing
- `processDatamap_postProcessFieldArray()` - After field processing
- `processDatamap_afterDatabaseOperations()` - After DB operations
- `processCmdmap_preProcess()` - Before command processing
- `processCmdmap_postProcess()` - After command processing
- `processCmdmap_afterFinish()` - After all commands finished
#### RTE Transformation Hooks (Still Official)
```php
<?php
// TYPO3 13+ RTE transformation hooks remain official pattern
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_parsehtml_proc.php']['transformation'][] =
\Vendor\Extension\RteTransformation\MyRteTransformationHook::class;
```
**Required Methods:**
- `transform_rte()` - Transform content from database to RTE
- `transform_db()` - Transform content from RTE to database
### When to Use SC_OPTIONS vs PSR-14 Events
| Scenario | Use SC_OPTIONS Hook | Use PSR-14 Event |
|----------|-------------------|------------------|
| DataHandler field processing | ✅ Yes (official) | ❌ No event available |
| RTE content transformation | ✅ Yes (official) | ❌ No event available |
| Backend user authentication | ❌ Migrated | ✅ Use PSR-14 events |
| Frontend rendering | ❌ Migrated | ✅ Use PSR-14 events |
| Page generation | ❌ Migrated | ✅ Use PSR-14 events |
| Cache clearing | ❌ Migrated | ✅ Use PSR-14 events |
## PSR-14 Event Listeners (Preferred)
For most scenarios, PSR-14 events are the modern TYPO3 13+ approach.
### Event Listener Configuration
```yaml
# Configuration/Services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
Vendor\Extension\:
resource: '../Classes/*'
# PSR-14 Event Listener
Vendor\Extension\EventListener\MyEventListener:
tags:
- name: event.listener
identifier: 'my-extension/my-event-listener'
event: TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedInEvent
```
### Event Listener Implementation
```php
<?php
declare(strict_types=1);
namespace Vendor\Extension\EventListener;
use TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedInEvent;
/**
* PSR-14 Event Listener for user login.
*/
final class MyEventListener
{
public function __invoke(AfterUserLoggedInEvent $event): void
{
$user = $event->getUser();
// Your logic here
}
}
```
### Common TYPO3 13 Events
**Authentication Events:**
- `AfterUserLoggedInEvent`
- `BeforeUserLogoutEvent`
- `AfterUserLoggedOutEvent`
**Backend Events:**
- `ModifyButtonBarEvent`
- `ModifyDatabaseQueryForContentEvent`
- `BeforePagePreviewUriGeneratedEvent`
**DataHandler Events:**
- `AfterDataInsertedEvent`
- `AfterDataUpdatedEvent`
- `AfterRecordDeletedEvent`
**Page Events:**
- `AfterPageTreeItemsPreparedEvent`
- `ModifyPageLayoutContentEvent`
**Complete Reference:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/EventDispatcher/Index.html
## Migration Strategy
### Step 1: Identify Hook Type
```php
// Check if hook is in ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['...']['...'][]
```
### Step 2: Check Official Documentation
- **DataHandler hooks:** Still official, keep using SC_OPTIONS
- **RTE transformation:** Still official, keep using SC_OPTIONS
- **Other hooks:** Check if PSR-14 event exists
### Step 3: Migrate or Modernize
**If PSR-14 event exists:**
```php
// OLD: ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'][]
= \Vendor\Extension\Hook\MyHook::class;
// NEW: Configuration/Services.yaml + EventListener class
Vendor\Extension\EventListener\MyEventListener:
tags:
- name: event.listener
identifier: 'my-extension/after-login'
event: TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedInEvent
```
**If no PSR-14 event exists (DataHandler, RTE):**
```php
// KEEP: Still official in TYPO3 13+
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][]
= \Vendor\Extension\Database\MyDataHandlerHook::class;
// MODERNIZE: Add dependency injection
// Configuration/Services.yaml
Vendor\Extension\Database\MyDataHandlerHook:
public: true
arguments:
$resourceFactory: '@TYPO3\CMS\Core\Resource\ResourceFactory'
$context: '@TYPO3\CMS\Core\Context\Context'
$logManager: '@TYPO3\CMS\Core\Log\LogManager'
```
## Best Practices
### 1. Constructor Dependency Injection
Even for SC_OPTIONS hooks, use constructor injection (TYPO3 13+):
```php
<?php
declare(strict_types=1);
namespace Vendor\Extension\Database;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Resource\ResourceFactory;
/**
* DataHandler hook with dependency injection.
*/
final class MyDataHandlerHook
{
public function __construct(
private readonly ResourceFactory $resourceFactory,
private readonly Context $context,
private readonly LogManager $logManager,
) {}
public function processDatamap_postProcessFieldArray(
string $status,
string $table,
string $id,
array &$fieldArray,
\TYPO3\CMS\Core\DataHandling\DataHandler &$dataHandler,
): void {
// Use injected dependencies
$file = $this->resourceFactory->getFileObject($fileId);
}
}
```
### 2. Avoid GeneralUtility::makeInstance()
```php
// ❌ BAD: Using makeInstance (legacy pattern)
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
// ✅ GOOD: Constructor injection (TYPO3 13+ pattern)
public function __construct(
private readonly ResourceFactory $resourceFactory,
) {}
```
### 3. Configure Services Explicitly
```yaml
# Configuration/Services.yaml
services:
Vendor\Extension\Database\MyDataHandlerHook:
public: true # Required for SC_OPTIONS hooks
arguments:
$resourceFactory: '@TYPO3\CMS\Core\Resource\ResourceFactory'
$context: '@TYPO3\CMS\Core\Context\Context'
$logManager: '@TYPO3\CMS\Core\Log\LogManager'
```
## Acceptable $GLOBALS Usage
Even in TYPO3 13+, certain `$GLOBALS` usage is acceptable:
### ✅ Acceptable $GLOBALS
```php
// TCA access (no alternative available)
$GLOBALS['TCA']['tt_content']['columns']['bodytext']
// Current request (framework-provided)
$GLOBALS['TYPO3_REQUEST']
// Backend user context (framework-provided)
$GLOBALS['BE_USER']
// Frontend user context (framework-provided)
$GLOBALS['TSFE']
```
### ❌ Avoid $GLOBALS
```php
// Database connection (use ConnectionPool)
$GLOBALS['TYPO3_DB']
// Extension configuration (use ExtensionConfiguration)
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['my_ext']
// Object instantiation (use dependency injection)
GeneralUtility::makeInstance(SomeClass::class)
```
## Resources
- [TYPO3 Hooks Documentation](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Hooks/Index.html)
- [PSR-14 Events](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/EventDispatcher/Index.html)
- [DataHandler Hooks](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Hooks/DataHandler/Index.html)
- [Dependency Injection](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/DependencyInjection/Index.html)

View File

@@ -0,0 +1,918 @@
# TYPO3 PHP Architecture Standards
**Source:** TYPO3 Core API Reference - PHP Architecture
**Purpose:** Dependency injection, services, events, Extbase, middleware patterns
## Dependency Injection
TYPO3 uses **Symfony's Dependency Injection Container** for service management.
### Constructor Injection (Preferred)
```php
// ✅ Right: Constructor injection with readonly properties
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use Vendor\ExtensionKey\Domain\Repository\UserRepository;
final class UserController extends ActionController
{
public function __construct(
private readonly UserRepository $userRepository
) {}
public function listAction(): ResponseInterface
{
$users = $this->userRepository->findAll();
$this->view->assign('users', $users);
return $this->htmlResponse();
}
}
```
### Method Injection (inject* Methods)
```php
// ✅ Right: Method injection for abstract classes
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Controller;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use Vendor\ExtensionKey\Domain\Repository\UserRepository;
class UserController extends ActionController
{
protected ?UserRepository $userRepository = null;
public function injectUserRepository(UserRepository $userRepository): void
{
$this->userRepository = $userRepository;
}
}
```
**When to Use Method Injection:**
- Extending abstract core classes (ActionController, AbstractValidator)
- Avoiding breaking changes when base class constructor changes
- Optional dependencies
**When to Use Constructor Injection:**
- All new code (preferred)
- Required dependencies
- Better testability
### Interface Injection
```php
// ✅ Right: Depend on interfaces, not implementations
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use Vendor\ExtensionKey\Domain\Repository\UserRepositoryInterface;
final class UserController extends ActionController
{
public function __construct(
private readonly UserRepositoryInterface $userRepository
) {}
}
```
## Service Configuration
### Configuration/Services.yaml
```yaml
# ✅ Right: Proper service configuration
services:
_defaults:
autowire: true
autoconfigure: true
public: false
# Auto-register all classes
Vendor\ExtensionKey\:
resource: '../Classes/*'
# Explicit service configuration
Vendor\ExtensionKey\Service\MyService:
arguments:
$configValue: '%env(MY_CONFIG_VALUE)%'
# Factory pattern for Connection
Vendor\ExtensionKey\Domain\Repository\MyTableRepository:
factory: ['@TYPO3\CMS\Core\Database\ConnectionPool', 'getConnectionForTable']
arguments:
- 'my_table'
# Interface binding
Vendor\ExtensionKey\Domain\Repository\UserRepositoryInterface:
class: Vendor\ExtensionKey\Domain\Repository\UserRepository
```
### Autowire Attribute (TYPO3 v12+)
```php
// ✅ Right: Inject configuration using Autowire attribute
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Service;
use TYPO3\CMS\Core\DependencyInjection\Attribute\Autowire;
final class MyService
{
public function __construct(
#[Autowire(expression: 'service("configuration.extension").get("my_extension", "mySetting")')]
private readonly string $myExtensionSetting
) {}
}
```
## PSR-14 Event Dispatcher
### Defining Custom Events
```php
// ✅ Right: Immutable event class with getters/setters
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Event;
final class BeforeUserCreatedEvent
{
public function __construct(
private string $username,
private string $email,
private array $additionalData = []
) {}
public function getUsername(): string
{
return $this->username;
}
public function getEmail(): string
{
return $this->email;
}
public function getAdditionalData(): array
{
return $this->additionalData;
}
public function setAdditionalData(array $additionalData): void
{
$this->additionalData = $additionalData;
}
}
```
### Dispatching Events
```php
// ✅ Right: Inject and dispatch events
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Service;
use Psr\EventDispatcher\EventDispatcherInterface;
use Vendor\ExtensionKey\Event\BeforeUserCreatedEvent;
final class UserService
{
public function __construct(
private readonly EventDispatcherInterface $eventDispatcher
) {}
public function createUser(string $username, string $email): void
{
$event = new BeforeUserCreatedEvent($username, $email);
$event = $this->eventDispatcher->dispatch($event);
// Use potentially modified data from event
$finalUsername = $event->getUsername();
$finalEmail = $event->getEmail();
// Create user with final data
}
}
```
### Event Listeners
```php
// ✅ Right: Event listener with AsEventListener attribute
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\EventListener;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use Vendor\ExtensionKey\Event\BeforeUserCreatedEvent;
#[AsEventListener(
identifier: 'vendor/extension-key/validate-user-creation',
event: BeforeUserCreatedEvent::class
)]
final class ValidateUserCreationListener
{
public function __invoke(BeforeUserCreatedEvent $event): void
{
// Validate email format
if (!filter_var($event->getEmail(), FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException('Invalid email format');
}
// Add custom data
$event->setAdditionalData([
'validated_at' => time(),
'validator' => 'ValidateUserCreationListener',
]);
}
}
```
### Event Listener Registration (Services.yaml)
```yaml
# Alternative: Register event listeners in Services.yaml
services:
Vendor\ExtensionKey\EventListener\ValidateUserCreationListener:
tags:
- name: event.listener
identifier: 'vendor/extension-key/validate-user-creation'
event: Vendor\ExtensionKey\Event\BeforeUserCreatedEvent
method: '__invoke'
```
### PSR-14 Event Class Standards (TYPO3 13+)
Modern event classes should follow these quality standards:
```php
// ✅ Right: Modern event class with final keyword and readonly properties
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Event;
use Psr\Http\Message\ServerRequestInterface;
final class NewsListActionEvent // ✅ Use 'final' keyword
{
public function __construct(
private NewsController $newsController,
private array $assignedValues,
private readonly ServerRequestInterface $request // ✅ Use 'readonly' for immutable properties
) {}
public function getNewsController(): NewsController
{
return $this->newsController;
}
public function getAssignedValues(): array
{
return $this->assignedValues;
}
public function setAssignedValues(array $assignedValues): void
{
$this->assignedValues = $assignedValues;
}
public function getRequest(): ServerRequestInterface
{
return $this->request; // Read-only, no setter
}
}
```
**Event Class Quality Checklist:**
- [ ] Use `final` keyword (prevents inheritance, ensures immutability)
- [ ] Use `readonly` for properties that should never change after construction
- [ ] Provide getters for all properties
- [ ] Provide setters ONLY for properties that should be modifiable
- [ ] Type hint all properties and methods
- [ ] Document the purpose and usage of the event
**Why `final` for Events?**
- Events are data carriers, not meant to be extended
- Prevents unexpected behavior from inheritance
- Makes event behavior predictable and testable
- Follows modern PHP best practices
**Why `readonly` for Properties?**
- Some event data should never change (e.g., original request, user context)
- Explicit immutability prevents accidental modifications
- Clearly communicates intent to event listeners
- Available in PHP 8.1+ (TYPO3 13 minimum is PHP 8.1)
## TYPO3 13 Site Sets
**Purpose:** Modern configuration distribution system replacing static TypoScript includes
### Site Sets Structure
```
Configuration/Sets/
├── MyExtension/ # Base configuration set
│ ├── config.yaml # Set metadata and dependencies
│ ├── setup.typoscript # Frontend TypoScript
│ ├── constants.typoscript
│ └── settings.definitions.yaml # Setting definitions for extension configuration
├── RecordLinks/ # Optional feature set
│ ├── config.yaml
│ └── setup.typoscript
└── Bootstrap5/ # Frontend framework preset
├── config.yaml
├── setup.typoscript
└── settings.yaml
```
### config.yaml Structure
```yaml
# ✅ Right: Proper Site Set configuration
name: vendor/extension-key
label: Extension Name Base Configuration
# Dependencies on other sets
dependencies:
- typo3/fluid-styled-content
- vendor/extension-key-styles
# Load order priority (optional)
priority: 50
# Settings that can be overridden
settings:
mySetting:
value: 'default value'
type: string
label: 'My Setting Label'
description: 'Description of what this setting does'
```
### settings.definitions.yaml
```yaml
# ✅ Right: Define extension settings with validation
settings:
# Text input
mySetting:
type: string
default: 'default value'
label: 'LLL:EXT:extension_key/Resources/Private/Language/locallang.xlf:settings.mySetting'
description: 'LLL:EXT:extension_key/Resources/Private/Language/locallang.xlf:settings.mySetting.description'
# Boolean checkbox
enableFeature:
type: bool
default: false
label: 'Enable Feature'
# Integer input
itemsPerPage:
type: int
default: 10
label: 'Items per page'
validators:
- name: NumberRange
options:
minimum: 1
maximum: 100
# Select dropdown
layout:
type: string
default: 'default'
label: 'Layout'
enum:
default: 'Default'
compact: 'Compact'
detailed: 'Detailed'
```
### Benefits of Site Sets
1. **Modular Configuration**: Split configuration into focused, reusable sets
2. **Dependency Management**: Declare dependencies on other sets
3. **Override Capability**: Sites can override set settings without editing files
4. **Type Safety**: Settings are validated with defined types
5. **Better UX**: Settings UI auto-generated from definitions
6. **Version Control**: Configuration changes tracked properly
### Migration from Static TypoScript
```php
// ❌ Old: Static TypoScript includes (TYPO3 12 and earlier)
Configuration/TCA/Overrides/sys_template.php:
<?php
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addStaticFile(
'extension_key',
'Configuration/TypoScript',
'Extension Name'
);
```
```yaml
# ✅ New: Site Sets (TYPO3 13+)
Configuration/Sets/ExtensionKey/config.yaml:
name: vendor/extension-key
label: Extension Name
```
**Site Sets Conformance Checklist:**
- [ ] Configuration/Sets/ directory exists
- [ ] At least one base set with config.yaml
- [ ] settings.definitions.yaml defines all extension settings
- [ ] Set names follow vendor/package naming convention
- [ ] Dependencies declared in config.yaml
- [ ] Labels use LLL: references for translations
- [ ] Settings have appropriate type validation
## Advanced Services.yaml Patterns
Beyond basic service registration, modern TYPO3 extensions use advanced Services.yaml patterns.
### Event Listeners
```yaml
# ✅ Right: Event listener registration
services:
Vendor\ExtensionKey\EventListener\HrefLangEventListener:
tags:
- name: event.listener
identifier: 'ext-extension-key/modify-hreflang'
event: TYPO3\CMS\Frontend\Event\ModifyHrefLangTagsEvent
method: '__invoke'
# Multiple listeners for same event
Vendor\ExtensionKey\EventListener\PageCacheListener:
tags:
- name: event.listener
identifier: 'ext-extension-key/cache-before'
event: TYPO3\CMS\Core\Cache\Event\BeforePageCacheIdentifierIsHashedEvent
- name: event.listener
identifier: 'ext-extension-key/cache-after'
event: TYPO3\CMS\Core\Cache\Event\AfterPageCacheIdentifierIsHashedEvent
```
### Console Commands
```yaml
# ✅ Right: Console command registration
services:
Vendor\ExtensionKey\Command\ProxyClassRebuildCommand:
tags:
- name: 'console.command'
command: 'extension:rebuildProxyClasses'
description: 'Rebuild Extbase proxy classes'
schedulable: false # Cannot be run via scheduler
Vendor\ExtensionKey\Command\CleanupCommand:
tags:
- name: 'console.command'
command: 'extension:cleanup'
description: 'Clean up old records'
schedulable: true # Can be run via scheduler
hidden: false # Visible in command list
```
### Data Processors
```yaml
# ✅ Right: Data processor registration for Fluid templates
services:
Vendor\ExtensionKey\DataProcessing\AddNewsToMenuProcessor:
tags:
- name: 'data.processor'
identifier: 'add-news-to-menu'
Vendor\ExtensionKey\DataProcessing\CategoryProcessor:
tags:
- name: 'data.processor'
identifier: 'category-processor'
```
### Cache Services
```yaml
# ✅ Right: Cache service configuration
services:
cache.extension_custom:
class: TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
factory:
- '@TYPO3\CMS\Core\Cache\CacheManager'
- 'getCache'
arguments:
- 'extension_custom'
```
### Advanced Service Patterns
```yaml
# ✅ Right: Comprehensive Services.yaml with advanced patterns
services:
_defaults:
autowire: true
autoconfigure: true
public: false
# Auto-register all classes
Vendor\ExtensionKey\:
resource: '../Classes/*'
exclude:
- '../Classes/Domain/Model/*' # Exclude Extbase models
# Event Listeners
Vendor\ExtensionKey\EventListener\NewsListActionListener:
tags:
- name: event.listener
identifier: 'ext-extension-key/news-list'
event: Vendor\ExtensionKey\Event\NewsListActionEvent
# Console Commands
Vendor\ExtensionKey\Command\ImportCommand:
tags:
- name: 'console.command'
command: 'news:import'
description: 'Import news from external source'
schedulable: true
# Data Processors
Vendor\ExtensionKey\DataProcessing\MenuProcessor:
tags:
- name: 'data.processor'
identifier: 'news-menu-processor'
# Cache Factory
cache.news_category:
class: TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
factory: ['@TYPO3\CMS\Core\Cache\CacheManager', 'getCache']
arguments: ['news_category']
# ViewHelper registration (if needed for testing)
Vendor\ExtensionKey\ViewHelpers\FormatViewHelper:
public: true
```
**Advanced Services.yaml Conformance Checklist:**
- [ ] Event listeners registered with proper tags
- [ ] Console commands tagged with schedulable flag
- [ ] Data processors registered with unique identifiers
- [ ] Cache services use factory pattern
- [ ] ViewHelpers marked public if needed externally
- [ ] Service tags include all required attributes (identifier, event, method)
- [ ] Commands have meaningful names and descriptions
## PSR-15 Middleware
### Middleware Structure
```php
// ✅ Right: PSR-15 middleware implementation
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Middleware;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
final class StatusCheckMiddleware implements MiddlewareInterface
{
public function __construct(
private readonly ResponseFactoryInterface $responseFactory
) {}
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
// Check for specific condition
if (($request->getQueryParams()['status'] ?? null) === 'check') {
$response = $this->responseFactory->createResponse(200, 'OK');
$response->getBody()->write(json_encode([
'status' => 'ok',
'message' => 'System is healthy'
]));
return $response->withHeader('Content-Type', 'application/json');
}
// Pass to next middleware
return $handler->handle($request);
}
}
```
### Middleware Registration
```php
// Configuration/RequestMiddlewares.php
<?php
return [
'frontend' => [
'vendor/extension-key/status-check' => [
'target' => \Vendor\ExtensionKey\Middleware\StatusCheckMiddleware::class,
'before' => [
'typo3/cms-frontend/page-resolver',
],
'after' => [
'typo3/cms-core/normalized-params-attribute',
],
],
],
];
```
## Extbase Architecture
### Domain Models
```php
// ✅ Right: Extbase domain model
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
class Product extends AbstractEntity
{
protected string $title = '';
protected float $price = 0.0;
protected bool $available = true;
public function getTitle(): string
{
return $this->title;
}
public function setTitle(string $title): void
{
$this->title = $title;
}
public function getPrice(): float
{
return $this->price;
}
public function setPrice(float $price): void
{
$this->price = $price;
}
public function isAvailable(): bool
{
return $this->available;
}
public function setAvailable(bool $available): void
{
$this->available = $available;
}
}
```
### Repositories
```php
// ✅ Right: Extbase repository with dependency injection
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Domain\Repository;
use TYPO3\CMS\Extbase\Persistence\Repository;
use Vendor\ExtensionKey\Domain\Model\Product;
class ProductRepository extends Repository
{
/**
* Find products by price range
*
* @param float $minPrice
* @param float $maxPrice
* @return array<Product>
*/
public function findByPriceRange(float $minPrice, float $maxPrice): array
{
$query = $this->createQuery();
$query->matching(
$query->logicalAnd(
$query->greaterThanOrEqual('price', $minPrice),
$query->lessThanOrEqual('price', $maxPrice)
)
);
return $query->execute()->toArray();
}
}
```
### Controllers
```php
// ✅ Right: Extbase controller with dependency injection
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use Vendor\ExtensionKey\Domain\Repository\ProductRepository;
final class ProductController extends ActionController
{
public function __construct(
private readonly ProductRepository $productRepository
) {}
public function listAction(): ResponseInterface
{
$products = $this->productRepository->findAll();
$this->view->assign('products', $products);
return $this->htmlResponse();
}
public function showAction(int $productId): ResponseInterface
{
$product = $this->productRepository->findByUid($productId);
$this->view->assign('product', $product);
return $this->htmlResponse();
}
}
```
### Validators
```php
// ✅ Right: Extbase validator with dependency injection
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Domain\Validator;
use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator;
use Vendor\ExtensionKey\Domain\Repository\ProductRepository;
class UniqueProductTitleValidator extends AbstractValidator
{
public function __construct(
private readonly ProductRepository $productRepository
) {}
protected function isValid(mixed $value): void
{
if (!is_string($value)) {
$this->addError('Value must be a string', 1234567890);
return;
}
$existingProduct = $this->productRepository->findOneByTitle($value);
if ($existingProduct !== null) {
$this->addError(
'Product with title "%s" already exists',
1234567891,
[$value]
);
}
}
}
```
## Common Patterns
### Factory Pattern
```php
// ✅ Right: Factory for Connection objects
services:
Vendor\ExtensionKey\Domain\Repository\MyRepository:
factory: ['@TYPO3\CMS\Core\Database\ConnectionPool', 'getConnectionForTable']
arguments:
- 'my_table'
```
### Singleton Services
```php
// ✅ Right: Use DI container, not Singleton pattern
// Services are automatically singleton by default
// ❌ Wrong: Don't use GeneralUtility::makeInstance() for new code
use TYPO3\CMS\Core\Utility\GeneralUtility;
$service = GeneralUtility::makeInstance(MyService::class); // Deprecated
```
### PSR Interfaces
```php
// ✅ Right: Use PSR interfaces
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Client\ClientInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\LoggerInterface;
use Psr\Clock\ClockInterface;
// Inject PSR-compliant services
public function __construct(
private readonly LoggerInterface $logger,
private readonly ClockInterface $clock
) {}
```
## Anti-Patterns to Avoid
### ❌ Wrong: Direct instantiation
```php
$repository = new ProductRepository(); // Missing dependencies
```
### ❌ Wrong: Using GeneralUtility::makeInstance()
```php
use TYPO3\CMS\Core\Utility\GeneralUtility;
$repository = GeneralUtility::makeInstance(ProductRepository::class);
```
### ❌ Wrong: Global state access
```php
$user = $GLOBALS['BE_USER']; // Avoid global state
$typoScript = $GLOBALS['TSFE']->tmpl->setup;
```
### ✅ Right: Dependency injection
```php
public function __construct(
private readonly ProductRepository $repository,
private readonly Context $context
) {}
```
## Conformance Checklist
### Basic Dependency Injection
- [ ] Constructor injection used for all dependencies
- [ ] Services registered in Configuration/Services.yaml
- [ ] No direct class instantiation (new MyClass())
- [ ] No GeneralUtility::makeInstance() for new services
- [ ] PSR interfaces used (ResponseInterface, LoggerInterface, etc.)
- [ ] No global state access ($GLOBALS)
### PSR-14 Events (Mandatory)
- [ ] PSR-14 events used instead of hooks
- [ ] Event classes are immutable with proper getters/setters
- [ ] Event listeners use #[AsEventListener] attribute or Services.yaml tags
- [ ] Event classes use `final` keyword (TYPO3 13+)
- [ ] Event classes use `readonly` for immutable properties (TYPO3 13+)
### TYPO3 13 Site Sets (Mandatory for TYPO3 13)
- [ ] Configuration/Sets/ directory exists
- [ ] Base set has config.yaml with proper metadata
- [ ] settings.definitions.yaml defines extension settings with types
- [ ] Set names follow vendor/package convention
- [ ] Dependencies declared in config.yaml
### Advanced Services.yaml (Mandatory)
- [ ] Event listeners registered with proper tags
- [ ] Console commands tagged with schedulable flag
- [ ] Data processors registered with unique identifiers
- [ ] Cache services use factory pattern
- [ ] Service tags include all required attributes
### PSR-15 Middleware
- [ ] PSR-15 middlewares registered in RequestMiddlewares.php
- [ ] Middleware ordering defined with before/after
### Extbase Architecture
- [ ] Extbase models extend AbstractEntity
- [ ] Repositories extend Repository base class
- [ ] Controllers use constructor injection
- [ ] Validators extend AbstractValidator
### Factory Pattern
- [ ] Factory pattern for complex object creation (e.g., Connection objects)

View File

@@ -0,0 +1,421 @@
# runTests.sh Validation Guide
**Purpose:** Validate Build/Scripts/runTests.sh against TYPO3 Best Practices (Tea extension reference)
## Why Validate runTests.sh?
The `runTests.sh` script is the **central orchestration tool** for TYPO3 extension quality workflows. An outdated or misconfigured script can lead to:
- ❌ Testing with wrong PHP/TYPO3 versions (false positives/negatives)
- ❌ Missing database compatibility issues
- ❌ Inconsistent local vs CI environments
- ❌ Developer confusion with incorrect defaults
## Reference Implementation
**Source of Truth:** https://github.com/TYPO3BestPractices/tea/blob/main/Build/Scripts/runTests.sh
The Tea extension maintains the canonical runTests.sh implementation, updated for latest TYPO3 standards.
## Critical Validation Points
### 1. PHP Version Configuration
**Check Lines ~318 and ~365:**
```bash
# Default PHP version
PHP_VERSION="X.X"
# PHP version validation regex
if ! [[ ${PHP_VERSION} =~ ^(X.X|X.X|X.X)$ ]]; then
```
**Validation:**
1. Read extension's composer.json `require.php` constraint
2. Extract minimum PHP version (e.g., `^8.2` → minimum 8.2)
3. Verify runTests.sh default matches minimum
4. Verify version regex includes all supported versions
**Example Check:**
```bash
# Extension composer.json
"require": {
"php": "^8.2 || ^8.3 || ^8.4"
}
# runTests.sh SHOULD have:
PHP_VERSION="8.2" # ✅ Matches minimum
if ! [[ ${PHP_VERSION} =~ ^(8.2|8.3|8.4)$ ]]; then # ✅ All supported
# runTests.sh SHOULD NOT have:
PHP_VERSION="7.4" # ❌ Below minimum
if ! [[ ${PHP_VERSION} =~ ^(7.4|8.0|8.1|8.2|8.3)$ ]]; then # ❌ Includes unsupported
```
**Severity:** 🔴 **High** - Testing with wrong PHP version invalidates results
### 2. TYPO3 Version Configuration
**Check Lines ~315 and ~374:**
```bash
# Default TYPO3 version
TYPO3_VERSION="XX"
# TYPO3 version validation
if ! [[ ${TYPO3_VERSION} =~ ^(11|12|13)$ ]]; then
```
**Validation:**
1. Read extension's composer.json TYPO3 core dependency
2. Extract target TYPO3 version (e.g., `^13.4` → TYPO3 13)
3. Verify runTests.sh default matches target
4. Check composerInstallHighest/Lowest version constraints
**Example Check:**
```bash
# Extension composer.json
"require": {
"typo3/cms-core": "^13.4"
}
# runTests.sh SHOULD have:
TYPO3_VERSION="13" # ✅ Matches target
# In composerInstallHighest (line ~530):
if [ ${TYPO3_VERSION} -eq 13 ]; then
composer require --no-ansi --no-interaction --no-progress --no-install \
typo3/cms-core:^13.4 # ✅ Matches composer.json
# runTests.sh SHOULD NOT have:
TYPO3_VERSION="11" # ❌ Below target
```
**Severity:** 🔴 **High** - Testing against wrong TYPO3 version
### 3. Database Version Support
**Check Lines ~48-107 (handleDbmsOptions function):**
```bash
mariadb)
[ -z "${DBMS_VERSION}" ] && DBMS_VERSION="X.X"
if ! [[ ${DBMS_VERSION} =~ ^(10.2|10.3|...|11.1)$ ]]; then
```
**Validation:**
1. Check MariaDB, MySQL, PostgreSQL version lists are current
2. Verify default versions are maintained (not EOL)
3. Cross-reference with TYPO3 core database support matrix
**Current Database Support (TYPO3 13):**
| DBMS | Supported Versions | Default | EOL Status |
|------|-------------------|---------|------------|
| MariaDB | 10.4-10.11, 11.0-11.4 | 10.11 | 10.4+ maintained |
| MySQL | 8.0, 8.1, 8.2, 8.3, 8.4 | 8.0 | 8.0 maintained until 2026 |
| PostgreSQL | 10-16 | 16 | 10-11 EOL, 12+ maintained |
| SQLite | 3.x | 3.x | Always latest |
**Example Check:**
```bash
# runTests.sh MariaDB (line ~48)
[ -z "${DBMS_VERSION}" ] && DBMS_VERSION="10.11" # ✅ LTS version
if ! [[ ${DBMS_VERSION} =~ ^(10.4|10.5|10.6|10.11|11.0|11.1|11.2|11.3|11.4)$ ]]; then
# ❌ BAD - EOL version as default:
[ -z "${DBMS_VERSION}" ] && DBMS_VERSION="10.2" # EOL 2023
# runTests.sh PostgreSQL (line ~79)
[ -z "${DBMS_VERSION}" ] && DBMS_VERSION="16" # ✅ Latest stable
if ! [[ ${DBMS_VERSION} =~ ^(10|11|12|13|14|15|16)$ ]]; then
```
**Severity:** 🟡 **Medium** - May miss database-specific compatibility issues
### 4. Network Name Configuration
**Check Line ~331:**
```bash
NETWORK="extension-name-${SUFFIX}"
```
**Validation:**
1. Should match extension key or composer package name
2. Should NOT be hardcoded to "friendsoftypo3-tea" (copy-paste artifact)
**Example Check:**
```bash
# Extension key: rte_ckeditor_image
# Composer package: netresearch/rte-ckeditor-image
# ✅ Good options:
NETWORK="rte-ckeditor-image-${SUFFIX}"
NETWORK="netresearch-rte-ckeditor-image-${SUFFIX}"
# ❌ Bad (copy-paste from Tea):
NETWORK="friendsoftypo3-tea-${SUFFIX}"
```
**Severity:** 🟢 **Low** - Cosmetic, but indicates lack of customization
### 5. Test Suite Commands
**Check Lines ~580, ~620 (functional and unit test commands):**
```bash
functional)
COMMAND=(.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml ...)
unit)
COMMAND=(.Build/bin/phpunit -c Build/phpunit/UnitTests.xml ...)
```
**Validation:**
1. Paths match actual PHPUnit config locations
2. Config files exist and are properly named
3. Exclude groups match available database types
**Example Check:**
```bash
# Verify config files exist:
ls -la Build/phpunit/UnitTests.xml # Must exist
ls -la Build/phpunit/FunctionalTests.xml # Must exist
# Check command paths:
COMMAND=(.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml ...)
└─────┬─────┘ └──────────┬───────────┘
✅ Matches ✅ Matches actual
.Build/bin/ Build/phpunit/
from composer.json directory structure
```
**Severity:** 🔴 **High** - Tests won't run if paths are wrong
### 6. Container Image Versions
**Check Lines ~446-451:**
```bash
IMAGE_PHP="ghcr.io/typo3/core-testing-$(echo "php${PHP_VERSION}" | sed -e 's/\.//'):latest"
IMAGE_ALPINE="docker.io/alpine:3.8"
IMAGE_DOCS="ghcr.io/typo3-documentation/render-guides:latest"
IMAGE_MARIADB="docker.io/mariadb:${DBMS_VERSION}"
IMAGE_MYSQL="docker.io/mysql:${DBMS_VERSION}"
IMAGE_POSTGRES="docker.io/postgres:${DBMS_VERSION}-alpine"
```
**Validation:**
1. PHP testing image uses official TYPO3 images
2. Alpine version is reasonably current (not ancient)
3. Documentation renderer is latest official TYPO3 image
**Severity:** 🟢 **Low** - Usually works, but outdated Alpine may have issues
## Conformance Evaluation Workflow
### Step 1: Extract Extension Requirements
```bash
# Read composer.json
cat composer.json | jq -r '.require.php' # e.g., "^8.2 || ^8.3 || ^8.4"
cat composer.json | jq -r '.require."typo3/cms-core"' # e.g., "^13.4"
# Parse minimum versions
MIN_PHP=$(echo "^8.2 || ^8.3" | grep -oE '[0-9]+\.[0-9]+' | head -1) # 8.2
TARGET_TYPO3=$(echo "^13.4" | grep -oE '[0-9]+') # 13
```
### Step 2: Validate runTests.sh Defaults
```bash
# Check PHP version default (line ~318)
grep '^PHP_VERSION=' Build/Scripts/runTests.sh
# Expected: PHP_VERSION="8.2" (matches MIN_PHP)
# Check TYPO3 version default (line ~315)
grep '^TYPO3_VERSION=' Build/Scripts/runTests.sh
# Expected: TYPO3_VERSION="13" (matches TARGET_TYPO3)
```
### Step 3: Validate PHP Version Regex
```bash
# Extract PHP version regex (line ~365)
grep -A 2 'if ! \[\[ \${PHP_VERSION}' Build/Scripts/runTests.sh
# Expected pattern for "^8.2 || ^8.3 || ^8.4":
# ^(8.2|8.3|8.4)$
# ❌ Outdated pattern:
# ^(7.4|8.0|8.1|8.2|8.3)$
```
### Step 4: Validate TYPO3 Version Constraints in Composer Install
```bash
# Check composerInstallHighest TYPO3 13 block (line ~530)
sed -n '/if \[ \${TYPO3_VERSION} -eq 13 \];/,/fi/p' Build/Scripts/runTests.sh
# Should match composer.json requirements:
# typo3/cms-core:^13.4
# typo3/cms-backend:^13.4
# etc.
```
### Step 5: Validate Network Name
```bash
# Check network name (line ~331)
grep '^NETWORK=' Build/Scripts/runTests.sh
# Extract extension key from composer.json or ext_emconf.php
EXT_KEY=$(jq -r '.extra.typo3.cms."extension-key"' composer.json)
# Expected: NETWORK="${EXT_KEY}-${SUFFIX}" or similar
# ❌ Wrong: NETWORK="friendsoftypo3-tea-${SUFFIX}"
```
## Automated Validation Script
Create `scripts/validate-runtests.sh`:
```bash
#!/bin/bash
set -e
echo "🔍 Validating Build/Scripts/runTests.sh against extension requirements..."
# Extract requirements
MIN_PHP=$(jq -r '.require.php' composer.json | grep -oE '[0-9]+\.[0-9]+' | head -1)
TARGET_TYPO3=$(jq -r '.require."typo3/cms-core"' composer.json | grep -oE '^[0-9]+' | head -1)
EXT_KEY=$(jq -r '.extra.typo3.cms."extension-key"' composer.json)
echo "📋 Extension Requirements:"
echo " PHP: ${MIN_PHP}+"
echo " TYPO3: ${TARGET_TYPO3}"
echo " Extension Key: ${EXT_KEY}"
echo ""
# Validate PHP version default
RUNTESTS_PHP=$(grep '^PHP_VERSION=' Build/Scripts/runTests.sh | cut -d'"' -f2)
if [ "${RUNTESTS_PHP}" != "${MIN_PHP}" ]; then
echo "❌ PHP version mismatch: runTests.sh uses ${RUNTESTS_PHP}, should be ${MIN_PHP}"
exit 1
else
echo "✅ PHP version default: ${RUNTESTS_PHP}"
fi
# Validate TYPO3 version default
RUNTESTS_TYPO3=$(grep '^TYPO3_VERSION=' Build/Scripts/runTests.sh | cut -d'"' -f2)
if [ "${RUNTESTS_TYPO3}" != "${TARGET_TYPO3}" ]; then
echo "❌ TYPO3 version mismatch: runTests.sh uses ${RUNTESTS_TYPO3}, should be ${TARGET_TYPO3}"
exit 1
else
echo "✅ TYPO3 version default: ${RUNTESTS_TYPO3}"
fi
# Validate network name
NETWORK_NAME=$(grep '^NETWORK=' Build/Scripts/runTests.sh | cut -d'"' -f2 | sed 's/-${SUFFIX}$//')
if [[ "${NETWORK_NAME}" == "friendsoftypo3-tea" ]]; then
echo "⚠️ Network name is copy-paste from Tea extension: ${NETWORK_NAME}"
echo " Should be: ${EXT_KEY}-\${SUFFIX}"
else
echo "✅ Network name: ${NETWORK_NAME}-\${SUFFIX}"
fi
echo ""
echo "✅ runTests.sh validation complete"
```
## Conformance Report Integration
### When evaluating runTests.sh:
**In "Best Practices" Section:**
```markdown
### Build Scripts
**runTests.sh Analysis:**
- ✅ Script present and executable
- ✅ PHP version default matches composer.json minimum (8.2)
- ✅ TYPO3 version default matches target (13)
- ✅ PHP version regex includes all supported versions (8.2, 8.3, 8.4)
- ⚠️ Network name uses Tea extension default (cosmetic issue)
- ✅ Test suite commands match actual file structure
- ✅ Database version support is current
**Or with issues:**
- ❌ PHP version default (7.4) below extension minimum (8.2)
- File: Build/Scripts/runTests.sh:318
- Severity: High
- Fix: Change `PHP_VERSION="7.4"` to `PHP_VERSION="8.2"`
- ❌ TYPO3 version default (11) below extension target (13)
- File: Build/Scripts/runTests.sh:315
- Severity: High
- Fix: Change `TYPO3_VERSION="11"` to `TYPO3_VERSION="13"`
- ❌ PHP version regex includes unsupported versions
- File: Build/Scripts/runTests.sh:365
- Current: `^(7.4|8.0|8.1|8.2|8.3)$`
- Expected: `^(8.2|8.3|8.4)$`
- Severity: Medium
- Fix: Remove unsupported versions from regex
```
## Scoring Impact
**Best Practices Score Deductions:**
| Issue | Severity | Score Impact |
|-------|----------|--------------|
| PHP version default outdated | High | -3 points |
| TYPO3 version default outdated | High | -3 points |
| PHP version regex includes unsupported | Medium | -2 points |
| Database versions EOL | Medium | -2 points |
| Network name copy-paste | Low | -1 point |
| Missing runTests.sh | Critical | -10 points |
**Maximum deduction for runTests.sh issues:** -6 points (out of 20 for Best Practices)
## Quick Reference Checklist
**When evaluating Build/Scripts/runTests.sh:**
```
□ File exists and is executable
□ PHP_VERSION default matches composer.json minimum
□ TYPO3_VERSION default matches composer.json target
□ PHP version regex matches composer.json constraint exactly
□ TYPO3_VERSION regex includes supported versions only
□ Database version lists are current (not EOL)
□ Database version defaults are maintained LTS versions
□ Network name is customized (not "friendsoftypo3-tea")
□ Test suite paths match actual directory structure
□ Container images use official TYPO3 testing images
```
**Comparison Strategy:**
1. Download latest Tea runTests.sh as reference
2. Compare line-by-line for structural differences
3. Validate version-specific values against extension requirements
4. Flag any outdated patterns or hardcoded Tea-specific values
## Resources
- **Tea Extension runTests.sh:** https://github.com/TYPO3BestPractices/tea/blob/main/Build/Scripts/runTests.sh
- **TYPO3 Testing Documentation:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Testing/
- **Database Compatibility:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Database/

View File

@@ -0,0 +1,558 @@
# TYPO3 Testing Standards
**Source:** TYPO3 Core API Reference - Testing
**Purpose:** Unit, functional, and acceptance testing standards for TYPO3 extensions
## Testing Framework
TYPO3 uses **typo3/testing-framework** for comprehensive testing:
```bash
# Install testing framework
composer require --dev \
"typo3/testing-framework":"^8.0.9" \
"phpunit/phpunit":"^10.5"
```
## Unit Testing
### Unit Test Structure
```php
// ✅ Right: Proper unit test structure
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Tests\Unit\Service;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
use Vendor\ExtensionKey\Service\CalculationService;
class CalculationServiceTest extends UnitTestCase
{
private CalculationService $subject;
protected function setUp(): void
{
parent::setUp();
$this->subject = new CalculationService();
}
/**
* @test
*/
public function addReturnsCorrectSum(): void
{
$result = $this->subject->add(2, 3);
$this->assertEquals(5, $result);
}
/**
* @test
*/
public function multiplyReturnsCorrectProduct(): void
{
$result = $this->subject->multiply(4, 5);
$this->assertEquals(20, $result);
}
}
```
### PHPUnit Configuration
```xml
<!-- Build/phpunit/UnitTests.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
bootstrap="../../vendor/typo3/testing-framework/Resources/Core/Build/UnitTestsBootstrap.php"
colors="true"
beStrictAboutTestsThatDoNotTestAnything="true"
failOnWarning="true"
failOnRisky="true"
stopOnFailure="false"
>
<testsuites>
<testsuite name="Unit tests">
<directory>../../Tests/Unit/</directory>
</testsuite>
</testsuites>
</phpunit>
```
### Running Unit Tests
```bash
# Direct execution
vendor/bin/phpunit -c Build/phpunit/UnitTests.xml
# DDEV execution
ddev exec php vendor/bin/phpunit -c Build/phpunit/UnitTests.xml
# Run specific test
vendor/bin/phpunit -c Build/phpunit/UnitTests.xml --filter "CalculationServiceTest"
```
### Unit Test Best Practices
**✅ Do:**
- Test single units (methods, functions) in isolation
- Mock external dependencies
- Test edge cases and boundary conditions
- Use descriptive test method names
- Follow naming: `methodName<Condition>Returns<Expected>`
- Keep tests fast (no database, no external services)
**❌ Don't:**
- Access database in unit tests
- Depend on file system
- Make HTTP requests
- Test framework internals
- Write integration tests as unit tests
## Functional Testing
### Functional Test Structure
```php
// ✅ Right: Proper functional test structure
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Tests\Functional\Domain\Repository;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
use Vendor\ExtensionKey\Domain\Repository\ProductRepository;
class ProductRepositoryTest extends FunctionalTestCase
{
protected array $testExtensionsToLoad = [
'typo3conf/ext/my_extension',
];
protected ProductRepository $subject;
protected function setUp(): void
{
parent::setUp();
// Load test data
$this->importCSVDataSet(__DIR__ . '/Fixtures/products.csv');
// Set up backend user
$this->setUpBackendUser(1);
// Initialize subject
$this->subject = $this->get(ProductRepository::class);
}
/**
* @test
*/
public function findAllReturnsAllProducts(): void
{
$products = $this->subject->findAll();
$this->assertCount(3, $products);
}
/**
* @test
*/
public function findByPriceRangeReturnsMatchingProducts(): void
{
$products = $this->subject->findByPriceRange(10.0, 50.0);
$this->assertCount(2, $products);
}
}
```
### PHPUnit Functional Configuration
```xml
<!-- Build/phpunit/FunctionalTests.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
bootstrap="../../vendor/typo3/testing-framework/Resources/Core/Build/FunctionalTestsBootstrap.php"
colors="true"
beStrictAboutTestsThatDoNotTestAnything="true"
failOnWarning="true"
failOnRisky="true"
stopOnFailure="false"
>
<testsuites>
<testsuite name="Functional tests">
<directory>../../Tests/Functional/</directory>
</testsuite>
</testsuites>
</phpunit>
```
### Running Functional Tests
```bash
# With MySQL/MariaDB
ddev exec \
typo3DatabaseDriver='mysqli' \
typo3DatabaseHost='db' \
typo3DatabasePort=3306 \
typo3DatabaseUsername='root' \
typo3DatabasePassword='root' \
typo3DatabaseName='func' \
php vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml
# With SQLite (simpler)
ddev exec \
typo3DatabaseDriver=pdo_sqlite \
php vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml
# With PostgreSQL
ddev exec \
typo3DatabaseDriver='pdo_pgsql' \
typo3DatabaseHost='postgres' \
typo3DatabasePort=5432 \
typo3DatabaseUsername='postgres' \
typo3DatabasePassword='postgres' \
typo3DatabaseName='func' \
php vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml
```
### Test Data Fixtures
```csv
# Tests/Functional/Fixtures/products.csv
tx_myext_product,uid,pid,title,price,available
,1,0,Product A,29.99,1
,2,0,Product B,49.99,1
,3,0,Product C,99.99,0
```
### Loading Extensions in Tests
```php
// Load extension under test
protected array $testExtensionsToLoad = [
'typo3conf/ext/my_extension',
];
// Load additional core extensions
protected array $coreExtensionsToLoad = [
'typo3/cms-workspaces',
];
// Load fixture extensions
protected array $testExtensionsToLoad = [
'typo3conf/ext/my_extension',
'typo3conf/ext/my_extension/Tests/Functional/Fixtures/Extensions/fixture_extension',
];
```
## Acceptance Testing
### Codeception Setup
```yaml
# Tests/codeception.yml
namespace: Vendor\ExtensionKey\Tests\Acceptance\Support
suites:
acceptance:
actor: AcceptanceTester
path: .
modules:
enabled:
- Asserts
- WebDriver:
url: https://myproject.ddev.site
browser: chrome
host: ddev-myproject-chrome
wait: 1
window_size: 1280x1024
extensions:
enabled:
- Codeception\Extension\RunFailed
- Codeception\Extension\Recorder
paths:
tests: Acceptance
output: ../var/log/_output
data: .
support: Acceptance/Support
settings:
shuffle: false
lint: true
colors: true
```
### Acceptance Test Structure
```php
// ✅ Right: Backend acceptance test
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Tests\Acceptance\Backend;
use Vendor\ExtensionKey\Tests\Acceptance\Support\BackendTester;
use TYPO3\TestingFramework\Core\Acceptance\Helper\Topbar;
class ModuleCest
{
public function _before(BackendTester $I): void
{
$I->useExistingSession('admin');
}
/**
* @param BackendTester $I
*/
public function moduleCanBeAccessed(BackendTester $I): void
{
$I->click(Topbar::$dropdownToggleSelector, '#typo3-cms-backend-backend-toolbaritems-helptoolbaritem');
$I->canSee('My Module');
$I->click('My Module');
$I->switchToContentFrame();
$I->see('Module Content', 'h1');
}
/**
* @param BackendTester $I
*/
public function formSubmissionWorks(BackendTester $I): void
{
$I->amOnPage('/typo3/module/my-module');
$I->switchToContentFrame();
$I->fillField('title', 'Test Title');
$I->click('Save');
$I->see('Record saved successfully');
}
}
```
### Frontend Acceptance Test
```php
// ✅ Right: Frontend acceptance test
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Tests\Acceptance\Frontend;
use Vendor\ExtensionKey\Tests\Acceptance\Support\AcceptanceTester;
class FrontendPagesCest
{
/**
* @param AcceptanceTester $I
*/
public function homepageIsRendered(AcceptanceTester $I): void
{
$I->amOnPage('/');
$I->see('Welcome to TYPO3');
$I->seeElement('h1');
}
/**
* @param AcceptanceTester $I
*/
public function navigationWorks(AcceptanceTester $I): void
{
$I->amOnPage('/');
$I->click('Products');
$I->see('Our Products', 'h1');
$I->seeInCurrentUrl('/products');
}
}
```
### Running Acceptance Tests
```bash
# Run acceptance tests via DDEV
ddev exec bin/codecept run acceptance -d -c Tests/codeception.yml
# Run specific test
ddev exec bin/codecept run acceptance ModuleCest -c Tests/codeception.yml
# Generate new test
ddev exec bin/codecept generate:cest acceptance MyNewTest -c Tests/codeception.yml
```
## Test Organization
### Directory Structure
```
Tests/
├── Unit/
│ ├── Controller/
│ │ └── ProductControllerTest.php
│ ├── Domain/
│ │ ├── Model/
│ │ │ └── ProductTest.php
│ │ └── Repository/
│ │ └── ProductRepositoryTest.php
│ └── Service/
│ └── CalculationServiceTest.php
├── Functional/
│ ├── Domain/
│ │ └── Repository/
│ │ ├── ProductRepositoryTest.php
│ │ └── Fixtures/
│ │ └── products.csv
│ └── Controller/
│ └── ProductControllerTest.php
└── Acceptance/
├── Backend/
│ └── ModuleCest.php
├── Frontend/
│ └── FrontendPagesCest.php
└── Support/
├── AcceptanceTester.php
└── BackendTester.php
```
### Naming Conventions
**Unit Tests:**
- Pattern: `<ClassName>Test.php`
- Example: `ProductRepository.php``ProductRepositoryTest.php`
- Location: Mirror `Classes/` structure in `Tests/Unit/`
**Functional Tests:**
- Pattern: `<ClassName>Test.php`
- Example: `ProductRepository.php``ProductRepositoryTest.php`
- Location: Mirror `Classes/` structure in `Tests/Functional/`
**Acceptance Tests:**
- Pattern: `<Feature>Cest.php`
- Example: `ModuleCest.php`, `LoginCest.php`
- Location: `Tests/Acceptance/Backend/` or `Tests/Acceptance/Frontend/`
## PHPUnit Attributes (PHP 8.0+)
```php
// ✅ Right: Using PHPUnit attributes
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Tests\Unit\Service;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\DataProvider;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
class CalculationServiceTest extends UnitTestCase
{
#[Test]
public function addReturnsCorrectSum(): void
{
$this->assertEquals(5, $this->subject->add(2, 3));
}
public static function priceDataProvider(): \Generator
{
yield 'standard price' => [
'price' => 100.0,
'taxRate' => 0.19,
'expected' => 119.0,
];
yield 'zero price' => [
'price' => 0.0,
'taxRate' => 0.19,
'expected' => 0.0,
];
}
#[Test]
#[DataProvider('priceDataProvider')]
public function calculatePriceWithTax(float $price, float $taxRate, float $expected): void
{
$result = $this->subject->calculatePriceWithTax($price, $taxRate);
$this->assertEquals($expected, $result);
}
}
```
## CI/CD Integration
### GitHub Actions
```yaml
# .github/workflows/tests.yml
name: Tests
on: [push, pull_request]
jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['8.1', '8.2', '8.3']
typo3: ['12.4', '13.0']
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
- name: Install dependencies
run: composer install
- name: Lint PHP
run: find . -name \*.php ! -path "./vendor/*" -exec php -l {} \;
- name: Unit Tests
run: vendor/bin/phpunit -c Build/phpunit/UnitTests.xml
- name: Functional Tests
run: |
typo3DatabaseDriver=pdo_sqlite \
vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml
```
## Conformance Checklist
### Unit Tests
- [ ] Unit tests extend `UnitTestCase`
- [ ] Tests located in `Tests/Unit/` mirroring `Classes/`
- [ ] Test files named `<ClassName>Test.php`
- [ ] No database access in unit tests
- [ ] No file system access in unit tests
- [ ] All public methods tested
- [ ] Edge cases and boundaries tested
- [ ] #[Test] attribute or @test annotation used
### Functional Tests
- [ ] Functional tests extend `FunctionalTestCase`
- [ ] Tests located in `Tests/Functional/`
- [ ] `setUp()` calls `parent::setUp()` first
- [ ] Extensions loaded via `$testExtensionsToLoad`
- [ ] Test data loaded via `importCSVDataSet()`
- [ ] Database operations tested
- [ ] Backend user initialized when needed
### Acceptance Tests
- [ ] Acceptance tests use Codeception
- [ ] Tests located in `Tests/Acceptance/`
- [ ] Test files named `<Feature>Cest.php`
- [ ] codeception.yml properly configured
- [ ] Backend tests use `useExistingSession('admin')`
- [ ] Frame switching used correctly
- [ ] Tests verify user-visible behavior
### General
- [ ] PHPUnit configuration files present
- [ ] All tests pass locally
- [ ] CI/CD pipeline configured
- [ ] Test coverage >70% for new code
- [ ] Data providers use named arguments
- [ ] Descriptive test method names

View File

@@ -0,0 +1,408 @@
# TYPO3 v13 Deprecations and Modern Alternatives
**Sources:** TYPO3 Core API Reference v13.4
**Purpose:** Track v13 deprecations, migration paths, and modern configuration approaches
## Deprecated Files (v13.1+)
### ext_typoscript_constants.typoscript
**Status:** DEPRECATED since TYPO3 v13.1
**Purpose:** Provided global TypoScript constants
**Migration Paths:**
**1. Preferred: Site Settings Definitions**
```yaml
# Configuration/Sets/MySet/settings.definitions.yaml
settings:
myext:
itemsPerPage:
type: int
default: 10
label: 'Items per page'
```
**2. For Global Constants:**
```php
// ext_localconf.php
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScript(
'my_extension',
'constants',
'@import "EXT:my_extension/Configuration/TypoScript/constants.typoscript"'
);
```
**Detection:**
```bash
[ -f "ext_typoscript_constants.typoscript" ] && echo "⚠️ DEPRECATED: Migrate to Site sets"
```
**Impact:** "This file takes no effect in sites that use Site sets."
---
### ext_typoscript_setup.typoscript
**Status:** DEPRECATED since TYPO3 v13.1
**Purpose:** Provided global TypoScript setup
**Migration Paths:**
**1. Preferred: Site Sets**
```yaml
# Configuration/Sets/MySet/config.yaml
name: my-vendor/my-set
label: 'My Extension Set'
imports:
- { resource: 'EXT:fluid_styled_content/Configuration/Sets/FluidStyledContent/config.yaml' }
```
```typoscript
# Configuration/Sets/MySet/setup.typoscript
plugin.tx_myextension {
settings {
itemsPerPage = 10
}
}
```
**2. For Global Loading:**
```php
// ext_localconf.php
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScript(
'my_extension',
'setup',
'@import "EXT:my_extension/Configuration/TypoScript/setup.typoscript"'
);
```
**Detection:**
```bash
[ -f "ext_typoscript_setup.typoscript" ] && echo "⚠️ DEPRECATED: Migrate to Site sets"
```
**Impact:** "This file takes no effect in sites that use Site sets. This file works for backward compatibility reasons only in installations that depend on TypoScript records only."
---
## Deprecated Methods (Removal in v14)
### ExtensionManagementUtility::addUserTSConfig()
**Status:** DEPRECATED, will be removed with TYPO3 v14.0
**Old Approach:**
```php
// ext_localconf.php - DEPRECATED
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addUserTSConfig('
options.pageTree.showPageIdWithTitle = 1
options.defaultUploadFolder = 1:/user_uploads/
');
```
**Modern Approach:**
```
# Configuration/user.tsconfig
options.pageTree.showPageIdWithTitle = 1
options.defaultUploadFolder = 1:/user_uploads/
```
**Detection:**
```bash
grep "addUserTSConfig" ext_localconf.php && echo "❌ DEPRECATED: Use Configuration/user.tsconfig"
```
---
## Modern Configuration Files (v12+)
### Configuration/user.tsconfig
**Since:** TYPO3 v12
**Purpose:** User TSconfig loaded for all backend users
**Location:** `Configuration/user.tsconfig`
**Example:**
```
# Default user settings
options.pageTree.showPageIdWithTitle = 1
options.defaultUploadFolder = 1:/user_uploads/
# Hide modules
options.hideModules = web_layout, web_info
```
**Validation:**
```bash
[ -f "Configuration/user.tsconfig" ] && echo "✅ Modern user TSconfig" || echo "⚠️ Consider adding user TSconfig"
```
---
### Configuration/page.tsconfig
**Since:** TYPO3 v12
**Purpose:** Page TSconfig loaded globally
**Location:** `Configuration/page.tsconfig`
**Example:**
```
# Default page configuration
TCEFORM.pages.layout.disabled = 1
TCEMAIN.table.pages.disablePrependAtCopy = 1
# Backend layout
mod.web_layout.BackendLayouts {
standard {
title = Standard Layout
icon = EXT:my_ext/Resources/Public/Icons/layout.svg
config {
backend_layout {
colCount = 2
rowCount = 1
rows {
1 {
columns {
1 {
name = Main
colPos = 0
}
2 {
name = Sidebar
colPos = 1
}
}
}
}
}
}
}
}
```
**Validation:**
```bash
[ -f "Configuration/page.tsconfig" ] && echo "✅ Modern page TSconfig" || echo "⚠️ Consider adding page TSconfig"
```
---
## Modern Backend Configuration (v13)
### Configuration/Backend/Modules.php
**Since:** TYPO3 v13.0
**Purpose:** Backend module registration (replaces ext_tables.php)
**Location:** `Configuration/Backend/Modules.php`
**Example:**
```php
<?php
return [
'web_myext' => [
'parent' => 'web',
'position' => ['after' => 'web_list'],
'access' => 'user',
'workspaces' => 'live',
'path' => '/module/web/myext',
'labels' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang_mod.xlf',
'extensionName' => 'MyExt',
'controllerActions' => [
\Vendor\MyExt\Controller\BackendController::class => [
'list',
'detail',
'update',
],
],
],
];
```
**Old Approach (DEPRECATED):**
```php
// ext_tables.php - DEPRECATED
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerModule(
'MyExt',
'web',
'mymodule',
'after:list',
[
\Vendor\MyExt\Controller\BackendController::class => 'list,detail,update',
],
[
'access' => 'user,group',
'labels' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang_mod.xlf',
]
);
```
**Migration Script:** TYPO3 provides "Check TCA in ext_tables.php" upgrade tool
**Validation:**
```bash
[ -f "Configuration/Backend/Modules.php" ] && echo "✅ Modern backend modules" || echo "⚠️ Check for modules in ext_tables.php"
```
---
## Site Sets (v13 Recommended Approach)
### Configuration/Sets Structure
```
Configuration/Sets/
└── MySet/
├── config.yaml (REQUIRED)
├── settings.definitions.yaml
├── setup.typoscript
├── constants.typoscript (optional)
├── page.tsconfig
└── user.tsconfig
```
### config.yaml (Required)
```yaml
name: my-vendor/my-set
label: 'My Extension Configuration Set'
# Dependencies
imports:
- { resource: 'EXT:fluid_styled_content/Configuration/Sets/FluidStyledContent/config.yaml' }
# Settings with defaults
settings:
myext:
itemsPerPage: 10
showImages: true
```
### settings.definitions.yaml
```yaml
settings:
myext:
itemsPerPage:
type: int
default: 10
label: 'Items per page'
description: 'Number of items displayed per page in list view'
showImages:
type: bool
default: true
label: 'Show images'
description: 'Display images in list view'
templateLayout:
type: string
default: 'default'
label: 'Template layout'
enum:
default: 'Default Layout'
grid: 'Grid Layout'
list: 'List Layout'
```
### setup.typoscript
```typoscript
plugin.tx_myextension {
view {
templateRootPaths.0 = EXT:my_extension/Resources/Private/Templates/
partialRootPaths.0 = EXT:my_extension/Resources/Private/Partials/
layoutRootPaths.0 = EXT:my_extension/Resources/Private/Layouts/
}
settings {
itemsPerPage = {$settings.myext.itemsPerPage}
showImages = {$settings.myext.showImages}
}
}
```
### Activation in Site Configuration
```yaml
# config/sites/mysite/config.yaml
base: 'https://example.com/'
rootPageId: 1
sets:
- my-vendor/my-set # Activates the set
```
---
## Migration Checklist
### For v12 → v13 Migration
- [ ] Move backend module registration from ext_tables.php to Configuration/Backend/Modules.php
- [ ] Replace `addUserTSConfig()` with Configuration/user.tsconfig
- [ ] Move page TSconfig from ext_localconf.php to Configuration/page.tsconfig
- [ ] Deprecate ext_typoscript_constants.typoscript (use Site sets)
- [ ] Deprecate ext_typoscript_setup.typoscript (use Site sets)
### For Modern v13 Extensions
- [ ] Use Configuration/Sets/ for TypoScript configuration
- [ ] Use settings.definitions.yaml for extension settings
- [ ] Use Configuration/Backend/Modules.php for backend modules
- [ ] Use Configuration/user.tsconfig for user TSconfig
- [ ] Use Configuration/page.tsconfig for page TSconfig
- [ ] Use Configuration/Icons.php for icon registration
---
## Validation Commands
```bash
#!/bin/bash
# check-v13-deprecations.sh
echo "=== Checking for TYPO3 v13 Deprecations ==="
echo ""
# Check deprecated files
if [ -f "ext_typoscript_constants.typoscript" ]; then
echo "⚠️ DEPRECATED: ext_typoscript_constants.typoscript (v13.1)"
echo " → Migrate to Configuration/Sets/ with settings.definitions.yaml"
fi
if [ -f "ext_typoscript_setup.typoscript" ]; then
echo "⚠️ DEPRECATED: ext_typoscript_setup.typoscript (v13.1)"
echo " → Migrate to Configuration/Sets/ with setup.typoscript"
fi
# Check deprecated methods
if grep -q "addUserTSConfig" ext_localconf.php 2>/dev/null; then
echo "❌ DEPRECATED: addUserTSConfig() - Removal in v14"
echo " → Use Configuration/user.tsconfig"
fi
# Check for backend modules in ext_tables.php
if grep -q "registerModule" ext_tables.php 2>/dev/null; then
echo "⚠️ DEPRECATED: Backend modules in ext_tables.php"
echo " → Migrate to Configuration/Backend/Modules.php"
fi
# Check modern files presence
echo ""
echo "=== Modern Configuration Files ===" [ -d "Configuration/Sets" ] && echo "✅ Configuration/Sets/ present" || echo "⚠️ Consider adding Site sets"
[ -f "Configuration/user.tsconfig" ] && echo "✅ Configuration/user.tsconfig present"
[ -f "Configuration/page.tsconfig" ] && echo "✅ Configuration/page.tsconfig present"
[ -f "Configuration/Backend/Modules.php" ] && echo "✅ Configuration/Backend/Modules.php present"
echo ""
echo "Deprecation check complete"
```
---
## Quick Reference Matrix
| Old Approach | Modern Approach (v13) | Status |
|--------------|----------------------|--------|
| ext_typoscript_constants.typoscript | Configuration/Sets/*/settings.definitions.yaml | Deprecated v13.1 |
| ext_typoscript_setup.typoscript | Configuration/Sets/*/setup.typoscript | Deprecated v13.1 |
| addUserTSConfig() in ext_localconf.php | Configuration/user.tsconfig | Removal in v14 |
| Page TSconfig in ext_localconf.php | Configuration/page.tsconfig | Modern v12+ |
| registerModule() in ext_tables.php | Configuration/Backend/Modules.php | Modern v13+ |
| Static files in ext_tables.php | Configuration/TCA/Overrides/sys_template.php | Modern |
| TCA in ext_tables.php | Configuration/TCA/*.php | Modern |

View File

@@ -0,0 +1,150 @@
# TYPO3 and PHP Version Requirements
**Purpose:** Definitive version compatibility matrix for TYPO3 conformance checking
**Last Updated:** 2025-01-18
## Official Version Support Matrix
### TYPO3 12 LTS
**Release:** April 2022
**End of Life:** October 2026
**PHP Support:** 8.1 - 8.4
| PHP Version | Support Status | Since TYPO3 Version |
|------------|----------------|---------------------|
| 8.1 | ✅ Supported | 12.0.0 |
| 8.2 | ✅ Supported | 12.1.0 |
| 8.3 | ✅ Supported | 12.4.0 |
| 8.4 | ✅ Supported | 12.4.24 (Dec 2024) |
**Minimum Requirements:**
- PHP: 8.1.0
- Database: MariaDB 10.4+ / MySQL 8.0+ / PostgreSQL 10.0+ / SQLite 3.8.3+
### TYPO3 13 LTS
**Release:** October 2024
**End of Life:** April 2028
**PHP Support:** 8.2 - 8.4
| PHP Version | Support Status | Since TYPO3 Version |
|------------|----------------|---------------------|
| 8.1 | ❌ Not Supported | - |
| 8.2 | ✅ Supported | 13.0.0 |
| 8.3 | ✅ Supported | 13.0.0 |
| 8.4 | ✅ Supported | 13.4.0 |
**Minimum Requirements:**
- PHP: 8.2.0
- Database: MariaDB 10.4+ / MySQL 8.0+ / PostgreSQL 10.0+ / SQLite 3.8.3+
## Conformance Checker Standards
The TYPO3 conformance checker validates extensions against:
**Target Versions:**
- TYPO3: 12.4 LTS / 13.x
- PHP: 8.1 / 8.2 / 8.3 / 8.4
- PSR Standards: PSR-11 (DI), PSR-12 (Coding Style), PSR-14 (Events), PSR-15 (Middleware)
**Why This Range:**
- Covers both TYPO3 12 LTS and 13 LTS
- PHP 8.1+ ensures support for all modern PHP features used in TYPO3 extensions
- Extensions can target TYPO3 12 (PHP 8.1+) and/or TYPO3 13 (PHP 8.2+)
## Extension composer.json Examples
### TYPO3 12 LTS Only
```json
{
"require": {
"php": "^8.1 || ^8.2 || ^8.3 || ^8.4",
"typo3/cms-core": "^12.4"
}
}
```
### TYPO3 13 LTS Only
```json
{
"require": {
"php": "^8.2 || ^8.3 || ^8.4",
"typo3/cms-core": "^13.4"
}
}
```
### TYPO3 12 and 13 LTS (Recommended for New Extensions)
```json
{
"require": {
"php": "^8.2 || ^8.3 || ^8.4",
"typo3/cms-core": "^12.4 || ^13.4"
}
}
```
**Note:** When targeting both TYPO3 12 and 13, use PHP 8.2+ as minimum to satisfy TYPO3 13's requirements.
## PHP Feature Availability
### PHP 8.1 Features (TYPO3 12+)
- Enumerations
- Readonly properties
- First-class callable syntax
- New in initializers
- Pure intersection types
- Never return type
- Final class constants
- Fibers
### PHP 8.2 Features (TYPO3 13+)
- Readonly classes
- Disjunctive Normal Form (DNF) types
- Null, false, and true as standalone types
- Constants in traits
- Deprecated dynamic properties
### PHP 8.3 Features (TYPO3 12.4+ / 13+)
- Typed class constants
- Dynamic class constant fetch
- `#[\Override]` attribute
- `json_validate()` function
### PHP 8.4 Features (TYPO3 12.4.24+ / 13.4+)
- Property hooks
- Asymmetric visibility
- New array functions
- HTML5 support in DOM extension
## Migration Paths
### From TYPO3 11 to 12
1. Update PHP to 8.1+ (recommended: 8.2+)
2. Update extension to TYPO3 12 compatibility
3. Test thoroughly on PHP 8.2+ for future TYPO3 13 compatibility
### From TYPO3 12 to 13
1. Ensure PHP 8.2+ is already in use
2. Update TYPO3 dependencies to ^13.4
3. Remove deprecated API usage
4. Update Services.yaml for TYPO3 13 changes (if any)
## Deprecation Timeline
**PHP Versions:**
- PHP 8.0: End of Life - November 2023 (Not supported by TYPO3 12/13)
- PHP 8.1: Security fixes until November 2025
- PHP 8.2: Security fixes until December 2026
- PHP 8.3: Security fixes until December 2027
- PHP 8.4: Security fixes until December 2028
**Recommendation:** Target PHP 8.2+ for new extensions to ensure long-term support alignment with TYPO3 13 LTS lifecycle.
## References
- [TYPO3 12 System Requirements](https://docs.typo3.org/m/typo3/reference-coreapi/12.4/en-us/Installation/Index.html)
- [TYPO3 13 System Requirements](https://docs.typo3.org/m/typo3/reference-coreapi/13.4/en-us/Administration/Installation/SystemRequirements/Index.html)
- [PHP Release Cycles](https://www.php.net/supported-versions.php)
- [TYPO3 Roadmap](https://typo3.org/cms/roadmap)