efabrica/phpstan-rules

Maintainers

Package info

github.com/efabrica-team/phpstan-rules

pkg:composer/efabrica/phpstan-rules

Statistics

Installs: 3 191

Dependents: 2

Suggesters: 0

Stars: 2

Open Issues: 4

0.8.1 2026-01-27 07:47 UTC

README

Extension for PHPStan is providing several services and rules to help find bugs in your applications.

PHP unit PHPStan level PHP static analysis Latest Stable Version Total Downloads

Installation

To use this extension, require it in Composer:

composer require --dev efabrica/phpstan-rules

Setup

This extension adds several rules. You can use them all by including these files in your phpstan.neon:

includes:
    - vendor/efabrica/phpstan-rules/extension.neon
    - vendor/efabrica/phpstan-rules/rules.neon

Or include just:

includes:
    - vendor/efabrica/phpstan-rules/extension.neon

and pick rules you want to use

Guzzle - ClientCallWithoutOptionRule

Finds all calls of GuzzleHttp\Client methods without some option e.g. timeout, connect_timeout

services:
    -
        factory: Efabrica\PHPStanRules\Rule\Guzzle\ClientCallWithoutOptionRule(['timeout', 'connect_timeout'])
        tags:
            - phpstan.rules.rule
use GuzzleHttp\Client;

$guzzleClient = new Client();
$guzzleClient->request('GET', 'https://example.com/api/url');

use GuzzleHttp\Client;

$guzzleClient = new Client();
$guzzleClient->request('GET', 'https://example.com/api/url', ['timeout' => 3, 'connect_timeout' => 1]);

👍

Tomaj/Nette API - InputParamNameRule

Checks if names of all input parameters. Every name has to contain only alphanumeric characters and _

services:  
    -
        factory: Efabrica\PHPStanRules\Rule\Tomaj\NetteApi\InputParamNameRule
        tags:
            - phpstan.rules.rule
use Tomaj\NetteApi\Handlers\BaseHandler;

final class SomeHandler extends BaseHandler
{
    public function params(): array
    {
        return [
            new GetInputParam('my-name')
        ];
    }
}

use Tomaj\NetteApi\Handlers\BaseHandler;

final class SomeHandler extends BaseHandler
{
    public function params(): array
    {
        return [
            new GetInputParam('my_name')
        ];
    }
}

👍

Check trait context - TraitContextRule

Checks if traits are used only in context of classes specified in them via comment @context {Type}

services:  
    -
        factory: Efabrica\PHPStanRules\Rule\General\TraitContextRule
        tags:
            - phpstan.rules.rule
/**
 * @context MyInterface
 */
trait MyTrait
{

}

final class SomeClass
{
    use MyTrait;
}

/**
 * @context MyInterface
 */
trait MyTrait
{

}

final class SomeClass implements MyInterface
{
    use MyTrait;
}

👍

Check calling method in object method

Checks if some method is not used in disabled context - specific method of object.

parameters:
    disabledMethodCalls:
        -
            context: 'WithCallInterface::checkedMethod'
            disabled: 'ClassWithDisabledMethod::disabledMethod'

services:
    -
        factory: Efabrica\PHPStanRules\Rule\General\DisableMethodCallInContextRule(%disabledMethodCalls%)
        tags:
            - phpstan.rules.rule
class ClassWithDisabledMethod implements WithDisabledMethodInterface
{
    public function disabledMethod() {} // this method shouldn't be called in WithCallInterface::checkedMethod
}
final class SomeClass implements WithCallInterface
{
    public function checkedMethod(): array
    {
        return [(new ClassWithDisabledMethod)->disabledMethod()]
    }
}

final class SomeClass implements WithCallInterface
{
    public function checkedMethod(): array
    {
        return [(new ClassWithDisabledMethod)]
    }
}

👍

Check calling method with required parameters

Checks if some method is called with all required parameters with corresponding types.

parameters:
    requiredParametersInMethodCalls:
        -
            context: 'SomeClass::someMethod'
            parameters:
                -
                    name: someParameter
                    type: string
                    tip: 'Always use parameter someParameter as string because...'

services:
    -
        factory: Efabrica\PHPStanRules\Rule\General\RequiredParametersInMethodCallRule(%requiredParametersInMethodCalls%)
        tags:
            - phpstan.rules.rule
class SomeClass
{
    public function someMethod(?string $someParameter = null): void
    {
        // this method should be called with string value of $someParameter
    }
}
class Foo
{
    public function bar(SomeClass $someClass)
    {
        $someClass->someMethod();
    }
}

class Foo
{
    public function bar(SomeClass $someClass)
    {
        $someClass->someMethod('baz');
    }
}

👍

Do not concatenate translated strings

Every language has its own word order in sentences, we can't use e.g. variables at the same place for all languages. There are mechanisms in translate libraries e.g. symfony/translator - we can use placeholders like %name% etc. This rule checks if you use translated messages and then concat them with some other strings.

