slam/phpstan-extensions
PHPStan extensions with extra strict rules: unused variables, closure parameter typehints, enforce ::class notation, forbid goto, naming conventions, validate PHPUnit annotation FQCNs, and restrict access to globals/static properties in specific contexts.
Installation:
composer require --dev slam/phpstan-extensions
For automatic inclusion with phpstan/extension-installer, no further steps are needed.
Manual Inclusion (if not using extension-installer):
Add to your phpstan.neon:
includes:
- vendor/slam/phpstan-extensions/conf/slam-rules.neon
First Use Case: Run PHPStan with the default config:
vendor/bin/phpstan analyse src
Focus on UnusedVariableRule and StringToClassRule for immediate feedback—these catch common refactoring opportunities.
Enforcing Naming Conventions:
Use ClassNotationRule to standardize class naming (e.g., Abstract prefix for abstract classes, Interface suffix for interfaces).
# phpstan.neon
includes:
- vendor/slam/phpstan-extensions/conf/slam-rules.neon
rules:
SlamPhpStan\ClassNotationRule:
enabled: true
Dependency Injection for Time:
Replace raw DateTime calls with abstractions (e.g., lcobucci/clock) using not-now-rules.neon:
includes:
- vendor/slam/phpstan-extensions/conf/not-now-rules.neon
Pattern: Wrap DateTime logic in a service with a clock dependency:
class OrderService {
public function __construct(private Clock $clock) {}
public function getCurrentOrderDate(): DateTimeImmutable {
return $this->clock->now();
}
}
Symfony-Specific Rules: Enforce Symfony’s filesystem/process components over raw PHP functions:
includes:
- vendor/slam/phpstan-extensions/conf/symfony-rules.neon
Pattern: Replace file_exists() with Symfony\Component\Filesystem\Filesystem::exists().
PHPUnit Annotations:
Validate @expectedException, @covers, etc., with PhpUnitFqcnAnnotationRule:
/**
* @expectedException \RuntimeException
*/
public function testException(): void {}
Pattern: Ensure exception classes exist and are fully qualified.
Singleton Prohibition:
Block $_GET, $_POST, or Yii::$app in models using AccessGlobalVariableWithinContextRule:
rules:
SlamPhpStan\AccessGlobalVariableWithinContextRule:
forbiddenGlobals: ['$_GET', '$_POST']
forbiddenClasses: ['yii\db\ActiveRecordInterface']
Layered Configuration:
Combine configs for different frameworks (e.g., Symfony + Yii) by including multiple .neon files:
includes:
- vendor/slam/phpstan-extensions/conf/slam-rules.neon
- vendor/slam/phpstan-extensions/conf/symfony-rules.neon
- vendor/slam/phpstan-extensions/conf/yii-rules.neon
CI/CD Pipeline: Add PHPStan with Slam extensions to your CI (e.g., GitHub Actions):
- name: Run PHPStan
run: vendor/bin/phpstan analyse --level=max src --configuration=phpstan.neon
Gradual Adoption:
Start with StringToClassRule and UnusedVariableRule (low effort, high impact). Enable stricter rules (e.g., MissingClosureParameterTypehintRule) in phases.
UnusedVariableRule False Positives:
compact(), array_merge(), or dynamic function calls may be flagged incorrectly.compact() better) or suppress false positives:
rules:
SlamPhpStan\UnusedVariableRule:
ignoreVariables: ['_temp']
MissingClosureParameterTypehintRule Strictness:
rules:
SlamPhpStan\MissingClosureParameterTypehintRule:
paths:
- '!vendor/**'
- '!legacy/**'
not-now-rules.neon Limitations:
strtotime() or date() may miss edge cases (e.g., dynamic strings).rules:
SlamPhpStan\NoTimeRule:
ignoreFunctions: ['my_custom_date_function']
Symfony/Yii Rules Overhead:
SymfonyFilesystemRule may break legacy code relying on raw PHP functions.ignoreNodes:
rules:
SlamPhpStan\SymfonyFilesystemRule:
ignoreNodes:
- 'Symfony\Component\Filesystem\Filesystem::isReadable' # if needed
Error Identifiers:
Use the --error-format=github flag to see rule-specific identifiers (added in v6.4.0):
vendor/bin/phpstan analyse --error-format=github src
Example output:
❌ [SlamPhpStan\StringToClassRule] Class string 'App\Models\User' should use ::class notation.
Rule-Specific Configuration:
Override defaults per rule. Example for ClassNotationRule:
rules:
SlamPhpStan\ClassNotationRule:
interfaceSuffix: 'I' # Customize suffix
abstractPrefix: 'Base' # Customize prefix
Performance: Exclude tests or large directories from strict rules to speed up analysis:
parameters:
excludeFiles:
- 'tests/**'
- 'vendor/**'
Custom Rules:
Extend existing rules by subclassing SlamPhpStan\Rule\Rule and overriding refineNodes() or processNode().
Dynamic Config:
Use PHPStan’s parameters to dynamically enable/disable rules based on environment variables:
parameters:
strictMode: '%env(bool:STRICT_MODE,false)%'
rules:
SlamPhpStan\MissingClosureParameterTypehintRule:
enabled: '%parameters.strictMode%'
Community Contributions:
Facade pattern or custom abstractions).Pair with phpstan/extension-installer:
Automatically load extensions without manual includes:
composer require --dev phpstan/extension-installer
Add to composer.json:
"extra": {
"installer-paths": {
"config/phpstan/extensions": ["slam/phpstan-extensions"]
}
}
Visual Studio Code Integration: Use the PHPStan extension for real-time feedback:
// .vscode/settings.json
{
"phpstan.executablePath": "vendor/bin/phpstan",
"phpstan.neonPath": "phpstan.neon"
}
Legacy Code Migration:
Use not-now-rules.neon incrementally. Start by suppressing rules for specific files, then refactor:
rules:
SlamPhpStan\NoTimeRule:
ignoreFiles: ['legacy/DateHelper.php']
How can I help you explore Laravel packages today?