Initial commit
This commit is contained in:
395
skills/typo3-testing/references/quality-tools.md
Normal file
395
skills/typo3-testing/references/quality-tools.md
Normal file
@@ -0,0 +1,395 @@
|
||||
# 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
|
||||
|
||||
```bash
|
||||
composer require --dev phpstan/phpstan phpstan/phpstan-strict-rules saschaegerer/phpstan-typo3
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
Create `Build/phpstan.neon`:
|
||||
|
||||
```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
|
||||
|
||||
```bash
|
||||
# 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 (`mixed` must 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
|
||||
|
||||
```php
|
||||
/** @phpstan-ignore-next-line */
|
||||
$value = $this->legacyMethod();
|
||||
|
||||
// Or in neon file
|
||||
parameters:
|
||||
ignoreErrors:
|
||||
- '#Call to an undefined method.*::getRepository\(\)#'
|
||||
```
|
||||
|
||||
### TYPO3-Specific Rules
|
||||
|
||||
```php
|
||||
// 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
|
||||
|
||||
```bash
|
||||
composer require --dev rector/rector ssch/typo3-rector
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
Create `rector.php`:
|
||||
|
||||
```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
|
||||
|
||||
```bash
|
||||
# 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**:
|
||||
```php
|
||||
// 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**:
|
||||
```php
|
||||
// Before
|
||||
public function process($data)
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
|
||||
// After
|
||||
public function process(array $data): array
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
```
|
||||
|
||||
## php-cs-fixer
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
composer require --dev friendsofphp/php-cs-fixer
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
Create `Build/php-cs-fixer.php`:
|
||||
|
||||
```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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```php
|
||||
// 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
|
||||
|
||||
```bash
|
||||
composer require --dev overtrue/phplint
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
Create `.phplint.yml`:
|
||||
|
||||
```yaml
|
||||
path: ./
|
||||
jobs: 10
|
||||
cache: var/cache/phplint.cache
|
||||
exclude:
|
||||
- vendor
|
||||
- var
|
||||
- .Build
|
||||
extensions:
|
||||
- php
|
||||
```
|
||||
|
||||
### Running phplint
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```json
|
||||
{
|
||||
"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 audit` checks for known security vulnerabilities in dependencies. Run this regularly and especially before releases.
|
||||
|
||||
## Pre-commit Hook
|
||||
|
||||
Create `.git/hooks/pre-commit`:
|
||||
|
||||
```bash
|
||||
#!/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
|
||||
|
||||
```yaml
|
||||
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
|
||||
|
||||
1. **PHPStan**: Settings → PHP → Quality Tools → PHPStan
|
||||
2. **php-cs-fixer**: Settings → PHP → Quality Tools → PHP CS Fixer
|
||||
3. **File Watchers**: Auto-run on file save
|
||||
|
||||
### VS Code
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
1. **PHPStan Level 10**: Aim for `level: max` in modern TYPO3 13 projects
|
||||
2. **Baseline for Legacy**: Use baselines to track existing issues during migration
|
||||
3. **Security Audits**: Run `composer audit` regularly and in CI
|
||||
4. **Auto-fix in CI**: Run fixes automatically, fail on violations
|
||||
5. **Consistent Rules**: Share config across team
|
||||
6. **Pre-commit Checks**: Catch issues before commit (lint, PHPStan, CGL, security)
|
||||
7. **Latest PHP**: Run quality tools with latest PHP version (8.4+)
|
||||
8. **Regular Updates**: Keep tools and rules updated
|
||||
|
||||
## Resources
|
||||
|
||||
- [PHPStan Documentation](https://phpstan.org/user-guide/getting-started)
|
||||
- [Rector Documentation](https://getrector.com/documentation)
|
||||
- [PHP CS Fixer Documentation](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer)
|
||||
- [TYPO3 Coding Guidelines](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/CodingGuidelines/)
|
||||
Reference in New Issue
Block a user