PHPStan Symfony Framework extensions and rules

Overview

PHPStan Symfony Framework extensions and rules

Build Latest Stable Version License

This extension provides following features:

  • Provides correct return type for ContainerInterface::get() and ::has() methods.
  • Provides correct return type for Controller::get() and ::has() methods.
  • Provides correct return type for AbstractController::get() and ::has() methods.
  • Provides correct return type for ContainerInterface::getParameter() and ::hasParameter() methods.
  • Provides correct return type for ParameterBagInterface::get() and ::has() methods.
  • Provides correct return type for Controller::getParameter() method.
  • Provides correct return type for AbstractController::getParameter() method.
  • Provides correct return type for Request::getContent() method based on the $asResource parameter.
  • Provides correct return type for HeaderBag::get() method based on the $first parameter.
  • Provides correct return type for Envelope::all() method based on the $stampFqcn parameter.
  • Provides correct return type for InputBag::get() method based on the $default parameter.
  • Provides correct return type for InputBag::all() method based on the $key parameter.
  • Provides correct return types for TreeBuilder and NodeDefinition objects.
  • Notifies you when you try to get an unregistered service from the container.
  • Notifies you when you try to get a private service from the container.
  • Optionally correct return types for InputInterface::getArgument(), ::getOption, ::hasArgument, and ::hasOption.

Installation

To use this extension, require it in Composer:

composer require --dev phpstan/phpstan-symfony

If you also install phpstan/extension-installer then you're all set!

Manual installation

If you don't want to use phpstan/extension-installer, include extension.neon in your project's PHPStan config:

includes:
    - vendor/phpstan/phpstan-symfony/extension.neon

To perform framework-specific checks, include also this file:

includes:
    - vendor/phpstan/phpstan-symfony/rules.neon

Configuration

You have to provide a path to srcDevDebugProjectContainer.xml or similar XML file describing your container.

parameters:
    symfony:
        containerXmlPath: var/cache/dev/srcDevDebugProjectContainer.xml
        # or with Symfony 4.2+
        containerXmlPath: var/cache/dev/srcApp_KernelDevDebugContainer.xml
        # or with Symfony 5+
        containerXmlPath: var/cache/dev/App_KernelDevDebugContainer.xml
    # If you're using PHP config files for Symfony 5.3+, you also need this for auto-loading of `Symfony\Config`:
    scanDirectories:
        - var/cache/dev/Symfony/Config

Constant hassers

Sometimes, when you are dealing with optional dependencies, the ::has() methods can cause problems. For example, the following construct would complain that the condition is always either on or off, depending on whether you have the dependency for service installed:

if ($this->has('service')) {
    // ...
}

In that case, you can disable the ::has() method return type resolving like this:

parameters:
	symfony:
		constantHassers: false

Be aware that it may hide genuine errors in your application.

Analysis of Symfony Console Commands

You can opt in for more advanced analysis of Symfony Console Commands by providing the console application from your own application. This will allow the correct argument and option types to be inferred when accessing $input->getArgument() or $input->getOption().

parameters:
	symfony:
		consoleApplicationLoader: tests/console-application.php

Symfony 4:

// tests/console-application.php

use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;

require __DIR__ . '/../config/bootstrap.php';
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
return new Application($kernel);

Symfony 5:

// tests/console-application.php

use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Dotenv\Dotenv;

require __DIR__ . '/../vendor/autoload.php';

(new Dotenv())->bootEnv(__DIR__ . '/../.env');

$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
return new Application($kernel);

You may then encounter an error with PhpParser:

Compile Error: Cannot Declare interface PhpParser\NodeVisitor, because the name is already in use

If this is the case, you should create a new environment for your application that will disable inlining. In config/packages/phpstan_env/parameters.yaml:

parameters:
    container.dumper.inline_class_loader: false

Call the new env in your console-application.php:

