# phpstan-symfony: teaching PHPStan about a Symfony container

> phpstan-symfony is the PHPStan extension that makes static analysis understand a Symfony application, mostly by resolving return types the framework cannot know on its own, such as which type the container returns or which argument type a console command declares. It also adds rules for unregistered and private services and for undefined console arguments.

**phpstan/phpstan-symfony** — Symfony extension for PHPStan

- Repository: https://github.com/phpstan/phpstan-symfony
- Stars: 806 · Forks: 104
- Language: PHP
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/phpstan-phpstan-symfony

## Return types the framework cannot infer

The extension exists to fix one large class of problem: methods whose return type depends on runtime state that the analyser cannot see.

The container is the biggest case. The extension provides a correct return type for the get and has methods of the container interface, of the controller and of the abstract controller, for getParameter and hasParameter on the container interface, and for the get and has methods of the parameter bag interface. The controller equivalents of the parameter methods are covered as well.

Then there are the methods whose type depends on an argument. The getContent method of a request is typed based on the asResource parameter, so the analyser knows whether you get a string or a resource. The get method of a header bag is typed based on the first parameter, the all method of an envelope based on the stamp class name argument, the get method of an input bag based on the default parameter and its all method based on the key parameter, and the getCookies method of a response header bag based on the format argument.

The message bus and the serializer are covered too. The deserialize method of the serializer interface and the denormalize method of the denormalizer interface are typed from the type argument, so the analyser knows your denormalised object is the class you asked for. The handle method of the handle trait is typed from the message handler map, and the extension offers configurable return type resolution for methods that use that trait internally.

The remaining entries follow the same pattern: the form interface error method typed by its deep and flatten parameters, the resource locator of the kernel, the configuration getter of an extension, the cache interface get method typed from the callback return type, and the browser kit assertion trait client getter.

So the extension has two jobs in one: resolving types from arguments, and resolving types from the compiled container.

## The rules that catch real mistakes

Beyond return types, the extension adds rules, and these are the part that finds actual bugs rather than noise.

You are notified when you try to get an unregistered service from the container, which is the classic symptom of a service that was never defined or was removed by a config change. You are also notified when you try to get a private service from the container, which catches code that depends on a service being public even though the definition says it is private, a problem that often appears when an interface is extracted later and the implementation is forgotten to be aliased.

For the console, the extension notifies you when you access undefined console command arguments or options, and it validates the default values of console command arguments and options. Those two checks catch the class of bug where a command declares an option in one file and reads a different name in another.

Autowiring is covered too. The extension recognises the required annotation and the Required attribute for autowiring properties and methods, which means a constructor or setter that is marked as required is treated as needing a value rather than being silently optional.

The last two entries are optional refinements rather than rules. Correct return types for the console input methods that read arguments and options can be turned on, and so can a correct return type for the helper getter of a command. They are optional because they depend on the extension being able to see your console application, which is the subject of a later section.

## Installation and the two neon files

The extension is a Composer dev dependency.

```bash
composer require --dev phpstan/phpstan-symfony
```

If you also install the PHPStan extension installer, the README says you are all set, because the installer registers extensions automatically.

Without it, you include the files yourself in your PHPStan configuration. The extension file comes first, and then the rules file if you want the framework specific checks rather than only the type resolution.

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

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

That split is the practical difference between the two includes. The extension file is what provides the return types, and the rules file is what provides the diagnostics about unregistered services, private services and console arguments.

The repository root shows the same split as files, with an extension.neon and a rules.neon at the top level next to the source directory, the stubs directory for the framework stubs, the tests, and a PHPStan configuration for analysing the extension itself with a baseline file.

## The container XML path is the configuration you must get right

The extension needs to know where your compiled container is, and the README is explicit that you have to provide the path to a file such as the debug project container XML.

```yaml
parameters:
    symfony:
        containerXmlPath: var/cache/dev/srcDevDebugProjectContainer.xml
```

The path depends on your Symfony version, and the README lists the alternatives: the app kernel debug container file for Symfony 4.2 and later, and the newer kernel debug container file for Symfony 5 and later.

If you use PHP configuration files for Symfony 5.3 and later, there are two more settings. The scan directories setting points at the cached Symfony Config directory, and the scan files setting points at the container configurator class, because the helper functions such as the service and env functions live there and have to be loaded for the analyser to understand them.

This configuration is the part that most often causes confusion, and the reason is a build ordering problem rather than a documentation problem. The container XML file is a build artifact. It exists after the cache has been warmed and it changes whenever the container changes. If your static analysis runs against a stale file, the extension is analysing an old container and will report services that no longer exist, or miss ones that do.

