symplify/phpstan-rules
Extra PHPStan rules by Symplify to catch bugs, improve code quality, and enforce consistent conventions. Easy to install and configure, with a broad set of checks for Symfony/Laravel and modern PHP features to keep your codebase clean.
This release is a summary of 14.6.0...14.6.33, so you can have idea what is new, thanks to release notification.
This release introduces numerous new PHPStan rules, focusing on Symfony, Doctrine, and PHPUnit, with contributions from new community members enhancing the project's functionality.
It also introduced new standalone set for Symfony PHP configs:
includes:
- vendor/symplify/phpstan-rules/config/symfony-config-rules.neon
Each rule includes simple PHP code snippets, marked with :x: for non-compliant code and :+1: for compliant code.
NoGetInControllerRule in https://github.com/symplify/phpstan-rules/pull/158class SomeController
{
public function __invoke()
{
$this->get('some_service');
}
}
:x:
class SomeController
{
public function __invoke(SomeService $someService)
{
$someService->run();
}
}
:+1:
NoGetInCommandRule in https://github.com/symplify/phpstan-rules/pull/184class SomeCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->get('some_service');
return 0;
}
}
:x:
class SomeCommand extends Command
{
public function __construct(private SomeService $someService)
{
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->someService->run();
return 0;
}
}
:+1:
NoRoutingPrefixRule in https://github.com/symplify/phpstan-rules/pull/172#[Route('/api/')]
class SomeController
{
#[Route('/something')]
public function __invoke()
{
}
}
:x:
class SomeController
{
#[Route('/api/something')]
public function __invoke()
{
}
}
:+1:
NoClassLevelRouteRule in https://github.com/symplify/phpstan-rules/pull/173#[Route('/some')]
class SomeController
{
public function __invoke()
{
}
}
:x:
class SomeController
{
#[Route('/some')]
public function __invoke()
{
}
}
:+1:
NoFindTaggedServiceIdsCallRule in https://github.com/symplify/phpstan-rules/pull/174class SomeClass
{
public function __construct(ContainerBuilder $containerBuilder)
{
$containerBuilder->findTaggedServiceIds('some_tag');
}
}
:x:
class SomeClass
{
public function __construct(ContainerBuilder $containerBuilder)
{
$containerBuilder->getServiceIds();
}
}
:+1:
NoRouteTrailingSlashPathRule in https://github.com/symplify/phpstan-rules/pull/176class SomeController
{
#[Route('/some/')]
public function __invoke()
{
}
}
:x:
class SomeController
{
#[Route('/some')]
public function __invoke()
{
}
}
:+1:
FormTypeClassNameRule to require clear naming for form types in https://github.com/symplify/phpstan-rules/pull/169class UserForm extends AbstractType
{
}
:x:
class UserType extends AbstractType
{
}
:+1:
RouteGenerateControllerClassRequireNameRule in https://github.com/symplify/phpstan-rules/pull/180$this->generateUrl(SomeController::class);
:x:
$this->generateUrl('some_route_name');
:+1:
RequiredOnlyInAbstractRule in https://github.com/symplify/phpstan-rules/pull/164class SomeService
{
#[Required]
public function setSomeDependency()
{
}
}
:x:
abstract class SomeService
{
#[Required]
public function setSomeDependency()
{
}
}
:+1:
NoConstructorAndRequiredTogetherRule in https://github.com/symplify/phpstan-rules/pull/168class SomeService
{
public function __construct()
{
}
#[Required]
public function setSomeDependency()
{
}
}
:x:
class SomeService
{
#[Required]
public function seteters
{
}
}
:+1:
SingleRequiredMethodRule to spot multiple @required methods in Symfony projects in https://github.com/symplify/phpstan-rules/pull/163class SomeService
{
#[Required]
public function setFirst()
{
}
#[Required]
public function setSecond()
{
}
}
:x:
class SomeService
{
#[Required]
public function setFirst()
{
}
}
:+1:
ServicesExcludedDirectoryMustExistRule in https://github.com/symplify/phpstan-rules/pull/202use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $configurator): void {
$services = $configurator->serivces();
$services->load('App\\', __DIR__ . '/../src')
->exclude([__DIR__ . '/this-path-does-not-exist']);
};
:x:
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $configurator): void {
$services = $configurator->services();
$services->load('App\\', __DIR__ . '/../src')
->exclude([__DIR__ . '/../src/ValueObject']);
};
:+1:
NoBundleResourceConfigRule in https://github.com/symplify/phpstan-rules/pull/203services:
resource: '../src/Bundle/SomeBundle/*'
:x:
services:
resource: '../src/App/*'
:+1:
AlreadyRegisteredAutodiscoveryServiceRule in https://github.com/symplify/phpstan-rules/pull/204use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->load('App\\', __DIR__ . '/../src')
->exclude([__DIR__ . '/src/Services']);
$services->set(SomeService::class);
};
:x:
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->load('App\\', __DIR__ . '/../src')
->exclude([__DIR__ . '/src/Services']);
};
:+1:
TaggedIteratorOverRepeatedServiceCallRule in https://github.com/symplify/phpstan-rules/pull/209class SomeClass
{
public function __construct(ContainerBuilder $containerBuilder)
{
$containerBuilder->getDefinition('some_service')->addTag('some_tag');
$containerBuilder->getDefinition('some_service')->addTag('some_tag');
}
}
:x:
class SomeClass
{
public function __construct(ContainerBuilder $containerBuilder)
{
$containerBuilder->getDefinition('some_service')->addTag('some_tag');
}
}
:+1:
NoDuplicateArg(s)AutowireByTypeRule and NoServiceSameNameSetClassRule in https://github.com/symplify/phpstan-rules/pull/210use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(SomeService::class)
->args([
ref(SomeService::class),
]);
};
:x:
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(SomeService::class);
};
:+1:
RequireIsGrantedEnumRule in https://github.com/symplify/phpstan-rules/pull/213use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('ROLE_USER')]
class SomeController
{
}
:x:
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted(SomeEnum::ROLE_USER)]
class SomeController
{
}
:+1:
NoBareAndSecurityIsGrantedContentsRule in https://github.com/symplify/phpstan-rules/pull/214Instead of using one long "and" condition join, split into multiple standalone #[IsGranted] attributes
rules:
- Symplify\PHPStanRules\Rules\Symfony\NoBareAndSecurityIsGrantedContentsRule
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('has_role(ROLE_USER) and has_role(ROLE_ADMIN)')]
class SomeController
{
}
:x:
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('ROLE_USER')]
#[IsGranted('ROLE_ADMIN')]
class SomeController
{
}
:+1:
PreferAutowireAttributeOverConfigParamRule in https://github.com/symplify/phpstan-rules/pull/215services:
App\SomeService:
arguments:
$someParam: '%some_param%'
:x:
class SomeService
{
#[Autowire('%some_param%')]
private string $someParam;
}
:+1:
RequireQueryBuilderOnRepositoryRule in https://github.com/symplify/phpstan-rules/pull/158class SomeRepository extends EntityRepository
{
public function findSomething()
{
$this->getEntityManager()->createQuery('SELECT ...');
}
}
:x:
class SomeRepository extends EntityRepository
{
public function findSomething()
{
$this->createQueryBuilder('e')->select('...');
}
}
:+1:
NoGetRepositoryOnServiceRepositoryEntityRule in https://github.com/symplify/phpstan-rules/pull/182class SomeService
{
public function __construct(EntityManagerInterface $entityManager)
{
$entityManager->getRepository(SomeEntity::class);
}
}
:x:
class SomeService
{
public function __construct(SomeRepository $someRepository)
{
$someRepository->findAll();
}
}
:+1:
RequiredDoctrineServiceRepositoryParentRule in https://github.com/symplify/phpstan-rules/pull/208class SomeRepository
{
public function findSomething()
{
}
}
:x:
class SomeRepository extends EntityRepository
{
public function findSomething()
{
}
}
:+1:
NoListenerWithoutContractRule in https://github.com/symplify/phpstan-rules/pull/201class SomeListener
{
public function __invoke(SomeEvent $event)
{
}
}
:x:
class SomeListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [SomeEvent::class => 'onEvent'];
}
public function onEvent(SomeEvent $event)
{
}
}
:+1:
NoMockObjectAndRealObjectPropertyRule in https://github.com/symplify/phpstan-rules/pull/170class SomeTest extends TestCase
{
private SomeService $someService;
private MockObject $someServiceMock;
protected function setUp(): void
{
$this->someServiceMock = $this->createMock(SomeService::class);
}
}
:x:
class SomeTest extends TestCase
{
private MockObject $someServiceMock;
protected function setUp(): void
{
$this->someServiceMock = $this->createMock(SomeService::class);
}
}
:+1:
NoAssertFuncCallInTestsRule in https://github.com/symplify/phpstan-rules/pull/175class SomeTest extends TestCase
{
public function testSomething()
{
assert($this->someService->run());
}
}
:x:
class SomeTest extends TestCase
{
public function testSomething()
{
$this->assertTrue($this->someService->run());
}
}
:+1:
NoValueObjectInServiceConstructorRule in https://github.com/symplify/phpstan-rules/pull/152class SomeService
{
public function __construct(ValueObject $valueObject)
{
}
}
:x:
class SomeService
{
public function __construct(ServiceDependency $serviceDependency)
{
}
}
:+1:
ForbiddenNewArgumentRule in https://github.com/symplify/phpstan-rules/pull/158class SomeClass
{
public function run()
{
new SomeArgument();
}
}
:x:
class SomeClass
{
public function run(SomeArgument $someArgument)
{
}
}
:+1:
MaximumIgnoredErrorCountRule enabled in configuration in https://github.com/symplify/phpstan-rules/pull/162parameters:
ignoreErrors:
- '#Some error 1#'
- '#Some error 2#'
- '#Some error 3#'
:x:
parameters:
ignoreErrors:
- '#Some error 1#'
:+1:
StringFileAbsolutePathExistsRule in https://github.com/symplify/phpstan-rules/pull/171class SomeClass
{
public function run()
{
require_once '/non/existent/path.php';
}
}
:x:
class SomeClass
{
public function run()
{
require_once '/existing/path.php';
}
}
:+1:
NoJustPropertyAssignRule in https://github.com/symplify/phpstan-rules/pull/177class SomeClass
{
public function run($value)
{
$this->value = $value;
}
}
:x:
class SomeClass
{
public function run($value)
{
$this->value = $this->processValue($value);
}
}
:+1:
NoProtectedClassStmtRule in https://github.com/symplify/phpstan-rules/pull/185protected class SomeClass
{
}
:x:
class SomeClass
{
}
:+1:
NoArrayMapWithArrayCallableRule in https://github.com/symplify/phpstan-rules/pull/217$values = array_map(['SomeClass', 'method'], $items);
:x:
$values = array_map(fn($item) => SomeClass::method($item), $items);
:+1:
Add 15 PHPStan rules from Handyman, mostly Symfony, Doctrine, and PHPUnit related in https://github.com/symplify/phpstan-rules/pull/153
class SomeClass
{
public function run($value)
{
$value->call();
}
}
:x:
class SomeClass
{
public function run(SomeObject $value)
{
$value->call();
}
}
:+1:
@Myks92 made their first contribution in https://github.com/symplify/phpstan-rules/pull/190
@kkevindev made their first contribution in https://github.com/symplify/phpstan-rules/pull/205
Thanks to @staabm for git export improvements, @samsonasik for PHPStan version updates and rule refinements, @Myks92 for Doctrine-related contributions, and @kkevindev for README improvements.
Full Changelog: https://github.com/symplify/phpstan-rules/compare/14.0.0...14.6.3
NoValueObjectInServiceConstructorRule to improve service architectureGeneral
NoConstructorOverrideRuleSymfony
NoAbstractControllerConstructorRuleNoRequiredOutsideClassRuleSingleArgEventDispatchRuleNoListenerWithoutContractRuleNoStringInGetSubscribedEventsRuleDoctrine
NoGetRepositoryOutsideServiceRuleNoParentRepositoryRuleNoRepositoryCallInDataFixtureRulePHPUnit
PublicStaticDataProviderRuleNoMockOnlyTestRuleNoDocumentMockingRuleNoEntityMockingRuleFollowing rules were quite complex and niche to use in the wild. Instead, developers should decide based on context.
NoSingleInterfaceImplementerRuleNoReturnArrayVariableListRuleCheckClassNamespaceFollowPsr4Rule, use https://github.com/shipmonk-rnd/composer-dependency-analyser that does the same job betterRegexSuffixInRegexConstantRule, NoInlineStringRegexRule, AnnotateRegexClassConstWithRegexLinkRule to ease working with regular expressionsAllow d in short names by @TomasVotruba in https://github.com/symplify/phpstan-rules/pull/116
Skip attributes on setter rule by @TomasVotruba in https://github.com/symplify/phpstan-rules/pull/120
Allow abstract classes to be implemented in NoSingleInterfaceImplementerRule by @TomasVotruba in https://github.com/symplify/phpstan-rules/pull/135
Improve no single interface by @TomasVotruba in https://github.com/symplify/phpstan-rules/pull/136
Few rules seemed like too pedantic in practice, I always ignored them in most projects. Time to let go to make this set more practical:
Full Changelog: https://github.com/symplify/phpstan-rules/compare/12.5.0...13.0.0
This release is using downgrade-package approach, so instead of PHP 8.0, you can be as low as PHP 7.2 and make use of 100+ PHPStan rules there are.
This allowed the downgrade, as we needed the /src to be fully downgradable, without any external PHP 8.0 dependency
While at it, we did cleaning of rules that were mostly designed for single spot in code-review once time in history. These rules were not practical and only cluttered the whole rule overview. Also we found few duplicates. Now the ruleset is more concise and eaiser to search through :+1:
How can I help you explore Laravel packages today?