8.9 KiB
8.9 KiB
Quality Tools for TYPO3 Development
Automated code quality and static analysis tools for TYPO3 extensions.
Overview
- PHPStan: Static analysis for type safety and bugs
- Rector: Automated code refactoring and modernization
- php-cs-fixer: Code style enforcement (PSR-12, TYPO3 CGL)
- phplint: PHP syntax validation
PHPStan
Installation
composer require --dev phpstan/phpstan phpstan/phpstan-strict-rules saschaegerer/phpstan-typo3
Configuration
Create Build/phpstan.neon:
includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon
- vendor/saschaegerer/phpstan-typo3/extension.neon
parameters:
level: max # Level 10 - maximum strictness
paths:
- Classes
- Tests
excludePaths:
- Tests/Acceptance/_output/*
reportUnmatchedIgnoredErrors: true
checkGenericClassInNonGenericObjectType: false
checkMissingIterableValueType: false
Running PHPStan
# Via runTests.sh
Build/Scripts/runTests.sh -s phpstan
# Directly
vendor/bin/phpstan analyze --configuration Build/phpstan.neon
# With baseline (ignore existing errors)
vendor/bin/phpstan analyze --generate-baseline Build/phpstan-baseline.neon
# Clear cache
vendor/bin/phpstan clear-result-cache
PHPStan Rule Levels
Level 0-10 (use max for level 10): Increasing strictness
- Level 0: Basic checks (undefined variables, unknown functions)
- Level 5: Type checks, unknown properties, unknown methods
- Level 9: Strict mixed types, unused parameters
- Level 10 (max): Maximum strictness - explicit mixed types, pure functions
Recommendation: Start with level 5, aim for level 10 (max) in modern TYPO3 13 projects.
Why Level 10?
- Enforces explicit type declarations (
mixedmust be declared, not implicit) - Catches more potential bugs at development time
- Aligns with TYPO3 13 strict typing standards (
declare(strict_types=1)) - Required for PHPStan Level 10 compliant extensions
Ignoring Errors
/** @phpstan-ignore-next-line */
$value = $this->legacyMethod();
// Or in neon file
parameters:
ignoreErrors:
- '#Call to an undefined method.*::getRepository\(\)#'
TYPO3-Specific Rules
// PHPStan understands TYPO3 classes
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('pages');
// ✅ PHPStan knows this returns QueryBuilder
// Detects TYPO3 API misuse
TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(MyService::class);
// ✅ Checks if MyService is a valid class
Rector
Installation
composer require --dev rector/rector ssch/typo3-rector
Configuration
Create rector.php:
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\LevelSetList;
use Rector\Set\ValueObject\SetList;
use Ssch\TYPO3Rector\Set\Typo3SetList;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/Classes',
__DIR__ . '/Tests',
])
->withSkip([
__DIR__ . '/Tests/Acceptance/_output',
])
->withPhpSets(php82: true)
->withSets([
LevelSetList::UP_TO_PHP_82,
SetList::CODE_QUALITY,
SetList::DEAD_CODE,
SetList::TYPE_DECLARATION,
Typo3SetList::TYPO3_13,
]);
Running Rector
# Dry run (show changes)
vendor/bin/rector process --dry-run
# Apply changes
vendor/bin/rector process
# Via runTests.sh
Build/Scripts/runTests.sh -s rector
Common Refactorings
TYPO3 API Modernization:
// Before
$GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*', 'pages', 'uid=1');
// After (Rector auto-refactors)
GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable('pages')
->select(['*'], 'pages', ['uid' => 1])
->fetchAllAssociative();
Type Declarations:
// Before
public function process($data)
{
return $data;
}
// After
public function process(array $data): array
{
return $data;
}
php-cs-fixer
Installation
composer require --dev friendsofphp/php-cs-fixer
Configuration
Create Build/php-cs-fixer.php:
<?php
declare(strict_types=1);
$finder = (new PhpCsFixer\Finder())
->in(__DIR__ . '/../Classes')
->in(__DIR__ . '/../Tests')
->exclude('_output');
return (new PhpCsFixer\Config())
->setRules([
'@PSR12' => true,
'@PhpCsFixer' => true,
'array_syntax' => ['syntax' => 'short'],
'concat_space' => ['spacing' => 'one'],
'declare_strict_types' => true,
'ordered_imports' => ['sort_algorithm' => 'alpha'],
'no_unused_imports' => true,
'single_line_throw' => false,
'phpdoc_align' => false,
'phpdoc_no_empty_return' => false,
'phpdoc_summary' => false,
])
->setRiskyAllowed(true)
->setFinder($finder);
Running php-cs-fixer
# Check only (dry run)
vendor/bin/php-cs-fixer fix --config Build/php-cs-fixer.php --dry-run --diff
# Fix files
vendor/bin/php-cs-fixer fix --config Build/php-cs-fixer.php
# Via runTests.sh
Build/Scripts/runTests.sh -s cgl
Common Rules
// array_syntax: short
$array = [1, 2, 3]; // ✅
$array = array(1, 2, 3); // ❌
// concat_space: one
$message = 'Hello ' . $name; // ✅
$message = 'Hello '.$name; // ❌
// declare_strict_types
<?php
declare(strict_types=1); // ✅ Required at top of file
// ordered_imports
use Vendor\Extension\Domain\Model\Product; // ✅ Alphabetical
use Vendor\Extension\Domain\Repository\ProductRepository;
phplint
Installation
composer require --dev overtrue/phplint
Configuration
Create .phplint.yml:
path: ./
jobs: 10
cache: var/cache/phplint.cache
exclude:
- vendor
- var
- .Build
extensions:
- php
Running phplint
# Lint all PHP files
vendor/bin/phplint
# Via runTests.sh
Build/Scripts/runTests.sh -s lint
# Specific directory
vendor/bin/phplint Classes/
Composer Script Integration
{
"scripts": {
"ci:test:php:lint": "phplint",
"ci:test:php:phpstan": "phpstan analyze --configuration Build/phpstan.neon --no-progress",
"ci:test:php:rector": "rector process --dry-run",
"ci:test:php:cgl": "php-cs-fixer fix --config Build/php-cs-fixer.php --dry-run --diff",
"ci:test:php:security": "composer audit",
"fix:cgl": "php-cs-fixer fix --config Build/php-cs-fixer.php",
"fix:rector": "rector process",
"ci:test": [
"@ci:test:php:lint",
"@ci:test:php:phpstan",
"@ci:test:php:rector",
"@ci:test:php:cgl",
"@ci:test:php:security"
]
}
}
Security Note:
composer auditchecks for known security vulnerabilities in dependencies. Run this regularly and especially before releases.
Pre-commit Hook
Create .git/hooks/pre-commit:
#!/bin/sh
echo "Running quality checks..."
# Lint
vendor/bin/phplint || exit 1
# PHPStan
vendor/bin/phpstan analyze --configuration Build/phpstan.neon --error-format=table --no-progress || exit 1
# Code style
vendor/bin/php-cs-fixer fix --config Build/php-cs-fixer.php --dry-run --diff || exit 1
echo "✓ All checks passed"
CI/CD Integration
GitHub Actions
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4' # Use latest PHP for quality tools
- run: composer install
- run: composer ci:test:php:lint
- run: composer ci:test:php:phpstan
- run: composer ci:test:php:cgl
- run: composer ci:test:php:rector
- run: composer ci:test:php:security
IDE Integration
PHPStorm
- PHPStan: Settings → PHP → Quality Tools → PHPStan
- php-cs-fixer: Settings → PHP → Quality Tools → PHP CS Fixer
- File Watchers: Auto-run on file save
VS Code
{
"php.validate.executablePath": "/usr/bin/php",
"phpstan.enabled": true,
"phpstan.configFile": "Build/phpstan.neon",
"php-cs-fixer.onsave": true,
"php-cs-fixer.config": "Build/php-cs-fixer.php"
}
Best Practices
- PHPStan Level 10: Aim for
level: maxin modern TYPO3 13 projects - Baseline for Legacy: Use baselines to track existing issues during migration
- Security Audits: Run
composer auditregularly and in CI - Auto-fix in CI: Run fixes automatically, fail on violations
- Consistent Rules: Share config across team
- Pre-commit Checks: Catch issues before commit (lint, PHPStan, CGL, security)
- Latest PHP: Run quality tools with latest PHP version (8.4+)
- Regular Updates: Keep tools and rules updated