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.neonEach 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\NoBareAndSecurityIsGrantedContentsRuleuse 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