So the container file has to be regenerated before the analysis step, in the same way the compiled classes are.

## Constant hassers, and the console application loader

Two configuration options deserve their own attention because they change what the extension reports.

The first is the constant hassers setting. The problem it solves appears when you deal with optional dependencies. A check such as whether a service exists, guarded by a has call, will be reported by PHPStan as a condition that is always either on or off, depending on whether the dependency is installed, because the extension has resolved the has method to a constant.

```yaml
parameters:
	symfony:
		constantHassers: false
```

The README says plainly that you can disable the has method return type resolving like that, and then warns that it may hide genuine errors in your application. That is the right trade-off framing, because turning it off restores the original behaviour of the container method and with it the risk of a missing service being missed.

The second option is the console application loader, which is how you get the more advanced console analysis.

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

By providing the console application from your own application, the extension can infer the correct argument and option types when the code reads them from the input object. The README then shows what that file looks like for Symfony 4, for Symfony 5, and for a single command application, each of which boots the kernel or the autoloader and returns an application instance.

There is a caveat at the end of that section, about a compile error from PhpParser about declaring an interface twice. The README starts to explain it, and that error usually means the same classes are loaded twice in the analysis, which is a problem with how the loader file pulls in the kernel rather than with the extension itself.

## How the extension is built and maintained

The Makefile shows the quality gates for the project itself, and they are the same ones a PHP project of this kind would run.

The check target runs lint, the code style check, the tests and PHPStan. The lint step is a parallel lint over the source and tests directories. The code style tooling comes from the PHPStan build-cs repository, cloned by the cs-install target and pinned to the 2.x branch, and the style check and the fixer are two targets over the same standard.

The PHPStan step analyses the source and the tests at level 8 with the project configuration, and there is a separate target to generate the baseline file that the same configuration then uses. A baseline in an extension like this is normal, since the extension analyses code that mixes framework magic with static analysis.

The repository layout supports that structure, with a stubs directory for the framework stubs the extension ships, a tests directory, a Makefile, a phpunit configuration, and a CLAUDE.md file alongside the usual GitHub and editor configuration.

On versions, the tags are 2.1.0 from 2026-10-06, 2.0.20 from 2026-06-16 and 2.0.19 from 2026-05-29, and the default branch is 2.1.x, with the last push on 2026-10-19. So the extension is on a 2.x line with frequent patch releases, which is the expected pattern for a package that has to keep up with both PHPStan and Symfony releases.

The licence is MIT, with the LICENSE file at the root of the repository.

## Conclusion

phpstan-symfony suits a team running PHPStan on a Symfony application, because the extension removes the class of false positives that come from the container and the console, and adds checks for services that are not registered or are private. It is the wrong choice if you do not run PHPStan, or if your project has no container to inspect, and it is the wrong choice if you are unwilling to ship a container XML file into your analysis pipeline. Verify first that the container XML path your configuration points at is regenerated in continuous integration, because a stale cache file makes the extension analyse an old container, and read the constant hassers note before disabling it, since turning it off hides genuine errors as well as the false positives.

## FAQ

### What does phpstan-symfony add to PHPStan?

It resolves return types that PHPStan cannot infer on its own, such as the container get and has methods, the controller and parameter bag equivalents, the request content method based on its asResource argument, the serializer denormalize method based on the type argument and the message bus handle method, and it adds rules for unregistered or private services and for undefined console arguments and options.

### How do I install the Symfony extension for PHPStan?

Require it with composer require --dev phpstan/phpstan-symfony and, if you install the PHPStan extension installer, it is registered automatically. Otherwise include the extension.neon and rules.neon files from the vendor directory in your PHPStan configuration.

### Why does PHPStan complain that a container has check is always true or false?

Because the extension resolves the has methods to a constant, which trips the always true or always false rule for optional dependencies. You can turn that resolution off with the constantHassers parameter set to false, though the README warns that this may hide genuine errors.

### How do I get correct types for console command arguments?

Point the consoleApplicationLoader parameter at a PHP file that boots your kernel and returns the console application. The README shows variants for Symfony 4, Symfony 5 and a single command application.

## Sources

- [Issues](https://github.com/phpstan/phpstan-symfony/issues)
- [License: MIT](https://github.com/phpstan/phpstan-symfony/blob/2.1.x/LICENSE)
- [phpstan/phpstan-symfony on GitHub](https://github.com/phpstan/phpstan-symfony)
- [README](https://github.com/phpstan/phpstan-symfony/blob/2.1.x/README.md)
- [Releases](https://github.com/phpstan/phpstan-symfony/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/phpstan-phpstan-symfony
