thecodingmachine/phpstan-safe-rule
PHPStan rule set that flags calls to “unsafe” PHP functions that can return false on failure and suggests using the thecodingmachine/safe equivalents that throw exceptions, helping enforce safer, exception-based error handling in your codebase.
Installation:
composer require --dev thecodingmachine/phpstan-safe-rule thecodingmachine/safe
Ensure thecodingmachine/safe is installed as a dependency (not just dev-dependency) since it provides the safe alternatives.
Enable the Rule:
Add the extension to your phpstan.neon:
includes:
- vendor/thecodingmachine/phpstan-safe-rule/extension.neon
First Use Case: Run PHPStan on your codebase:
vendor/bin/phpstan analyse src
The rule will flag unsafe function calls (e.g., file_get_contents() without null checks) and suggest using Safe\file_get_contents() instead.
Quick Check: Test with a known unsafe call:
$content = file_get_contents('nonexistent.txt'); // Will trigger a warning
PHPStan will suggest replacing it with:
use Safe\file_get_contents;
$content = file_get_contents('nonexistent.txt'); // Now throws on failure
GitHub Actions Example:
- name: Run PHPStan Safe Rule
run: vendor/bin/phpstan analyse --level=5 --error-format=github
Fail the build if unsafe calls are detected.
Parallel Execution: Split analysis by directory in CI to reduce runtime:
vendor/bin/phpstan analyse src/Modules --memory-limit=1G
Start Loose, Tighten Later:
Begin with --level=1 (basic checks) and incrementally raise to --level=5 (strict).
# phpstan.neon
parameters:
level: 1
Ignore Legacy Code:
ignoreErrors:
- '#.*legacy/.*#'
Batch Replacement:
Use sed or IDE refactoring (e.g., PHPStorm "Replace with Safe") to swap unsafe calls:
# Example: Replace file_get_contents with Safe\file_get_contents
find src -type f -name "*.php" -exec sed -i 's/file_get_contents/Safe\\file_get_contents/g' {} +
Alias for Convenience:
Create a SafeFunctions.php trait to reduce verbosity:
trait SafeFunctions {
protected function safeFileGetContents(string $path): string {
return Safe\file_get_contents($path);
}
}
SafeRule class to handle project-specific cases:
// app/Rules/CustomSafeRule.php
use PHPStan\Rules\Rule;
use TheCodingMachine\SafeRule\SafeRule;
class CustomSafeRule extends SafeRule {
public function getNodeType(): string {
return 'Php\Node\Expr\MethodCall';
}
protected function customCheck(callable $node): void {
// Add logic for project-specific unsafe patterns
}
}
Register in phpstan.neon:
services:
- TheCodingMachine\SafeRule\SafeRule
- App\Rules\CustomSafeRule
Safe\preg_match now correctly types $matches).
Example:
$matches = Safe\preg_match('/\d+/', $subject); // $matches is now typed as ?array<string>
Missing thecodingmachine/safe:
The rule will not work without thecodingmachine/safe installed as a runtime dependency (not just dev-dependency).
Fix: Run composer require thecodingmachine/safe.
False Positives with JSON_THROW_ON_ERROR:
The rule may flag json_encode()/json_decode() calls even with JSON_THROW_ON_ERROR.
Workaround: Whitelist in phpstan.neon:
ignoreErrors:
- '#.*json_(encode|decode).*JSON_THROW_ON_ERROR.*#'
Reflection Overhead: The rule uses lazy reflection processing (since v1.4.3). If you encounter performance issues, ensure your PHPStan version supports this optimization.
Inspect Rule Output:
Use --error-format=json to debug false positives:
vendor/bin/phpstan analyse --error-format=json > errors.json
Parse the JSON to identify problematic files/methods.
Disable Specific Rules: Temporarily disable the rule for a file:
// @phpstan-ignore-file
Check Loading Order:
Ensure extension.neon is included after your base config to avoid rule conflicts:
# Correct order
extends: phpstan:level-5
includes:
- vendor/thecodingmachine/phpstan-safe-rule/extension.neon
Run on Changed Files:
Use --diff in CI to analyze only modified files:
vendor/bin/phpstan analyse --diff
Memory Limits: Increase memory for large codebases:
vendor/bin/phpstan analyse --memory-limit=2G
Custom Safe Functions: Add support for project-specific safe wrappers by extending the rule:
// app/Rules/ProjectSafeRule.php
use TheCodingMachine\SafeRule\SafeRule;
class ProjectSafeRule extends SafeRule {
protected function getUnsafeFunctions(): array {
return array_merge(parent::getUnsafeFunctions(), [
'my_project\unsafe_function' => 'my_project\Safe\safe_function',
]);
}
}
Override Error Messages:
Customize suggestions in the rule’s createError() method:
protected function createError(callable $node, string $message): Error {
return new Error(
$this->getNodeName($node),
$message . ' Use `Safe\\' . $this->getSafeFunctionName($node) . '()` instead.'
);
}
PHP Version Compatibility: Requires PHP 8.1+ (since v1.4.0). Ensure your environment matches.
PHPStan Version: Works with PHPStan 2.0+ (since v1.3.0). Downgrade if using an older version:
composer require phpstan/phpstan:^1.0
Strict Mode Conflicts:
If using phpstan-strict-rules, disable overlapping rules to avoid redundancy:
parameters:
rules:
TheCodingMachine\SafeRule\SafeRule: true
PhpStan\Rules\StrictRules\StrictFunctionCallRule: false
Pair with roave/security-advisories:
Combine with roave/security-advisories to catch unsafe functions and vulnerable dependencies:
composer require --dev roave/security-advisories
Visual Studio Code Integration:
Add to settings.json for real-time feedback:
"phpstan.executablePath": "vendor/bin/phpstan",
"phpstan.neon": "./phpstan.neon",
"phpstan.triggerMode": "workspace"
Benchmark Safe vs. Unsafe: Measure performance impact of safe wrappers:
$time = microtime(true);
Safe\file_get_contents('large_file.txt');
echo "Safe: " . (microtime(true) - $time) . "s";
How can I help you explore Laravel packages today?