Skip to content

Releases: symplify/phpstan-rules

Released PHPStan Rules 14.6.3 with 28 new rules 🚀🚀🚀

19 Apr 15:24
Compare
Choose a tag to compare

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 exte...
Read more

Released 14.1

18 Dec 16:20
Compare
Choose a tag to compare
  • upgraded to PHPStan 2.0
  • upgraded to PHP Parser 5.3

New Rules 🥳

  • #152 - Added NoValueObjectInServiceConstructorRule to improve service architecture
  • #153 - Added many new rules

General

  • Added NoConstructorOverrideRule

Symfony

  • Added NoAbstractControllerConstructorRule
  • Added NoRequiredOutsideClassRule
  • Added SingleArgEventDispatchRule
  • Added NoListenerWithoutContractRule
  • Added NoStringInGetSubscribedEventsRule

Doctrine

  • Added NoGetRepositoryOutsideServiceRule
  • Added NoParentRepositoryRule
  • Added NoRepositoryCallInDataFixtureRule

PHPUnit

  • Added PublicStaticDataProviderRule
  • Added NoMockOnlyTestRule
  • Added NoDocumentMockingRule
  • Added NoEntityMockingRule

Removed rules 💀

Following rules were quite complex and niche to use in the wild. Instead, developers should decide based on context.

  • #156 - Removed NoSingleInterfaceImplementerRule
  • #154 - Removed NoReturnArrayVariableListRule
  • #150 - Removed CheckClassNamespaceFollowPsr4Rule, use https://github.com/shipmonk-rnd/composer-dependency-analyser that does the same job better
  • #151 - Removed RegexSuffixInRegexConstantRule, NoInlineStringRegexRule, AnnotateRegexClassConstWithRegexLinkRule to ease working with regular expressions

13.0.0

13 Jun 02:31
Compare
Choose a tag to compare

Added Rules 🥳


What's Changed 🔨


Removed 💀

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:

  • Remove NoMissingDirPathRule, as often not clear if target file or required file path by @TomasVotruba in #129
  • Remove NoAbstractMethodRule as way too opinionated by @TomasVotruba in #121
  • Remove ForbiddenSameNamedNewInstanceRule as rarely a buggy spot by @TomasVotruba in #123
  • Remove NoVoidGetterMethodRule as not that practical by @TomasVotruba in #125
  • Remove NoEmptyClassRule, as rarely true and already coverd by class-leak by @TomasVotruba in #124
  • Remove NoShortNameRule, as devs are educated and not a thing anymore by @TomasVotruba in #127
  • Remove NoRelativeFilePathRule as more exceptoins by @TomasVotruba in #118
  • Remove RequireEnumDocBlockOnConstantListPassRule as reports many false positive, better shift to native enums by @TomasVotruba in #119
  • Remove NoDuplicatedShortClassNameRule as overly detailed by @TomasVotruba in #133
  • Remove NoNullableArrayPropertyRule as depends on use-case, cached etc by @TomasVotruba in #134
  • Remove BoolishClassMethodPrefixRule as very limited and often ignored by @TomasVotruba in #114
  • Remove NoProtectedClassElementRule as overly strict by @TomasVotruba in #115

Full Changelog: 12.5.0...13.0.0

Released PHPStan Rules 11.1 with PHP 7.2 support

08 Aug 21:34
Compare
Choose a tag to compare

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.

Decoupling from Astral package 🥳

This allowed the downgrade, as we needed the /src to be fully downgradable, without any external PHP 8.0 dependency


Removed Rules 💀

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 👍

  • deprecated-packages/symplify#4321 - [PHPStanRules] Remove dependency on symplify/package-builder, remove couple of very specific not practical rules - NoMagicClosureRule, NoBinaryOpCallCompareRule, NoGetRepositoryOutsideConstructorRule, RequireConstantInAttributeArgumentRule, RequireStringArgumentInConstructorRule, ValueObjectDestructRule