Skip to content

Released PHPStan Rules 14.6.3 with 28 new rules 🚀🚀🚀

Latest
Compare
Choose a tag to compare
@TomasVotruba TomasVotruba released this 19 Apr 15:24
· 12 commits to main since this release

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