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
{
}👍
"*Test.php" file cannot be located outside "Tests" namespace
// file: "SomeTest.php
namespace App;
class SomeTest
{
}❌
// file: "SomeTest.php
namespace App\Tests;
class SomeTest
{
}👍
Interface must be located in "Contract" namespace
namespace App\Repository;
interface ProductRepositoryInterface
{
}❌
namespace App\Contract\Repository;
interface ProductRepositoryInterface
{
}👍
sprintf() call mask types does not match provided arguments types
echo sprintf('My name is %s and I have %d children', 10, 'Tomas');❌
echo sprintf('My name is %s and I have %d children', 'Tomas', 10);👍
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)
{
}
}👍
Define in which namespaces (using *, ** or ? glob-like pattern matching) can classes extending specified class or implementing specified interface exist
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\ClassExtendingExclusiveNamespaceRule
tags: [phpstan.rules.rule]
arguments:
guards:
Symfony\Component\Form\FormTypeInterface:
- 'App\Form\**'↓
namespace App;
// AbstractType implements \Symfony\Component\Form\FormTypeInterface
use Symfony\Component\Form\AbstractType;
class UserForm extends AbstractType
{
}❌
namespace App\Form;
use Symfony\Component\Form\AbstractType;
class UserForm extends AbstractType
{
}👍
services:
-
class: Symplify\PHPStanRules\Rules\ClassExtendingExclusiveNamespaceRule
tags: [phpstan.rules.rule]
arguments:
guards:
'App\Component\PriceEngine\**':
- 'App\Component\PriceEngine\**'
- 'App\Component\PriceEngineImpl\**'↓
namespace App\Services;
use App\Component\PriceEngine\PriceProviderInterface;
class CustomerProductProvider extends PriceProviderInterface
{
}❌
namespace App\Component\PriceEngineImpl;
use App\Component\PriceEngine\PriceProviderInterface;
class CustomerProductProvider extends PriceProviderInterface
{
}👍
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
{
}👍
Constants "%s" should be extract to standalone enum class
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\Enum\EmbeddedEnumClassConstSpotterRule
tags: [phpstan.rules.rule]
arguments:
parentTypes:
- AbstractObject↓
class SomeProduct extends AbstractObject
{
public const STATUS_ENABLED = 1;
public const STATUS_DISABLED = 0;
}❌
class SomeProduct extends AbstractObject
{
}
class SomeStatus
{
public const ENABLED = 1;
public const DISABLED = 0;
}👍
The string value "%s" is repeated %d times. Refactor to enum to avoid typos and make clear allowed values
$this->addFlash('info', 'Some message');
$this->addFlash('info', 'Another message');❌
$this->addFlash(FlashType::INFO, 'Some message');
$this->addFlash(FlashType::INFO, 'Another message');👍
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'↓
final class CheckboxController
{
public function __construct(
private EntityManagerInterface $entityManager
) {
}
}❌
final class CheckboxRepository
{
public function __construct(
private EntityManagerInterface $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
{
}👍
Instead of magic property "%s" access use direct explicit "%s->%s()" method call
use Nette\SmartObject;
final class MagicObject
{
// adds magic __get() and __set() methods
use SmartObject;
private $name;
public function getName()
{
return $this->name;
}
}
$magicObject = new MagicObject();
// magic re-directed to method
$magicObject->name;❌
use Nette\SmartObject;
final class MagicObject
{
// adds magic __get() and __set() methods
use SmartObject;
private $name;
public function getName()
{
return $this->name;
}
}
$magicObject = new MagicObject();
// explicit
$magicObject->getName();👍
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 method calls [$this, "method"] are not allowed. Use explicit method instead to help PhpStorm, PHPStan and Rector understand your code
usort($items, [$this, "method"]);❌
usort($items, function (array $apples) {
return $this->method($apples);
};👍
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');
}
}👍
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;
}👍
foreach(), while(), for() or if() cannot contain a complex expression. Extract it to a new variable on a line before
foreach ($this->getData($arg) as $key => $item) {
// ...
}❌
$data = $this->getData($arg);
foreach ($arg as $key => $item) {
// ...
}👍
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 '...';
}👍
services:
-
class: Symplify\PHPStanRules\Rules\ForbiddenFuncCallRule
tags: [phpstan.rules.rule]
arguments:
forbiddenFunctions:
dump: 'seems you missed some debugging function'↓
class SomeClass
{
dump('hello world');
return true;
}❌
class SomeClass
{
return true;
}👍
Method "%s()" only calling another method call and has no added value. Use the inlined call instead
class SomeClass
{
public function run()
{
return $this->away();
}
private function away()
{
return mt_rand(0, 100);
}
}❌
class SomeClass
{
public function run()
{
return mt_rand(0, 100);
}
}👍
Multiple class/interface/trait is not allowed in single file
// src/SomeClass.php
class SomeClass
{
}
interface SomeInterface
{
}❌
// src/SomeClass.php
class SomeClass
{
}
// src/SomeInterface.php
interface SomeInterface
{
}👍
Named arguments do not add any value here. Use normal arguments in the same order
return strlen(string: 'name');❌
return strlen('name');👍
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);
}
}👍
"%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('...');👍
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)
{
}
}👍
Property with protected modifier is not allowed. Use interface contract method instead
class SomeClass
{
protected $repository;
}❌
class SomeClass implements RepositoryAwareInterface
{
public function getRepository()
{
// ....
}
}👍
New objects with "%s" name are overridden. This can lead to unwanted bugs, please pick a different name to avoid it.
$product = new Product();
$product = new Product();
$this->productRepository->save($product);❌
$firstProduct = new Product();
$secondProduct = new Product();
$this->productRepository->save($firstProduct);👍
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, ...);👍
If/else construction can be replace with more robust match()
class SomeClass
{
public function spot($value)
{
if ($value === 100) {
$items = ['yes'];
} else {
$items = ['no'];
}
return $items;
}
}❌
class SomeClass
{
public function spot($value)
{
return match($value) {
100 => ['yes'],
default => ['no'],
};
}
}👍
Parameters should use "%s" types as the only types passed to this method
use PhpParser\Node\Expr\MethodCall;
final class SomeClass
{
public function run(SomeService $someService, MethodCall $methodCall)
{
$someService->isCheck($node);
}
}
final class SomeService
{
public function isCheck($methodCall)
{
}
}❌
use PhpParser\Node\Expr\MethodCall;
final class SomeClass
{
public function run(SomeService $someService, MethodCall $methodCall)
{
$someService->isCheck($node);
}
}
final class SomeService
{
public function isCheck(MethodCall $methodCall)
{
}
}👍
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();
}👍
Instead of abstract class, use specific service with composition
final class NormalHelper extends AbstractHelper
{
}
abstract class AbstractHelper
{
}❌
final class NormalHelper
{
public function __construct(
private SpecificHelper $specificHelper
) {
}
}
final class SpecificHelper
{
}👍
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 function getItems()
{
return $this->getValues();
}
/**
* @return array<string, Value>
*/
private function getValues()
{
}
}❌
final class SomeClass
{
public function 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 !== [];👍
Class has a static method must so must contains "Static" in its name
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 = 'no';
}❌
class SomeValues
{
public const YES = 'yes';
public const NO = 'no';
}👍
Do not use constructor in tests. Move to setUp() method
final class SomeTest
{
public function __construct()
{
// ...
}
}❌
final class SomeTest
{
public function setUp()
{
// ...
}
}👍
Use custom exceptions instead of native "%s"
throw new RuntimeException('...');❌
use App\Exception\FileNotFoundException;
throw new FileNotFoundException('...');👍
This call has duplicate argument
function run($one, $one);❌
function run($one, $two);👍
The "%s" constant contains duplicated regex "%s". Instead of duplicated regexes, extract domain regexes together to save maintenance
class SomeClass
{
private const CLASS_NAME_REGEX = '#[\w\\]+#';
}
class AnotherClass
{
private const DIFFERENT_NAME_REGEX = '#[\w\\]+#';
}❌
class ClassRegexRecipies
{
private const NAME_REGEX = '#[\w\\]+#';
}👍
Class with base "%s" name is already used in "%s". Use unique name to make classes easy to recognize
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\NoDuplicatedShortClassNameRule
tags: [phpstan.rules.rule]
arguments:
toleratedNestingLevel: 1↓
namespace App;
class SomeClass
{
}
namespace App\Nested;
class SomeClass
{
}❌
namespace App;
class SomeClass
{
}
namespace App\Nested;
class AnotherClass
{
}👍
Method name "%s()" is used in multiple traits. Make it it unique to avoid conflicts
trait FirstTrait
{
public function run()
{
}
}
trait SecondTrait
{
public function run()
{
}
}❌
trait FirstTrait
{
public function run()
{
}
}
trait SecondTrait
{
public function fly()
{
}
}👍
Lowered variable "%s" is used in various-cased names: "%s", unite it to one
final class SomeClass
{
public function run()
{
$run = 1;
}
public function go()
{
$ruN = 2;
}
}❌
final class SomeClass
{
public function run()
{
$run = 1;
}
public function go()
{
$run = 2;
}
}👍
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();
}
}👍
There should be no empty class
class SomeClass
{
}❌
class SomeClass
{
public function getSome()
{
}
}👍
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);
}
}👍
There are 2 way to get "%s" value: public property and getter now - pick one to avoid variant behavior.
final class SomeProduct
{
public $name;
public function getName(): string
{
return $this->name;
}
}❌
final class SomeProduct
{
private $name;
public function getName(): string
{
return $this->name;
}
}👍
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 SomeClass();
}
if (isset($object)) {
return $object;
}
}
}❌
class SomeClass
{
public function run()
{
$object = null;
if (random_int(0, 1)) {
$object = new SomeClass();
}
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()
{
// ...
}
}👍
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 assert is tautology that compares to itself. Fix it to different values
use PHPUnit\Framework\TestCase;
final class AssertMirror extends TestCase
{
public function test()
{
$this->assertSame(1, 1);
}
}❌
use PHPUnit\Framework\TestCase;
final class AssertMirror extends TestCase
{
public function test()
{
$value = 200;
$this->assertSame(1, $value);
}
}👍
Complete known array shape to the method @return type
function run(string $name)
{
return ['name' => $name];
}❌
/**
* @return array{name: string}
*/
function run(string $name)
{
return ['name' => $name];
}👍
Method call return value that should be used, but is not
class SomeClass
{
public function run()
{
$this->getResult();
}
private function getResult()
{
return [];
}
}❌
final class SomeClass
{
public function run()
{
return $this->getResult();
}
private function getResult()
{
return [];
}
}👍
The path "%s" was not found
$filePath = __DIR__ . '/missing_location.txt';❌
$filePath = __DIR__ . '/existing_location.txt';👍
Add explicit array type to assigned "%s" expression
class SomeClass
{
private $items = [];
public function addItem(string $key, string $value)
{
$this->items[$key] = $value;
}
}❌
class SomeClass
{
/**
* @var array<string, string>
*/
private $items = [];
public function addItem(string $key, string $value)
{
$this->items[$key] = $value;
}
}👍
Make callable type explicit. Here is how: https://phpstan.org/writing-php-code/phpdoc-types#callables
function run(callable $callable)
{
return $callable(100);
}❌
/**
* @param callable(): int $callable
*/
function run(callable $callable): int
{
return $callable(100);
}👍
Anonymous variable in a %s->...() method call can lead to false dead methods. Make sure the variable type is known
function run($unknownType)
{
return $unknownType->call();
}❌
function run(KnownType $knownType)
{
return $knownType->call();
}👍
Anonymous variables in a "%s->..." property fetch can lead to false dead property. Make sure the variable type is known
function run($unknownType)
{
return $unknownType->name;
}❌
function run(KnownType $knownType)
{
return $knownType->name;
}👍
Use value object over multi array assign
$values = [];
$values['person']['name'] = 'Tom';
$values['person']['surname'] = 'Dev';❌
$values = [];
$values[] = new Person('Tom', 'Dev');👍
Use separate function calls with readable variable names
$filteredValues = array_filter(array_map($callback, $items));❌
$mappedItems = array_map($callback, $items);
$filteredValues = array_filter($mappedItems);👍
Use required typed property over of nullable array property
final class SomeClass
{
private ?array $property = null;
}❌
final class SomeClass
{
private array $property = [];
}👍
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
{
}👍
Instead of protected element in final class use private element or contract method
final class SomeClass
{
protected function run()
{
}
}❌
final class SomeClass
{
private function run()
{
}
}👍
Class cannot have public properties. Use getter/setters instead
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\Privatization\NoPublicPropertyByTypeRule
tags: [phpstan.rules.rule]
arguments:
classTypes:
- Entity↓
final class Person extends Entity
{
public $name;
}❌
final class Person extends Entity
{
private $name;
public function getName()
{
return $this->name;
}
}👍
Use explicit return value over magic &reference
class SomeClass
{
public function run(&$value)
{
}
}❌
class SomeClass
{
public function run($value)
{
return $value;
}
}👍
Relative file path "%s" is not allowed, use absolute one with DIR
$filePath = 'some_file.txt';❌
$filePath = __DIR__ . '/some_file.txt';👍
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): int
{
return 1000;
}
}❌
final class SomeClass
{
private $name;
public function setName(string $name): void
{
$this->name = $name;
}
}👍
The compare assert arguments are switched. Move the expected value to the 1st left
use PHPUnit\Framework\TestCase;
final class SomeFlippedAssert extends TestCase
{
public function test()
{
$value = 1000;
$this->assertSame($value, 10);
}
}❌
use PHPUnit\Framework\TestCase;
final class SomeFlippedAssert extends TestCase
{
public function test()
{
$value = 1000;
$this->assertSame(10, $value);
}
}👍
Do not use static property
final class SomeClass
{
private static $customFileNames = [];
}❌
final class SomeClass
{
private $customFileNames = [];
}👍
Getter method must return something, not void
final class SomeClass
{
public function getData(): void
{
// ...
}
}❌
final class SomeClass
{
public function getData(): array
{
// ...
}
}👍
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('...');
}
}👍
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(): void
{
$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;
protected function setUp(): void
{
$this->obj = new stdClass;
}
public function provideFoo()
{
yield [true];
}
/**
* @dataProvider provideFoo
*/
public function testFoo($value)
{
$this->obj->x = $value;
$this->assertTrue($this->obj->x);
}
}👍
Abstract class name "%s" must be prefixed with "Abstract"
abstract class SomeClass
{
}❌
abstract class AbstractSomeClass
{
}👍
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: 3↓
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()
{
}
}👍
Attribute must be located in "Attribute" namespace
// app/Entity/SomeAttribute.php
namespace App\Controller;
#[\Attribute]
final class SomeAttribute
{
}❌
// app/Attribute/SomeAttribute.php
namespace App\Attribute;
#[\Attribute]
final class SomeAttribute
{
}👍
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 constant
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\Enum\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);
}
}👍
On passing a constant, the method should have an enum type. See https://phpstan.org/writing-php-code/phpdoc-types#literals-and-constants
final class Direction
{
public const LEFT = 'left';
public const RIGHT = 'right';
}
final class Driver
{
public function goToWork()
{
$this->turn(Direction::LEFT);
}
private function turn(string $direction)
{
// ...
}
}❌
final class Direction
{
public const LEFT = 'left';
public const RIGHT = 'right';
}
final class Driver
{
public function goToWork()
{
$this->turn(Direction::LEFT);
}
/**
* @param Direction::*
*/
private function turn(string $direction)
{
// ...
}
}👍
Exception must be located in "Exception" namespace
// app/Controller/SomeException.php
namespace App\Controller;
final class SomeException extends Exception
{
}❌
// app/Exception/SomeException.php
namespace App\Exception;
final class SomeException extends Exception
{
}👍
New expression argument on position %d must use constant over value
🔧 configure it!
services:
-
class: Symplify\PHPStanRules\Rules\Enum\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);👍
Provide more specific return type "%s" over abstract one
final class IssueControlFactory
{
public function create(): Control
{
return new IssueControl();
}
}
final class IssueControl extends Control
{
}❌
final class IssueControlFactory
{
public function create(): IssueControl
{
return new IssueControl();
}
}
final class IssueControl extends Control
{
}👍
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');
}
}👍
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'];
}
}
}👍
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()
{
$this->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
{
}👍
Interface must be suffixed with "Interface" exclusively
interface SomeClass
{
}❌
interface SomeInterface
{
}👍
Trait must be suffixed by "Trait" exclusively
trait SomeClass
{
}❌
trait SomeTrait
{
}👍
Switch construction can be replace with more robust match()
switch ($key) {
case 1:
return 100;
case 2:
return 200;
default:
return 300;
};❌
return match($key) {
1 => 100,
2 => 200,
default => 300,
};👍
Class constant "%s" is never used
final class Direction
{
public LEFT = 'left';
public RIGHT = 'right';
public STOP = 'stop';
}
if ($direction === Direction::LEFT) {
echo 'left';
}
if ($direction === Direction::RIGHT) {
echo 'right';
}❌
final class Direction
{
public LEFT = 'left';
public RIGHT = 'right';
}
if ($direction === Direction::LEFT) {
echo 'left';
}
if ($direction === Direction::RIGHT) {
echo 'right';
}👍
Class method "%s()" is never used
final class Car
{
public function turn()
{
}
public function stay()
{
}
}
final class Driver
{
public function driveCar(Car $car)
{
$car->turn();
}
}❌
final class Car
{
public function turn()
{
}
}
final class Driver
{
public function driveCar(Car $car)
{
$car->turn();
}
}👍
Constant "%s" must be uppercase
final class SomeClass
{
public const some = 'value';
}❌
final class SomeClass
{
public const SOME = 'value';
}👍
Instead of calling all public methods of value object, pass it directly
final class UsingPublicMethods
{
public function run(SomeValueObject $someValueObject)
{
$this->process($someValueObject->getName(), $someValueObject->getSurname());
}
private function process(string $getName, string $getSurname)
{
// ...
}
}❌
final class UsingPublicMethods
{
public function run(SomeValueObject $someValueObject)
{
$this->process($someValueObject);
}
private function process(SomeValueObject $someValueObject)
{
// ...
}
}👍
Instead of array shape, use value object with specific types in constructor and getters
/**
* @return array{line: int}
*/
function createConfiguration()
{
return ['line' => 100];
}❌
function createConfiguration()
{
return new Configuration(100);
}
final class Configuration
{
public function __construct(
private int $line
) {
}
public function getLine(): int
{
return $this->line;
}
}👍