Add regex101.com link to that shows the regex in practise, so it will be easier to maintain in case of bug/extension in the future
class SomeClass
{
private const COMPLICATED_REGEX = '#some_complicated_stu|ff#';
}
❌
class SomeClass
{
/**
* @see https://regex101.com/r/SZr0X5/12
*/
private const COMPLICATED_REGEX = '#some_complicated_stu|ff#';
}
👍
Method "%s()"
returns bool type, so the name should start with is/has/was...
class SomeClass
{
public function old(): bool
{
return $this->age > 100;
}
}
❌
class SomeClass
{
public function isOld(): bool
{
return $this->age > 100;
}
}
👍
Class was not found
#[SomeAttribute(firstName: 'MissingClass::class')]
class SomeClass
{
}
❌
#[SomeAttribute(firstName: ExistingClass::class)]
class SomeClass
{
}
👍
Class like namespace "%s" does not follow PSR-4 configuration in composer.json
// defined "Foo\Bar" namespace in composer.json > autoload > psr-4
namespace Foo;
class Baz
{
}
❌
// defined "Foo\Bar" namespace in composer.json > autoload > psr-4
namespace Foo\Bar;
class Baz
{
}
👍
Move constant expression to __construct()
, setUp()
method or constant
class SomeClass
{
public function someMethod()
{
$mainPath = getcwd() . '/absolute_path';
return __DIR__ . $mainPath;
}
}
❌
class SomeClass
{
private $mainPath;
public function __construct()
{
$this->mainPath = getcwd() . '/absolute_path';
}
public function someMethod()
{
return $this->mainPath;
}
}
👍
"*Test.php" file cannot be located outside "Tests" namespace
// file: "SomeTest.php
namespace App;
class SomeTest
{
}
❌
// file: "SomeTest.php
namespace App\Tests;
class SomeTest
{
}
👍
Argument and options "%s" got confused
class SomeClass extends Command
{
protected function configure(): void
{
$this->addOption('source');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$source = $input->getArgument('source');
}
}
❌
class SomeClass extends Command
{
protected function configure(): void
{
$this->addArgument('source');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$source = $input->getArgument('source');
}
}
👍
Method parameters must be compatible with its parent
class ParentClass
{
public function run(string $someParameter)
{
}
}
class SomeClass extends ParentClass
{
public function run($someParameter)
{
}
}
❌
class ParentClass
{
public function run(string $someParameter)
{
}
}
class SomeClass extends ParentClass
{
public function run(string $someParameter)
{
}
}
👍
Class "%s" used in annotation is missing
/**
* @SomeAnnotation(value=MissingClass::class)
*/
class SomeClass
{
}
❌
/**
* @SomeAnnotation(value=ExistingClass::class)
*/
class SomeClass
{
}
👍
autowire()
, autoconfigure()
, and public()
are required in config service
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->defaults()
->public();
};
❌
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->defaults()
->public()
->autowire()
->autoconfigure();
};
👍
Interface must be located in "Contract" namespace
namespace App\Repository;
interface ProductRepositoryInterface
{
}
❌
namespace App\Contract\Repository;
interface ProductRepositoryInterface
{
}
👍
Autowired/inject method name must respect "autowire/inject" + class name
final class SomeClass
{
/**
* @required
*/
public function autowireRandom(...)
{
// ...
}
}
❌
final class SomeClass
{
/**
* @required
*/
public function autowireSomeClass(...)
{
// ...
}
}
👍
Parameter %d should use "%s" type as the only type passed to this method
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
class SomeClass
{
public function run(MethodCall $node)
{
$this->isCheck($node);
}
private function isCheck(Node $node)
{
}
}
❌
use PhpParser\Node\Expr\MethodCall;
class SomeClass
{
public function run(MethodCall $node)
{
$this->isCheck($node);
}
private function isCheck(MethodCall $node)
{
}
}
👍
Use $class->namespaceName
instead of $class->name
that only returns short class name
use PhpParser\Node\Stmt\Class_;
final class SomeClass
{
public function run(Class_ $class)
{
$className = (string) $class->name;
}
}
❌
use PhpParser\Node\Stmt\Class_;
final class SomeClass
{
public function run(Class_ $class)
{
$className = (string) $class->namespacedName;
}
}
👍
Cognitive complexity of class/trait must be under specific limit
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\CognitiveComplexity\Rules\ClassLikeCognitiveComplexityRule
tags: [phpstan.rules.rule]
arguments:
maxClassCognitiveComplexity: 10
↓
class SomeClass
{
public function simple($value)
{
if ($value !== 1) {
if ($value !== 2) {
return false;
}
}
return true;
}
public function another($value)
{
if ($value !== 1 && $value !== 2) {
return false;
}
return true;
}
}
❌
class SomeClass
{
public function simple($value)
{
return $this->someOtherService->count($value);
}
public function another($value)
{
return $this->someOtherService->delete($value);
}
}
👍
services:
-
class: Symplify\PHPStanRules\CognitiveComplexity\Rules\ClassLikeCognitiveComplexityRule
tags: [phpstan.rules.rule]
arguments:
limitsByTypes:
Symfony\Component\Console\Command\Command: 5
↓
use Symfony\Component\Console\Command\Command;
class SomeCommand extends Command
{
public function configure()
{
$this->setName('...');
}
public function execute()
{
if (...) {
// ...
} else {
// ...
}
}
}
❌
use Symfony\Component\Console\Command\Command;
class SomeCommand extends Command
{
public function configure()
{
$this->setName('...');
}
public function execute()
{
return $this->externalService->resolve(...);
}
}
👍
Class should have suffix "%s" to respect parent type
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\ClassNameRespectsParentSuffixRule
tags: [phpstan.rules.rule]
arguments:
parentClasses:
- Symfony\Component\Console\Command\Command
↓
class Some extends Command
{
}
❌
class SomeCommand extends Command
{
}
👍
Static constant map should be extracted from this method
class SomeClass
{
public function run($value)
{
if ($value instanceof SomeType) {
return 100;
}
if ($value instanceof AnotherType) {
return 1000;
}
return 200;
}
}
❌
class SomeClass
{
/**
* @var array<string, int>
*/
private const TYPE_TO_VALUE = [
SomeType::class => 100,
AnotherType::class => 1000,
];
public function run($value)
{
foreach (self::TYPE_TO_VALUE as $type => $value) {
if (is_a($value, $type, true)) {
return $value;
}
}
return 200;
}
}
👍
Method name should be different to its parameter name, in a verb form
class SomeClass
{
public function apple(string $apple)
{
// ...
}
}
❌
class SomeClass
{
public function eatApple(string $apple)
{
// ...
}
}
👍
Method "%s()"
is using too many parameters - %d. Make it under %d
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\ExcessiveParameterListRule
tags: [phpstan.rules.rule]
arguments:
maxParameterCount: 2
↓
class SomeClass
{
public function __construct($one, $two, $three)
{
// ...
}
}
❌
class SomeClass
{
public function __construct($one, $two)
{
// ...
}
}
👍
Too many public elements on class - %d. Narrow it down under %d
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\ExcessivePublicCountRule
tags: [phpstan.rules.rule]
arguments:
maxPublicClassElementCount: 2
↓
class SomeClass
{
public $one;
public $two;
public $three;
}
❌
class SomeClass
{
public $one;
public $two;
}
👍
Dependency of specific type can be used only in specific class types
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\ExclusiveDependencyRule
tags: [phpstan.rules.rule]
arguments:
allowedExclusiveDependencyInTypes:
Doctrine\ORM\EntityManager:
- *Repository
Doctrine\ORM\EntityManagerInterface:
- *Repository
↓
class CheckboxController
{
/**
* @var EntityManagerInterface
*/
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
}
❌
class CheckboxRepository
{
/**
* @var EntityManagerInterface
*/
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
}
👍
Exclusive namespace can only contain classes of specific type, nothing else
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\ExclusiveNamespaceRule
tags: [phpstan.rules.rule]
arguments:
namespaceParts:
- Presenter
↓
namespace App\Presenter;
class SomeRepository
{
}
❌
namespace App\Presenter;
class SomePresenter
{
}
👍
Anonymous class is not allowed.
new class() {
};
❌
class SomeClass
{
}
new SomeClass();
👍
Array destruct is not allowed. Use value object to pass data instead
final class SomeClass
{
public function run(): void
{
[$firstValue, $secondValue] = $this->getRandomData();
}
}
❌
final class SomeClass
{
public function run(): void
{
$valueObject = $this->getValueObject();
$firstValue = $valueObject->getFirstValue();
$secondValue = $valueObject->getSecondValue();
}
}
👍
Array with keys is not allowed. Use value object to pass data instead
final class SomeClass
{
public function run()
{
return [
'name' => 'John',
'surname' => 'Dope',
];
}
}
❌
final class SomeClass
{
public function run()
{
return new Person('John', 'Dope');
}
}
👍
Attribute key "%s" cannot be used
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\ForbiddenAttributteArgumentRule
tags: [phpstan.rules.rule]
arguments:
$argumentsByAttributes:
Doctrine\ORM\Mapping\Entity:
- repositoryClass
↓
use Doctrine\ORM\Mapping\Entity;
#[Entity(repositoryClass: SomeRepository::class)]
class SomeClass
{
}
❌
use Doctrine\ORM\Mapping\Entity;
#[Entity]
class SomeClass
{
}
👍
Method call or Static Call on %s is not allowed
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\ForbiddenCallOnTypeRule
tags: [phpstan.rules.rule]
arguments:
forbiddenTypes:
- Symfony\Component\DependencyInjection\Container
↓
use Symfony\Component\DependencyInjection\Container;
class SomeClass
{
/**
* @var Container
*/
private $some;
public function __construct(Container $some)
{
$this->some = $some;
}
public function call()
{
$this->some->call();
}
}
❌
use Other\SpecificService;
class SomeClass
{
/**
* @var SpecificService
*/
private $specificService;
public function __construct(SpecificService $specificService)
{
$this->specificService = $specificService;
}
public function call()
{
$this->specificService->call();
}
}
👍
Constants in this class are not allowed, move them to custom Enum class instead
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\Enum\ForbiddenClassConstRule
tags: [phpstan.rules.rule]
arguments:
classTypes:
- AbstractEntity
↓
final class Product extends AbstractEntity
{
public const TYPE_HIDDEN = 0;
public const TYPE_VISIBLE = 1;
}
❌
final class Product extends AbstractEntity
{
}
class ProductVisibility extends Enum
{
public const HIDDEN = 0;
public const VISIBLE = 1;
}
👍
For complex configuration use value object over array
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set('...')
->call('...', [[
'options' => ['Cake\Network\Response', ['withLocation', 'withHeader']],
]]);
};
❌
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set('...')
->call('...', [[
'options' => inline_value_objects([
new SomeValueObject('Cake\Network\Response', ['withLocation', 'withHeader']),
]),
]]);
};
👍
foreach(...), while()
, for()
or if(...) cannot contains a complex expression. Extract it to a new variable assign on line before
foreach ($this->getData($arg) as $key => $item) {
// ...
}
❌
$data = $this->getData($arg);
foreach ($arg as $key => $item) {
// ...
}
👍
Do not use "%s" function with complex content, make it more readable with extracted method or single-line statement
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\ForbiddenComplexFuncCallRule
tags: [phpstan.rules.rule]
arguments:
forbiddenComplexFunctions:
- array_filter
maximumStmtCount: 2
↓
$filteredElements = array_filter($elemnets, function ($item) {
if ($item) {
return true;
}
if ($item === null) {
return true;
}
return false;
};
❌
$filteredElements = array_filter($elemnets, function ($item) {
return $item instanceof KeepItSimple;
};
👍
Object instance of "%s" is forbidden to be passed to constructor
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\ForbiddenDependencyByTypeRule
tags: [phpstan.rules.rule]
arguments:
forbiddenTypes:
- EntityManager
↓
class SomeClass
{
public function __construct(EntityManager $entityManager)
{
// ...
}
}
❌
class SomeClass
{
public function __construct(ProductRepository $productRepository)
{
// ...
}
}
👍
Function "%s()"
cannot be used/left in the code
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\ForbiddenFuncCallRule
tags: [phpstan.rules.rule]
arguments:
forbiddenFunctions:
- eval
↓
class SomeClass
{
return eval('...');
}
❌
class SomeClass
{
return echo '...';
}
👍
Method call on new expression is not allowed.
(new SomeClass())->run();
❌
$someClass = new SomeClass();
$someClass->run();
👍
Prevent using certain method calls on certains types
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\ForbiddenMethodCallOnTypeRule
tags: [phpstan.rules.rule]
arguments:
forbiddenMethodNamesByTypes:
SpecificType:
- nope
↓
class SomeClass
{
public function process(SpecificType $specificType)
{
$specificType->nope();
}
}
❌
class SomeClass
{
public function process(SpecificType $specificType)
{
$specificType->yes();
}
}
👍
Multiple class/interface/trait is not allowed in single file
class SomeClass
{
}
interface SomeInterface
{
}
❌
// SomeClass.php
class SomeClass
{
}
// SomeInterface.php
interface SomeInterface
{
}
👍
Decouple method call in assert to standalone line to make test core more readable
use PHPUnit\Framework\TestCase;
final class SomeClass extends TestCase
{
public function test()
{
$this->assertSame('oooo', $this->someMethodCall());
}
}
❌
use PHPUnit\Framework\TestCase;
final class SomeClass extends TestCase
{
public function test()
{
$result = $this->someMethodCall();
$this->assertSame('oooo', $result);
}
}
👍
Nested foreach with empty statement is not allowed
$collectedFileErrors = [];
foreach ($errors as $fileErrors) {
foreach ($fileErrors as $fileError) {
$collectedFileErrors[] = $fileError;
}
}
❌
$collectedFileErrors = [];
foreach ($fileErrors as $fileError) {
$collectedFileErrors[] = $fileError;
}
👍
Assign to already injected property is not allowed
abstract class AbstractParent
{
/**
* @inject
* @var SomeType
*/
protected $someType;
}
final class SomeChild extends AbstractParent
{
public function __construct(AnotherType $anotherType)
{
$this->someType = $anotherType;
}
}
❌
abstract class AbstractParent
{
/**
* @inject
* @var SomeType
*/
protected $someType;
}
final class SomeChild extends AbstractParent
{
}
👍
"new" outside factory is not allowed for object type "%s"
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\ForbiddenNewOutsideFactoryServiceRule
tags: [phpstan.rules.rule]
arguments:
types:
- AnotherObject
↓
class SomeClass
{
public function process()
{
$anotherObject = new AnotherObject();
// ...
}
}
❌
class SomeClass
{
public function __construt(AnotherObjectFactory $anotherObjectFactory)
{
$this->anotherObjectFactory = $anotherObjectFactory;
}
public function process()
{
$anotherObject = $this->anotherObjectFactory = $anotherObjectFactory->create();
// ...
}
}
👍
"%s" is forbidden to use
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\ForbiddenNodeRule
tags: [phpstan.rules.rule]
arguments:
forbiddenNodes:
- PhpParser\Node\Expr\ErrorSuppress
↓
return @strlen('...');
❌
return strlen('...');
👍
Parameter "%s" cannot be nullable
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\ForbiddenNullableParameterRule
tags: [phpstan.rules.rule]
arguments:
forbiddenTypes:
- PhpParser\Node
allowedTypes:
- PhpParser\Node\Scalar\String_
↓
use PhpParser\Node;
class SomeClass
{
public function run(?Node $node = null): void
{
}
}
❌
use PhpParser\Node;
class SomeClass
{
public function run(Node $node): void
{
}
}
👍
Return type "%s" cannot be nullable
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\ForbiddenNullableReturnRule
tags: [phpstan.rules.rule]
arguments:
forbiddenTypes:
- PhpParser\Node
allowedTypes:
- PhpParser\Node\Scalar\String_
↓
use PhpParser\Node;
class SomeClass
{
public function run(): ?Node
{
}
}
❌
use PhpParser\Node;
class SomeClass
{
public function run(): Node
{
}
}
👍
Removing parent param type is forbidden
interface RectorInterface
{
public function refactor(Node $node);
}
final class SomeRector implements RectorInterface
{
public function refactor($node)
{
}
}
❌
interface RectorInterface
{
public function refactor(Node $node);
}
final class SomeRector implements RectorInterface
{
public function refactor(Node $node)
{
}
}
👍
Private method in is not allowed here - it should only delegate to others. Decouple the private method to a new service class
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\ForbiddenPrivateMethodByTypeRule
tags: [phpstan.rules.rule]
arguments:
forbiddenTypes:
- Command
↓
class SomeCommand extends Command
{
public function run()
{
$this->somePrivateMethod();
}
private function somePrivateMethod()
{
// ...
}
}
❌
class SomeCommand extends Command
{
/**
* @var ExternalService
*/
private $externalService;
public function __construct(ExternalService $externalService)
{
$this->externalService = $externalService;
}
public function run()
{
$this->externalService->someMethod();
}
}
👍
Property with protected modifier is not allowed. Use interface contract method instead
class SomeClass
{
protected $repository;
}
❌
class SomeClass implements RepositoryAwareInterface
{
public function getRepository()
{
// ....
}
}
👍
Cannot return include_once/require_once
class SomeClass
{
public function run()
{
return require_once 'Test.php';
}
}
❌
class SomeClass
{
public function run()
{
require_once 'Test.php';
}
}
👍
Spread operator is not allowed.
$args = [$firstValue, $secondValue];
$message = sprintf('%s', ...$args);
❌
$message = sprintf('%s', $firstValue, $secondValue);
👍
"Tests" namespace can be only in "/tests" directory
// file path: "src/SomeClass.php
namespace App\Tests;
class SomeClass
{
}
❌
// file path: "tests/SomeClass.php
namespace App\Tests;
class SomeClass
{
}
👍
$this
as argument is not allowed. Refactor method to service composition
$this->someService->process($this, ...);
❌
$this->someService->process($value, ...);
👍
Cognitive complexity of function/method must be under specific limit
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\CognitiveComplexity\Rules\FunctionLikeCognitiveComplexityRule
tags: [phpstan.rules.rule]
arguments:
maxMethodCognitiveComplexity: 5
↓
class SomeClass
{
public function simple($value)
{
if ($value !== 1) {
if ($value !== 2) {
return false;
}
}
return true;
}
}
❌
class SomeClass
{
public function simple($value)
{
if ($value === 1) {
return true;
}
return $value === 2;
}
}
👍
Class that implements specific interface, must use related class in new SomeClass
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\IfImplementsInterfaceThenNewTypeRule
tags: [phpstan.rules.rule]
arguments:
newTypesByInterface:
ConfigurableRuleInterface: ConfiguredCodeSample
↓
class SomeRule implements ConfigurableRuleInterface
{
public function some()
{
$codeSample = new CodeSample();
}
}
❌
class SomeRule implements ConfigurableRuleInterface
{
public function some()
{
$configuredCodeSample = new ConfiguredCodeSample();
}
}
👍
Class must implement "%s" interface
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\IfNewTypeThenImplementInterfaceRule
tags: [phpstan.rules.rule]
arguments:
interfacesByNewTypes:
ConfiguredCodeSample: ConfiguredRuleInterface
↓
class SomeRule
{
public function run()
{
return new ConfiguredCodeSample('...');
}
}
❌
class SomeRule implements ConfiguredRuleInterface
{
public function run()
{
return new ConfiguredCodeSample('...');
}
}
👍
Use controller class name based on route name instead
use Symfony\Component\Routing\Annotation\Route;
final class SecurityController extends AbstractController
{
#[Route(path: '/logout', name: 'logout')]
public function __invoke(): Response
{
}
}
❌
use Symfony\Component\Routing\Annotation\Route;
final class LogoutController extends AbstractController
{
#[Route(path: '/logout', name: 'logout')]
public function __invoke(): Response
{
}
}
👍
Constant type should be "%s", but is "%s"
class SomeClass
{
/**
* @var int
*/
private const LIMIT = 'max';
}
❌
class SomeClass
{
/**
* @var string
*/
private const LIMIT = 'max';
}
👍
Use explicit interface contract or a service over unclear abstract methods
abstract class SomeClass
{
abstract public function run();
}
❌
abstract class SomeClass implements RunnableInterface
{
}
interface RunnableInterface
{
public function run();
}
👍
Use explicit methods over array access on object
class SomeClass
{
public function run(MagicArrayObject $magicArrayObject)
{
return $magicArrayObject['more_magic'];
}
}
❌
class SomeClass
{
public function run(MagicArrayObject $magicArrayObject)
{
return $magicArrayObject->getExplicitValue();
}
}
👍
Use another value object over array with string-keys and objects, array<string, ValueObject>
final class SomeClass
{
public getItems()
{
return $this->getValues();
}
/**
* @return array<string, Value>
*/
private function getValues()
{
}
}
❌
final class SomeClass
{
public getItems()
{
return $this->getValues();
}
/**
* @return WrappingValue[]
*/
private function getValues()
{
// ...
}
}
👍
No magic closure function call is allowed, use explicit class with method instead
return array_filter($items, function ($item) {
}) !== [];
❌
$values = array_filter($items, function ($item) {
});
return $values !== [];
👍
Do not use chained method calls. Put each on separated lines.
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\ObjectCalisthenics\Rules\NoChainMethodCallRule
tags: [phpstan.rules.rule]
arguments:
allowedChainTypes:
- AllowedFluent
↓
$this->runThis()
->runThat();
$fluentClass = new AllowedFluent();
$fluentClass->one()
->two();
❌
$this->runThis();
$this->runThat();
$fluentClass = new AllowedFluent();
$fluentClass->one()
->two();
👍
Class "%s" with static method must have "Static" in its name it explicit
class SomeClass
{
public static function getSome()
{
}
}
❌
class SomeStaticClass
{
public static function getSome()
{
}
}
👍
Reserve interface for contract only. Move constant holder to a class soon-to-be Enum
interface SomeContract
{
public const YES = 'yes';
public const NO = 'ne';
}
❌
class SomeValues
{
public const YES = 'yes';
public const NO = 'ne';
}
👍
Do not use constructor in tests. Move to setUp()
method
final class SomeTest
{
public function __construct()
{
// ...
}
}
❌
final class SomeTest
{
protected function setUp()
{
// ...
}
}
👍
Instead of container injection, use specific service
class SomeClass
{
public function __construct(ContainerInterface $container)
{
$this->someDependency = $container->get('...');
}
}
❌
class SomeClass
{
public function __construct(SomeDependency $someDependency)
{
$this->someDependency = $someDependency;
}
}
👍
Use custom exceptions instead of native "%s"
throw new RuntimeException('...');
❌
use App\Exception\FileNotFoundException;
throw new FileNotFoundException('...');
👍
Parameter "%s" cannot have default value
class SomeClass
{
public function run($value = true): void
{
}
}
❌
class SomeClass
{
public function run($value): void
{
}
}
👍
Use dependency injection instead of dependency juggling
public function __construct($service)
{
$this->service = $service;
}
public function run($someObject)
{
return $someObject->someMethod($this->service);
}
❌
public function run($someObject)
{
return $someObject->someMethod();
}
👍
Class with base "%s" name is already used in "%s". Use unique name to make classes easy to recognize
namespace App;
class SomeClass
{
}
namespace App\Nested;
class SomeClass
{
}
❌
namespace App;
class SomeClass
{
}
namespace App\Nested;
class AnotherClass
{
}
👍
Use explicit names over dynamic ones
class SomeClass
{
public function old(): bool
{
return $this->${variable};
}
}
❌
class SomeClass
{
public function old(): bool
{
return $this->specificMethodName();
}
}
👍
Use non-dynamic property on static calls or class const fetches
class SomeClass
{
public function run()
{
return $this->connection::literal();
}
}
❌
class SomeClass
{
public function run()
{
return Connection::literal();
}
}
👍
Do not use "else/elseif". Refactor to early return
if (...) {
return 1;
} else {
return 2;
}
❌
if (...) {
return 1;
}
return 2;
👍
There should be no empty class
class SomeClass
{
}
❌
class SomeClass
{
public function getSome()
{
}
}
👍
Do not use factory/method call in constructor. Put factory in config and get service with dependency injection
class SomeClass
{
private $someDependency;
public function __construct(SomeFactory $factory)
{
$this->someDependency = $factory->build();
}
}
❌
class SomeClass
{
private $someDependency;
public function __construct(SomeDependency $someDependency)
{
$this->someDependency = $someDependency;
}
}
👍
Separate function "%s()"
in method call to standalone row to improve readability
final class SomeClass
{
public function run($value): void
{
$this->someMethod(strlen('fooo'));
}
// ...
}
❌
final class SomeClass
{
public function run($value): void
{
$fooLength = strlen('fooo');
$this->someMethod($fooLength);
}
// ...
}
👍
Do not use "$entityManager->getRepository()"
outside of the constructor of repository service or setUp()
method in test case
final class SomeController
{
public function someAction(EntityManager $entityManager): void
{
$someEntityRepository = $entityManager->getRepository(SomeEntity::class);
}
}
❌
final class SomeRepository
{
public function __construct(EntityManager $entityManager): void
{
$someEntityRepository = $entityManager->getRepository(SomeEntity::class);
}
}
👍
Do not inherit from abstract class, better use composition
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\NoInheritanceRule
tags: [phpstan.rules.rule]
arguments:
allowedParentTypes:
- AnotherParent
↓
class SomeClass extends AbstratcClass
{
public function run()
{
$this->parentMethod();
}
}
❌
class SomeClass
{
private function __construct(
private $dependency Dependency
) {
}
public function run()
{
$this->dependency->otherMethod();
}
}
👍
Use constructor on final classes, instead of property injection
final class SomePresenter
{
/**
* @inject
*/
public $property;
}
❌
abstract class SomePresenter
{
/**
* @inject
*/
public $property;
}
👍
Use local named constant instead of inline string for regex to explain meaning by constant name
class SomeClass
{
public function run($value)
{
return preg_match('#some_stu|ff#', $value);
}
}
❌
class SomeClass
{
/**
* @var string
*/
public const SOME_STUFF_REGEX = '#some_stu|ff#';
public function run($value)
{
return preg_match(self::SOME_STUFF_REGEX, $value);
}
}
👍
Use default null value and nullable compare instead of isset on object
class SomeClass
{
public function run()
{
if (random_int(0, 1)) {
$object = new self();
}
if (isset($object)) {
return $object;
}
}
}
❌
class SomeClass
{
public function run()
{
$object = null;
if (random_int(0, 1)) {
$object = new self();
}
if ($object !== null) {
return $object;
}
}
}
👍
No magic closure function call is allowed, use explicit class with method instead
(static function () {
// ...
})
❌
final class HelpfulName
{
public function clearName()
{
// ...
}
}
👍
Missing sprintf()
function for a mask
return 'Hey %s';
❌
return sprintf('Hey %s', 'Matthias');
👍
Do not use @method
tag in class docblock
/**
* @method getMagic() string
*/
class SomeClass
{
public function __call()
{
// more magic
}
}
❌
class SomeClass
{
public function getExplicitValue()
{
return 'explicit';
}
}
👍
The path "%s" was not found
class SomeClass
{
public function run()
{
return __DIR__ . '/missing_location.txt';
}
}
❌
class SomeClass
{
public function run()
{
return __DIR__ . '/existing_location.txt';
}
}
👍
Use void instead of modify and return self object
final class SomeClass
{
public function modify(ComposerJson $composerJson): ComposerJson
{
$composerJson->addPackage('some-package');
return $composerJson;
}
}
❌
final class SomeClass
{
public function modify(ComposerJson $composerJson): void
{
$composerJson->addPackage('some-package');
}
}
👍
Use value object over multi array assign
final class SomeClass
{
public function run()
{
$values = [];
$values['person']['name'] = 'Tom';
$values['person']['surname'] = 'Dev';
}
}
❌
final class SomeClass
{
public function run()
{
$values = [];
$values[] = new Person('Tom', 'Dev');
}
}
👍
Use separate function calls with readable variable names
class SomeClass
{
public function run()
{
return array_filter(array_map($callback, $items));
}
}
❌
class SomeClass
{
public function run()
{
$mappedItems = array_map($callback, $items);
return array_filter($mappedItems);
}
}
👍
Avoid using magical unclear array access and use explicit "$this->getComponent()"
instead
use Nette\Application\UI\Presenter;
class SomeClass extends Presenter
{
public function render()
{
return $this['someControl'];
}
}
❌
use Nette\Application\UI\Presenter;
class SomeClass extends Presenter
{
public function render()
{
return $this->getComponent('someControl');
}
}
👍
Avoid double template variable override of "%s"
use Nette\Application\UI\Presenter;
class SomeClass extends Presenter
{
public function render()
{
$this->template->key = '1';
$this->template->key = '2';
}
}
❌
use Nette\Application\UI\Presenter;
class SomeClass extends Presenter
{
public function render()
{
$this->template->key = '2';
}
}
👍
Use either __construct()
or @inject, not both together
class SomeClass
{
private $someType;
public function __construct()
{
// ...
}
public function injectSomeType($someType)
{
$this->someType = $someType;
}
}
❌
class SomeClass
{
private $someType;
public function __construct($someType)
{
$this->someType = $someType;
}
}
👍
Passed "%s" variable that are not used in the template
use Nette\Application\UI\Control;
final class SomeControl extends Control
{
public function render()
{
$this->template->render(__DIR__ . '/some_file.latte', [
'non_existing_variable' => 'value',
]);
}
}
❌
use Nette\Application\UI\Control;
final class SomeControl extends Control
{
public function render()
{
$this->template->render(__DIR__ . '/some_file.latte', [
'existing_variable' => 'value',
]);
}
}
👍
Missing "%s" variable that are not passed to the template
use Nette\Application\UI\Control;
final class SomeControl extends Control
{
public function render()
{
$this->template->render(__DIR__ . '/some_file.latte');
}
}
❌
use Nette\Application\UI\Control;
final class SomeControl extends Control
{
public function render()
{
$this->template->render(__DIR__ . '/some_file.latte', [
'existing_variable' => 'value',
]);
}
}
👍
Avoid $this->template->variable
for read access, as it can be defined anywhere. Use local $variable
instead
use Nette\Application\UI\Presenter;
class SomeClass extends Presenter
{
public function render()
{
if ($this->template->key === 'value') {
return;
}
}
}
❌
use Nette\Application\UI\Presenter;
class SomeClass extends Presenter
{
public function render()
{
$this->template->key = 'value';
}
}
👍
Use required typed property over of nullable array property
final class SomeClass
{
private ?array $property = null;
}
❌
final class SomeClass
{
private array $property;
}
👍
Use required typed property over of nullable property
final class SomeClass
{
private ?DateTime $property = null;
}
❌
final class SomeClass
{
private DateTime $property;
}
👍
Do not call parent method if parent method is empty
class ParentClass
{
public function someMethod()
{
}
}
class SomeClass extends ParentClass
{
public function someMethod()
{
parent::someMethod();
}
}
❌
class ParentClass
{
public function someMethod()
{
}
}
class SomeClass extends ParentClass
{
public function someMethod()
{
}
}
👍
Do not call parent method if no override process
class SomeClass extends Printer
{
public function print($nodes)
{
return parent::print($nodes);
}
}
❌
class SomeClass extends Printer
{
}
👍
Post operation are forbidden, as they make 2 values at the same line. Use pre instead
class SomeClass
{
public function run($value = 1)
{
// 1 ... 0
if ($value--) {
}
}
}
❌
class SomeClass
{
public function run($value = 1)
{
// 0
if (--$value) {
}
}
}
👍
Instead of protected element in final class use private element or contract method
final class SomeClass
{
private function run()
{
}
}
❌
final class SomeClass
{
private function run()
{
}
}
👍
Use explicit return value over magic &reference
class SomeClass
{
public function run(&$value)
{
}
}
❌
class SomeClass
{
public function run($value)
{
return $value;
}
}
👍
Use value object over return of values
class ReturnVariables
{
public function run($value, $value2): array
{
return [$value, $value2];
}
}
❌
final class ReturnVariables
{
public function run($value, $value2): ValueObject
{
return new ValueObject($value, $value2);
}
}
👍
Setter method cannot return anything, only set value
final class SomeClass
{
private $name;
public function setName(string $name)
{
return 1000;
}
}
❌
final class SomeClass
{
private $name;
public function setName(string $name): void
{
$this->name = $name;
}
}
👍
Do not use setter on a service
class SomeService
{
public function setSomeValue($value)
{
}
}
❌
class SomeEntity
{
public function setSomeValue($value)
{
}
}
👍
Do not name "%s", shorter than %d chars
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\ObjectCalisthenics\Rules\NoShortNameRule
tags: [phpstan.rules.rule]
arguments:
minNameLength: 3
↓
function is()
{
}
❌
function isClass()
{
}
👍
Do not use static property
final class SomeClass
{
private static $customFileNames = [];
}
❌
final class SomeClass
{
private $customFileNames = [];
}
👍
Value Object class name "%s" must be without "ValueObject" suffix. The correct class name is "%s".
class SomeValueObject
{
public function __construct(string $name)
{
$this->name = $name;
}
}
❌
class Some
{
public function __construct(string $name)
{
$this->name = $name;
}
}
👍
Do not use trait, extract to a service and dependency injection instead
trait SomeTrait
{
public function run()
{
}
}
❌
class SomeService
{
public function run(...)
{
}
}
👍
Getter method must return something, not void
final class SomeClass
{
public function getData(): void
{
}
}
❌
final class SomeClass
{
public function getData(): array
{
}
}
👍
Use defined constant %s::%s over string %s
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\PreferConstantValueRule
tags: [phpstan.rules.rule]
arguments:
constantHoldingObjects:
Symplify\ComposerJsonManipulator\ValueObject\ComposerJsonSection:
- "REQUIRE(_.*)?"
- "AUTOLOAD(_.*)?"
↓
class SomeClass
{
public function run()
{
return 'require';
}
}
❌
use Symplify\ComposerJsonManipulator\ValueObject\ComposerJsonSection;
class SomeClass
{
public function run()
{
return ComposerJsonSection::REQUIRE;
}
}
👍
Use attribute instead of "%s" annotation
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\PreferredAttributeOverAnnotationRule
tags: [phpstan.rules.rule]
arguments:
annotations:
- Symfony\Component\Routing\Annotation\Route
↓
use Symfony\Component\Routing\Annotation\Route;
class SomeController
{
/**
* @Route()
*/
public function action()
{
}
}
❌
use Symfony\Component\Routing\Annotation\Route;
class SomeController
{
#Route()
public function action()
{
}
}
👍
Instead of "%s" class/interface use "%s"
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\PreferredClassRule
tags: [phpstan.rules.rule]
arguments:
oldToPreferredClasses:
SplFileInfo: Symplify\SmartFileSystem\SmartFileInfo
↓
class SomeClass
{
public function run()
{
return new SplFileInfo('...');
}
}
❌
use Symplify\SmartFileSystem\SmartFileInfo;
class SomeClass
{
public function run()
{
return new SmartFileInfo('...');
}
}
👍
Use "%s->%s()"
method call over "%s()"
func call
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\PreferredMethodCallOverFuncCallRule
tags: [phpstan.rules.rule]
arguments:
funcCallToPreferredMethodCalls:
strlen:
- Nette\Utils\Strings
- length
↓
class SomeClass
{
public function run($value)
{
return strlen($value);
}
}
❌
use Nette\Utils\Strings;
class SomeClass
{
public function __construct(Strings $strings)
{
$this->strings = $strings;
}
public function run($value)
{
return $this->strings->length($value);
}
}
👍
Code configured at setUp()
cannot be used in data provider. Move it to test()
method
final class UseDataFromSetupInTestDataProviderTest extends TestCase
{
private $data;
protected function setUp()
{
$this->data = true;
}
public function provideFoo()
{
yield [$this->data];
}
/**
* @dataProvider provideFoo
*/
public function testFoo($value)
{
$this->assertTrue($value);
}
}
❌
use stdClass;
final class UseRawDataForTestDataProviderTest
{
private $obj;
private function setUp()
{
$this->obj = new stdClass();
}
public function provideFoo()
{
yield [true];
}
/**
* @dataProvider provideFoo
*/
public function testFoo($value)
{
$this->obj->x = $value;
$this->assertTrue($this->obj->x);
}
}
👍
Use "%s::%s()"
static call over "%s()"
func call
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\PreferredStaticCallOverFuncCallRule
tags: [phpstan.rules.rule]
arguments:
funcCallToPreferredStaticCalls:
strlen:
- Nette\Utils\Strings
- length
↓
class SomeClass
{
public function run($value)
{
return strlen($value);
}
}
❌
use Nette\Utils\Strings;
class SomeClass
{
public function run($value)
{
return Strings::length($value);
}
}
👍
Abstract class name "%s" must be prefixed with "Abstract"
abstract class SomeClass
{
}
❌
abstract class AbstractSomeClass
{
}
👍
Set param value is overriden. Merge it to previous set above
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$parameters = $containerConfigurator->parameters();
$parameters->set('some_param', [1]);
$parameters->set('some_param', [2]);
};
❌
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$parameters = $containerConfigurator->parameters();
$parameters->set('some_param', [1, 2]);
};
👍
Content of method "%s()"
is duplicated with method "%s()"
in "%s" class. Use unique content or service instead
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\PreventDuplicateClassMethodRule
tags: [phpstan.rules.rule]
arguments:
minimumLineCount: 1
↓
class SomeClass
{
public function someMethod()
{
echo 'statement';
$value = new SmartFinder();
}
}
class AnotherClass
{
public function someMethod()
{
echo 'statement';
$differentValue = new SmartFinder();
}
}
❌
class SomeClass
{
public function someMethod()
{
echo 'statement';
$value = new SmartFinder();
}
}
}
👍
Change "%s()"
method visibility to "%s" to respect parent method visibility.
class SomeParentClass
{
public function run()
{
}
}
class SomeClass
{
protected function run()
{
}
}
❌
class SomeParentClass
{
public function run()
{
}
}
class SomeClass
{
public function run()
{
}
}
👍
Name your constant with "_REGEX" suffix, instead of "%s"
class SomeClass
{
public const SOME_NAME = '#some\s+name#';
public function run($value)
{
$somePath = preg_match(self::SOME_NAME, $value);
}
}
❌
class SomeClass
{
public const SOME_NAME_REGEX = '#some\s+name#';
public function run($value)
{
$somePath = preg_match(self::SOME_NAME_REGEX, $value);
}
}
👍
Attribute must have all names explicitly defined
use Symfony\Component\Routing\Annotation\Route;
class SomeController
{
#[Route('/path')]
public function someAction()
{
}
}
❌
use Symfony\Component\Routing\Annotation\Route;
class SomeController
{
#[Route(path: '/path')]
public function someAction()
{
}
}
👍
Argument "%s" must be a constant
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\RequireConstantInAttributeArgumentRule
tags: [phpstan.rules.rule]
arguments:
attributeWithNames:
Symfony\Component\Routing\Annotation\Route:
- name
↓
use Symfony\Component\Routing\Annotation\Route;
final class SomeClass
{
#[Route(path: '/archive', name: 'blog_archive')]
public function __invoke()
{
}
}
❌
use Symfony\Component\Routing\Annotation\Route;
final class SomeClass
{
#[Route(path: '/archive', name: RouteName::BLOG_ARCHIVE)]
public function __invoke()
{
}
}
👍
Parameter argument on position %d must use %s constant
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\RequireConstantInMethodCallPositionRule
tags: [phpstan.rules.rule]
arguments:
requiredLocalConstantInMethodCall:
SomeType:
someMethod:
- 0
↓
class SomeClass
{
public function someMethod(SomeType $someType)
{
$someType->someMethod('hey');
}
}
❌
class SomeClass
{
private const HEY = 'hey'
public function someMethod(SomeType $someType)
{
$someType->someMethod(self::HEY);
}
}
👍
The "%s()"
method must use data provider
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\RequireDataProviderTestMethodRule
tags: [phpstan.rules.rule]
arguments:
classesRequiringDataProvider:
- *RectorTestCase
↓
class SomeRectorTestCase extends RectorTestCase
{
public function test()
{
}
}
❌
class SomeRectorTestCase extends RectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test($value)
{
}
public function provideData()
{
// ...
}
}
👍
Use invokable controller with __invoke()
method instead of named action method
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
final class SomeController extends AbstractController
{
/**
* @Route()
*/
public function someMethod()
{
}
}
❌
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
final class SomeController extends AbstractController
{
/**
* @Route()
*/
public function __invoke()
{
}
}
👍
Method call argument on position %d must use constant (e.g. "Option::NAME") over value
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\RequireMethodCallArgumentConstantRule
tags: [phpstan.rules.rule]
arguments:
constantArgByMethodByType:
SomeClass:
call:
- 0
↓
class AnotherClass
{
public function run(SomeClass $someClass)
{
$someClass->call('name');
}
}
❌
class AnotherClass
{
private OPTION_NAME = 'name';
public function run(SomeClass $someClass)
{
$someClass->call(self::OPTION_NAME);
}
}
👍
Second argument of $this->render("template.twig",
[...]) method should be explicit array, to avoid accidental variable override, see https://tomasvotruba.com/blog/2021/02/15/how-dangerous-is-your-nette-template-assign/
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class SomeController extends AbstractController
{
public function default()
{
$parameters['name'] = 'John';
$parameters['name'] = 'Doe';
return $this->render('...', $parameters);
}
}
❌
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class SomeController extends AbstractController
{
public function default()
{
return $this->render('...', [
'name' => 'John',
]);
}
}
👍
New expression argument on position %d must use constant over value
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\RequireNewArgumentConstantRule
tags: [phpstan.rules.rule]
arguments:
constantArgByNewByType:
Symfony\Component\Console\Input\InputOption:
- 2
↓
use Symfony\Component\Console\Input\InputOption;
$inputOption = new InputOption('name', null, 2);
❌
use Symfony\Component\Console\Input\InputOption;
$inputOption = new InputOption('name', null, InputOption::VALUE_REQUIRED);
👍
"%s" in sprintf()
format must be quoted
class SomeClass
{
public function run()
{
echo sprintf('%s value', $variable);
}
}
❌
class SomeClass
{
public function run()
{
echo sprintf('"%s" value', $variable);
}
}
👍
Skipped tested file must start with "Skip" prefix
use PHPStan\Testing\RuleTestCase;
final class SomeRuleTest extends RuleTestCase
{
/**
* @dataProvider provideData()
* @param array<string|int> $expectedErrorMessagesWithLines
*/
public function testRule(string $filePath, array $expectedErrorMessagesWithLines): void
{
$this->analyse([$filePath], $expectedErrorMessagesWithLines);
}
public function provideData(): Iterator
{
yield [__DIR__ . '/Fixture/NewWithInterface.php', []];
}
protected function getRule(): Rule
{
return new SomeRule());
}
}
❌
use PHPStan\Testing\RuleTestCase;
final class SomeRuleTest extends RuleTestCase
{
/**
* @dataProvider provideData()
* @param array<string|int> $expectedErrorMessagesWithLines
*/
public function testRule(string $filePath, array $expectedErrorMessagesWithLines): void
{
$this->analyse([$filePath], $expectedErrorMessagesWithLines);
}
public function provideData(): Iterator
{
yield [__DIR__ . '/Fixture/SkipNewWithInterface.php', []];
}
protected function getRule(): Rule
{
return new SomeRule());
}
}
👍
Use quoted string in constructor "new %s()"
argument on position %d instead of "::class. It prevent scoping of the class in building prefixed package.
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\RequireStringArgumentInConstructorRule
tags: [phpstan.rules.rule]
arguments:
stringArgPositionsByType:
SomeClass:
- 0
↓
class AnotherClass
{
public function run()
{
new SomeClass(YetAnotherClass:class);
}
}
❌
class AnotherClass
{
public function run()
{
new SomeClass('YetAnotherClass');
}
}
👍
Use quoted string in method call "%s()"
argument on position %d instead of "::class. It prevent scoping of the class in building prefixed package.
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\RequireStringArgumentInMethodCallRule
tags: [phpstan.rules.rule]
arguments:
stringArgPositionByMethodByType:
SomeClass:
someMethod:
- 0
↓
class AnotherClass
{
public function run(SomeClass $someClass)
{
$someClass->someMethod(YetAnotherClass:class);
}
}
❌
class AnotherClass
{
public function run(SomeClass $someClass)
{
$someClass->someMethod('YetAnotherClass'');
}
}
👍
Regex must use string named capture groups instead of numeric
use Nette\Utils\Strings;
class SomeClass
{
private const REGEX = '#(a content)#';
public function run()
{
$matches = Strings::match('a content', self::REGEX);
if ($matches) {
echo $matches[1];
}
}
}
❌
use Nette\Utils\Strings;
class SomeClass
{
private const REGEX = '#(?<content>a content)#';
public function run()
{
$matches = Strings::match('a content', self::REGEX);
if ($matches) {
echo $matches['content'];
}
}
}
👍
Set control template explicitly in $this->template->setFile(...)
or $this->template->render(...)
use Nette\Application\UI\Control;
final class SomeControl extends Control
{
public function render()
{
}
}
❌
use Nette\Application\UI\Control;
final class SomeControl extends Control
{
public function render()
{
$this->template->render('some_file.latte');
}
}
👍
Use "$this->()" instead of "self::()" to call local method
class SomeClass
{
public function run()
{
self::execute();
}
private function execute()
{
}
}
❌
class SomeClass
{
public function run()
{
$this->execute();
}
private function execute()
{
}
}
👍
Use "$this->()" instead of "parent::()" unless in the same named method
class SomeParentClass
{
public function run()
{
}
}
class SomeClass extends SomeParentClass
{
public function go()
{
parent::run();
}
}
❌
class SomeParentClass
{
public function run()
{
}
}
class SomeClass extends SomeParentClass
{
public function go()
{
$tihs->run();
}
}
👍
Enum constants "%s" are duplicated. Make them unique instead
use MyCLabs\Enum\Enum;
class SomeClass extends Enum
{
private const YES = 'yes';
private const NO = 'yes';
}
❌
use MyCLabs\Enum\Enum;
class SomeClass extends Enum
{
private const YES = 'yes';
private const NO = 'no';
}
👍
Class name starting with "Abstract" must have an abstract
keyword
class AbstractClass
{
}
❌
abstract class AbstractClass
{
}
👍
Class "%s" is missing @see
annotation with test case class reference
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\SeeAnnotationToTestRule
tags: [phpstan.rules.rule]
arguments:
requiredSeeTypes:
- Rule
↓
class SomeClass extends Rule
{
}
❌
/**
* @see SomeClassTest
*/
class SomeClass extends Rule
{
}
👍
Make specific service suffix to use similar value object names for configuring in Symfony configs
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\ServiceAndValueObjectHaveSameStartsRule
tags: [phpstan.rules.rule]
arguments:
classSuffixes:
- Rector
↓
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(SomeRector::class)
->call('configure', [[new Another()]]);
};
❌
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(SomeRector::class)
->call('configure', [[new Some()]]);
};
👍
Do not indent more than %dx in class methods
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\ObjectCalisthenics\Rules\SingleIndentationInMethodRule
tags: [phpstan.rules.rule]
arguments:
maxNestingLevel:
- 2
↓
function someFunction()
{
if (...) {
if (...) {
}
}
}
❌
function someFunction()
{
if (! ...) {
}
if (!...) {
}
}
👍
Use single inject*() class method per class
class SomeClass
{
private $type;
private $anotherType;
public function injectOne(Type $type)
{
$this->type = $type;
}
public function injectTwo(AnotherType $anotherType)
{
$this->anotherType = $anotherType;
}
}
❌
class SomeClass
{
private $type;
private $anotherType;
public function injectSomeClass(Type $type, AnotherType $anotherType) {
$this->type = $type;
$this->anotherType = $anotherType;
}
}
👍
Interface must be suffixed with "Interface" exclusively
interface SomeClass
{
}
❌
interface SomeInterface
{
}
👍
Trait must be suffixed by "Trait" exclusively
trait SomeClass
{
}
❌
trait SomeTrait
{
}
👍
new is limited to %d "new (new ))" nesting to each other. You have %d nesting.
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\TooDeepNewClassNestingRule
tags: [phpstan.rules.rule]
arguments:
maxNewClassNesting: 2
↓
$someObject = new A(new B(new C()));
❌
$firstObject = new B(new C());
$someObject = new A($firstObject);
👍
%s has %d lines, it is too long. Shorted it under %d lines
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\ObjectCalisthenics\Rules\TooLongClassLikeRule
tags: [phpstan.rules.rule]
arguments:
maxClassLikeLength: 3
↓
class SomeClass
{
public function someMethod()
{
if (...) {
return 1;
} else {
return 2;
}
}
}
❌
class SomeClass
{
public function someMethod()
{
return (...) ? 1 : 2;
}
}
👍
%s has %d lines, it is too long. Shorted it under %d lines
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\ObjectCalisthenics\Rules\TooLongFunctionLikeRule
tags: [phpstan.rules.rule]
arguments:
maxFunctionLikeLength: 3
↓
function some()
{
if (...) {
return 1;
} else {
return 2;
}
}
❌
function some()
{
return (...) ? 1 : 2;
}
👍
Variable "$%s" is too long with %d chars. Narrow it under %d chars
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\TooLongVariableRule
tags: [phpstan.rules.rule]
arguments:
maxVariableLength: 10
↓
class SomeClass
{
public function run()
{
return $superLongVariableName;
}
}
❌
class SomeClass
{
public function run()
{
return $shortName;
}
}
👍
Method has too many methods %d. Try narrowing it down under %d
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\ObjectCalisthenics\Rules\TooManyMethodsRule
tags: [phpstan.rules.rule]
arguments:
maxMethodCount: 1
↓
class SomeClass
{
public function firstMethod()
{
}
public function secondMethod()
{
}
}
❌
class SomeClass
{
public function firstMethod()
{
}
}
👍
Class has too many properties %d. Try narrowing it down under %d
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\ObjectCalisthenics\Rules\TooManyPropertiesRule
tags: [phpstan.rules.rule]
arguments:
maxPropertyCount: 2
↓
class SomeClass
{
private $some;
private $another;
private $third;
}
❌
class SomeClass
{
private $some;
private $another;
}
👍
Constant "%s" must be uppercase
final class SomeClass
{
public const some = 'value';
}
❌
final class SomeClass
{
public const SOME = 'value';
}
👍
Nette @inject
annotation/#[Inject] must be valid
class SomeClass
{
/**
* @inject
*/
private $someDependency;
}
❌
class SomeClass
{
/**
* @inject
*/
public $someDependency;
}
👍