sanmai / phpstan-rules
Custom PHPStan rules for enforcing code quality standards
Fund package maintenance!
sanmai
Installs: 39
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 0
Forks: 0
Open Issues: 0
Type:phpstan-extension
Requires
- php: ^8.2
- nikic/php-parser: ^5.5
- phpstan/phpstan: ^2.0
Requires (Dev)
- ergebnis/composer-normalize: ^2.8
- friendsofphp/php-cs-fixer: ^3.17
- infection/infection: >=0.10.5
- league/pipeline: ^0.3 || ^1.0
- php-coveralls/php-coveralls: ^2.4.1
- phpunit/phpunit: ^9.5 || ^10.0
- sanmai/pipeline: ^6.17
- vimeo/psalm: >=2
This package is auto-updated.
Last update: 2025-07-03 16:00:42 UTC
README
A collection of opinionated PHPStan rules focused on enforcing functional programming patterns and reducing complexity. These rules are tailored specifically to the kind of code LLMs are prone to produce.
Philosophy
These rules encourage:
- Functional programming patterns over imperative nested structures
- Early returns and guard clauses for better readability
- Reduced cyclomatic complexity through flatter code structures
- Explicit code flow that's easier to test and maintain
These principles align well with libraries like sanmai/pipeline
that provide functional programming patterns as alternatives to nested loops.
Installation
composer require --dev sanmai/phpstan-rules
Configuration
Include the extension in your project's phpstan.neon
:
includes: - vendor/sanmai/phpstan-rules/extension.neon
That's it! The rules will be automatically registered and start analyzing your code.
Rules
NoNestedLoopsRule
Prevents nested loops within the same function scope.
This rule encourages extracting nested loops into separate methods or using functional approaches like array_map()
, array_filter()
, or the sanmai/pipeline
library.
Bad
foreach ($users as $user) { foreach ($user->getPosts() as $post) { // Error: Nested loops are not allowed if ($post->isPublished()) { $titles[] = $post->getTitle(); } } }
Good - Using sanmai/pipeline
use function Pipeline\take; $titles = take($users) ->map(fn($user) => yield from $user->getPosts()) ->filter(fn($post) => $post->isPublished()) ->cast(fn($post) => $post->getTitle()) ->toList();
NoNestedIfStatementsRule
Discourages simple nested if statements without else branches.
This rule promotes combining conditions with logical operators or using guard clauses for flatter code structure.
Bad
if ($user->isActive()) { if ($user->hasPermission('edit')) { // Error: Nested if statements should be avoided $this->grantAccess(); } }
Good - Combined conditions
if ($user->isActive() && $user->hasPermission('edit')) { $this->grantAccess(); }
Good - Guard clauses
if (!$user->isActive()) { return; } if (!$user->hasPermission('edit')) { return; } $this->grantAccess();
RequireGuardClausesInLoopsRule
Enforces the use of guard clauses in loops instead of wrapping the main logic in if statements.
This rule encourages early returns/continues to reduce nesting and improve readability.
Exception: Loops where the if statement contains only return
, yield
, yield from
, or throw
statements are allowed, as these are common patterns for filtering/searching operations.
Bad - Loop with only if
foreach ($items as $item) { if ($item->isValid()) { // Error: Use guard clauses $item->process(); $item->save(); } }
Good - Guard clause
foreach ($items as $item) { if (!$item->isValid()) { continue; } $item->process(); $item->save(); }
Good - If with other statements (allowed)
foreach ($items as $item) { if (count($buffer) >= $limit) { // OK: Loop has more than just the if array_shift($buffer); } $buffer[] = $item; }
NoElseRule
Forbids the use of else
statements.
This rule enforces the use of early returns and guard clauses instead of else
branches, leading to flatter and more readable code.
Bad
if ($user->isActive()) { return $user->getName(); } else { // Error: Else statements are not allowed return 'Guest'; }
Good
if (!$user->isActive()) { return 'Guest'; } return $user->getName();
NoEmptyRule
Forbids the use of the empty()
function.
This rule encourages more explicit checks instead of the ambiguous empty()
function, which can hide bugs and make code harder to understand.
Bad
if (empty($data)) { // Error: The empty() function is not allowed return null; }
Good
// Be explicit about what you're checking if ($data === null) { return null; } // Or for arrays if ($data === []) { return null; } // Or for strings if ($data === '') { return null; }
NoCountZeroComparisonRule
Forbids comparing count()
with 0.
This rule encourages using direct array comparisons (=== []
or !== []
) instead of counting elements, which is more efficient and clearer.
Bad
if (count($items) === 0) { // Error: Avoid comparing count() with 0 return 'No items'; } if (count($items) > 0) { // Error: Avoid comparing count() with 0 process($items); }
Good
if ($items === []) { return 'No items'; } if ($items !== []) { process($items); } // Other count comparisons are fine if (count($items) === 1) { return 'Single item'; }
Ignoring Rules
Please refer to the PHPStan documentation.
Contributing
Found a bug or have a suggestion? Please open an issue.