Installation:
composer require --dev amashukov/rector-php-rules
Configure Rector:
Add the package to your rector.php with the rules you want to enforce. Start with a minimal setup to avoid overwhelming your team:
return RectorConfig::configure()
->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
->withRules([
NoPhpstanIgnoreRector::class,
NoSuperglobalAccessRector::class,
NoAssertCallInSrcRector::class,
]);
First Use Case: Run a dry run to identify violations without modifying files:
vendor/bin/rector process --dry-run
This will show you where the rules would apply, allowing you to review and address issues incrementally.
Incremental Adoption:
NoPhpstanIgnoreRector, NoSuperglobalAccessRector) that enforce best practices without rewriting logic.NoAssertCallInSrcRector, RequirePsrClockInterfaceRector) as the team adapts.rector.php to scope rules to specific directories (e.g., skip migrations/ or vendor/).CI Integration:
# .github/workflows/rector.yml
jobs:
rector:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: composer install
- run: vendor/bin/rector process --dry-run
Team Onboarding:
README or CONTRIBUTING.md.Customizing Rules:
->skip([
__DIR__ . '/src/Some/Excluded/Class.php',
__DIR__ . '/tests/Unit/OldTests/',
])
Pair with PHPStan:
NoPhpstanIgnoreRector alongside PHPStan to eliminate suppressions. Configure PHPStan to run in level 9 (strictest) mode:
composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse --level=9 src/
Combine with Other Tools:
NoPhpstanIgnoreRector to also target @psalm-suppress annotations.NoCommentsOutsideInterfaceMethodDocBlockRector alongside PHP-CS-Fixer or Pint for consistent formatting:
composer require --dev friendsofphp/pint
vendor/bin/pint
vendor/bin/rector process
Testing Rules:
NoDirectDbMutationInFunctionalTestsRector), ensure your test suite uses factories or repositories instead of direct DB access:
// BAD (direct DB)
$user = DB::table('users')->find(1);
// GOOD (via repository)
$user = $this->userRepository->find(1);
YAML Hygiene:
YamlNoCommentsRector to config files (e.g., config/services.yaml) to enforce comment-free YAML:
->withConfiguredRule(YamlNoCommentsRector::class, [
YamlNoCommentsRector::PATHS => [__DIR__ . '/config'],
])
False Positives in Legacy Code:
NoSuperglobalAccessRector or NoEnvironmentCheckInSrcRector may flag legacy code that relies on globals or env checks. Use skip() to exclude such files temporarily while refactoring:
->skip([__DIR__ . '/src/Legacy/EnvChecker.php'])
Overly Aggressive Rules:
NoCommentsOutsideInterfaceMethodDocBlockRector) may remove useful documentation. Review the output of // RECTOR-BAN markers carefully before applying such rules broadly.Test-Specific Rules:
NoAssertInsideIfInFunctionalTestsRector or NoArrayAssertContainsInTestsRector can break existing tests. Refactor tests incrementally to avoid massive failures:
// BAD (conditional assertion)
if ($status === 'active') {
self::assertTrue($user->isActive());
}
// GOOD (extract helper)
private function assertUserIsActive(User $user): void {
self::assertTrue($user->isActive());
}
YAML Rule Quirks:
YamlNoCommentsRector strips all comments by default, including those in *.yaml files. If you rely on comments in configs (e.g., for ADRs), exclude those files:
->withConfiguredRule(YamlNoCommentsRector::class, [
YamlNoCommentsRector::PATHS => [__DIR__ . '/config/production'],
YamlNoCommentsRector::SKIP => [__DIR__ . '/config/adr/*.yaml'],
])
Dry-Run First:
Always run rector process --dry-run to see violations before applying changes:
vendor/bin/rector process --dry-run --format=json > rector-violations.json
Inspect Markers:
Look for // RECTOR-BAN: comments in your code. These indicate where rules would apply:
// RECTOR-BAN: NoSuperglobalAccessRector found $_ENV['API_KEY'] at line 42
Rule-Specific Debugging:
NoAssertCallInSrcRector, ensure you replace assert() with explicit throw or if checks.RequirePsrClockInterfaceRector, verify that all new DateTime() calls are replaced with injected ClockInterface:
// BAD
$now = new DateTime();
// GOOD
$now = $this->clock->now();
CI Debugging: If Rector fails in CI, check the exit code:
1: Violations found (fix them).2: Configuration error (check rector.php).Custom Rules:
Extend the package by creating your own Rector rules. For example, to ban strtolower() in favor of Stringable::toLowerCase():
use Rector\Core\Contract\RectorInterface;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use PhpParser\Node;
final class NoStrtolowerRector implements RectorInterface {
public function getRuleDefinition(): RuleDefinition {
return new RuleDefinition('Bans strtolower() in favor of Stringable::toLowerCase().');
}
public function refactor(Node $node): ?Node {
$betterNodeFinder = new BetterNodeFinder();
$nodes = $betterNodeFinder->findInstanceOf($node, FunctionCall::class);
foreach ($nodes as $functionCall) {
if ($functionCall->name->toString() === 'strtolower') {
return new Node\Expr\MethodCall(
$functionCall->args[0]->value,
'toLowerCase'
);
}
}
return null;
}
}
Conditional Rules:
Use Rector’s Condition to apply rules conditionally (e.g., only in src/):
->withRules([
new NoSuperglobalAccessRector(),
])
->withConditions([
new NotMatchCondition('tests/'),
])
Post-Rector Hooks: Combine Rector with other tools using composer scripts:
{
"scripts": {
"rector": "vendor/bin/rect
How can I help you explore Laravel packages today?