parameters:
    translateCalls:
        - iAmTranslateFunction
        - Efabrica\PHPStanRules\Tests\Rule\General\DisabledConcatenationWithTranslatedStringsRule\Source\TranslatorInterface::iAmTranslateMethod
        - Efabrica\PHPStanRules\Tests\Rule\General\DisabledConcatenationWithTranslatedStringsRule\Source\TranslatorInterface::iAmTranslateStaticMethod
    allowedTranslateConcatenationPatterns:
        - '[\s]*<.*?>[\s]*<\/.*?>[\s]*'
        - '[\s]*This is allowed text[\s]*'
        - '[\s]*\#[0-9]+[\s]*'

services:
    -
        factory: Efabrica\PHPStanRules\Rule\General\DisabledConcatenationWithTranslatedStringsRule(%translateCalls%)
        tags:
            - phpstan.rules.rule
$message = 'Hello';
$name = 'Mark';
echo $translator->iAmTranslateMethod($message) . ' ' . $name;

$message = 'Hello %name%';
$name = 'Mark';
echo $translator->iAmTranslateMethod($message, ['name' => $name];

👍

Forbidden constructor parameters types

This rule checks if constructor contains forbidden parameter types.

parameters:
    forbiddenConstructorParametersTypes:
        -
            context: 'SomeClass'
            forbiddenTypes:
                -
                    type: ForbiddenType
                    tip: 'ForbiddenType is not allowed, use NotForbiddenType instead'

services:
    -
        factory: Efabrica\PHPStanRules\Rule\General\ForbiddenConstructorParametersTypesRule(%forbiddenConstructorParametersTypes%)
        tags:
            - phpstan.rules.rule
class SomeClass
{

}

class ForbiddenType
{

}

class NotForbiddenType
{

}
class Foo extends SomeClass
{
    public function __construct(ForbiddenType $type)
    {
    
    }
}

class Foo extends SomeClass
{
    public function __construct(NotForbiddenType $type)
    {
    
    }
}

👍

Enforce arrow function

Checks closures and reports those that only contain a single return expression, because they can be replaced with fn. This rule is optional and is not enabled by default in rules.neon.

services:
    -
        factory: Efabrica\PHPStanRules\Rule\General\EnforceArrowFunctionRule
        tags:
            - phpstan.rules.rule
$doubled = array_map(function (int $number): int {
    return $number * 2;
}, $numbers);

$doubled = array_map(fn (int $number): int => $number * 2, $numbers);

👍

Performance - DisabledCallsInLoopsRule

Some functions are not recommended to be called in loops. For example array_merge.

services:
    -
        factory: Efabrica\PHPStanRules\Rule\Performance\DisabledCallsInLoopsRule
        tags:
            - phpstan.rules.rule
$result = [];
for ($i = 0; $i < 100; $i++) {
    $result = array_merge($result, $data[$i]);
}

$result = array_merge([], ...$data);

👍

Performance - UseNetteDatabaseSelectionFetchTogetherWithLimitRule

Nette\Database\Table\Selection::fetch() should be used with limit(1) to avoid loading more rows than needed.

services:
    -
        factory: Efabrica\PHPStanRules\Rule\Performance\UseNetteDatabaseSelectionFetchTogetherWithLimitRule
        tags:
            - phpstan.rules.rule
return $selection->where(['category_id' => 1])->fetch();

return $selection->where(['category_id' => 1])->limit(1)->fetch();

👍

Check calls in conditions

This rule checks if there are some (slow) calls (function call, method call, static method call) in if conditions before other expressions. There can be set list of slow calls in parameters. If not set, all calls are considered slower than other expressions.

parameters:
    conditionSlowCalls:
        - 'file_*'            # all functions starting with file_
        - 'Foo\Bar\Baz->foo'  # method foo called on object of type Foo\Bar\Baz
        - 'Foo\Bar\Baz::bar'  # static method bar from Foo\Bar\Baz
class SomeClass
{
    public function doSomething(bool $someOption): void
    {
        if (file_exists($someFile) && $someOption) {
            // do something
        }
    }
}

class SomeClass
{
    public function doSomething(bool $someOption): void
    {
        if ($someOption && file_exists($someFile)) {
            // do something
        }
    }
}

👍

Performance - UseArrayComparisonInsteadOfCountInConditionRule

This rule checks if count($array) is used in if / elseif conditions for empty/non-empty checks and recommends array comparison with [].

services:
    -
        factory: Efabrica\PHPStanRules\Rule\Performance\UseArrayComparisonInsteadOfCountInConditionRule
        tags:
            - phpstan.rules.rule
if (count($items) > 0) {
    // ...
}

if ($items !== []) {
    // ...
}

👍

Nette DI - PresenterInjectedPropertiesExtension

Will not report uninitialized properties with @Inject or #[Inject] attribute.

services:
    -
        class: Efabrica\PHPStanRules\Rule\Nette\PresenterInjectedPropertiesExtension
        tags:
            - phpstan.properties.readWriteExtension
class InjectPresenter
{
    /** @var SomeInjectedClass @inject */
    public SomeInjectedClass $someService;
    ...
    
    #[Inject]
    public OtherInjectedClass $otherService;
    ...