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 ❌ for non-compliant code and 👍 for compliant code.
Symfony-related Rules
Add NoGetInControllerRule
in #158
class SomeController
{
public function __invoke()
{
$this->get('some_service');
}
}
❌
class SomeController
{
public function __invoke(SomeService $someService)
{
$someService->run();
}
}
👍
Add NoGetInCommandRule
in #184
class SomeCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->get('some_service');
return 0;
}
}
❌
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;
}
}
👍
Add NoRoutingPrefixRule
in #172
#[Route('/api/')]
class SomeController
{
#[Route('/something')]
public function __invoke()
{
}
}
❌
class SomeController
{
#[Route('/api/something')]
public function __invoke()
{
}
}
👍
Add NoClassLevelRouteRule
in #173
#[Route('/some')]
class SomeController
{
public function __invoke()
{
}
}
❌
class SomeController
{
#[Route('/some')]
public function __invoke()
{
}
}
👍
Add NoFindTaggedServiceIdsCallRule
in #174
class SomeClass
{
public function __construct(ContainerBuilder $containerBuilder)
{
$containerBuilder->findTaggedServiceIds('some_tag');
}
}
❌
class SomeClass
{
public function __construct(ContainerBuilder $containerBuilder)
{
$containerBuilder->getServiceIds();
}
}
👍
Add NoRouteTrailingSlashPathRule
in #176
class SomeController
{
#[Route('/some/')]
public function __invoke()
{
}
}
❌
class SomeController
{
#[Route('/some')]
public function __invoke()
{
}
}
👍
Add FormTypeClassNameRule
to require clear naming for form types in #169
class UserForm extends AbstractType
{
}
❌
class UserType extends AbstractType
{
}
👍
Add RouteGenerateControllerClassRequireNameRule
in #180
$this->generateUrl(SomeController::class);
❌
$this->generateUrl('some_route_name');
👍
Add RequiredOnlyInAbstractRule
in #164
class SomeService
{
#[Required]
public function setSomeDependency()
{
}
}
❌
abstract class SomeService
{
#[Required]
public function setSomeDependency()
{
}
}
👍
Add NoConstructorAndRequiredTogetherRule
in #168
class SomeService
{
public function __construct()
{
}
#[Required]
public function setSomeDependency()
{
}
}
❌
class SomeService
{
#[Required]
public function seteters
{
}
}
👍
Add SingleRequiredMethodRule
to spot multiple @required methods in Symfony projects in #163
class SomeService
{
#[Required]
public function setFirst()
{
}
#[Required]
public function setSecond()
{
}
}
❌
class SomeService
{
#[Required]
public function setFirst()
{
}
}
👍
Add ServicesExcludedDirectoryMustExistRule
in #202
use 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']);
};
❌
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']);
};
👍
Add NoBundleResourceConfigRule
in #203
services:
resource: '../src/Bundle/SomeBundle/*'
❌
services:
resource: '../src/App/*'
👍
Add AlreadyRegisteredAutodiscoveryServiceRule
in #204
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']);
$services->set(SomeService::class);
};
❌
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']);
};
👍
Add TaggedIteratorOverRepeatedServiceCallRule
in #209
class SomeClass
{
public function __construct(ContainerBuilder $containerBuilder)
{
$containerBuilder->getDefinition('some_service')->addTag('some_tag');
$containerBuilder->getDefinition('some_service')->addTag('some_tag');
}
}
❌
class SomeClass
{
public function __construct(ContainerBuilder $containerBuilder)
{
$containerBuilder->getDefinition('some_service')->addTag('some_tag');
}
}
👍
Add NoDuplicateArg(s)AutowireByTypeRule
and NoServiceSameNameSetClassRule
in #210
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(SomeService::class)
->args([
ref(SomeService::class),
]);
};
❌
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(SomeService::class);
};
👍
Add RequireIsGrantedEnumRule
in #213
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('ROLE_USER')]
class SomeController
{
}
❌
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted(SomeEnum::ROLE_USER)]
class SomeController
{
}
👍
Add NoBareAndSecurityIsGrantedContentsRule
in #214
NoBareAndSecurityIsGrantedContentsRule
Instead 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
{
}
❌
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('ROLE_USER')]
#[IsGranted('ROLE_ADMIN')]
class SomeController
{
}
👍
Add PreferAutowireAttributeOverConfigParamRule
in #215
services:
App\SomeService:
arguments:
$someParam: '%some_param%'
❌
class SomeService
{
#[Autowire('%some_param%')]
private string $someParam;
}
👍
Doctrine-related Rules
Add RequireQueryBuilderOnRepositoryRule
in #158
class SomeRepository extends EntityRepository
{
public function findSomething()
{
$this->getEntityManager()->createQuery('SELECT ...');
}
}
❌
class SomeRepository extends EntityRepository
{
public function findSomething()
{
$this->createQueryBuilder('e')->select('...');
}
}
👍
Add NoGetRepositoryOnServiceRepositoryEntityRule
in #182
class SomeService
{
public function __construct(EntityManagerInterface $entityManager)
{
$entityManager->getRepository(SomeEntity::class);
}
}
❌
class SomeService
{
public function __construct(SomeRepository $someRepository)
{
$someRepository->findAll();
}
}
👍
Add RequiredDoctrineServiceRepositoryParentRule
in #208
class SomeRepository
{
public function findSomething()
{
}
}
❌
class SomeRepository extends EntityRepository
{
public function findSomething()
{
}
}
👍
Add NoListenerWithoutContractRule
in #201
class SomeListener
{
public function __invoke(SomeEvent $event)
{
}
}
❌
class SomeListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [SomeEvent::class => 'onEvent'];
}
public function onEvent(SomeEvent $event)
{
}
}
👍
PHPUnit-related Rules
Add NoMockObjectAndRealObjectPropertyRule
in #170
class SomeTest extends TestCase
{
private SomeService $someService;
private MockObject $someServiceMock;
protected function setUp(): void
{
$this->someServiceMock = $this->createMock(SomeService::class);
}
}
❌
class SomeTest extends TestCase
{
private MockObject $someServiceMock;
protected function setUp(): void
{
$this->someServiceMock = $this->createMock(SomeService::class);
}
}
👍
Add NoAssertFuncCallInTestsRule
in #175
class SomeTest extends TestCase
{
public function testSomething()
{
assert($this->someService->run());
}
}
❌
class SomeTest extends TestCase
{
public function testSomething()
{
$this->assertTrue($this->someService->run());
}
}
👍
General Rules
Add NoValueObjectInServiceConstructorRule
in #152
class SomeService
{
public function __construct(ValueObject $valueObject)
{
}
}
❌
class SomeService
{
public function __construct(ServiceDependency $serviceDependency)
{
}
}
👍
Add ForbiddenNewArgumentRule
in #158
class SomeClass
{
public function run()
{
new SomeArgument();
}
}
❌
class SomeClass
{
public function run(SomeArgument $someArgument)
{
}
}
👍
Add MaximumIgnoredErrorCountRule
enabled in configuration in #162
parameters:
ignoreErrors:
- '#Some error 1#'
- '#Some error 2#'
- '#Some error 3#'
❌
parameters:
ignoreErrors:
- '#Some error 1#'
👍
Add StringFileAbsolutePathExistsRule
in #171
class SomeClass
{
public function run()
{
require_once '/non/existent/path.php';
}
}
❌
class SomeClass
{
public function run()
{
require_once '/existing/path.php';
}
}
👍
Add NoJustPropertyAssignRule
in #177
class SomeClass
{
public function run($value)
{
$this->value = $value;
}
}
❌
class SomeClass
{
public function run($value)
{
$this->value = $this->processValue($value);
}
}
👍
Add NoProtectedClassStmtRule
in #185
protected class SomeClass
{
}
❌
class SomeClass
{
}
👍
Add NoArrayMapWithArrayCallableRule
in #217
$values = array_map(['SomeClass', 'method'], $items);
❌
$values = array_map(fn($item) => SomeClass::method($item), $items);
👍
Add 15 PHPStan rules from Handyman, mostly Symfony, Doctrine, and PHPUnit related in #153
class SomeClass
{
public function run($value)
{
$value->call();
}
}
❌
class SomeClass
{
public function run(SomeObject $value)
{
$value->call();
}
}
👍
New Contributors
@Myks92 made their first contribution in #190
@kkevindev made their first contribution in #205
Contributions
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: 14.0.0...14.6.3