15 KiB
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:
# Composer generated files
.Build/
Validation Checklist
1. Build/ Directory Structure
Check for committed configuration files:
ls -la Build/
Required files for comprehensive quality:
Build/phpstan.neon- Static analysis configurationBuild/php-cs-fixer.php- Code style configurationBuild/phpunit/UnitTests.xml- Unit test configurationBuild/Scripts/runTests.sh- Test orchestration
Optional but recommended:
Build/phpunit/FunctionalTests.xml- Functional test configurationBuild/phpstan-baseline.neon- Known issues baselineBuild/rector.phporBuild/rector/rector.php- Refactoring rules
Severity if missing: 🟡 Medium - Quality tools not standardized
2. .Build/ Directory Gitignore
Check .gitignore for .Build/ exclusion:
grep -E '^\\.Build/' .gitignore
Expected pattern:
.Build/
Alternative patterns (also valid):
/.Build/
.Build/*
Check for accidental commits:
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:
# 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:
{
"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:
# 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:
// composer.json
{
"scripts": {
"ci:cgl": [
"php-cs-fixer fix --cache-file .Build/.php-cs-fixer.cache --dry-run --diff"
]
}
}
// Build/php-cs-fixer.php
return (new PhpCsFixer\Config())
->setCacheFile('.Build/.php-cs-fixer.cache')
->setRules([...]);
# 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):
# Composer-generated files
.Build/
composer.lock
Tea composer.json (relevant excerpt):
{
"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:
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:
# Remove from wrong location
rm Build/.php-cs-fixer.cache Build/.phpunit.result.cache
# Update configuration to use .Build/
Update php-cs-fixer config:
// Build/php-cs-fixer.php
return (new PhpCsFixer\Config())
->setCacheFile('.Build/.php-cs-fixer.cache') // ✅ Correct location
// ...
Update composer scripts:
{
"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:
git ls-files .Build/
# Should be empty
Problem: Generated files tracked in git (vendor/, bin/, etc.)
Fix:
# 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:
ls -la Build/
# Directory doesn't exist
Problem: No standardized quality tool configuration
Fix:
# 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:
# 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:
# 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:
### 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:
#!/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
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
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
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/