$kernel = new \App\Kernel('phpstan_env', (bool) $_SERVER['APP_DEBUG']);
Comments
  • `InputBag::get()` should be smarter

    `InputBag::get()` should be smarter

    While technically correct, the change in 716a70de8524928036d5bc0d880d1117fa77de82 leads to false positive reports for code like this:

    $abc = $request->query->get('abc');
    Assert::notNull($abc);
    expectString($abc); // 💥 Parameter #1 $str of function expectString expects string, bool|float|int|string given.
    

    AFAIK Symfony never converts query string values from string to other types. So unless the $defaultValue argument is specified, InputBag::get() always returns string|null. And if $defaultValue is specified, the return type should be string|T where T extends string|int|float|bool|null.

    opened by vhenzl 25
  • Add support for DenormalizerInterface::denormalize

    Add support for DenormalizerInterface::denormalize

    Hi people,

    As SerializerInterface, DenormalizerInterface has the same trouble.

    So I just copy the code and adapt it to work with DenormalizerInterface.

    opened by tyx 15
  • Request > Return `Session` instead of `SessionInterface`

    Request > Return `Session` instead of `SessionInterface`

    Symfony 6 removed the Session and FlashBagInterface services.

    That means that the only way to get the FlashBag (to add flashes) it to use $request->getSession()->getFlashBag().

    But the problem is, getFlashBag is not defined on the SessionInterface:

    Call to an undefined method Symfony\Component\HttpFoundation\Session\SessionInterface::getFlashBag().
    

    To make everyone's life easier, let's change the return type to Sesssion instead. This is what's returned in a default Symfony application.

    opened by ruudk 14
  • Call to method Request::hasSession() will always evaluate to true.

    Call to method Request::hasSession() will always evaluate to true.

    I'm not entirely sure this is a phpstan related issue.

    When I check for session, ie:

     if ($request->hasSession()) {
         $request->getSession()->set('key', 'value');
    }
    

    I got the folllowing phpstan error:

    Call to method Symfony\Component\HttpFoundation\Request::hasSession() will always evaluate to true.

    If I comment out the use of phpstan-symfony OR the phpstan-strict-rules plugin, the error disappears.

    Maybe related to https://github.com/phpstan/phpstan-symfony/issues/56

    Symfony version 5.0 Phpstan versions: phpstan/phpdoc-parser 0.4.4 phpstan/phpstan 0.12.26 phpstan/phpstan-deprecation-rules 0.12.4 phpstan/phpstan-doctrine 0.12.14 phpstan/phpstan-strict-rules 0.12.2 phpstan/phpstan-symfony 0.12.6

    opened by mmarton 13
  • Container has check throw condition is always true

    Container has check throw condition is always true

    if (!$container->has('doctrine.orm.entity_manager')) {
        throw new \InvalidArgumentException('Configure your entity manager correctly');
    }
    

    In my bundle I have a check for has service the phpstan throw the following error: If condition is always true. or Negated boolean is always false. because I think the service exist in my current configured container file. But this check is needed as somebody which don't have the other library imported or configured it different.

    I think has should always be possible to be true or false.

    0.10.2 
    opened by alexander-schranz 13
  • Support for parameterised service class

    Support for parameterised service class

    Fixes #226

    ~~It's simplest and optimistic implementation which does not support:~~

    • ~~%env()% syntax~~
    • ~~%app.class%_decorator mixed interpolation~~

    Let me know if we can start with this or should we work on most advanced scenarios 🙂

    opened by Wirone 12
  • False positive in Tests: Service XY is private

    False positive in Tests: Service XY is private

    When using the KernelTestCase and asking PhpStan to analyze a test, I have this error:

    image

    However, the static::$container that is in the KernelTestCase is supposed to be an instance of the TestContainer but is typed as ContainerInterface because it might happen that the test.container service can be absent from the container, therefore making static::$container be the full container (as of KernelTestCase here )

    Here is the code that triggers the error: Pierstoval/AgateApps/.../RedeemerTest.php#L61-L79

    If the test.container service exists in the container AND for this specific case, I think there shouldn't be any error.

    opened by Pierstoval 12
  • [RFC] Extract Symfony console related rules and extensions to a separate package

    [RFC] Extract Symfony console related rules and extensions to a separate package

    Hello,

    Recently I saw on Twitter a Laravel user was complaining about type related stuff in Laravel console commands. And saw that @ondrejmirtes saying Artisan is just a wrapper around the Symfony Console. And suggested phpstan-symfony can be used to analyze the commands better.

    So what I want to suggest is to extract all Symfony Console related rules and extension into a new package, so that it can also be used by Larastan. I'd like to avoid requiring the whole phpstan-symfony package there. I think that'd cause issues. Another option is to copy all the related code, but I don't like that solution.

    What are your thoughts?

    opened by canvural 11
  • Test AclProvider change

    Test AclProvider change

    Hi @VincentLanglet, I had to change the stubs you contributed because of generic covariance error (https://github.com/phpstan/phpstan-symfony/commit/41fff98845bb8c4c0dc6c56f82b75358e57ccef9). Can you please test phpstan/phpstan-symfony:dev-master to see if it works as expected in real-world projects? Thank you.

    opened by ondrejmirtes 9
  • FormInterface::getErrors could have a dynamic return extension

    FormInterface::getErrors could have a dynamic return extension

    Currently, its return type is a FormErrorIterator with inside Symfony\Component\Form\FormError|Symfony\Component\Form\FormErrorIterator

    The phpdoc is

    /**
         * Returns the errors of this form.
         *
         * @param bool $deep    Whether to include errors of child forms as well
         * @param bool $flatten Whether to flatten the list of errors in case
         *                      $deep is set to true
         *
         * @return FormErrorIterator An iterator over the {@link FormError}
         *                           instances that where added to this form
         */
        public function getErrors(bool $deep = false, bool $flatten = true);
    

    So I would say it's always an iterator on Symfony\Component\Form\FormError, except if the parameter true, false are provided.

    opened by VincentLanglet 9
  • Parameter are seen as constant values and throw an error on evaluate

    Parameter are seen as constant values and throw an error on evaluate

    First I want to thank you for this great piece of software it make our live a lot easier and avoids a lot of bugs!

    In the new update there was added that phpstan knows about the return type of getParameter. Parameter can be different between environments, so I think they should not be seen as constant values.

    The following code in our case:

            if (SuluKernel::CONTEXT_WEBSITE === $container->getParameter('sulu.context')) {
    

    Does produce now:

     ------ ------------------------------------------------------------------------------------------
      Line   src/Sulu/Bundle/WebsiteBundle/DependencyInjection/Compiler/RouteProviderCompilerPass.php
     ------ ------------------------------------------------------------------------------------------
      27     Strict comparison using === between 'website' and 'admin' will always evaluate to false.
     ------ ------------------------------------------------------------------------------------------
    

    Another way could be that we introduce a option like we constant_hassers, which we also used in our setup (see discussion here).

    opened by alexander-schranz 9
  • ServiceSubscriberTrait

    ServiceSubscriberTrait

    I have an issue with ServiceSubscriberTrait (https://symfony.com/doc/5.4/service_container/service_subscribers_locators.html#service-subscriber-trait) on symfony 5.4 and PHP 7.4. Error message is like Service "App\Service\MyService::router" is not registered in the container.

    How to reproduce:

    1. Install this extesion
    2. Add config as described in https://github.com/phpstan/phpstan-symfony#configuration
    // phpstan.neon
        symfony:
            containerXmlPath: var/cache/dev/App_KernelDevDebugContainer.xml
    
    1. Create class MyService and copy code from symfony docs.
    // src/Service/MyService.php
    <?php
    
    declare(strict_types=1);
    
    namespace App\Service;
    
    use Psr\Log\LoggerInterface;
    use Symfony\Component\Routing\RouterInterface;
    use Symfony\Contracts\Service\Attribute\SubscribedService;
    use Symfony\Contracts\Service\ServiceSubscriberInterface;
    use Symfony\Contracts\Service\ServiceSubscriberTrait;
    
    class MyService implements ServiceSubscriberInterface
    {
        use ServiceSubscriberTrait;
    
        public function doSomething()
        {
            // $this->router() ...
            // $this->logger() ...
        }
    
        #[SubscribedService]
        private function router(): RouterInterface
        {
            return $this->container->get(__METHOD__);
        }
    
        #[SubscribedService]
        private function logger(): LoggerInterface
        {
            return $this->container->get(__METHOD__);
        }
    }
    
    1. Run phpstan

    The output will be:

     ------ ------------------------------------------------------------------
      Line   src/Service/MyService.php
     ------ ------------------------------------------------------------------
      17     Method App\Service\MyService::doSomething() has no return type
             specified.
      24     Method App\Service\MyService::router() is unused.
      26     Service "App\Service\MyService::router" is not registered in the
             container.
      30     Method App\Service\MyService::logger() is unused.
      32     Service "App\Service\MyService::logger" is not registered in the
             container.
     ------ ------------------------------------------------------------------
    
    opened by specdrum-agc 2
  • AbstractType generics in psalm but not in phpstan

    AbstractType generics in psalm but not in phpstan

    Hello,

    I'm having issues with cross-usage of psalm and phpstan for form type classes extending AbstractType:

    • on psalm (v5) side with psalm-plugin-symfony, it's considered generic, thus adding the annotation /** @extends AbstractType<mixed> */ (or similar ^^) is needed to avoid getting MissingTemplate errors
    • on phpstan side with phpstan-symfony phpstan will complain that AbstractType is not generic

    I'm a bit lost in this situation, as I guess suppressing psalm's MissingTemplate errors is not the good thing to do 😅

    opened by kissifrot 2
  • DIC compiler pass has() false positive

    DIC compiler pass has() false positive

    I have a simple compiler pass like below:

    <?php
    declare(strict_types=1);
    
    namespace App\DependencyInjection\Compiler;
    
    use Faker\Generator;
    use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
    use Symfony\Component\DependencyInjection\ContainerBuilder;
    use Symfony\Component\DependencyInjection\Reference;
    
    final class AddCustomFakerGeneratorProvidersPass implements CompilerPassInterface
    {
        public const TAG = 'app.faker.generator_provider';
    
        public function process(ContainerBuilder $container): void
        {
            if (!$container->has(Generator::class)) {
                return;
            }
    
            $svc = $container->findDefinition(Generator::class);
            $taggedDeps = $container->findTaggedServiceIds(self::TAG);
    
            foreach ($taggedDeps as $id => $tags) {
                $svc->addMethodCall('addProvider', [new Reference($id)]);
            }
        }
    }
    

    PHPStan with Symfony extension reports that has() will always return true:

     ------ ----------------------------------------------------------------------- 
      Line   DependencyInjection/Compiler/AddCustomFakerGeneratorProvidersPass.php  
     ------ ----------------------------------------------------------------------- 
      17     Negated boolean expression is always true.                             
      21     Unreachable statement - code above always terminates.                  
     ------ ----------------------------------------------------------------------- 
    

    This is taken straight from Symfony docs at https://symfony.com/doc/current/service_container/tags.html#create-a-compiler-pass and they even add a separate warning stating "// always first check if the primary service is defined". Thus, I think this is a bug in PHPStan Symfony extension.

    Here's my full PHPStan config:

    includes:
        - vendor/phpstan/phpstan-symfony/extension.neon
        - vendor/phpstan/phpstan-deprecation-rules/rules.neon
        - vendor/phpstan/phpstan-strict-rules/rules.neon
    
    parameters:
        level: 7
        tmpDir: var/cache/_phpstan
        parallel:
            jobSize: 20
            maximumNumberOfProcesses: 16
            minimumNumberOfJobsPerProcess: 2
            processTimeout: 30.0
    
        symfony:
            containerXmlPath: var/cache/dev/App_KernelDevDebugContainer.xml
    
        excludePaths:
            - %rootDir%/../../../src/DataFixtures/*
            - %rootDir%/../../../src/Migrations/*
    
        universalObjectCratesClasses:
            - Faker\Generator
    
        ignoreErrors:
            - '#^Call to an undefined method Faker\\Generator::#'
    
        tipsOfTheDay: false
        polluteScopeWithLoopInitialAssignments: false
        polluteScopeWithAlwaysIterableForeach: false
        checkAlwaysTrueCheckTypeFunctionCall: true
        checkAlwaysTrueInstanceof: true
        checkAlwaysTrueStrictComparison: true
        checkExplicitMixedMissingReturn: true
        checkFunctionNameCase: true
        reportMaybesInMethodSignatures: true
        reportStaticMethodSignatures: true
        checkTooWideReturnTypesInProtectedAndPublicMethods: true
        treatPhpDocTypesAsCertain: false
        checkMissingIterableValueType: false # handled by PHPCs with more granularity
    
    opened by kiler129 1
  • Fallback to default helper set when no application loader is provided

    Fallback to default helper set when no application loader is provided

    I'm using the phpstan-symfony extension in a library so I'm not able to register the standard console application loader (as you would in a regular project)

    So I've added a fallback to the default helper set symfony will register during setup of the console application.

    See: https://github.com/symfony/symfony/blob/ea9ed6c821f76dfc5cf0639ebd1a23db971dc6e4/src/Symfony/Component/Console/Application.php#L1096-L1107

    Most of the getHelper cases can be handled by this default set. When someone has custom helpers they should use the consoleApplicationLoader option.

    opened by acrobat 1
  • Support for DI config in PHP file format

    Support for DI config in PHP file format

    Symfony supports PHP format for configuration (it's even default one now), so it would be great if this plugin could help with verifying DI configs. Assume we have:

    class Foo
    {
        public function __construct(Bar $bar, Baz $baz)
        {
        }
    }
    
    return static function (ContainerConfigurator $containerConfigurator): void {
        $services = $containerConfigurator->services();
    
        $services->set(Foo::class)
            ->public()
            ->args([Bar::class]);
    });
    

    Plugin should warn that Baz $baz argument is missing in the Foo service definition.

    Similarly it should support:

    • factories like ->factory([service(FooFactory::class), 'create'])
      • if factory's service exists
      • if factory's class has such method
    • fetching parameters (like ->args(['%foo%']))
    • named args (like ->arg('$bar', service(Bar::class))
    • expressions (using expr())

    That would really help with keeping quality in DI definitions with PHP format 🙂

    opened by Wirone 1
  • [Feature Request] Overriding dynamic service return types

    [Feature Request] Overriding dynamic service return types

    Currently there is no way to override dynamic service return types on the container that are specified in ServiceDynamicReturnTypeExtension.

    This is useful to provide more specific information e.g. if the service implement a generic class there is currently no way to teach phpstan that a specific service is of a specific type.

    I already build a custom DynamicMethodReturnTypeExtension that adds the generic types, but in the end phpstan makes a union for all types returned by any DynamicMethodReturnTypeExtension. This leads to the less specific type of the smyfony extension "winning" and in fact overriding my more specific type again.

    I would like to the a feature where you can specify the phpstan type of a service, or at least mark a service to be ignored by the symfony phpstan extension, so you can write your custom DynamicMethodReturnTypeExtension for those services.

    Happy to contribute such a feature, but I'm currently not sure how to best implement this. The only way I see now is adding a custom tag to the service definition and specifying the type there and that type will then take precedence over the class name of the service.

    opened by keulinho 0
Releases(1.2.19)
Owner
PHPStan
PHP Static Analysis Tool - discover bugs in your code without running it!
PHPStan
Zephir is a compiled high level language aimed to the creation of C-extensions for PHP.

Zephir - is a high level programming language that eases the creation and maintainability of extensions for PHP. Zephir extensions are exported to C c

Zephir Language 3.2k Dec 27, 2022
Symprowire is a PHP MVC Framework based and built on Symfony, using the ProcessWire CMS as DBAL and Service Provider.

Symprowire - PHP MVC Framework for ProcessWire 3.x Symprowire is a PHP MVC Framework based and built on Symfony using ProcessWire 3.x as DBAL and Serv

Luis Mendez 7 Jan 16, 2022
The Symfony PHP framework

Symfony is a PHP framework for web and console applications and a set of reusable PHP components. Symfony is used by thousands of web applications (in

Symfony 27.8k Jan 2, 2023
Ergonode is modern PIM platform based on Symfony and Vue.js frameworks.

Modern Product Information Management Platform Ergonode is modern PIM platform based on Symfony and Vue.js frameworks. It has modular structure and gi

Ergonode 100 Dec 19, 2022
CleverStyle Framework is simple, scalable, fast and secure full-stack PHP framework

CleverStyle Framework is simple, scalable, fast and secure full-stack PHP framework. It is free, Open Source and is distributed under Free Public Lice

Nazar Mokrynskyi 150 Apr 12, 2022
Framework X – the simple and fast micro framework for building reactive web applications that run anywhere.

Framework X Framework X – the simple and fast micro framework for building reactive web applications that run anywhere. Quickstart Documentation Tests

Christian Lück 620 Jan 7, 2023
Framework X is a simple and fast micro framework based on PHP

Framework X is a simple and fast micro framework based on PHP. I've created a simple CRUD application to understand how it works. I used twig and I created a custom middleware to handle PUT, DELETE methods.

Mahmut Bayri 6 Oct 14, 2022
Spiral Framework is a High-Performance PHP/Go Full-Stack framework and group of over sixty PSR-compatible components

Spiral HTTP Application Skeleton Spiral Framework is a High-Performance PHP/Go Full-Stack framework and group of over sixty PSR-compatible components.

Spiral Scout 152 Dec 18, 2022
Sunhill Framework is a simple, fast, and powerful PHP App Development Framework

Sunhill Framework is a simple, fast, and powerful PHP App Development Framework that enables you to develop more modern applications by using MVC (Model - View - Controller) pattern.

Mehmet Selcuk Batal 3 Dec 29, 2022
Yii2-symfonymailer - Yii 2 Symfony mailer extension.

Yii Mailer Library - Symfony Mailer Extension This extension provides a Symfony Mailer mail solution for Yii framework 2.0. For license information ch

Yii Software 28 Dec 22, 2022
This bundle aims to easily integrate & use the Froala editor in Symfony 4.4+/5.0+.

KMSFroalaEditorBundle Introduction This bundle aims to easily integrate & use the Froala editor in Symfony 4.4+/5.0+. If you want to use it with Symfo

Froala 102 Nov 15, 2022
I made my own simple php framework inspired from laravel framework.

Simple MVC About Since 2019, I started learning the php programming language and have worked on many projects using the php framework. Laravel is one

null 14 Aug 14, 2022
PHPR or PHP Array Framework is a framework highly dependent to an array structure.

this is new repository for php-framework Introduction PHPR or PHP Array Framework is a framework highly dependent to an array structure. PHPR Framewor

Agung Zon Blade 2 Feb 12, 2022
I made my own simple php framework inspired from laravel framework.

Simple MVC About Since 2019, I started learning the php programming language and have worked on many projects using the php framework. Laravel is one

Rizky Alamsyah 14 Aug 14, 2022
Leaf is a PHP framework that helps you create clean, simple but powerful web apps and APIs quickly and easily.

Leaf is a PHP framework that helps you create clean, simple but powerful web apps and APIs quickly and easily. Leaf introduces a cleaner and much simpler structure to the PHP language while maintaining it's flexibility. With a simple structure and a shallow learning curve, it's an excellent way to rapidly build powerful and high performant web apps and APIs.

Leaf Framework 706 Jan 3, 2023
FlyCubePHP is an MVC Web Framework developed in PHP and repeating the ideology and principles of building WEB applications, embedded in Ruby on Rails.

FlyCubePHP FlyCubePHP is an MVC Web Framework developed in PHP and repeating the ideology and principles of building WEB applications, embedded in Rub

Anton 1 Dec 21, 2021
Implementing programming best practices and patterns, and creating a custom PHP framework from scratch.

Implementing programming best practices and patterns, and creating a custom PHP framework from scratch.

Sajidur Rahman 3 Jul 2, 2022
Kit is a lightweight, high-performance and event-driven web services framework that provides core components such as config, container, http, log and route.

Kit What is it Kit is a lightweight, high-performance and event-driven web services framework that provides core components such as config, container,

null 2 Sep 23, 2022
Yii 2: The Fast, Secure and Professional PHP Framework

Yii 2 is a modern framework designed to be a solid foundation for your PHP application. It is fast, secure and efficient and works right out of the bo

Yii Software 14k Dec 31, 2022