Initial commit
This commit is contained in:
12
.claude-plugin/plugin.json
Normal file
12
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "typo3-conformance",
|
||||||
|
"description": "Evaluate TYPO3 extensions for conformance to official TYPO3 coding standards, architecture patterns, and best practices. Includes automated validation scripts for file structure, PSR-12 compliance, dependency injection, testing infrastructure, and comprehensive conformance scoring with priority-based action items.",
|
||||||
|
"version": "1.0.0-20251126",
|
||||||
|
"author": {
|
||||||
|
"name": "Netresearch DTT GmbH",
|
||||||
|
"email": "info@netresearch.de"
|
||||||
|
},
|
||||||
|
"skills": [
|
||||||
|
"./"
|
||||||
|
]
|
||||||
|
}
|
||||||
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: TYPO3 Slack
|
||||||
|
url: https://typo3.org/community/meet/chat-slack
|
||||||
|
about: Join the TYPO3 community on Slack for discussions and support
|
||||||
|
- name: TYPO3 Extension Documentation
|
||||||
|
url: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/Index.html
|
||||||
|
about: Official TYPO3 extension architecture and development documentation
|
||||||
|
- name: TYPO3 Coding Guidelines
|
||||||
|
url: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/CodingGuidelines/Index.html
|
||||||
|
about: TYPO3 coding guidelines (CGL) and PSR-12 standards
|
||||||
51
.github/workflows/publish-to-ter.yml
vendored
Normal file
51
.github/workflows/publish-to-ter.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
name: Publish new extension version to TER
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
name: Publish new version to TER
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
TYPO3_EXTENSION_KEY: ${{ secrets.TYPO3_EXTENSION_KEY }}
|
||||||
|
TYPO3_API_TOKEN: ${{ secrets.TYPO3_TER_ACCESS_TOKEN }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Check tag
|
||||||
|
run: |
|
||||||
|
if ! [[ ${{ github.ref }} =~ ^refs/tags/v[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$ ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Get version
|
||||||
|
id: get-version
|
||||||
|
run: echo "version=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Get comment
|
||||||
|
id: get-comment
|
||||||
|
run: |
|
||||||
|
readonly local comment=$(git tag -n10 -l v${{ env.version }} | sed "s/^v[0-9.]*[ ]*//g")
|
||||||
|
|
||||||
|
if [[ -z "${comment// }" ]]; then
|
||||||
|
echo "comment=Released version ${{ env.version }} of ${{ env.TYPO3_EXTENSION_KEY }} -- for details see $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "comment=$comment -- for details see $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: 8.3
|
||||||
|
extensions: intl, mbstring, json, zip, curl
|
||||||
|
tools: composer:v2
|
||||||
|
|
||||||
|
- name: Install tailor
|
||||||
|
run: composer global require typo3/tailor --prefer-dist --no-progress --no-suggest
|
||||||
|
|
||||||
|
- name: Publish to TER
|
||||||
|
run: php ~/.composer/vendor/bin/tailor ter:publish --comment "${{ env.comment }}" ${{ env.version }}
|
||||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.serena/
|
||||||
|
claudedocs/
|
||||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# typo3-conformance
|
||||||
|
|
||||||
|
Evaluate TYPO3 extensions for conformance to official TYPO3 coding standards, architecture patterns, and best practices. Includes automated validation scripts for file structure, PSR-12 compliance, dependency injection, testing infrastructure, and comprehensive conformance scoring with priority-based action items.
|
||||||
991
SKILL.md
Normal file
991
SKILL.md
Normal file
@@ -0,0 +1,991 @@
|
|||||||
|
---
|
||||||
|
name: typo3-conformance
|
||||||
|
description: "Evaluate TYPO3 extensions for conformance to TYPO3 12/13 LTS standards, coding guidelines (PSR-12), and architecture patterns. Use when assessing extension quality, generating conformance reports, identifying technical debt, or planning modernization. Validates: extension architecture, composer.json, ext_emconf.php, ext_* files, v13 deprecations, backend module v13 compliance (ES6 modules, DocHeader, Modal/Notification APIs, Module.html layout, ARIA, extension key consistency, CSRF, icons), dependency injection, services, testing, Extbase patterns, Crowdin, GitHub workflows. Dual scoring (0-100 base + 0-22 excellence). Delegates to typo3-tests and typo3-docs skills for deep analysis. PHP 8.1-8.4 support."
|
||||||
|
license: Complete terms in LICENSE.txt
|
||||||
|
---
|
||||||
|
|
||||||
|
# TYPO3 Extension Conformance Checker
|
||||||
|
|
||||||
|
**Purpose:** Evaluate TYPO3 extensions for conformance to official TYPO3 coding standards, architecture patterns, and best practices.
|
||||||
|
|
||||||
|
**Activation:** This skill activates when analyzing TYPO3 extensions for standards compliance, code quality, or conformance checking.
|
||||||
|
|
||||||
|
## Skill Ecosystem Integration
|
||||||
|
|
||||||
|
Delegate to specialized skills for deep domain analysis:
|
||||||
|
|
||||||
|
**🔧 typo3-tests** (https://github.com/netresearch/typo3-testing-skill)
|
||||||
|
- Deep PHPUnit configuration analysis
|
||||||
|
- Test quality patterns (AAA, mocking, fixtures)
|
||||||
|
- TYPO3 Testing Framework validation
|
||||||
|
- Accurate coverage calculation
|
||||||
|
- Test anti-pattern detection
|
||||||
|
|
||||||
|
**📚 typo3-docs** (https://github.com/netresearch/typo3-docs-skill)
|
||||||
|
- RST syntax and TYPO3 directive validation
|
||||||
|
- Documentation rendering with Docker
|
||||||
|
- Modern tooling detection (guides.xml, screenshots.json)
|
||||||
|
- Cross-reference integrity checks
|
||||||
|
- Official TYPO3 documentation standards
|
||||||
|
|
||||||
|
**Delegation Strategy:**
|
||||||
|
- **Surface-level checks:** Perform directly with this skill
|
||||||
|
- **Deep analysis:** Delegate to specialized skills when available
|
||||||
|
- **Fallback:** Apply basic validation if specialized skills unavailable
|
||||||
|
- **Integration:** Incorporate results into conformance scoring
|
||||||
|
|
||||||
|
## Version Compatibility
|
||||||
|
|
||||||
|
**Target Standards:**
|
||||||
|
- **TYPO3:** 12.4 LTS / 13.x
|
||||||
|
- **PHP:** 8.1 / 8.2 / 8.3 / 8.4
|
||||||
|
- **TYPO3 12 LTS:** Supports PHP 8.1 - 8.4
|
||||||
|
- **TYPO3 13 LTS:** Requires PHP 8.2 - 8.4
|
||||||
|
|
||||||
|
**Reference:** See `references/version-requirements.md` for complete version compatibility matrix and migration paths.
|
||||||
|
|
||||||
|
## Critical Validation References
|
||||||
|
|
||||||
|
**New Advanced Validation Guides:**
|
||||||
|
- **`references/runtests-validation.md`** - Validate Build/Scripts/runTests.sh against Tea extension reference
|
||||||
|
- **`references/development-environment.md`** - Validate DDEV/Docker development environment setup
|
||||||
|
- **`references/directory-structure.md`** - Validate .Build/ vs Build/ directory separation
|
||||||
|
|
||||||
|
These guides provide line-by-line validation strategies, automated validation scripts, and scoring methodologies to ensure comprehensive conformance checks.
|
||||||
|
|
||||||
|
## Evaluation Workflow
|
||||||
|
|
||||||
|
### Step 1: Initial Assessment
|
||||||
|
|
||||||
|
**Identify Extension Context:**
|
||||||
|
- Extension key and location
|
||||||
|
- TYPO3 version compatibility (check composer.json)
|
||||||
|
- Extension type (Extbase plugin, backend module, content element, etc.)
|
||||||
|
- Scope of evaluation (full extension vs specific components)
|
||||||
|
|
||||||
|
**Quick Directory Scan:**
|
||||||
|
```bash
|
||||||
|
# Check for presence of key directories
|
||||||
|
ls -la | grep -E "Classes|Configuration|Resources|Tests|Documentation"
|
||||||
|
|
||||||
|
# Verify required files
|
||||||
|
ls -1 | grep -E "composer.json|ext_emconf.php"
|
||||||
|
|
||||||
|
# Check documentation
|
||||||
|
ls -1 Documentation/ | grep -E "Index.rst|Settings.cfg"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: File Structure Analysis
|
||||||
|
|
||||||
|
**Reference:** `references/extension-architecture.md`
|
||||||
|
|
||||||
|
**Check Required Files:**
|
||||||
|
- [ ] `composer.json` - Complete with PSR-4 autoloading
|
||||||
|
- [ ] `ext_emconf.php` - Proper metadata structure
|
||||||
|
- [ ] `Documentation/Index.rst` - Main documentation entry
|
||||||
|
- [ ] `Documentation/Settings.cfg` - Documentation settings
|
||||||
|
- [ ] `Classes/` directory exists
|
||||||
|
- [ ] `Configuration/` directory exists
|
||||||
|
- [ ] `Resources/` directory exists
|
||||||
|
|
||||||
|
**Validate Directory Structure:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check Classes/ organization
|
||||||
|
find Classes/ -type d | head -20
|
||||||
|
|
||||||
|
# Verify Configuration/ structure
|
||||||
|
ls -R Configuration/
|
||||||
|
|
||||||
|
# Check Resources/ separation
|
||||||
|
ls -R Resources/Private/ Resources/Public/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Common Issues to Flag:**
|
||||||
|
- ❌ PHP files in extension root (except ext_* files)
|
||||||
|
- ❌ Mixed directory naming (Controllers/ vs Controller/)
|
||||||
|
- ❌ Tests not mirroring Classes/ structure
|
||||||
|
- ❌ Missing required documentation files
|
||||||
|
- ❌ Non-standard directory names
|
||||||
|
|
||||||
|
**Output Format:**
|
||||||
|
```markdown
|
||||||
|
## File Structure Conformance
|
||||||
|
|
||||||
|
### ✅ Passed
|
||||||
|
- composer.json present with PSR-4 autoloading
|
||||||
|
- Classes/ directory properly organized
|
||||||
|
- Documentation/ complete with Index.rst and Settings.cfg
|
||||||
|
|
||||||
|
### ❌ Failed
|
||||||
|
- Missing Tests/ directory
|
||||||
|
- Configuration/Backend/ not found (backend modules using deprecated ext_tables.php)
|
||||||
|
- Resources/Private/Language/ missing XLIFF files
|
||||||
|
|
||||||
|
### ⚠️ Warnings
|
||||||
|
- Unusual directory: Classes/Helpers/ (should use Utility/)
|
||||||
|
- ext_tables.php present (consider migrating to Configuration/Backend/)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Coding Standards Analysis
|
||||||
|
|
||||||
|
**Reference:** `references/coding-guidelines.md`
|
||||||
|
|
||||||
|
**PHP Code Style Checks:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find all PHP files
|
||||||
|
find Classes/ Tests/ Configuration/ -name "*.php"
|
||||||
|
|
||||||
|
# Check for PSR-12 violations (sample)
|
||||||
|
grep -r "array(" Classes/ # Should use []
|
||||||
|
grep -r "<?php$" Classes/ | wc -l # Missing declare(strict_types=1)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Manual Inspection Points:**
|
||||||
|
|
||||||
|
**Naming Conventions:**
|
||||||
|
- Variables/methods: camelCase
|
||||||
|
- Classes: UpperCamelCase
|
||||||
|
- Constants: SCREAMING_SNAKE_CASE
|
||||||
|
- Namespaces: match directory structure
|
||||||
|
|
||||||
|
**Code Sample Analysis:**
|
||||||
|
```php
|
||||||
|
// Read sample files
|
||||||
|
cat Classes/Controller/ProductController.php
|
||||||
|
cat Classes/Domain/Model/Product.php
|
||||||
|
cat Classes/Domain/Repository/ProductRepository.php
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check for:**
|
||||||
|
- [ ] `declare(strict_types=1)` at top of all PHP files
|
||||||
|
- [ ] Proper PHPDoc comments on classes and public methods
|
||||||
|
- [ ] Type declarations on all properties and parameters
|
||||||
|
- [ ] Short array syntax `[]` (not `array()`)
|
||||||
|
- [ ] Proper namespace structure
|
||||||
|
- [ ] Correct use statements (sorted and grouped)
|
||||||
|
- [ ] Descriptive method names with verbs
|
||||||
|
- [ ] Proper indentation (4 spaces, no tabs)
|
||||||
|
- [ ] **Inclusive language** - no problematic terminology
|
||||||
|
|
||||||
|
**Inclusive Language Check:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check for non-inclusive terminology
|
||||||
|
grep -ri "master\|slave\|blacklist\|whitelist\|sanity" Classes/ Documentation/ --include="*.php" --include="*.rst" --include="*.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problematic Terms to Avoid:**
|
||||||
|
- ❌ "master/slave" → ✅ "primary/replica", "leader/follower", "main/secondary"
|
||||||
|
- ❌ "blacklist/whitelist" → ✅ "blocklist/allowlist", "denylist/permitlist", "exclusion list/inclusion list"
|
||||||
|
- ❌ "sanity check" → ✅ "confidence check", "validation check", "coherence check"
|
||||||
|
- ❌ "dummy" → ✅ "placeholder", "sample", "test"
|
||||||
|
- ❌ "grandfathered" → ✅ "legacy status", "existing entitlement"
|
||||||
|
|
||||||
|
**TYPO3 Community Values:**
|
||||||
|
The TYPO3 community is committed to inclusive language that welcomes all contributors. Code, comments, and documentation should use terminology that is respectful and professional.
|
||||||
|
|
||||||
|
**Output Format:**
|
||||||
|
```markdown
|
||||||
|
## Coding Standards Conformance
|
||||||
|
|
||||||
|
### ✅ Strengths
|
||||||
|
- All classes use UpperCamelCase naming
|
||||||
|
- Proper type declarations on methods
|
||||||
|
- PHPDoc comments present and complete
|
||||||
|
|
||||||
|
### ❌ Violations
|
||||||
|
- 15 files missing `declare(strict_types=1)`
|
||||||
|
- Classes/Controller/ProductController.php:1
|
||||||
|
- Classes/Service/CalculationService.php:1
|
||||||
|
- 8 instances of old array syntax `array()`
|
||||||
|
- Classes/Utility/ArrayHelper.php:45
|
||||||
|
- Classes/Domain/Model/Product.php:78
|
||||||
|
- 3 methods missing PHPDoc comments
|
||||||
|
- Classes/Service/EmailService.php:calculate()
|
||||||
|
- **5 instances of non-inclusive terminology**
|
||||||
|
- Classes/Service/FilterService.php:12 - "whitelist" → use "inclusion list" or "allowlist"
|
||||||
|
- Classes/Service/FilterService.php:45 - "blacklist" → use "exclusion list" or "blocklist"
|
||||||
|
- Documentation/Configuration/Index.rst:78 - "master configuration" → use "primary configuration" or "main configuration"
|
||||||
|
|
||||||
|
### ⚠️ Style Issues
|
||||||
|
- Inconsistent spacing around concatenation operators (12 instances)
|
||||||
|
- Some variables using snake_case (5 instances)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3.5: Backend Module v13 Compliance (If Applicable)
|
||||||
|
|
||||||
|
**Reference:** `references/backend-module-v13.md`
|
||||||
|
|
||||||
|
**Trigger:** Extension contains backend modules (Configuration/Backend/Modules.php or ext_tables.php with registerModule)
|
||||||
|
|
||||||
|
**Critical Checks:**
|
||||||
|
|
||||||
|
**Extension Key Consistency:**
|
||||||
|
```bash
|
||||||
|
# Check for mixed extension keys
|
||||||
|
grep -rn "EXT:.*/" Resources/Private/Templates/ | grep -v "EXT:${EXTENSION_KEY}/"
|
||||||
|
|
||||||
|
# Verify JavaScript uses correct name
|
||||||
|
grep -rn "alert\|console" Resources/Public/JavaScript/
|
||||||
|
```
|
||||||
|
|
||||||
|
**JavaScript Modernization:**
|
||||||
|
```bash
|
||||||
|
# Check for inline JavaScript (VIOLATION)
|
||||||
|
grep -rn "FooterAssets" Resources/Private/Templates/
|
||||||
|
grep -rn "<script" Resources/Private/Templates/
|
||||||
|
|
||||||
|
# Verify ES6 module exists
|
||||||
|
ls Resources/Public/JavaScript/BackendModule.js
|
||||||
|
|
||||||
|
# Check Modal/Notification API usage
|
||||||
|
grep -E "Modal\.confirm|Notification\.(success|error)" Resources/Public/JavaScript/*.js
|
||||||
|
```
|
||||||
|
|
||||||
|
**Layout Pattern:**
|
||||||
|
```bash
|
||||||
|
# Verify Module.html layout
|
||||||
|
ls Resources/Private/Layouts/Module.html
|
||||||
|
|
||||||
|
# Check all templates use Module layout
|
||||||
|
grep -n "f:layout name=" Resources/Private/Templates/Backend/**/*.html
|
||||||
|
```
|
||||||
|
|
||||||
|
**DocHeader Integration:**
|
||||||
|
```bash
|
||||||
|
# Check IconFactory injection
|
||||||
|
grep "IconFactory" Classes/Controller/Backend/*.php
|
||||||
|
|
||||||
|
# Verify DocHeader buttons
|
||||||
|
grep "addDocHeaderButtons\|makeLinkButton\|makeShortcutButton" Classes/Controller/Backend/*.php
|
||||||
|
```
|
||||||
|
|
||||||
|
**CSRF Protection:**
|
||||||
|
```bash
|
||||||
|
# Check for hardcoded URLs (VIOLATION)
|
||||||
|
grep -rn '"/typo3/' Resources/
|
||||||
|
|
||||||
|
# Verify uriBuilder usage
|
||||||
|
grep "uriFor(" Classes/Controller/Backend/*.php
|
||||||
|
```
|
||||||
|
|
||||||
|
**Accessibility:**
|
||||||
|
```bash
|
||||||
|
# Check ARIA labels
|
||||||
|
grep -rn "aria-label\|role=" Resources/Private/Templates/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Icon Registration:**
|
||||||
|
```bash
|
||||||
|
# Verify modern icon registration
|
||||||
|
ls Configuration/Icons.php
|
||||||
|
|
||||||
|
# Check for deprecated IconRegistry (VIOLATION)
|
||||||
|
grep -rn "IconRegistry" ext_localconf.php
|
||||||
|
```
|
||||||
|
|
||||||
|
**Scoring Impact:**
|
||||||
|
- Extension key consistency: -5 points if violations found
|
||||||
|
- Inline JavaScript: -8 points (non-CSP-compliant)
|
||||||
|
- Missing DocHeader: -4 points (poor UX)
|
||||||
|
- Hardcoded URLs: -6 points (security risk)
|
||||||
|
- No accessibility: -3 points (WCAG non-compliant)
|
||||||
|
- Deprecated icon registration: -2 points
|
||||||
|
|
||||||
|
**Read `references/backend-module-v13.md` for:**
|
||||||
|
- Complete before/after code examples
|
||||||
|
- ES6 module architecture patterns
|
||||||
|
- Modal/Notification API usage
|
||||||
|
- WCAG 2.1 accessibility requirements
|
||||||
|
- 10-phase modernization checklist
|
||||||
|
- Real-world modernization case study (45/100 → 95/100)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 4: PHP Architecture Evaluation
|
||||||
|
|
||||||
|
**Reference:** `references/php-architecture.md`
|
||||||
|
|
||||||
|
**Dependency Injection Analysis:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check for Services.yaml
|
||||||
|
cat Configuration/Services.yaml
|
||||||
|
|
||||||
|
# Find constructors in Controllers/Services
|
||||||
|
grep -A 10 "public function __construct" Classes/Controller/*.php
|
||||||
|
grep -A 10 "public function __construct" Classes/Service/*.php
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check for:**
|
||||||
|
- [ ] `Configuration/Services.yaml` present and properly configured
|
||||||
|
- [ ] Constructor injection used (not GeneralUtility::makeInstance)
|
||||||
|
- [ ] PSR-14 event listeners instead of hooks
|
||||||
|
- [ ] Event classes properly structured (immutable with getters/setters)
|
||||||
|
- [ ] PSR-15 middlewares if applicable
|
||||||
|
- [ ] Extbase patterns (models, repositories, controllers)
|
||||||
|
- [ ] No global state access ($GLOBALS)
|
||||||
|
|
||||||
|
**Anti-Pattern Detection:**
|
||||||
|
```bash
|
||||||
|
# Find deprecated patterns
|
||||||
|
grep -r "GeneralUtility::makeInstance" Classes/
|
||||||
|
grep -r "\$GLOBALS\[" Classes/
|
||||||
|
grep -r "inject[A-Z]" Classes/ # Method injection (check if justified)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Format:**
|
||||||
|
```markdown
|
||||||
|
## PHP Architecture Conformance
|
||||||
|
|
||||||
|
### ✅ Modern Patterns
|
||||||
|
- Dependency injection via constructor in all controllers
|
||||||
|
- PSR-14 events used for extensibility
|
||||||
|
- Configuration/Services.yaml properly configured
|
||||||
|
- Extbase repositories extend base Repository class
|
||||||
|
|
||||||
|
### ❌ Architecture Issues
|
||||||
|
- 12 instances of GeneralUtility::makeInstance()
|
||||||
|
- Classes/Service/LegacyService.php:45
|
||||||
|
- Classes/Utility/DatabaseHelper.php:89
|
||||||
|
- 5 instances of $GLOBALS access
|
||||||
|
- Classes/Controller/ProductController.php:123
|
||||||
|
- No event listeners found (using deprecated hooks)
|
||||||
|
|
||||||
|
### 💡 Recommendations
|
||||||
|
- Migrate hook implementations to PSR-14 events
|
||||||
|
- Refactor GeneralUtility::makeInstance() to constructor injection
|
||||||
|
- Remove global state dependencies
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Testing Infrastructure Assessment
|
||||||
|
|
||||||
|
**Reference:** `references/testing-standards.md`
|
||||||
|
|
||||||
|
**DELEGATION STRATEGY: For Deep Testing Analysis**
|
||||||
|
|
||||||
|
When Testing Standards category needs comprehensive validation, use skill delegation:
|
||||||
|
|
||||||
|
```
|
||||||
|
🔧 Use /skill typo3-tests (if available) for deep analysis:
|
||||||
|
- PHPUnit configuration quality and best practices
|
||||||
|
- Test code patterns (AAA, proper mocking, fixtures)
|
||||||
|
- TYPO3 Testing Framework usage validation
|
||||||
|
- Functional test database handling
|
||||||
|
- Accurate test coverage calculation
|
||||||
|
- Test quality metrics and anti-patterns
|
||||||
|
- Integration with TYPO3 core testing infrastructure
|
||||||
|
|
||||||
|
Return: Detailed testing conformance report with specific issues
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fallback: If typo3-tests skill unavailable, perform basic validation:**
|
||||||
|
|
||||||
|
**Test Coverage Analysis:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check test structure
|
||||||
|
ls -R Tests/
|
||||||
|
|
||||||
|
# Verify PHPUnit configuration
|
||||||
|
cat Build/phpunit/UnitTests.xml
|
||||||
|
cat Build/phpunit/FunctionalTests.xml
|
||||||
|
|
||||||
|
# Count test files
|
||||||
|
find Tests/Unit/ -name "*Test.php" | wc -l
|
||||||
|
find Tests/Functional/ -name "*Test.php" | wc -l
|
||||||
|
```
|
||||||
|
|
||||||
|
**Basic Evaluation Checklist:**
|
||||||
|
- [ ] Tests/Unit/ mirrors Classes/ structure
|
||||||
|
- [ ] Tests/Functional/ present with fixtures
|
||||||
|
- [ ] PHPUnit configuration files present
|
||||||
|
- [ ] Unit tests extend UnitTestCase
|
||||||
|
- [ ] Functional tests extend FunctionalTestCase
|
||||||
|
- [ ] Acceptance tests configured (Codeception)
|
||||||
|
- [ ] Test coverage >70% for new code
|
||||||
|
|
||||||
|
**Note:** Basic validation provides surface-level checks. For production-ready conformance reports, delegate to typo3-tests skill for comprehensive analysis
|
||||||
|
|
||||||
|
**Sample Test Inspection:**
|
||||||
|
```php
|
||||||
|
# Read sample test files
|
||||||
|
cat Tests/Unit/Service/CalculationServiceTest.php
|
||||||
|
cat Tests/Functional/Domain/Repository/ProductRepositoryTest.php
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Format:**
|
||||||
|
```markdown
|
||||||
|
## Testing Standards Conformance
|
||||||
|
|
||||||
|
### ✅ Test Infrastructure
|
||||||
|
- PHPUnit configuration files present
|
||||||
|
- Tests/ directory mirrors Classes/ structure
|
||||||
|
- Unit tests properly extend UnitTestCase
|
||||||
|
- Functional tests use fixtures correctly
|
||||||
|
|
||||||
|
### ❌ Testing Gaps
|
||||||
|
- No Tests/Functional/ directory found
|
||||||
|
- Missing PHPUnit configuration for functional tests
|
||||||
|
- 15 classes without corresponding unit tests:
|
||||||
|
- Classes/Service/EmailService.php
|
||||||
|
- Classes/Utility/StringHelper.php
|
||||||
|
- Classes/Domain/Repository/CategoryRepository.php
|
||||||
|
|
||||||
|
### 📊 Coverage Estimate
|
||||||
|
- Unit test files: 12
|
||||||
|
- Classes without tests: 15
|
||||||
|
- Estimated coverage: ~45% (below 70% recommendation)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: Standards Application
|
||||||
|
|
||||||
|
**Reference:** `references/best-practices.md`
|
||||||
|
|
||||||
|
**When evaluating build scripts** (see `references/runtests-validation.md`):
|
||||||
|
1. Verify Build/Scripts/runTests.sh exists and matches Tea extension reference
|
||||||
|
2. Confirm PHP_VERSION default matches composer.json minimum requirement
|
||||||
|
3. Validate TYPO3_VERSION default matches composer.json target version
|
||||||
|
4. Check PHP version regex includes only supported versions (8.1-8.4)
|
||||||
|
5. Ensure database version lists are current (no EOL versions)
|
||||||
|
6. Verify network name is customized (not "friendsoftypo3-tea")
|
||||||
|
7. Validate test suite paths match actual directory structure
|
||||||
|
|
||||||
|
**When evaluating development environment** (see `references/development-environment.md`):
|
||||||
|
1. Locate DDEV configuration (.ddev/config.yaml) or Docker Compose alternative
|
||||||
|
2. When DDEV present, verify type set to 'typo3'
|
||||||
|
3. Confirm DDEV PHP version matches composer.json minimum
|
||||||
|
4. Validate DDEV docroot matches composer.json web-dir
|
||||||
|
5. Check database is MariaDB 10.11+ or MySQL 8.0+
|
||||||
|
6. Flag missing DevContainer configuration as optional improvement
|
||||||
|
|
||||||
|
**When evaluating directory structure** (see `references/directory-structure.md`):
|
||||||
|
1. Verify Build/ directory exists with committed configuration
|
||||||
|
2. Confirm .Build/ is fully gitignored (entire directory)
|
||||||
|
3. Flag any .Build/ files committed to git as critical violation
|
||||||
|
4. Check cache files are in .Build/, not Build/
|
||||||
|
5. Validate Composer paths reference .Build/ (bin-dir, vendor-dir, web-dir)
|
||||||
|
6. Verify quality tool configs reference .Build/ for cache
|
||||||
|
|
||||||
|
**When evaluating project infrastructure**:
|
||||||
|
1. Check .editorconfig presence for consistent code formatting
|
||||||
|
2. Verify .gitignore properly configured with standard exclusions
|
||||||
|
3. Locate CI/CD pipeline (.github/workflows/ or .gitlab-ci.yml)
|
||||||
|
4. Confirm code quality tools configured (php-cs-fixer, phpstan)
|
||||||
|
5. Validate README.md provides clear setup instructions
|
||||||
|
6. Ensure LICENSE file present with appropriate open-source license
|
||||||
|
7. Check GitHub issue templates (.github/ISSUE_TEMPLATE/config.yml)
|
||||||
|
8. Verify TER publishing workflow (.github/workflows/publish-to-ter.yml)
|
||||||
|
|
||||||
|
**Security Practices:**
|
||||||
|
```bash
|
||||||
|
# Check for security issues
|
||||||
|
grep -r "->exec(" Classes/ # SQL injection risk
|
||||||
|
grep -r "htmlspecialchars" Resources/Private/Templates/ # XSS prevention
|
||||||
|
grep -r "GeneralUtility::validEmail" Classes/ # Email validation
|
||||||
|
```
|
||||||
|
|
||||||
|
**Documentation Quality:**
|
||||||
|
|
||||||
|
**DELEGATION STRATEGY: For Deep Documentation Analysis**
|
||||||
|
|
||||||
|
When Documentation Excellence validation is needed, use skill delegation:
|
||||||
|
|
||||||
|
```
|
||||||
|
📚 Use /skill typo3-docs (if available) for deep analysis:
|
||||||
|
- RST syntax validation and TYPO3 directive compliance
|
||||||
|
- Documentation structure conformance (Index.rst, Settings.cfg)
|
||||||
|
- TYPO3 documentation standards (guides.xml, screenshots.json)
|
||||||
|
- Rendering validation with Docker (official TYPO3 render-guides)
|
||||||
|
- Intersphinx references validation
|
||||||
|
- Code example syntax validation
|
||||||
|
- Cross-reference integrity
|
||||||
|
- Modern documentation tooling detection
|
||||||
|
|
||||||
|
Return: Comprehensive documentation conformance report
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fallback: If typo3-docs skill unavailable, perform basic validation:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check documentation completeness
|
||||||
|
ls -1 Documentation/ | wc -l
|
||||||
|
cat Documentation/Index.rst | head -50
|
||||||
|
|
||||||
|
# Check for required files
|
||||||
|
ls Documentation/Settings.cfg Documentation/guides.xml 2>/dev/null
|
||||||
|
|
||||||
|
# Count RST files for excellence scoring
|
||||||
|
find Documentation/ -name "*.rst" | wc -l
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Basic validation only checks file existence. For production-ready documentation conformance, delegate to typo3-docs skill for comprehensive RST validation and rendering checks
|
||||||
|
|
||||||
|
**Output Format:**
|
||||||
|
```markdown
|
||||||
|
## Best Practices Conformance
|
||||||
|
|
||||||
|
### ✅ Project Infrastructure
|
||||||
|
- .editorconfig present
|
||||||
|
- .gitignore configured properly
|
||||||
|
- README.md with installation instructions
|
||||||
|
- GitHub Actions CI/CD pipeline configured
|
||||||
|
|
||||||
|
### ❌ Missing Components
|
||||||
|
- No phpstan.neon configuration
|
||||||
|
- No .php-cs-fixer configuration
|
||||||
|
- LICENSE file missing
|
||||||
|
- Documentation/ incomplete (missing Developer/ section)
|
||||||
|
|
||||||
|
### 🔒 Security Review
|
||||||
|
- ✅ Query parameters properly escaped
|
||||||
|
- ⚠️ 3 instances of direct SQL queries (check for injection risks)
|
||||||
|
- ✅ Email validation using GeneralUtility::validEmail
|
||||||
|
- ❌ Missing CSRF tokens in 2 forms
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comprehensive Report Generation
|
||||||
|
|
||||||
|
### Final Conformance Report Template
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# TYPO3 Extension Conformance Report
|
||||||
|
|
||||||
|
**Extension:** {extension_name} (v{version})
|
||||||
|
**Evaluation Date:** {date}
|
||||||
|
**TYPO3 Compatibility:** {typo3_versions}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
**Base Conformance Score:** {score}/100
|
||||||
|
**Excellence Indicators:** {excellence_score}/20 (Bonus)
|
||||||
|
**Total Score:** {total_score}/120
|
||||||
|
|
||||||
|
### Base Conformance Breakdown (0-100 points)
|
||||||
|
- Extension Architecture: {score}/20
|
||||||
|
- Coding Guidelines: {score}/20
|
||||||
|
- PHP Architecture: {score}/20
|
||||||
|
- Testing Standards: {score}/20
|
||||||
|
- Best Practices: {score}/20
|
||||||
|
|
||||||
|
### Excellence Indicators (0-20 bonus points)
|
||||||
|
- Community & Internationalization: {score}/6
|
||||||
|
- Advanced Quality Tooling: {score}/7
|
||||||
|
- Documentation Excellence: {score}/4
|
||||||
|
- Extension Configuration: {score}/3
|
||||||
|
|
||||||
|
**Priority Issues:** {count_critical}
|
||||||
|
**Recommendations:** {count_recommendations}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Extension Architecture ({score}/20)
|
||||||
|
|
||||||
|
### ✅ Strengths
|
||||||
|
- {list strengths}
|
||||||
|
|
||||||
|
### ❌ Critical Issues
|
||||||
|
- {list critical issues with file:line references}
|
||||||
|
|
||||||
|
### ⚠️ Warnings
|
||||||
|
- {list warnings}
|
||||||
|
|
||||||
|
### 💡 Recommendations
|
||||||
|
1. {specific actionable recommendations}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Coding Guidelines ({score}/20)
|
||||||
|
|
||||||
|
{repeat same structure}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. PHP Architecture ({score}/20)
|
||||||
|
|
||||||
|
{repeat same structure}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Testing Standards ({score}/20)
|
||||||
|
|
||||||
|
{repeat same structure}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Best Practices ({score}/20)
|
||||||
|
|
||||||
|
{repeat same structure}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Priority Action Items
|
||||||
|
|
||||||
|
### High Priority (Fix Immediately)
|
||||||
|
1. {critical issues that break functionality or security}
|
||||||
|
|
||||||
|
### Medium Priority (Fix Soon)
|
||||||
|
1. {important conformance issues}
|
||||||
|
|
||||||
|
### Low Priority (Improve When Possible)
|
||||||
|
1. {minor style and optimization issues}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Detailed Issue List
|
||||||
|
|
||||||
|
| Category | Severity | File | Line | Issue | Recommendation |
|
||||||
|
|----------|----------|------|------|-------|----------------|
|
||||||
|
| Architecture | Critical | ext_tables.php | - | Using deprecated ext_tables.php | Migrate to Configuration/Backend/Modules.php |
|
||||||
|
| Coding | High | Classes/Controller/ProductController.php | 12 | Missing declare(strict_types=1) | Add at top of file |
|
||||||
|
| Architecture | High | Classes/Service/EmailService.php | 45 | Using GeneralUtility::makeInstance() | Switch to constructor injection |
|
||||||
|
| Testing | Medium | Tests/ | - | No functional tests | Create Tests/Functional/ with fixtures |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migration Guide
|
||||||
|
|
||||||
|
### Migrating from ext_tables.php to Configuration/Backend/
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Before (ext_tables.php) - DEPRECATED
|
||||||
|
ExtensionUtility::registerModule(...);
|
||||||
|
|
||||||
|
// After (Configuration/Backend/Modules.php) - MODERN
|
||||||
|
return [
|
||||||
|
'web_myext' => [
|
||||||
|
'parent' => 'web',
|
||||||
|
...
|
||||||
|
],
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### Converting to Constructor Injection
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Before - DEPRECATED
|
||||||
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||||
|
$repository = GeneralUtility::makeInstance(ProductRepository::class);
|
||||||
|
|
||||||
|
// After - MODERN
|
||||||
|
public function __construct(
|
||||||
|
private readonly ProductRepository $repository
|
||||||
|
) {}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pre-Evaluation Validation Procedures
|
||||||
|
|
||||||
|
Execute these validation steps systematically during conformance evaluation:
|
||||||
|
|
||||||
|
**File Structure Validation**
|
||||||
|
1. Verify composer.json exists with PSR-4 autoloading configuration
|
||||||
|
2. Confirm Classes/ directory follows namespace hierarchy
|
||||||
|
3. Check Configuration/ uses modern structure (no ext_tables.php dependencies)
|
||||||
|
4. Validate Resources/ separates Private/ and Public/ correctly
|
||||||
|
5. Ensure Tests/ mirrors Classes/ structure exactly
|
||||||
|
6. Confirm Documentation/ contains Index.rst and Settings.cfg
|
||||||
|
|
||||||
|
**Coding Standards Validation**
|
||||||
|
1. Scan all PHP files for declare(strict_types=1) at file start
|
||||||
|
2. Verify type declarations on all properties, parameters, and return types
|
||||||
|
3. Check PHPDoc blocks on all public methods and classes
|
||||||
|
4. Run PSR-12 compliance validation via PHPCS
|
||||||
|
5. Validate naming conventions (classes, methods, variables)
|
||||||
|
|
||||||
|
**PHP Architecture Validation**
|
||||||
|
1. Verify constructor injection used throughout codebase
|
||||||
|
2. Check Configuration/Services.yaml exists and configures services
|
||||||
|
3. Confirm PSR-14 events replace deprecated hooks
|
||||||
|
4. Search for GeneralUtility::makeInstance() usage (flag as violation)
|
||||||
|
5. Search for $GLOBALS access (flag as violation)
|
||||||
|
|
||||||
|
**Testing Validation**
|
||||||
|
1. Verify unit tests exist and execute successfully
|
||||||
|
2. Check functional tests include proper fixtures
|
||||||
|
3. Calculate test coverage (target >70%)
|
||||||
|
4. Confirm PHPUnit configuration files present (UnitTests.xml, FunctionalTests.xml)
|
||||||
|
5. Check acceptance tests if web interface present
|
||||||
|
|
||||||
|
**Standards Application Validation**
|
||||||
|
1. Verify development environment configured (DDEV or Docker Compose)
|
||||||
|
2. Check Build/Scripts/runTests.sh exists with accurate configuration
|
||||||
|
3. Validate directory structure (.Build/ vs Build/) separation
|
||||||
|
4. Confirm code quality tools configured (phpstan, php-cs-fixer)
|
||||||
|
5. Verify CI/CD pipeline setup (.github/workflows/ or .gitlab-ci.yml)
|
||||||
|
6. Review security practices implementation
|
||||||
|
7. Validate documentation completeness
|
||||||
|
8. Confirm README.md and LICENSE files present
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reference Material Usage
|
||||||
|
|
||||||
|
**When checking extension architecture patterns**, read `references/extension-architecture.md` for:
|
||||||
|
- Standard directory structures and naming conventions
|
||||||
|
- Required files and their purposes
|
||||||
|
- PSR-4 autoloading configuration examples
|
||||||
|
|
||||||
|
**When validating coding guidelines**, read `references/coding-guidelines.md` for:
|
||||||
|
- PSR-12 compliance requirements
|
||||||
|
- TYPO3-specific code style rules
|
||||||
|
- Type declaration standards
|
||||||
|
|
||||||
|
**When evaluating backend modules**, read `references/backend-module-v13.md` for:
|
||||||
|
- Extension key consistency validation
|
||||||
|
- JavaScript modernization (ES6 modules, no inline scripts)
|
||||||
|
- TYPO3 Modal and Notification API patterns
|
||||||
|
- DocHeader integration (ButtonBar, IconFactory)
|
||||||
|
- Module.html layout requirements
|
||||||
|
- ARIA accessibility standards
|
||||||
|
- Icon registration (Configuration/Icons.php)
|
||||||
|
- CSRF protection via uriBuilder
|
||||||
|
- 10-phase modernization checklist
|
||||||
|
- Real-world case study (45/100 → 95/100 compliance)
|
||||||
|
|
||||||
|
**When evaluating PHP architecture**, read `references/php-architecture.md` for:
|
||||||
|
- Dependency injection patterns
|
||||||
|
- Service configuration examples
|
||||||
|
- PSR-14 event system usage
|
||||||
|
|
||||||
|
**When analyzing testing standards**, read `references/testing-standards.md` for:
|
||||||
|
- PHPUnit configuration patterns
|
||||||
|
- Test structure requirements
|
||||||
|
- Coverage calculation methods
|
||||||
|
|
||||||
|
**When applying best practices**, read `references/best-practices.md` for:
|
||||||
|
- Development environment setup patterns
|
||||||
|
- Build script validation criteria
|
||||||
|
- Directory structure standards
|
||||||
|
|
||||||
|
**For build script validation**, read `references/runtests-validation.md` for:
|
||||||
|
- Line-by-line runTests.sh validation
|
||||||
|
- Tea extension reference comparison
|
||||||
|
- Common configuration errors
|
||||||
|
|
||||||
|
**For development environment checks**, read `references/development-environment.md` for:
|
||||||
|
- DDEV configuration standards
|
||||||
|
- Docker Compose alternatives
|
||||||
|
- PHP and database version requirements
|
||||||
|
|
||||||
|
**For directory structure evaluation**, read `references/directory-structure.md` for:
|
||||||
|
- .Build/ vs Build/ separation rules
|
||||||
|
- Composer path configuration
|
||||||
|
- Gitignore patterns
|
||||||
|
|
||||||
|
**For excellence indicators**, read `references/excellence-indicators.md` for:
|
||||||
|
- Crowdin integration validation
|
||||||
|
- Advanced quality tooling detection
|
||||||
|
- Documentation excellence criteria
|
||||||
|
|
||||||
|
**For Crowdin integration**, read `references/crowdin-integration.md` for:
|
||||||
|
- TYPO3-compliant crowdin.yml patterns
|
||||||
|
- Translation workflow validation
|
||||||
|
- Common misconfigurations
|
||||||
|
|
||||||
|
**For hooks and events migration**, read `references/hooks-and-events.md` for:
|
||||||
|
- PSR-14 event patterns
|
||||||
|
- Deprecated hook identification
|
||||||
|
- Migration strategies
|
||||||
|
|
||||||
|
**When encountering official documentation**, visit:
|
||||||
|
- TYPO3 Core API: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/
|
||||||
|
- Extension Architecture: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/
|
||||||
|
- Coding Guidelines: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/CodingGuidelines/
|
||||||
|
- Testing Documentation: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Testing/
|
||||||
|
- Tea Extension (Best Practice): https://github.com/TYPO3BestPractices/tea
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scoring System
|
||||||
|
|
||||||
|
### Overall Score Calculation
|
||||||
|
|
||||||
|
Each category (Architecture, Coding, PHP Architecture, Testing, Best Practices) is scored out of 20 points:
|
||||||
|
|
||||||
|
**Extension Architecture (20 points)**
|
||||||
|
- Required files present: 8 points
|
||||||
|
- Directory structure conformant: 6 points
|
||||||
|
- Naming conventions followed: 4 points
|
||||||
|
- No critical violations: 2 points
|
||||||
|
|
||||||
|
**Coding Guidelines (20 points)**
|
||||||
|
- PSR-12 compliance: 8 points
|
||||||
|
- Type declarations: 4 points
|
||||||
|
- PHPDoc completeness: 4 points
|
||||||
|
- Naming conventions: 4 points
|
||||||
|
|
||||||
|
**PHP Architecture (20 points)**
|
||||||
|
- Dependency injection: 8 points
|
||||||
|
- No deprecated patterns: 6 points
|
||||||
|
- Modern event system: 4 points
|
||||||
|
- Service configuration: 2 points
|
||||||
|
|
||||||
|
**Testing Standards (20 points)**
|
||||||
|
- Test coverage >70%: 10 points
|
||||||
|
- Proper test structure: 6 points
|
||||||
|
- Configuration files present: 4 points
|
||||||
|
|
||||||
|
**Best Practices (20 points)**
|
||||||
|
- Development environment (DDEV/Docker): 6 points
|
||||||
|
- DDEV configuration present: 4 points
|
||||||
|
- Configuration matches extension requirements: 2 points
|
||||||
|
- OR Docker Compose alternative: 3 points
|
||||||
|
- Build scripts (runTests.sh): 6 points
|
||||||
|
- Script present and executable: 2 points
|
||||||
|
- PHP/TYPO3 versions match extension: 3 points
|
||||||
|
- Database versions current: 1 point
|
||||||
|
- Directory structure (.Build/ vs Build/): 4 points
|
||||||
|
- .Build/ properly gitignored: 2 points
|
||||||
|
- Cache files in correct location: 1 point
|
||||||
|
- Composer paths aligned: 1 point
|
||||||
|
- Quality tools configured: 2 points
|
||||||
|
- Documentation complete: 2 points
|
||||||
|
|
||||||
|
**Note:** Previously this category scored only quality tools (6) and documentation (4). The new comprehensive approach validates development environment setup, build script accuracy, and directory structure standards, providing more thorough conformance assessment.
|
||||||
|
|
||||||
|
### Excellence Indicators (Bonus 0-20 points)
|
||||||
|
|
||||||
|
**Reference:** `references/excellence-indicators.md`
|
||||||
|
|
||||||
|
Excellence indicators are **optional features** that demonstrate exceptional quality and community engagement. Extensions are NOT penalized for missing these features - they provide bonus points only.
|
||||||
|
|
||||||
|
**Total Possible Score: 122 points** (100 base conformance + 22 excellence bonus)
|
||||||
|
|
||||||
|
**Category 1: Community & Internationalization (0-6 points)**
|
||||||
|
- Crowdin integration: 0-2 points
|
||||||
|
- Basic (crowdin.yml exists): +1 point
|
||||||
|
- TYPO3-compliant (preserve_hierarchy, wildcards, proper patterns, no download job): +2 points
|
||||||
|
- See `references/crowdin-integration.md` for comprehensive validation
|
||||||
|
- GitHub issue templates (.github/ISSUE_TEMPLATE/): +1 point
|
||||||
|
- .gitattributes with export-ignore: +1 point
|
||||||
|
- Professional README badges (stability, versions, downloads, CI): +2 points
|
||||||
|
|
||||||
|
**Category 2: Advanced Quality Tooling (0-9 points)**
|
||||||
|
- Fractor configuration (Build/fractor/fractor.php): +2 points
|
||||||
|
- TYPO3 CodingStandards package (typo3/coding-standards in composer.json): +2 points
|
||||||
|
- StyleCI integration (.styleci.yml): +1 point
|
||||||
|
- Makefile with self-documenting help: +1 point
|
||||||
|
- CI testing matrix (multiple PHP/TYPO3 versions): +1 point
|
||||||
|
- TER publishing workflow (.github/workflows/publish-to-ter.yml): +2 points
|
||||||
|
|
||||||
|
**Category 3: Documentation Excellence (0-4 points)**
|
||||||
|
- 50-99 RST files in Documentation/: +1 point
|
||||||
|
- 100-149 RST files: +2 points
|
||||||
|
- 150+ RST files: +3 points
|
||||||
|
- Modern documentation tooling (guides.xml, screenshots.json): +1 point
|
||||||
|
|
||||||
|
**Category 4: Extension Configuration (0-3 points)**
|
||||||
|
- ext_conf_template.txt with proper categorization: +1 point
|
||||||
|
- Composer documentation scripts (doc-init, doc-make, doc-watch): +1 point
|
||||||
|
- Multiple Configuration/Sets/ presets (for different use cases): +1 point
|
||||||
|
|
||||||
|
**Excellence Score Interpretation:**
|
||||||
|
- **0-5 points:** Standard extension (meets requirements)
|
||||||
|
- **6-11 points:** Good practices (actively maintained)
|
||||||
|
- **12-16 points:** Excellent quality (community reference level)
|
||||||
|
- **17-22 points:** Outstanding (georgringer/news level)
|
||||||
|
|
||||||
|
**Example Report Format:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## TYPO3 Extension Conformance Report
|
||||||
|
|
||||||
|
**Extension:** my_extension (v2.0.0)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Score Summary
|
||||||
|
|
||||||
|
**Base Conformance:** 94/100
|
||||||
|
- Extension Architecture: 18/20
|
||||||
|
- Coding Guidelines: 20/20
|
||||||
|
- PHP Architecture: 18/20
|
||||||
|
- Testing Standards: 18/20
|
||||||
|
- Best Practices: 20/20
|
||||||
|
|
||||||
|
**Excellence Indicators:** 14/22 (Bonus)
|
||||||
|
- Community & Internationalization: 5/6
|
||||||
|
- ✅ Crowdin integration (+2)
|
||||||
|
- ✅ Professional README badges (+2)
|
||||||
|
- ✅ GitHub issue templates (+1)
|
||||||
|
- ❌ No .gitattributes export-ignore
|
||||||
|
|
||||||
|
- Advanced Quality Tooling: 7/9
|
||||||
|
- ✅ Fractor configuration (+2)
|
||||||
|
- ✅ TYPO3 CodingStandards (+2)
|
||||||
|
- ✅ Makefile with help (+1)
|
||||||
|
- ✅ TER publishing workflow (+2)
|
||||||
|
- ❌ No StyleCI
|
||||||
|
- ❌ No CI testing matrix
|
||||||
|
|
||||||
|
- Documentation Excellence: 2/4
|
||||||
|
- ✅ 75 RST files (+1)
|
||||||
|
- ✅ Modern tooling (guides.xml) (+1)
|
||||||
|
|
||||||
|
- Extension Configuration: 1/3
|
||||||
|
- ✅ Composer doc scripts (+1)
|
||||||
|
- ❌ No ext_conf_template.txt
|
||||||
|
- ❌ Only one Configuration/Sets/ preset
|
||||||
|
|
||||||
|
**Total Score:** 108/122
|
||||||
|
|
||||||
|
**Rating:** Excellent - This extension demonstrates strong conformance and excellent quality practices.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important Notes:**
|
||||||
|
- Base conformance (0-100) is MANDATORY - this is pass/fail criteria
|
||||||
|
- Excellence indicators (0-22) are OPTIONAL - bonus points for exceptional quality
|
||||||
|
- Extensions scoring 100/100 base are fully conformant, regardless of excellence score
|
||||||
|
- Excellence indicators identify community reference extensions
|
||||||
|
|
||||||
|
**📚 When Documentation/ is Missing or Incomplete:**
|
||||||
|
|
||||||
|
If conformance check reveals missing or incomplete documentation (0-1 points in Documentation Excellence):
|
||||||
|
|
||||||
|
**Recommended Next Step:**
|
||||||
|
```
|
||||||
|
📚 Use typo3-docs skill to create comprehensive documentation:
|
||||||
|
- Creates complete Documentation/ structure (8 sections)
|
||||||
|
- Follows modern TYPO3 13.x standards
|
||||||
|
- Uses card-grid navigation (not plain toctree)
|
||||||
|
- Applies confval directives for configuration
|
||||||
|
- Includes Settings.cfg and proper RST structure
|
||||||
|
- Ready for docs.typo3.org deployment
|
||||||
|
|
||||||
|
After creation: Re-run conformance check to verify improvement
|
||||||
|
```
|
||||||
|
|
||||||
|
**Documentation is the most common conformance gap** - extensions often score 90-95/100 with 0/4 documentation. Creating proper documentation typically improves score to 94-99/100 base + 1-2/20 excellence.
|
||||||
|
|
||||||
|
### Severity Levels
|
||||||
|
|
||||||
|
**Critical (Blocker):**
|
||||||
|
- Security vulnerabilities
|
||||||
|
- Broken functionality
|
||||||
|
- Major architecture violations
|
||||||
|
|
||||||
|
**High (Must Fix):**
|
||||||
|
- Deprecated pattern usage
|
||||||
|
- Missing required files
|
||||||
|
- Significant PSR-12 violations
|
||||||
|
|
||||||
|
**Medium (Should Fix):**
|
||||||
|
- Missing tests
|
||||||
|
- Incomplete documentation
|
||||||
|
- Minor architecture issues
|
||||||
|
|
||||||
|
**Low (Nice to Have):**
|
||||||
|
- Code style inconsistencies
|
||||||
|
- Optional quality improvements
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Example 1: Quick Conformance Check
|
||||||
|
|
||||||
|
```
|
||||||
|
User: "Check if my TYPO3 extension follows current standards"
|
||||||
153
plugin.lock.json
Normal file
153
plugin.lock.json
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
{
|
||||||
|
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||||
|
"pluginId": "gh:netresearch/claude-code-marketplace:skills/typo3-conformance",
|
||||||
|
"normalized": {
|
||||||
|
"repo": null,
|
||||||
|
"ref": "refs/tags/v20251128.0",
|
||||||
|
"commit": "3f5580e1d75ee6d29e7f326445114c5d6c011214",
|
||||||
|
"treeHash": "443d62115c408288889d0a201c17893d3b60bf29f93f13dc941298582d5a04d9",
|
||||||
|
"generatedAt": "2025-11-28T10:27:19.840459Z",
|
||||||
|
"toolVersion": "publish_plugins.py@0.2.0"
|
||||||
|
},
|
||||||
|
"origin": {
|
||||||
|
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||||
|
"branch": "master",
|
||||||
|
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||||
|
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||||
|
},
|
||||||
|
"manifest": {
|
||||||
|
"name": "typo3-conformance",
|
||||||
|
"description": "Evaluate TYPO3 extensions for conformance to official TYPO3 coding standards, architecture patterns, and best practices. Includes automated validation scripts for file structure, PSR-12 compliance, dependency injection, testing infrastructure, and comprehensive conformance scoring with priority-based action items.",
|
||||||
|
"version": "1.0.0-20251126"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "README.md",
|
||||||
|
"sha256": "41d31179104fe126afc42e19e036ba0afa228ad37d4417f973aea624a941f74b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": ".gitignore",
|
||||||
|
"sha256": "d2dbcbb13e12a322c8987474608144d4852424e8db305fa8eacf5cddfc8d50da"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "SKILL.md",
|
||||||
|
"sha256": "68b223fd999a61f844e53158d7c0d2acaf3f2bb4188326ec9daf29c45ddad417"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/v13-deprecations.md",
|
||||||
|
"sha256": "a6374eaf550050c52292a7d6187d044a7990222ec107f0f076948e4c4498d224"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/backend-module-v13.md",
|
||||||
|
"sha256": "45c2a675563204470367611ab401768e52efd82539190e5d2037b09e71e1d16f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/ext-files-validation.md",
|
||||||
|
"sha256": "14856d9c803ca69d8c9628c86297e88b19b48dc12d409d7aae8a59b5956c6fd6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/best-practices.md",
|
||||||
|
"sha256": "305184bfe93aaa6ccada0fb278734a40c192301dfea7dad20163195021f5c1c6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/directory-structure.md",
|
||||||
|
"sha256": "cae94c8f7b5895b2b7ac8c756caa13eb2849858e1e95b2d2d1d4044b255364e5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/crowdin-integration.md",
|
||||||
|
"sha256": "92625735ff042ad0e99e0ec40732a430b2777653a53f15f381f5f725a54633ed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/excellence-indicators.md",
|
||||||
|
"sha256": "9cffb3cb62597c0b3b00624ed580de4d90aec51a50ec513a5e9823981c6b5f32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/testing-standards.md",
|
||||||
|
"sha256": "54ae9cfe6e1e682898f31a7756ca99e79a5d3f982c51646cc5b9dc030332ed1a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/composer-validation.md",
|
||||||
|
"sha256": "6542761396e7bc706925a84745e043e46d310d10d9e2a3ebfb754cd69e369c3e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/extension-architecture.md",
|
||||||
|
"sha256": "4023d9dc4294c464ab48b703d452568c5b42ad3c86665e97bbe0e1ef4851c00c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/ext-emconf-validation.md",
|
||||||
|
"sha256": "991b455b6eb042828c143aa9c08b725106979d7590d7b9b0adf9af5305013824"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/coding-guidelines.md",
|
||||||
|
"sha256": "30388716f84210020d177abfcdf6a28b4b78b35e5b609d7d82c7eaa222348302"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/runtests-validation.md",
|
||||||
|
"sha256": "e6cb139eb19592c42d8b1c87950632245e67fb34476110d63d7d70358900858b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/version-requirements.md",
|
||||||
|
"sha256": "daa17739122f2e1621f01fefc8b1389729b355aafba4258f182c65e127931589"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/hooks-and-events.md",
|
||||||
|
"sha256": "84b4b535d3ac6f8a7908a576cda9f1a05b3658804fcd3130ebd5f33f7d20b59e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/php-architecture.md",
|
||||||
|
"sha256": "a39281ae23be135cff2791780cc4b8e8d6b2584d64776e66f901649c440644fe"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/development-environment.md",
|
||||||
|
"sha256": "6036bfb46a66e58fdb8f22f99461fa50a92582e1e788c960742dee1c1b60456d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scripts/check-file-structure.sh",
|
||||||
|
"sha256": "0da9d33a9d83adda445785fbe04a12345da4df802e700702867a8ac16d06102d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scripts/generate-report.sh",
|
||||||
|
"sha256": "5792c0770a616fa9b23073005626a46e81d85c14c05c46c77aeca2c7b84bd832"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scripts/check-architecture.sh",
|
||||||
|
"sha256": "8e1fb2c2621c701ddd054dbb102fb43aa41c2541b45a14b4f18555c270b1d7a6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scripts/check-conformance.sh",
|
||||||
|
"sha256": "dcc37ae3a2d5eb50197e020d93be9e331348df82ae50fac3817400ca7b7cebb9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scripts/check-phpstan-baseline.sh",
|
||||||
|
"sha256": "e297e01caee0ecb47715e2c4631ba03b62e1fb9e24567c58d6c23f3539841735"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scripts/check-testing.sh",
|
||||||
|
"sha256": "70c8aaaa31b0d708424991c8a5e66786ff472bda356efedf8a75a579e7592e97"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scripts/check-coding-standards.sh",
|
||||||
|
"sha256": "22d162f56484650d1a801f7e418103408dfb643086c36d1684f289f4d5a81dd4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": ".claude-plugin/plugin.json",
|
||||||
|
"sha256": "bd83b72b91c54a3b6aa258d2fe809b0a875a4883037167b2c1998367cdc0686f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": ".github/workflows/publish-to-ter.yml",
|
||||||
|
"sha256": "62eee6d30da96da6b44d376939f46e823b019cd7e79fff19361eccff90795c4d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": ".github/ISSUE_TEMPLATE/config.yml",
|
||||||
|
"sha256": "8c74d3fce410f9dfcf021dee69ed6a840a64477ba3108baf276ad0d34a264ac7"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dirSha256": "443d62115c408288889d0a201c17893d3b60bf29f93f13dc941298582d5a04d9"
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"scannedAt": null,
|
||||||
|
"scannerVersion": null,
|
||||||
|
"flags": []
|
||||||
|
}
|
||||||
|
}
|
||||||
1059
references/backend-module-v13.md
Normal file
1059
references/backend-module-v13.md
Normal file
File diff suppressed because it is too large
Load Diff
917
references/best-practices.md
Normal file
917
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
references/coding-guidelines.md
Normal file
610
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
references/composer-validation.md
Normal file
468
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
references/crowdin-integration.md
Normal file
1344
references/crowdin-integration.md
Normal file
File diff suppressed because it is too large
Load Diff
524
references/development-environment.md
Normal file
524
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
references/directory-structure.md
Normal file
621
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
references/excellence-indicators.md
Normal file
712
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
references/ext-emconf-validation.md
Normal file
610
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
references/ext-files-validation.md
Normal file
413
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
references/extension-architecture.md
Normal file
273
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
references/hooks-and-events.md
Normal file
297
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
references/php-architecture.md
Normal file
918
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
references/runtests-validation.md
Normal file
421
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
references/testing-standards.md
Normal file
558
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
references/v13-deprecations.md
Normal file
408
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
references/version-requirements.md
Normal file
150
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)
|
||||||
180
scripts/check-architecture.sh
Executable file
180
scripts/check-architecture.sh
Executable file
@@ -0,0 +1,180 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#
|
||||||
|
# TYPO3 PHP Architecture Conformance Checker
|
||||||
|
#
|
||||||
|
# Validates dependency injection, services, events, and architectural patterns
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
PROJECT_DIR="${1:-.}"
|
||||||
|
cd "${PROJECT_DIR}"
|
||||||
|
|
||||||
|
echo "## 3. PHP Architecture Conformance"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
has_issues=0
|
||||||
|
|
||||||
|
### Check for Services.yaml
|
||||||
|
echo "### Dependency Injection Configuration"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -f "Configuration/Services.yaml" ]; then
|
||||||
|
echo "- ✅ Configuration/Services.yaml present"
|
||||||
|
|
||||||
|
# Check if it has basic DI configuration
|
||||||
|
if grep -q "autowire: true" Configuration/Services.yaml; then
|
||||||
|
echo " - ✅ Autowiring enabled"
|
||||||
|
else
|
||||||
|
echo " - ⚠️ Autowiring not enabled"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "autoconfigure: true" Configuration/Services.yaml; then
|
||||||
|
echo " - ✅ Autoconfiguration enabled"
|
||||||
|
else
|
||||||
|
echo " - ⚠️ Autoconfiguration not enabled"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "- ❌ Configuration/Services.yaml missing (CRITICAL)"
|
||||||
|
has_issues=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
### Check for deprecated patterns
|
||||||
|
echo ""
|
||||||
|
echo "### Deprecated Pattern Detection"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check for GeneralUtility::makeInstance
|
||||||
|
makeinstance_count=$(grep -r "GeneralUtility::makeInstance" Classes/ 2>/dev/null | wc -l)
|
||||||
|
if [ $makeinstance_count -eq 0 ]; then
|
||||||
|
echo "- ✅ No GeneralUtility::makeInstance() usage found"
|
||||||
|
else
|
||||||
|
echo "- ❌ ${makeinstance_count} instances of GeneralUtility::makeInstance() found"
|
||||||
|
echo " - Should use constructor injection instead"
|
||||||
|
has_issues=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for global state access
|
||||||
|
globals_count=$(grep -r '\$GLOBALS\[' Classes/ 2>/dev/null | wc -l)
|
||||||
|
if [ $globals_count -eq 0 ]; then
|
||||||
|
echo "- ✅ No \$GLOBALS access found"
|
||||||
|
else
|
||||||
|
echo "- ❌ ${globals_count} instances of \$GLOBALS access found"
|
||||||
|
echo " - Should use dependency injection instead"
|
||||||
|
has_issues=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
### Check for constructor injection
|
||||||
|
echo ""
|
||||||
|
echo "### Dependency Injection Patterns"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check for constructors with dependencies
|
||||||
|
constructors=$(grep -r "public function __construct" Classes/ 2>/dev/null | wc -l)
|
||||||
|
if [ $constructors -gt 0 ]; then
|
||||||
|
echo "- ✅ ${constructors} classes use constructors (potential DI)"
|
||||||
|
else
|
||||||
|
echo "- ⚠️ No constructor injection found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for method injection (inject* methods)
|
||||||
|
inject_methods=$(grep -r "public function inject[A-Z]" Classes/ 2>/dev/null | wc -l)
|
||||||
|
if [ $inject_methods -gt 0 ]; then
|
||||||
|
echo "- ⚠️ ${inject_methods} method injection patterns found (inject*)"
|
||||||
|
echo " - Consider using constructor injection instead (more modern)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
### Check for PSR-14 events
|
||||||
|
echo ""
|
||||||
|
echo "### Event System"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check for event classes
|
||||||
|
event_classes=$(find Classes/ -type d -name "Event" 2>/dev/null || echo "")
|
||||||
|
if [ -n "$event_classes" ]; then
|
||||||
|
event_count=$(find Classes/ -path "*/Event/*.php" 2>/dev/null | wc -l)
|
||||||
|
echo "- ✅ ${event_count} event classes found in Classes/Event/"
|
||||||
|
else
|
||||||
|
echo "- ⚠️ No Classes/Event/ directory found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for event listeners
|
||||||
|
listener_classes=$(find Classes/ -type d -name "EventListener" 2>/dev/null || echo "")
|
||||||
|
if [ -n "$listener_classes" ]; then
|
||||||
|
listener_count=$(find Classes/ -path "*/EventListener/*.php" 2>/dev/null | wc -l)
|
||||||
|
echo "- ✅ ${listener_count} event listeners found in Classes/EventListener/"
|
||||||
|
else
|
||||||
|
echo "- ⚠️ No Classes/EventListener/ directory found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
### Check for Extbase patterns
|
||||||
|
echo ""
|
||||||
|
echo "### Extbase Architecture"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check for domain models
|
||||||
|
if [ -d "Classes/Domain/Model" ]; then
|
||||||
|
model_count=$(find Classes/Domain/Model/ -name "*.php" 2>/dev/null | wc -l)
|
||||||
|
echo "- ✅ ${model_count} domain models found"
|
||||||
|
else
|
||||||
|
echo "- ℹ️ No Classes/Domain/Model/ (not using Extbase models)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for repositories
|
||||||
|
if [ -d "Classes/Domain/Repository" ]; then
|
||||||
|
repo_count=$(find Classes/Domain/Repository/ -name "*.php" 2>/dev/null | wc -l)
|
||||||
|
echo "- ✅ ${repo_count} repositories found"
|
||||||
|
|
||||||
|
# Check if repositories extend Repository
|
||||||
|
proper_repos=$(grep -r "extends.*Repository" Classes/Domain/Repository/ 2>/dev/null | wc -l)
|
||||||
|
if [ $proper_repos -gt 0 ]; then
|
||||||
|
echo " - ✅ Repositories extend base Repository class"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "- ℹ️ No Classes/Domain/Repository/ (not using Extbase repositories)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for controllers
|
||||||
|
if [ -d "Classes/Controller" ]; then
|
||||||
|
controller_count=$(find Classes/Controller/ -name "*.php" 2>/dev/null | wc -l)
|
||||||
|
echo "- ✅ ${controller_count} controllers found"
|
||||||
|
|
||||||
|
# Check if controllers extend ActionController
|
||||||
|
proper_controllers=$(grep -r "extends ActionController" Classes/Controller/ 2>/dev/null | wc -l)
|
||||||
|
if [ $proper_controllers -gt 0 ]; then
|
||||||
|
echo " - ✅ Controllers extend ActionController"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
### Check for PSR-15 middleware
|
||||||
|
echo ""
|
||||||
|
echo "### Middleware"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -f "Configuration/RequestMiddlewares.php" ]; then
|
||||||
|
echo "- ✅ Configuration/RequestMiddlewares.php present"
|
||||||
|
|
||||||
|
middleware_count=$(find Classes/ -path "*/Middleware/*.php" 2>/dev/null | wc -l)
|
||||||
|
if [ $middleware_count -gt 0 ]; then
|
||||||
|
echo " - ✅ ${middleware_count} middleware classes found"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "- ℹ️ No Configuration/RequestMiddlewares.php (not using custom middleware)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "### Summary"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ $has_issues -eq 0 ]; then
|
||||||
|
echo "- ✅ **PHP Architecture: PASSED**"
|
||||||
|
else
|
||||||
|
echo "- ⚠️ **PHP Architecture: ISSUES FOUND**"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "---"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
exit $has_issues
|
||||||
168
scripts/check-coding-standards.sh
Executable file
168
scripts/check-coding-standards.sh
Executable file
@@ -0,0 +1,168 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#
|
||||||
|
# TYPO3 Coding Standards Conformance Checker
|
||||||
|
#
|
||||||
|
# Validates PSR-12 compliance and TYPO3-specific code style
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
PROJECT_DIR="${1:-.}"
|
||||||
|
cd "${PROJECT_DIR}"
|
||||||
|
|
||||||
|
echo "## 2. Coding Standards Conformance"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
has_issues=0
|
||||||
|
|
||||||
|
# Find all PHP files in Classes/
|
||||||
|
if [ ! -d "Classes" ]; then
|
||||||
|
echo "- ❌ Classes/ directory not found"
|
||||||
|
echo ""
|
||||||
|
echo "---"
|
||||||
|
echo ""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
php_files=$(find Classes/ -name "*.php" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [ -z "$php_files" ]; then
|
||||||
|
echo "- ⚠️ No PHP files found in Classes/"
|
||||||
|
echo ""
|
||||||
|
echo "---"
|
||||||
|
echo ""
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
total_files=$(echo "$php_files" | wc -l)
|
||||||
|
echo "**Total PHP files:** $total_files"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
### Check for strict types
|
||||||
|
echo "### Strict Types Declaration"
|
||||||
|
echo ""
|
||||||
|
missing_strict=0
|
||||||
|
for file in $php_files; do
|
||||||
|
if ! grep -q "declare(strict_types=1)" "$file"; then
|
||||||
|
missing_strict=$((missing_strict + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $missing_strict -eq 0 ]; then
|
||||||
|
echo "- ✅ All files have declare(strict_types=1)"
|
||||||
|
else
|
||||||
|
echo "- ❌ ${missing_strict} files missing declare(strict_types=1)"
|
||||||
|
has_issues=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
### Check for old array syntax
|
||||||
|
echo ""
|
||||||
|
echo "### Array Syntax"
|
||||||
|
echo ""
|
||||||
|
old_array_count=$(grep -r "array(" Classes/ 2>/dev/null | wc -l)
|
||||||
|
if [ $old_array_count -eq 0 ]; then
|
||||||
|
echo "- ✅ No old array() syntax found"
|
||||||
|
else
|
||||||
|
echo "- ❌ ${old_array_count} instances of old array() syntax (should use [])"
|
||||||
|
has_issues=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
### Check for proper namespace
|
||||||
|
echo ""
|
||||||
|
echo "### Namespace Structure"
|
||||||
|
echo ""
|
||||||
|
files_without_namespace=0
|
||||||
|
for file in $php_files; do
|
||||||
|
if ! grep -q "^namespace " "$file"; then
|
||||||
|
files_without_namespace=$((files_without_namespace + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $files_without_namespace -eq 0 ]; then
|
||||||
|
echo "- ✅ All files have namespace declaration"
|
||||||
|
else
|
||||||
|
echo "- ❌ ${files_without_namespace} files missing namespace declaration"
|
||||||
|
has_issues=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
### Check for PHPDoc comments on classes
|
||||||
|
echo ""
|
||||||
|
echo "### PHPDoc Comments"
|
||||||
|
echo ""
|
||||||
|
classes_without_doc=0
|
||||||
|
for file in $php_files; do
|
||||||
|
# Simple check: look for /** before class declaration
|
||||||
|
if grep -q "^class " "$file" || grep -q "^final class " "$file"; then
|
||||||
|
if ! grep -B 5 "^class \|^final class " "$file" | grep -q "/\*\*"; then
|
||||||
|
classes_without_doc=$((classes_without_doc + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $classes_without_doc -eq 0 ]; then
|
||||||
|
echo "- ✅ All classes have PHPDoc comments"
|
||||||
|
else
|
||||||
|
echo "- ⚠️ ${classes_without_doc} classes missing PHPDoc comments"
|
||||||
|
fi
|
||||||
|
|
||||||
|
### Check naming conventions
|
||||||
|
echo ""
|
||||||
|
echo "### Naming Conventions"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check for snake_case in class names (should be UpperCamelCase)
|
||||||
|
snake_case_classes=$(grep -rE "^(final )?class [a-z][a-z0-9_]*" Classes/ 2>/dev/null | wc -l)
|
||||||
|
if [ $snake_case_classes -gt 0 ]; then
|
||||||
|
echo "- ❌ ${snake_case_classes} classes using incorrect naming (should be UpperCamelCase)"
|
||||||
|
has_issues=1
|
||||||
|
else
|
||||||
|
echo "- ✅ Class naming follows UpperCamelCase convention"
|
||||||
|
fi
|
||||||
|
|
||||||
|
### Check for tabs instead of spaces
|
||||||
|
echo ""
|
||||||
|
echo "### Indentation"
|
||||||
|
echo ""
|
||||||
|
files_with_tabs=0
|
||||||
|
for file in $php_files; do
|
||||||
|
if grep -qP "\t" "$file"; then
|
||||||
|
files_with_tabs=$((files_with_tabs + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $files_with_tabs -eq 0 ]; then
|
||||||
|
echo "- ✅ No tabs found (using spaces for indentation)"
|
||||||
|
else
|
||||||
|
echo "- ❌ ${files_with_tabs} files using tabs instead of spaces"
|
||||||
|
has_issues=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
### Check for proper use statements
|
||||||
|
echo ""
|
||||||
|
echo "### Use Statements"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if use statements are present and not duplicated
|
||||||
|
duplicate_uses=$(grep -rh "^use " Classes/ 2>/dev/null | sort | uniq -d | wc -l)
|
||||||
|
if [ $duplicate_uses -gt 0 ]; then
|
||||||
|
echo "- ⚠️ ${duplicate_uses} duplicate use statements found"
|
||||||
|
else
|
||||||
|
echo "- ✅ No duplicate use statements"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "### Summary"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ $has_issues -eq 0 ]; then
|
||||||
|
echo "- ✅ **Coding standards: PASSED**"
|
||||||
|
else
|
||||||
|
echo "- ⚠️ **Coding standards: ISSUES FOUND**"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "---"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
exit $has_issues
|
||||||
197
scripts/check-conformance.sh
Executable file
197
scripts/check-conformance.sh
Executable file
@@ -0,0 +1,197 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#
|
||||||
|
# TYPO3 Extension Conformance Checker
|
||||||
|
#
|
||||||
|
# Main script to orchestrate all conformance checks
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
PROJECT_DIR="${1:-.}"
|
||||||
|
REPORT_DIR="${PROJECT_DIR}/.conformance-reports"
|
||||||
|
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||||
|
REPORT_FILE="${REPORT_DIR}/conformance_${TIMESTAMP}.md"
|
||||||
|
|
||||||
|
# Script directory
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${BLUE}║ TYPO3 Extension Conformance Checker ║${NC}"
|
||||||
|
echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}Standards Compliance Check:${NC}"
|
||||||
|
echo -e " • TYPO3 Version: ${YELLOW}12.4 LTS / 13.x${NC}"
|
||||||
|
echo -e " • PHP Version: ${YELLOW}8.1 / 8.2 / 8.3 / 8.4${NC}"
|
||||||
|
echo -e " • PSR Standard: ${YELLOW}PSR-12 (Extended Coding Style)${NC}"
|
||||||
|
echo -e " • Architecture: ${YELLOW}Dependency Injection, PSR-14 Events${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Create report directory
|
||||||
|
mkdir -p "${REPORT_DIR}"
|
||||||
|
|
||||||
|
# Check if directory exists
|
||||||
|
if [ ! -d "${PROJECT_DIR}" ]; then
|
||||||
|
echo -e "${RED}✗ Error: Directory ${PROJECT_DIR} not found${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "${PROJECT_DIR}"
|
||||||
|
|
||||||
|
# Check if this is a TYPO3 extension
|
||||||
|
if [ ! -f "composer.json" ] && [ ! -f "ext_emconf.php" ]; then
|
||||||
|
echo -e "${RED}✗ Error: Not a TYPO3 extension (composer.json or ext_emconf.php not found)${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ TYPO3 Extension detected${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Initialize report
|
||||||
|
cat > "${REPORT_FILE}" <<'EOF'
|
||||||
|
# TYPO3 Extension Conformance Report
|
||||||
|
|
||||||
|
**Generated:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
||||||
|
**Project:** $(basename "$(pwd)")
|
||||||
|
|
||||||
|
## Standards Checked
|
||||||
|
|
||||||
|
This conformance check validates your extension against the following standards:
|
||||||
|
|
||||||
|
| Standard | Version/Specification |
|
||||||
|
|----------|----------------------|
|
||||||
|
| **TYPO3 Core** | 12.4 LTS / 13.x |
|
||||||
|
| **PHP** | 8.1 / 8.2 / 8.3 / 8.4 |
|
||||||
|
| **Coding Style** | PSR-12 (Extended Coding Style) |
|
||||||
|
| **Architecture** | Dependency Injection (PSR-11), PSR-14 Events, PSR-15 Middleware |
|
||||||
|
| **Testing** | PHPUnit 10+, TYPO3 Testing Framework |
|
||||||
|
| **Documentation** | reStructuredText (RST), TYPO3 Documentation Standards |
|
||||||
|
|
||||||
|
**Reference Documentation:**
|
||||||
|
- [TYPO3 Extension Architecture](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/)
|
||||||
|
- [TYPO3 Coding Guidelines](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/CodingGuidelines/)
|
||||||
|
- [PHP Architecture](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/PhpArchitecture/)
|
||||||
|
- [Testing Standards](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Testing/)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Category | Score | Status |
|
||||||
|
|----------|-------|--------|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Initialize scores
|
||||||
|
total_score=0
|
||||||
|
max_score=100
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Running conformance checks...${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 1. File Structure Check
|
||||||
|
echo -e "${BLUE}[1/5] Checking file structure...${NC}"
|
||||||
|
if bash "${SCRIPT_DIR}/check-file-structure.sh" "${PROJECT_DIR}" >> "${REPORT_FILE}"; then
|
||||||
|
echo -e "${GREEN} ✓ File structure check complete${NC}"
|
||||||
|
structure_score=18
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW} ⚠ File structure issues found${NC}"
|
||||||
|
structure_score=10
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 2. Coding Standards Check
|
||||||
|
echo -e "${BLUE}[2/5] Checking coding standards...${NC}"
|
||||||
|
if bash "${SCRIPT_DIR}/check-coding-standards.sh" "${PROJECT_DIR}" >> "${REPORT_FILE}"; then
|
||||||
|
echo -e "${GREEN} ✓ Coding standards check complete${NC}"
|
||||||
|
coding_score=18
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW} ⚠ Coding standards issues found${NC}"
|
||||||
|
coding_score=12
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 3. Architecture Check
|
||||||
|
echo -e "${BLUE}[3/5] Checking PHP architecture...${NC}"
|
||||||
|
if bash "${SCRIPT_DIR}/check-architecture.sh" "${PROJECT_DIR}" >> "${REPORT_FILE}"; then
|
||||||
|
echo -e "${GREEN} ✓ Architecture check complete${NC}"
|
||||||
|
arch_score=18
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW} ⚠ Architecture issues found${NC}"
|
||||||
|
arch_score=10
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 4. Testing Check
|
||||||
|
echo -e "${BLUE}[4/6] Checking testing infrastructure...${NC}"
|
||||||
|
if bash "${SCRIPT_DIR}/check-testing.sh" "${PROJECT_DIR}" >> "${REPORT_FILE}"; then
|
||||||
|
echo -e "${GREEN} ✓ Testing check complete${NC}"
|
||||||
|
test_score=16
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW} ⚠ Testing issues found${NC}"
|
||||||
|
test_score=8
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 5. PHPStan Baseline Check
|
||||||
|
echo -e "${BLUE}[5/6] Checking PHPStan baseline hygiene...${NC}"
|
||||||
|
if bash "${SCRIPT_DIR}/check-phpstan-baseline.sh" "${PROJECT_DIR}"; then
|
||||||
|
echo -e "${GREEN} ✓ PHPStan baseline hygiene check passed${NC}"
|
||||||
|
baseline_score=10
|
||||||
|
else
|
||||||
|
echo -e "${RED} ✗ PHPStan baseline violation detected${NC}"
|
||||||
|
baseline_score=0
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 6. Generate comprehensive report
|
||||||
|
echo -e "${BLUE}[6/6] Generating final report...${NC}"
|
||||||
|
bash "${SCRIPT_DIR}/generate-report.sh" "${PROJECT_DIR}" "${REPORT_FILE}" \
|
||||||
|
"${structure_score}" "${coding_score}" "${arch_score}" "${test_score}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Calculate total (including baseline hygiene score)
|
||||||
|
total_score=$((structure_score + coding_score + arch_score + test_score + baseline_score))
|
||||||
|
|
||||||
|
# Display summary
|
||||||
|
echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${BLUE}║ Conformance Results ║${NC}"
|
||||||
|
echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " File Structure: ${structure_score}/20"
|
||||||
|
echo -e " Coding Standards: ${coding_score}/20"
|
||||||
|
echo -e " PHP Architecture: ${arch_score}/20"
|
||||||
|
echo -e " Testing Standards: ${test_score}/20"
|
||||||
|
echo -e " Baseline Hygiene: ${baseline_score}/10"
|
||||||
|
echo -e " Best Practices: 10/10"
|
||||||
|
echo ""
|
||||||
|
echo -e " ${BLUE}Total Score: ${total_score}/100${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ ${total_score} -ge 80 ]; then
|
||||||
|
echo -e "${GREEN}✓ EXCELLENT conformance level${NC}"
|
||||||
|
elif [ ${total_score} -ge 60 ]; then
|
||||||
|
echo -e "${YELLOW}⚠ GOOD conformance level (some improvements recommended)${NC}"
|
||||||
|
elif [ ${total_score} -ge 40 ]; then
|
||||||
|
echo -e "${YELLOW}⚠ FAIR conformance level (several issues to address)${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ POOR conformance level (major improvements needed)${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}Report saved to: ${REPORT_FILE}${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Exit with appropriate code
|
||||||
|
if [ ${total_score} -ge 60 ]; then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
197
scripts/check-file-structure.sh
Executable file
197
scripts/check-file-structure.sh
Executable file
@@ -0,0 +1,197 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#
|
||||||
|
# TYPO3 File Structure Conformance Checker
|
||||||
|
#
|
||||||
|
# Validates extension directory structure and required files
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
PROJECT_DIR="${1:-.}"
|
||||||
|
cd "${PROJECT_DIR}"
|
||||||
|
|
||||||
|
echo "## 1. File Structure Conformance"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Track issues
|
||||||
|
has_issues=0
|
||||||
|
|
||||||
|
echo "### Required Files"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check required files
|
||||||
|
if [ -f "composer.json" ]; then
|
||||||
|
echo "- ✅ composer.json present"
|
||||||
|
else
|
||||||
|
echo "- ❌ composer.json missing (CRITICAL)"
|
||||||
|
has_issues=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "ext_emconf.php" ]; then
|
||||||
|
echo "- ✅ ext_emconf.php present"
|
||||||
|
else
|
||||||
|
echo "- ⚠️ ext_emconf.php missing (required for TER publication)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "Documentation/Index.rst" ]; then
|
||||||
|
echo "- ✅ Documentation/Index.rst present"
|
||||||
|
else
|
||||||
|
echo "- ⚠️ Documentation/Index.rst missing (required for docs.typo3.org)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "Documentation/Settings.cfg" ]; then
|
||||||
|
echo "- ✅ Documentation/Settings.cfg present"
|
||||||
|
else
|
||||||
|
echo "- ⚠️ Documentation/Settings.cfg missing (required for docs.typo3.org)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "### Directory Structure"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check core directories
|
||||||
|
if [ -d "Classes" ]; then
|
||||||
|
echo "- ✅ Classes/ directory present"
|
||||||
|
# Check for common subdirectories
|
||||||
|
if [ -d "Classes/Controller" ]; then
|
||||||
|
echo " - ✅ Classes/Controller/ found"
|
||||||
|
fi
|
||||||
|
if [ -d "Classes/Domain/Model" ]; then
|
||||||
|
echo " - ✅ Classes/Domain/Model/ found"
|
||||||
|
fi
|
||||||
|
if [ -d "Classes/Domain/Repository" ]; then
|
||||||
|
echo " - ✅ Classes/Domain/Repository/ found"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "- ❌ Classes/ directory missing (CRITICAL)"
|
||||||
|
has_issues=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "Configuration" ]; then
|
||||||
|
echo "- ✅ Configuration/ directory present"
|
||||||
|
if [ -d "Configuration/TCA" ]; then
|
||||||
|
echo " - ✅ Configuration/TCA/ found"
|
||||||
|
fi
|
||||||
|
if [ -f "Configuration/Services.yaml" ]; then
|
||||||
|
echo " - ✅ Configuration/Services.yaml found"
|
||||||
|
else
|
||||||
|
echo " - ⚠️ Configuration/Services.yaml missing (recommended)"
|
||||||
|
fi
|
||||||
|
if [ -d "Configuration/Backend" ]; then
|
||||||
|
echo " - ✅ Configuration/Backend/ found"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "- ⚠️ Configuration/ directory missing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "Resources" ]; then
|
||||||
|
echo "- ✅ Resources/ directory present"
|
||||||
|
if [ -d "Resources/Private" ] && [ -d "Resources/Public" ]; then
|
||||||
|
echo " - ✅ Resources/Private/ and Resources/Public/ properly separated"
|
||||||
|
else
|
||||||
|
echo " - ⚠️ Resources/ not properly separated into Private/ and Public/"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "- ⚠️ Resources/ directory missing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "Tests" ]; then
|
||||||
|
echo "- ✅ Tests/ directory present"
|
||||||
|
if [ -d "Tests/Unit" ]; then
|
||||||
|
echo " - ✅ Tests/Unit/ found"
|
||||||
|
else
|
||||||
|
echo " - ⚠️ Tests/Unit/ missing"
|
||||||
|
fi
|
||||||
|
if [ -d "Tests/Functional" ]; then
|
||||||
|
echo " - ✅ Tests/Functional/ found"
|
||||||
|
else
|
||||||
|
echo " - ⚠️ Tests/Functional/ missing"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "- ⚠️ Tests/ directory missing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "### Anti-Patterns Check"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check for PHP files in root (except ext_* files)
|
||||||
|
# Show all files but distinguish between tracked (issues) and untracked (info)
|
||||||
|
tracked_files=()
|
||||||
|
untracked_files=()
|
||||||
|
all_root_php_files=()
|
||||||
|
|
||||||
|
# Find all PHP files in root (except ext_* files)
|
||||||
|
while IFS= read -r file; do
|
||||||
|
filename=$(basename "$file")
|
||||||
|
if [[ "$filename" != ext_*.php ]]; then
|
||||||
|
all_root_php_files+=("$filename")
|
||||||
|
fi
|
||||||
|
done < <(find . -maxdepth 1 -name "*.php" 2>/dev/null || true)
|
||||||
|
|
||||||
|
# Check if files are tracked in git (if git repository exists)
|
||||||
|
if [ -d ".git" ]; then
|
||||||
|
for file in "${all_root_php_files[@]}"; do
|
||||||
|
if git ls-files --error-unmatch "$file" >/dev/null 2>&1; then
|
||||||
|
tracked_files+=("$file")
|
||||||
|
else
|
||||||
|
untracked_files+=("$file")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
# No git repository - treat all files as tracked (potential issues)
|
||||||
|
tracked_files=("${all_root_php_files[@]}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Report tracked files (these are issues)
|
||||||
|
if [ ${#tracked_files[@]} -gt 0 ]; then
|
||||||
|
if [ -d ".git" ]; then
|
||||||
|
echo "- ❌ ${#tracked_files[@]} PHP file(s) in root directory committed to repository:"
|
||||||
|
else
|
||||||
|
echo "- ❌ ${#tracked_files[@]} PHP file(s) found in root directory:"
|
||||||
|
fi
|
||||||
|
for file in "${tracked_files[@]}"; do
|
||||||
|
echo " - ${file} (ISSUE: should be in Classes/ or Build/)"
|
||||||
|
done
|
||||||
|
has_issues=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Report untracked files (informational only)
|
||||||
|
if [ ${#untracked_files[@]} -gt 0 ]; then
|
||||||
|
echo "- ℹ️ ${#untracked_files[@]} untracked PHP file(s) in root (ignored, not committed):"
|
||||||
|
for file in "${untracked_files[@]}"; do
|
||||||
|
echo " - ${file} (local file, not in repository)"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Success message if no files found
|
||||||
|
if [ ${#all_root_php_files[@]} -eq 0 ]; then
|
||||||
|
echo "- ✅ No PHP files in root (except ext_* files)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for deprecated ext_tables.php
|
||||||
|
if [ -f "ext_tables.php" ]; then
|
||||||
|
echo "- ⚠️ ext_tables.php present (consider migrating to Configuration/Backend/)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for wrong directory naming
|
||||||
|
if [ -d "Classes/Controllers" ]; then
|
||||||
|
echo "- ❌ Classes/Controllers/ found (should be Controller/ singular)"
|
||||||
|
has_issues=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "Classes/Helpers" ]; then
|
||||||
|
echo "- ⚠️ Classes/Helpers/ found (should use Utility/ instead)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "---"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Return appropriate exit code
|
||||||
|
if [ ${has_issues} -eq 0 ]; then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
110
scripts/check-phpstan-baseline.sh
Executable file
110
scripts/check-phpstan-baseline.sh
Executable file
@@ -0,0 +1,110 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# TYPO3 Extension Conformance Checker - PHPStan Baseline Validation
|
||||||
|
# Verifies that new code does not add errors to phpstan-baseline.neon
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./check-phpstan-baseline.sh [path-to-extension]
|
||||||
|
#
|
||||||
|
# Returns:
|
||||||
|
# 0 = No baseline additions detected
|
||||||
|
# 1 = New errors added to baseline (violation)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
PROJECT_ROOT="${1:-.}"
|
||||||
|
cd "$PROJECT_ROOT" || exit 1
|
||||||
|
|
||||||
|
echo "Checking PHPStan baseline hygiene in: $PROJECT_ROOT"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Check if git repository
|
||||||
|
if [ ! -d ".git" ]; then
|
||||||
|
echo -e "${YELLOW}⚠️ Not a git repository - skipping baseline check${NC}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find baseline file
|
||||||
|
BASELINE_FILE=""
|
||||||
|
for path in "Build/phpstan-baseline.neon" "phpstan-baseline.neon" ".phpstan/baseline.neon"; do
|
||||||
|
if [ -f "$path" ]; then
|
||||||
|
BASELINE_FILE="$path"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$BASELINE_FILE" ]; then
|
||||||
|
echo -e "${GREEN}✅ No baseline file found - all code passes PHPStan level 10!${NC}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Found baseline file: $BASELINE_FILE"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Check if baseline is modified in current changes
|
||||||
|
if ! git diff --quiet "$BASELINE_FILE" 2>/dev/null; then
|
||||||
|
echo -e "${YELLOW}⚠️ Baseline file has uncommitted changes${NC}"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Extract error counts from diff
|
||||||
|
BEFORE_COUNT=$(git show "HEAD:$BASELINE_FILE" 2>/dev/null | grep -E "^\s+count:\s+[0-9]+" | head -1 | grep -oE "[0-9]+" || echo "0")
|
||||||
|
AFTER_COUNT=$(grep -E "^\s+count:\s+[0-9]+" "$BASELINE_FILE" | head -1 | grep -oE "[0-9]+" || echo "0")
|
||||||
|
|
||||||
|
if [ "$AFTER_COUNT" -gt "$BEFORE_COUNT" ]; then
|
||||||
|
ADDED=$((AFTER_COUNT - BEFORE_COUNT))
|
||||||
|
echo -e "${RED}❌ BASELINE VIOLATION DETECTED${NC}"
|
||||||
|
echo
|
||||||
|
echo "Error count increased: $BEFORE_COUNT → $AFTER_COUNT (+$ADDED errors)"
|
||||||
|
echo
|
||||||
|
echo "New code added $ADDED errors to the baseline!"
|
||||||
|
echo
|
||||||
|
echo "The baseline exists only for legacy code."
|
||||||
|
echo "All new code MUST pass PHPStan level 10 without baseline suppression."
|
||||||
|
echo
|
||||||
|
echo -e "${YELLOW}How to fix:${NC}"
|
||||||
|
echo "1. Run: composer ci:php:stan"
|
||||||
|
echo "2. Review the new errors reported"
|
||||||
|
echo "3. Fix the underlying issues (see coding-guidelines.md for patterns)"
|
||||||
|
echo "4. Revert baseline changes: git checkout $BASELINE_FILE"
|
||||||
|
echo "5. Verify: composer ci:php:stan should pass with original baseline"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
elif [ "$AFTER_COUNT" -lt "$BEFORE_COUNT" ]; then
|
||||||
|
REMOVED=$((BEFORE_COUNT - AFTER_COUNT))
|
||||||
|
echo -e "${GREEN}✅ Excellent! Baseline reduced by $REMOVED errors${NC}"
|
||||||
|
echo
|
||||||
|
echo "You fixed existing baseline issues - great work!"
|
||||||
|
echo
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠️ Baseline modified but count unchanged${NC}"
|
||||||
|
echo
|
||||||
|
echo "Review the baseline diff to ensure changes are intentional:"
|
||||||
|
echo " git diff $BASELINE_FILE"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${GREEN}✅ No changes to baseline file${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for baseline in staged changes
|
||||||
|
if git diff --cached --quiet "$BASELINE_FILE" 2>/dev/null; then
|
||||||
|
echo -e "${GREEN}✅ No baseline changes staged for commit${NC}"
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
echo -e "${YELLOW}⚠️ Warning: Baseline file is staged for commit${NC}"
|
||||||
|
echo
|
||||||
|
echo "Review staged baseline changes:"
|
||||||
|
echo " git diff --cached $BASELINE_FILE"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo -e "${GREEN}✅ PHPStan baseline hygiene check passed${NC}"
|
||||||
|
exit 0
|
||||||
199
scripts/check-testing.sh
Executable file
199
scripts/check-testing.sh
Executable file
@@ -0,0 +1,199 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#
|
||||||
|
# TYPO3 Testing Standards Conformance Checker
|
||||||
|
#
|
||||||
|
# Validates testing infrastructure and test coverage
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
PROJECT_DIR="${1:-.}"
|
||||||
|
cd "${PROJECT_DIR}"
|
||||||
|
|
||||||
|
echo "## 4. Testing Standards Conformance"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
has_issues=0
|
||||||
|
|
||||||
|
### Check for Tests directory
|
||||||
|
echo "### Test Infrastructure"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ ! -d "Tests" ]; then
|
||||||
|
echo "- ❌ Tests/ directory missing (CRITICAL)"
|
||||||
|
has_issues=1
|
||||||
|
echo ""
|
||||||
|
echo "---"
|
||||||
|
echo ""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "- ✅ Tests/ directory present"
|
||||||
|
|
||||||
|
### Check for PHPUnit configuration
|
||||||
|
echo ""
|
||||||
|
echo "### PHPUnit Configuration"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -f "Build/phpunit/UnitTests.xml" ] || [ -f "phpunit.xml" ]; then
|
||||||
|
echo "- ✅ Unit test configuration found"
|
||||||
|
else
|
||||||
|
echo "- ❌ No Unit test configuration (Build/phpunit/UnitTests.xml or phpunit.xml)"
|
||||||
|
has_issues=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "Build/phpunit/FunctionalTests.xml" ]; then
|
||||||
|
echo "- ✅ Functional test configuration found"
|
||||||
|
else
|
||||||
|
echo "- ⚠️ No Functional test configuration (Build/phpunit/FunctionalTests.xml)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
echo ""
|
||||||
|
echo "### Unit Tests"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -d "Tests/Unit" ]; then
|
||||||
|
unit_test_count=$(find Tests/Unit/ -name "*Test.php" 2>/dev/null | wc -l)
|
||||||
|
echo "- ✅ Tests/Unit/ directory present"
|
||||||
|
echo " - **${unit_test_count} unit test files found**"
|
||||||
|
|
||||||
|
if [ $unit_test_count -eq 0 ]; then
|
||||||
|
echo " - ⚠️ No unit tests found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if tests mirror Classes structure
|
||||||
|
if [ -d "Classes/Controller" ] && [ ! -d "Tests/Unit/Controller" ]; then
|
||||||
|
echo " - ⚠️ Tests/Unit/Controller/ missing (Classes/Controller/ exists)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "Classes/Service" ] && [ ! -d "Tests/Unit/Service" ]; then
|
||||||
|
echo " - ⚠️ Tests/Unit/Service/ missing (Classes/Service/ exists)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "Classes/Domain/Repository" ] && [ ! -d "Tests/Unit/Domain/Repository" ]; then
|
||||||
|
echo " - ⚠️ Tests/Unit/Domain/Repository/ missing (Classes/Domain/Repository/ exists)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "- ❌ Tests/Unit/ directory missing"
|
||||||
|
has_issues=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
### Functional Tests
|
||||||
|
echo ""
|
||||||
|
echo "### Functional Tests"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -d "Tests/Functional" ]; then
|
||||||
|
func_test_count=$(find Tests/Functional/ -name "*Test.php" 2>/dev/null | wc -l)
|
||||||
|
echo "- ✅ Tests/Functional/ directory present"
|
||||||
|
echo " - **${func_test_count} functional test files found**"
|
||||||
|
|
||||||
|
# Check for fixtures
|
||||||
|
if [ -d "Tests/Functional/Fixtures" ]; then
|
||||||
|
fixture_count=$(find Tests/Functional/Fixtures/ -name "*.csv" -o -name "*.xml" 2>/dev/null | wc -l)
|
||||||
|
echo " - ✅ Tests/Functional/Fixtures/ found (${fixture_count} fixture files)"
|
||||||
|
else
|
||||||
|
if [ $func_test_count -gt 0 ]; then
|
||||||
|
echo " - ⚠️ No Tests/Functional/Fixtures/ (functional tests may need fixtures)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "- ⚠️ Tests/Functional/ directory missing"
|
||||||
|
echo " - Functional tests recommended for repository and database operations"
|
||||||
|
fi
|
||||||
|
|
||||||
|
### Acceptance Tests
|
||||||
|
echo ""
|
||||||
|
echo "### Acceptance Tests"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -d "Tests/Acceptance" ]; then
|
||||||
|
accept_test_count=$(find Tests/Acceptance/ -name "*Cest.php" 2>/dev/null | wc -l)
|
||||||
|
echo "- ✅ Tests/Acceptance/ directory present"
|
||||||
|
echo " - **${accept_test_count} acceptance test files found**"
|
||||||
|
|
||||||
|
if [ -f "Tests/codeception.yml" ]; then
|
||||||
|
echo " - ✅ codeception.yml configuration found"
|
||||||
|
else
|
||||||
|
echo " - ⚠️ codeception.yml configuration missing"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "- ℹ️ Tests/Acceptance/ not found (optional for most extensions)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
### Test Coverage Estimate
|
||||||
|
echo ""
|
||||||
|
echo "### Test Coverage Estimate"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -d "Classes" ]; then
|
||||||
|
class_count=$(find Classes/ -name "*.php" 2>/dev/null | wc -l)
|
||||||
|
|
||||||
|
if [ -d "Tests/Unit" ]; then
|
||||||
|
unit_count=$(find Tests/Unit/ -name "*Test.php" 2>/dev/null | wc -l)
|
||||||
|
else
|
||||||
|
unit_count=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "Tests/Functional" ]; then
|
||||||
|
func_count=$(find Tests/Functional/ -name "*Test.php" 2>/dev/null | wc -l)
|
||||||
|
else
|
||||||
|
func_count=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
total_tests=$((unit_count + func_count))
|
||||||
|
|
||||||
|
echo "- **Total Classes:** $class_count"
|
||||||
|
echo "- **Total Tests:** $total_tests"
|
||||||
|
|
||||||
|
if [ $class_count -gt 0 ]; then
|
||||||
|
coverage_ratio=$((total_tests * 100 / class_count))
|
||||||
|
echo "- **Test Ratio:** ${coverage_ratio}%"
|
||||||
|
|
||||||
|
if [ $coverage_ratio -ge 70 ]; then
|
||||||
|
echo " - ✅ Good test coverage (≥70%)"
|
||||||
|
elif [ $coverage_ratio -ge 50 ]; then
|
||||||
|
echo " - ⚠️ Moderate test coverage (50-70%)"
|
||||||
|
else
|
||||||
|
echo " - ❌ Low test coverage (<50%)"
|
||||||
|
has_issues=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
### Check for testing framework dependency
|
||||||
|
echo ""
|
||||||
|
echo "### Testing Framework Dependency"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -f "composer.json" ]; then
|
||||||
|
if grep -q "typo3/testing-framework" composer.json; then
|
||||||
|
echo "- ✅ typo3/testing-framework in composer.json"
|
||||||
|
else
|
||||||
|
echo "- ⚠️ typo3/testing-framework not found in composer.json"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "phpunit/phpunit" composer.json; then
|
||||||
|
echo "- ✅ phpunit/phpunit in composer.json"
|
||||||
|
else
|
||||||
|
echo "- ⚠️ phpunit/phpunit not found in composer.json"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "### Summary"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ $has_issues -eq 0 ]; then
|
||||||
|
echo "- ✅ **Testing Standards: PASSED**"
|
||||||
|
else
|
||||||
|
echo "- ⚠️ **Testing Standards: ISSUES FOUND**"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "---"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
exit $has_issues
|
||||||
143
scripts/generate-report.sh
Executable file
143
scripts/generate-report.sh
Executable file
@@ -0,0 +1,143 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#
|
||||||
|
# Generate final conformance report with recommendations
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
PROJECT_DIR="${1}"
|
||||||
|
REPORT_FILE="${2}"
|
||||||
|
STRUCTURE_SCORE="${3:-15}"
|
||||||
|
CODING_SCORE="${4:-15}"
|
||||||
|
ARCH_SCORE="${5:-15}"
|
||||||
|
TEST_SCORE="${6:-15}"
|
||||||
|
|
||||||
|
cd "${PROJECT_DIR}"
|
||||||
|
|
||||||
|
# Calculate total
|
||||||
|
TOTAL_SCORE=$((STRUCTURE_SCORE + CODING_SCORE + ARCH_SCORE + TEST_SCORE + 10))
|
||||||
|
|
||||||
|
# Update summary table
|
||||||
|
sed -i "s/| Extension Architecture .*/| Extension Architecture | ${STRUCTURE_SCORE}\/20 | $(if [ ${STRUCTURE_SCORE} -ge 15 ]; then echo "✅ Passed"; else echo "⚠️ Issues"; fi) |/" "${REPORT_FILE}"
|
||||||
|
sed -i "/| Extension Architecture /a | Coding Guidelines | ${CODING_SCORE}/20 | $(if [ ${CODING_SCORE} -ge 15 ]; then echo "✅ Passed"; else echo "⚠️ Issues"; fi) |" "${REPORT_FILE}" 2>/dev/null || true
|
||||||
|
sed -i "/| Coding Guidelines /a | PHP Architecture | ${ARCH_SCORE}/20 | $(if [ ${ARCH_SCORE} -ge 15 ]; then echo "✅ Passed"; else echo "⚠️ Issues"; fi) |" "${REPORT_FILE}" 2>/dev/null || true
|
||||||
|
sed -i "/| PHP Architecture /a | Testing Standards | ${TEST_SCORE}/20 | $(if [ ${TEST_SCORE} -ge 15 ]; then echo "✅ Passed"; else echo "⚠️ Issues"; fi) |" "${REPORT_FILE}" 2>/dev/null || true
|
||||||
|
sed -i "/| Testing Standards /a | Best Practices | 10/20 | ℹ️ Partial |" "${REPORT_FILE}" 2>/dev/null || true
|
||||||
|
sed -i "/| Best Practices /a | **TOTAL** | **${TOTAL_SCORE}/100** | $(if [ ${TOTAL_SCORE} -ge 80 ]; then echo "✅ Excellent"; elif [ ${TOTAL_SCORE} -ge 60 ]; then echo "✅ Good"; else echo "⚠️ Fair"; fi) |" "${REPORT_FILE}" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Add final sections
|
||||||
|
cat >> "${REPORT_FILE}" <<EOF
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Best Practices Assessment
|
||||||
|
|
||||||
|
### Project Infrastructure
|
||||||
|
- **README.md:** $(if [ -f "README.md" ]; then echo "✅ Present"; else echo "❌ Missing"; fi)
|
||||||
|
- **LICENSE:** $(if [ -f "LICENSE" ]; then echo "✅ Present"; else echo "❌ Missing"; fi)
|
||||||
|
- **.editorconfig:** $(if [ -f ".editorconfig" ]; then echo "✅ Present"; else echo "⚠️ Missing"; fi)
|
||||||
|
- **.gitignore:** $(if [ -f ".gitignore" ]; then echo "✅ Present"; else echo "⚠️ Missing"; fi)
|
||||||
|
|
||||||
|
### Code Quality Tools
|
||||||
|
- **php-cs-fixer:** $(if [ -f ".php-cs-fixer.dist.php" ] || [ -f ".php-cs-fixer.php" ] || [ -f "Build/.php-cs-fixer.dist.php" ] || [ -f "Build/.php-cs-fixer.php" ]; then echo "✅ Configured"; else echo "⚠️ Not configured"; fi)
|
||||||
|
- **phpstan:** $(if [ -f "phpstan.neon" ] || [ -f "phpstan.neon.dist" ] || [ -f "Build/phpstan.neon" ] || [ -f "Build/phpstan.neon.dist" ]; then echo "✅ Configured"; else echo "⚠️ Not configured"; fi)
|
||||||
|
- **rector:** $(if [ -f "rector.php" ] || [ -f "Build/rector.php" ]; then echo "✅ Configured"; else echo "ℹ️ Not configured"; fi)
|
||||||
|
|
||||||
|
### CI/CD Pipeline
|
||||||
|
- **GitHub Actions:** $(if [ -d ".github/workflows" ]; then echo "✅ Configured"; else echo "⚠️ Not found"; fi)
|
||||||
|
- **GitLab CI:** $(if [ -f ".gitlab-ci.yml" ]; then echo "✅ Configured"; else echo "ℹ️ Not found"; fi)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overall Assessment
|
||||||
|
|
||||||
|
**Total Score: ${TOTAL_SCORE}/100**
|
||||||
|
|
||||||
|
$(if [ ${TOTAL_SCORE} -ge 80 ]; then
|
||||||
|
cat <<END
|
||||||
|
### ✅ EXCELLENT Conformance Level
|
||||||
|
|
||||||
|
Your TYPO3 extension demonstrates strong adherence to official standards and best practices.
|
||||||
|
|
||||||
|
**Strengths:**
|
||||||
|
- Well-structured architecture following TYPO3 conventions
|
||||||
|
- Modern PHP patterns with dependency injection
|
||||||
|
- Good code quality and testing coverage
|
||||||
|
- Proper documentation and infrastructure
|
||||||
|
|
||||||
|
**Minor Improvements:**
|
||||||
|
- Continue maintaining high standards
|
||||||
|
- Keep dependencies updated
|
||||||
|
- Monitor code coverage trends
|
||||||
|
END
|
||||||
|
elif [ ${TOTAL_SCORE} -ge 60 ]; then
|
||||||
|
cat <<END
|
||||||
|
### ✅ GOOD Conformance Level
|
||||||
|
|
||||||
|
Your TYPO3 extension follows most standards with some areas for improvement.
|
||||||
|
|
||||||
|
**Next Steps:**
|
||||||
|
1. Address critical issues identified above
|
||||||
|
2. Improve test coverage
|
||||||
|
3. Add missing configuration files
|
||||||
|
4. Update deprecated patterns
|
||||||
|
|
||||||
|
**Timeline:** 2-4 weeks for improvements
|
||||||
|
END
|
||||||
|
else
|
||||||
|
cat <<END
|
||||||
|
### ⚠️ FAIR Conformance Level
|
||||||
|
|
||||||
|
Your TYPO3 extension requires significant improvements to meet TYPO3 standards.
|
||||||
|
|
||||||
|
**Priority Actions:**
|
||||||
|
1. Fix critical file structure issues
|
||||||
|
2. Migrate deprecated patterns (GeneralUtility::makeInstance, \$GLOBALS)
|
||||||
|
3. Add comprehensive testing infrastructure
|
||||||
|
4. Improve code quality (strict types, PHPDoc, PSR-12)
|
||||||
|
5. Add project infrastructure (CI/CD, quality tools)
|
||||||
|
|
||||||
|
**Timeline:** 4-8 weeks for comprehensive improvements
|
||||||
|
END
|
||||||
|
fi)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Action Checklist
|
||||||
|
|
||||||
|
### High Priority (Fix Now)
|
||||||
|
$(if [ ${STRUCTURE_SCORE} -lt 15 ]; then echo "- [ ] Fix critical file structure issues (missing required files/directories)"; fi)
|
||||||
|
$(if grep -q "GeneralUtility::makeInstance" Classes/ 2>/dev/null; then echo "- [ ] Migrate GeneralUtility::makeInstance to constructor injection"; fi)
|
||||||
|
$(if grep -q '\$GLOBALS\[' Classes/ 2>/dev/null; then echo "- [ ] Remove \$GLOBALS access, use dependency injection"; fi)
|
||||||
|
$(if [ ! -f "Configuration/Services.yaml" ]; then echo "- [ ] Add Configuration/Services.yaml with DI configuration"; fi)
|
||||||
|
|
||||||
|
### Medium Priority (Fix Soon)
|
||||||
|
$(if [ ${CODING_SCORE} -lt 15 ]; then echo "- [ ] Add declare(strict_types=1) to all PHP files"; fi)
|
||||||
|
$(if [ ${CODING_SCORE} -lt 15 ]; then echo "- [ ] Replace array() with [] short syntax"; fi)
|
||||||
|
$(if [ ${TEST_SCORE} -lt 15 ]; then echo "- [ ] Add unit tests for untested classes"; fi)
|
||||||
|
$(if [ ! -d "Tests/Functional" ]; then echo "- [ ] Add functional tests for repositories"; fi)
|
||||||
|
|
||||||
|
### Low Priority (Improve When Possible)
|
||||||
|
$(if [ ! -f ".php-cs-fixer.dist.php" ] && [ ! -f ".php-cs-fixer.php" ] && [ ! -f "Build/.php-cs-fixer.dist.php" ] && [ ! -f "Build/.php-cs-fixer.php" ]; then echo "- [ ] Configure PHP CS Fixer"; fi)
|
||||||
|
$(if [ ! -f "phpstan.neon" ] && [ ! -f "phpstan.neon.dist" ] && [ ! -f "Build/phpstan.neon" ] && [ ! -f "Build/phpstan.neon.dist" ]; then echo "- [ ] Configure PHPStan for static analysis"; fi)
|
||||||
|
$(if [ ! -d ".github/workflows" ]; then echo "- [ ] Set up CI/CD pipeline (GitHub Actions)"; fi)
|
||||||
|
- [ ] Improve PHPDoc comments coverage
|
||||||
|
- [ ] Add .editorconfig for consistent formatting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- **TYPO3 Extension Architecture:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/
|
||||||
|
- **Coding Guidelines:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/CodingGuidelines/
|
||||||
|
- **Dependency Injection:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/DependencyInjection/
|
||||||
|
- **Testing Documentation:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Testing/
|
||||||
|
- **Tea Extension (Best Practice):** https://github.com/TYPO3BestPractices/tea
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Report generated by TYPO3 Extension Conformance Checker*
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Final report generated successfully"
|
||||||
Reference in New Issue
Block a user