Initial commit
This commit is contained in:
1059
skills/typo3-conformance/references/backend-module-v13.md
Normal file
1059
skills/typo3-conformance/references/backend-module-v13.md
Normal file
File diff suppressed because it is too large
Load Diff
917
skills/typo3-conformance/references/best-practices.md
Normal file
917
skills/typo3-conformance/references/best-practices.md
Normal 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
|
||||
610
skills/typo3-conformance/references/coding-guidelines.md
Normal file
610
skills/typo3-conformance/references/coding-guidelines.md
Normal 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)
|
||||
468
skills/typo3-conformance/references/composer-validation.md
Normal file
468
skills/typo3-conformance/references/composer-validation.md
Normal 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"
|
||||
```
|
||||
1344
skills/typo3-conformance/references/crowdin-integration.md
Normal file
1344
skills/typo3-conformance/references/crowdin-integration.md
Normal file
File diff suppressed because it is too large
Load Diff
524
skills/typo3-conformance/references/development-environment.md
Normal file
524
skills/typo3-conformance/references/development-environment.md
Normal 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/
|
||||
621
skills/typo3-conformance/references/directory-structure.md
Normal file
621
skills/typo3-conformance/references/directory-structure.md
Normal 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/
|
||||
712
skills/typo3-conformance/references/excellence-indicators.md
Normal file
712
skills/typo3-conformance/references/excellence-indicators.md
Normal 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
|
||||
[](https://extensions.typo3.org/extension/news/)
|
||||
[](https://get.typo3.org/version/12)
|
||||
[](https://get.typo3.org/version/13)
|
||||
[](https://packagist.org/packages/georgringer/news)
|
||||

|
||||
[](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/
|
||||
610
skills/typo3-conformance/references/ext-emconf-validation.md
Normal file
610
skills/typo3-conformance/references/ext-emconf-validation.md
Normal 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"
|
||||
```
|
||||
413
skills/typo3-conformance/references/ext-files-validation.md
Normal file
413
skills/typo3-conformance/references/ext-files-validation.md
Normal 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');
|
||||
```
|
||||
273
skills/typo3-conformance/references/extension-architecture.md
Normal file
273
skills/typo3-conformance/references/extension-architecture.md
Normal 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
|
||||
297
skills/typo3-conformance/references/hooks-and-events.md
Normal file
297
skills/typo3-conformance/references/hooks-and-events.md
Normal 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)
|
||||
918
skills/typo3-conformance/references/php-architecture.md
Normal file
918
skills/typo3-conformance/references/php-architecture.md
Normal 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)
|
||||
421
skills/typo3-conformance/references/runtests-validation.md
Normal file
421
skills/typo3-conformance/references/runtests-validation.md
Normal 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/
|
||||
558
skills/typo3-conformance/references/testing-standards.md
Normal file
558
skills/typo3-conformance/references/testing-standards.md
Normal 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
|
||||
408
skills/typo3-conformance/references/v13-deprecations.md
Normal file
408
skills/typo3-conformance/references/v13-deprecations.md
Normal 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 |
|
||||
150
skills/typo3-conformance/references/version-requirements.md
Normal file
150
skills/typo3-conformance/references/version-requirements.md
Normal 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)
|
||||
Reference in New Issue
Block a user