diff --git a/.gitattributes b/.gitattributes
index 24fe5a9f..94b7c8c2 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,8 +1,12 @@
+*.php text eol=lf
+
+.github export-ignore
 tests export-ignore
-.coveralls.yml export-ignore
+tmp export-ignore
+.editorconfig export-ignore
 .gitattributes export-ignore
 .gitignore export-ignore
-.travis.yml export-ignore
-build.xml export-ignore
-phpcs.xml export-ignore
+Makefile export-ignore
 phpstan.neon export-ignore
+phpstan-baseline.neon export-ignore
+phpunit.xml export-ignore
diff --git a/.github/renovate.json b/.github/renovate.json
new file mode 100644
index 00000000..d3f5961e
--- /dev/null
+++ b/.github/renovate.json
@@ -0,0 +1,19 @@
+{
+  "extends": [
+    "config:base",
+    "schedule:weekly"
+  ],
+  "rangeStrategy": "update-lockfile",
+  "packageRules": [
+    {
+      "matchPaths": ["+(composer.json)"],
+      "enabled": true,
+      "groupName": "root-composer"
+    },
+    {
+      "matchPaths": [".github/**"],
+      "enabled": true,
+      "groupName": "github-actions"
+    }
+  ]
+}
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..db0977ec
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,161 @@
+# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
+
+name: "Build"
+
+on:
+  pull_request:
+  push:
+    branches:
+      - "2.0.x"
+
+jobs:
+  lint:
+    name: "Lint"
+    runs-on: "ubuntu-latest"
+
+    strategy:
+      matrix:
+        php-version:
+          - "7.4"
+          - "8.0"
+          - "8.1"
+          - "8.2"
+          - "8.3"
+          - "8.4"
+
+    steps:
+      - name: "Checkout"
+        uses: actions/checkout@v5
+
+      - name: "Install PHP"
+        uses: "shivammathur/setup-php@v2"
+        with:
+          coverage: "none"
+          php-version: "${{ matrix.php-version }}"
+
+      - name: "Validate Composer"
+        run: "composer validate"
+
+      - name: "Install dependencies"
+        run: "composer install --no-interaction --no-progress"
+
+      - name: "Lint"
+        run: "make lint"
+
+  coding-standard:
+    name: "Coding Standard"
+
+    runs-on: "ubuntu-latest"
+
+    steps:
+      - name: "Checkout"
+        uses: actions/checkout@v5
+
+      - name: "Checkout build-cs"
+        uses: actions/checkout@v5
+        with:
+          repository: "phpstan/build-cs"
+          path: "build-cs"
+          ref: "2.x"
+
+      - name: "Install PHP"
+        uses: "shivammathur/setup-php@v2"
+        with:
+          coverage: "none"
+          php-version: "8.2"
+
+      - name: "Validate Composer"
+        run: "composer validate"
+
+      - name: "Install dependencies"
+        run: "composer install --no-interaction --no-progress"
+
+      - name: "Install build-cs dependencies"
+        working-directory: "build-cs"
+        run: "composer install --no-interaction --no-progress"
+
+      - name: "Lint"
+        run: "make lint"
+
+      - name: "Coding Standard"
+        run: "make cs"
+
+  tests:
+    name: "Tests"
+    runs-on: "ubuntu-latest"
+
+    strategy:
+      fail-fast: false
+      matrix:
+        php-version:
+          - "7.4"
+          - "8.0"
+          - "8.1"
+          - "8.2"
+          - "8.3"
+          - "8.4"
+        dependencies:
+          - "lowest"
+          - "highest"
+
+    steps:
+      - name: "Checkout"
+        uses: actions/checkout@v5
+
+      - name: "Install PHP"
+        uses: "shivammathur/setup-php@v2"
+        with:
+          coverage: "none"
+          php-version: "${{ matrix.php-version }}"
+
+      - name: "Install lowest dependencies"
+        if: ${{ matrix.dependencies == 'lowest' }}
+        run: "composer update --prefer-lowest --no-interaction --no-progress"
+
+      - name: "Install highest dependencies"
+        if: ${{ matrix.dependencies == 'highest' }}
+        run: "composer update --no-interaction --no-progress"
+
+      - name: "Tests"
+        run: "make tests"
+
+  static-analysis:
+    name: "PHPStan"
+    runs-on: "ubuntu-latest"
+
+    strategy:
+      fail-fast: false
+      matrix:
+        php-version:
+          - "7.4"
+          - "8.0"
+          - "8.1"
+          - "8.2"
+          - "8.3"
+          - "8.4"
+        dependencies:
+          - "lowest"
+          - "highest"
+
+    steps:
+      - name: "Checkout"
+        uses: actions/checkout@v5
+
+      - name: "Install PHP"
+        uses: "shivammathur/setup-php@v2"
+        with:
+          coverage: "none"
+          php-version: "${{ matrix.php-version }}"
+          extensions: mbstring
+          tools: composer:v2
+
+      - name: "Install lowest dependencies"
+        if: ${{ matrix.dependencies == 'lowest' }}
+        run: "composer update --prefer-lowest --no-interaction --no-progress"
+
+      - name: "Install highest dependencies"
+        if: ${{ matrix.dependencies == 'highest' }}
+        run: "composer update --no-interaction --no-progress"
+
+      - name: "PHPStan"
+        run: "make phpstan"
diff --git a/.github/workflows/create-tag.yml b/.github/workflows/create-tag.yml
new file mode 100644
index 00000000..fd918164
--- /dev/null
+++ b/.github/workflows/create-tag.yml
@@ -0,0 +1,53 @@
+# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
+
+name: "Create tag"
+
+on:
+  # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch
+  workflow_dispatch:
+    inputs:
+      version:
+        description: 'Next version'
+        required: true
+        default: 'patch'
+        type: choice
+        options:
+          - patch
+          - minor
+
+jobs:
+  create-tag:
+    name: "Create tag"
+    runs-on: "ubuntu-latest"
+    steps:
+      - name: "Checkout"
+        uses: actions/checkout@v5
+        with:
+          fetch-depth: 0
+          token: ${{ secrets.PHPSTAN_BOT_TOKEN }}
+
+      - name: 'Get Previous tag'
+        id: previoustag
+        uses: "WyriHaximus/github-action-get-previous-tag@v1"
+        env:
+          GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+
+      - name: 'Get next versions'
+        id: semvers
+        uses: "WyriHaximus/github-action-next-semvers@v1"
+        with:
+          version: ${{ steps.previoustag.outputs.tag }}
+
+      - name: "Create new minor tag"
+        uses: rickstaa/action-create-tag@v1
+        if: inputs.version == 'minor'
+        with:
+          tag: ${{ steps.semvers.outputs.minor }}
+          message: ${{ steps.semvers.outputs.minor }}
+
+      - name: "Create new patch tag"
+        uses: rickstaa/action-create-tag@v1
+        if: inputs.version == 'patch'
+        with:
+          tag: ${{ steps.semvers.outputs.patch }}
+          message: ${{ steps.semvers.outputs.patch }}
diff --git a/.github/workflows/lock-closed-issues.yml b/.github/workflows/lock-closed-issues.yml
new file mode 100644
index 00000000..047fe906
--- /dev/null
+++ b/.github/workflows/lock-closed-issues.yml
@@ -0,0 +1,23 @@
+name: 'Lock Issues'
+
+on:
+  schedule:
+    - cron: '5 0 * * *'
+
+jobs:
+  lock:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: dessant/lock-threads@v5
+        with:
+          github-token: ${{ github.token }}
+          issue-inactive-days: '31'
+          exclude-issue-created-before: ''
+          exclude-any-issue-labels: ''
+          add-issue-labels: ''
+          issue-comment: >
+            This thread has been automatically locked since there has not been
+            any recent activity after it was closed. Please open a new issue for
+            related bugs.
+          issue-lock-reason: 'resolved'
+          process-only: 'issues'
diff --git a/.github/workflows/release-toot.yml b/.github/workflows/release-toot.yml
new file mode 100644
index 00000000..1ba4fd77
--- /dev/null
+++ b/.github/workflows/release-toot.yml
@@ -0,0 +1,21 @@
+name: Toot release
+
+# More triggers
+# https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#release
+on:
+  release:
+    types: [published]
+
+jobs:
+  toot:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: cbrgm/mastodon-github-action@v2
+        if: ${{ !github.event.repository.private }}
+        with:
+          # GitHub event payload
+          # https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#release
+          message: "New release: ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} ${{ github.event.release.html_url }} #phpstan"
+        env:
+          MASTODON_URL: https://phpc.social
+          MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }}
diff --git a/.github/workflows/release-tweet.yml b/.github/workflows/release-tweet.yml
new file mode 100644
index 00000000..d81f34ca
--- /dev/null
+++ b/.github/workflows/release-tweet.yml
@@ -0,0 +1,24 @@
+name: Tweet release
+
+# More triggers
+# https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#release
+on:
+  release:
+    types: [published]
+
+jobs:
+  tweet:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: Eomm/why-don-t-you-tweet@v2
+        if: ${{ !github.event.repository.private }}
+        with:
+          # GitHub event payload
+          # https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#release
+          tweet-message: "New release: ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} ${{ github.event.release.html_url }} #phpstan"
+        env:
+          # Get your tokens from https://developer.twitter.com/apps
+          TWITTER_CONSUMER_API_KEY: ${{ secrets.TWITTER_CONSUMER_API_KEY }}
+          TWITTER_CONSUMER_API_SECRET: ${{ secrets.TWITTER_CONSUMER_API_SECRET }}
+          TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
+          TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..ed7e51ad
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,33 @@
+# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
+
+name: "Create release"
+
+on:
+  push:
+    tags:
+      - '*'
+
+jobs:
+  deploy:
+    name: "Deploy"
+    runs-on: "ubuntu-latest"
+
+    steps:
+      - name: "Checkout"
+        uses: actions/checkout@v5
+
+      - name: Generate changelog
+        id: changelog
+        uses: metcalfc/changelog-generator@v4.6.2
+        with:
+          myToken: ${{ secrets.PHPSTAN_BOT_TOKEN }}
+
+      - name: "Create release"
+        id: create-release
+        uses: actions/create-release@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.PHPSTAN_BOT_TOKEN }}
+        with:
+          tag_name: ${{ github.ref }}
+          release_name: ${{ github.ref }}
+          body: ${{ steps.changelog.outputs.changelog }}
diff --git a/.gitignore b/.gitignore
index ca398d28..7de9f3c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
 /tests/tmp
+/build-cs
 /vendor
-composer.lock
+/composer.lock
+.phpunit.result.cache
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index ab4b6e40..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-language: php
-php:
-  - 7.1
-  - 7.2
-  - 7.3
-env:
-  - dependencies=lowest
-  - dependencies=highest
-before_script:
-  - composer self-update
-  - if [ "$dependencies" = "lowest" ]; then composer update --prefer-lowest --no-interaction; fi;
-  - if [ "$dependencies" = "highest" ]; then composer update --no-interaction; fi;
-script:
-  - vendor/bin/phing
-  - >
-    wget https://github.com/maglnet/ComposerRequireChecker/releases/download/1.1.0/composer-require-checker.phar
-    && php composer-require-checker.phar check composer.json
diff --git a/LICENSE b/LICENSE
index 0b9f74d9..cb2e557c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,7 @@
 The MIT License (MIT)
 
 Copyright (c) 2017 Lukáš Unger
+Copyright (c) 2025 PHPStan s.r.o.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..1ee557df
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,33 @@
+.PHONY: check
+check: lint cs tests phpstan
+
+.PHONY: tests
+tests:
+	php vendor/bin/phpunit
+
+.PHONY: lint
+lint:
+	php vendor/bin/parallel-lint --colors \
+		src tests
+
+.PHONY: cs-install
+cs-install:
+	git clone https://github.com/phpstan/build-cs.git || true
+	git -C build-cs fetch origin && git -C build-cs reset --hard origin/2.x
+	composer install --working-dir build-cs
+
+.PHONY: cs
+cs:
+	php build-cs/vendor/bin/phpcs --standard=build-cs/phpcs.xml src tests
+
+.PHONY: cs-fix
+cs-fix:
+	php build-cs/vendor/bin/phpcbf --standard=build-cs/phpcs.xml src tests
+
+.PHONY: phpstan
+phpstan:
+	php vendor/bin/phpstan analyse -l 8 -c phpstan.neon src tests
+
+.PHONY: phpstan-generate-baseline
+phpstan-generate-baseline:
+	php vendor/bin/phpstan analyse -l 8 -c phpstan.neon src tests -b phpstan-baseline.neon
diff --git a/README.md b/README.md
index 87f25054..25a155c1 100644
--- a/README.md
+++ b/README.md
@@ -1,40 +1,79 @@
 # PHPStan Symfony Framework extensions and rules
 
-[![Build Status](https://travis-ci.org/phpstan/phpstan-symfony.svg)](https://travis-ci.org/phpstan/phpstan-symfony)
+[![Build](https://github.com/phpstan/phpstan-symfony/workflows/Build/badge.svg)](https://github.com/phpstan/phpstan-symfony/actions)
 [![Latest Stable Version](https://poser.pugx.org/phpstan/phpstan-symfony/v/stable)](https://packagist.org/packages/phpstan/phpstan-symfony)
 [![License](https://poser.pugx.org/phpstan/phpstan-symfony/license)](https://packagist.org/packages/phpstan/phpstan-symfony)
 
-* [PHPStan](https://github.com/phpstan/phpstan)
+* [PHPStan](https://phpstan.org/)
 
 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`.
 
-## Usage
+
+## Installation
 
 To use this extension, require it in [Composer](https://getcomposer.org/):
 
-```bash
+```
 composer require --dev phpstan/phpstan-symfony
 ```
 
-And include extension.neon in your project's PHPStan config:
+If you also install [phpstan/extension-installer](https://github.com/phpstan/extension-installer) then you're all set!
+
+<details>
+  <summary>Manual installation</summary>
+
+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
-parameters:
-	symfony:
-		container_xml_path: %rootDir%/../../../var/cache/dev/srcDevDebugProjectContainer.xml
-		# or with Symfony 4.2+
-		container_xml_path: '%rootDir%/../../../var/cache/dev/srcApp_KernelDevDebugContainer.xml' 
+    - vendor/phpstan/phpstan-symfony/extension.neon
+```
+
+To perform framework-specific checks, include also this file:
+
+```
+includes:
+    - vendor/phpstan/phpstan-symfony/rules.neon
 ```
+</details>
 
-You have to provide a path to `srcDevDebugProjectContainer.xml` or similar xml file describing your container.
+# Configuration
+
+You have to provide a path to `srcDevDebugProjectContainer.xml` or similar XML file describing your container.
+
+```yaml
+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
+    # If you're using PHP config files (including the ones under packages/*.php) for Symfony 5.3+,
+    # you need this to load the helper functions (i.e. service(), env()):
+    scanFiles:
+        - vendor/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php
+```
 
 ## Constant hassers
 
@@ -46,12 +85,86 @@ if ($this->has('service')) {
 }
 ```
 
-In that case, you can disable the `::has()` method return type resolving like this: 
+In that case, you can disable the `::has()` method return type resolving like this:
 
 ```
 parameters:
 	symfony:
-		constant_hassers: false
+		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](https://symfony.com/doc/current/console.html)
+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()`.
+
+```neon
+parameters:
+	symfony:
+		consoleApplicationLoader: tests/console-application.php
+```
+
+Symfony 4:
+
+```php
+// 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:
+
+```php
+// 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);
+```
+
+[Single Command Application](https://symfony.com/doc/current/components/console/single_command_tool.html):
+
+```php
+// tests/console-application.php
+
+use App\Application; // where Application extends Symfony\Component\Console\SingleCommandApplication
+use Symfony\Component\Console;
+
+require __DIR__ . '/../vendor/autoload.php';
+
+$application = new Console\Application();
+$application->add(new Application());
+
+return $application;
+```
+
+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`:
+
+```yaml
+parameters:
+    container.dumper.inline_class_loader: false
+```
+
+Call the new env in your `console-application.php`:
+
+```php
+$kernel = new \App\Kernel('phpstan_env', (bool) $_SERVER['APP_DEBUG']);
+```
diff --git a/build.xml b/build.xml
deleted file mode 100644
index 4c3e8284..00000000
--- a/build.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<project name="PHPStan Symfony Framework extensions and rules" default="check">
-
-    <target name="check" depends="
-        composer,
-        lint,
-        cs,
-        tests,
-        phpstan
-    "/>
-
-    <target name="composer">
-        <exec
-                executable="composer"
-                logoutput="true"
-                passthru="true"
-                checkreturn="true"
-        >
-            <arg value="install"/>
-        </exec>
-    </target>
-
-    <target name="lint">
-        <exec
-                executable="vendor/bin/parallel-lint"
-                logoutput="true"
-                passthru="true"
-                checkreturn="true"
-        >
-            <arg path="src"/>
-            <arg path="tests"/>
-        </exec>
-    </target>
-
-    <target name="cs">
-        <exec
-                executable="vendor/bin/phpcs"
-                logoutput="true"
-                passthru="true"
-                checkreturn="true"
-        >
-            <arg value="--extensions=php"/>
-            <arg value="--encoding=utf-8"/>
-            <arg value="--tab-width=4"/>
-            <arg value="-sp"/>
-            <arg path="src"/>
-            <arg path="tests"/>
-        </exec>
-    </target>
-
-    <target name="cs-fix">
-        <exec
-                executable="vendor/bin/phpcbf"
-                logoutput="true"
-                passthru="true"
-                checkreturn="true"
-        >
-            <arg value="--extensions=php"/>
-            <arg value="--encoding=utf-8"/>
-            <arg value="--tab-width=4"/>
-            <arg value="-sp"/>
-            <arg path="src"/>
-            <arg path="tests"/>
-        </exec>
-    </target>
-
-    <target name="tests">
-        <exec
-                executable="vendor/bin/phpunit"
-                logoutput="true"
-                passthru="true"
-                checkreturn="true"
-        >
-            <arg value="-c"/>
-            <arg value="tests/phpunit.xml"/>
-            <arg path="tests"/>
-        </exec>
-    </target>
-
-    <target name="phpstan">
-        <exec
-                executable="vendor/bin/phpstan"
-                logoutput="true"
-                passthru="true"
-                checkreturn="true"
-        >
-            <arg value="analyse"/>
-            <arg value="-l"/>
-            <arg value="7"/>
-            <arg value="-c"/>
-            <arg path="phpstan.neon"/>
-            <arg path="src"/>
-            <arg path="tests"/>
-        </exec>
-    </target>
-
-</project>
diff --git a/composer.json b/composer.json
index 4126f9e2..c03d2c99 100644
--- a/composer.json
+++ b/composer.json
@@ -1,7 +1,10 @@
 {
   "name": "phpstan/phpstan-symfony",
+  "type": "phpstan-extension",
   "description": "Symfony Framework extensions and rules for PHPStan",
-  "license": ["MIT"],
+  "license": [
+    "MIT"
+  ],
   "authors": [
     {
       "name": "Lukáš Unger",
@@ -9,41 +12,52 @@
       "homepage": "https://lookyman.net"
     }
   ],
-  "minimum-stability": "dev",
-  "prefer-stable": true,
-  "extra": {
-    "branch-alias": {
-      "dev-master": "0.11-dev"
-    }
-  },
   "require": {
-    "php": "^7.1",
+    "php": "^7.4 || ^8.0",
     "ext-simplexml": "*",
-    "phpstan/phpstan": "^0.11.1",
-    "nikic/php-parser": "^4.0"
-  },
-  "require-dev": {
-    "consistence/coding-standard": "^3.0.1",
-    "jakub-onderka/php-parallel-lint": "^1.0",
-    "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4",
-    "phpunit/phpunit": "^7.0",
-    "phing/phing": "^2.16.0",
-    "phpstan/phpstan-strict-rules": "^0.11",
-    "slevomat/coding-standard": "^4.5.2",
-    "phpstan/phpstan-phpunit": "^0.11",
-    "symfony/framework-bundle": "^3.0 || ^4.0",
-    "squizlabs/php_codesniffer": "^3.3.2",
-    "symfony/serializer": "^3|^4"
+    "phpstan/phpstan": "^2.1.13"
   },
   "conflict": {
     "symfony/framework-bundle": "<3.0"
   },
+  "require-dev": {
+    "php-parallel-lint/php-parallel-lint": "^1.2",
+    "phpstan/phpstan-phpunit": "^2.0",
+    "phpstan/phpstan-strict-rules": "^2.0",
+    "phpunit/phpunit": "^9.6",
+    "psr/container": "1.1.2",
+    "symfony/config": "^5.4 || ^6.1",
+    "symfony/console": "^5.4 || ^6.1",
+    "symfony/dependency-injection": "^5.4 || ^6.1",
+    "symfony/form": "^5.4 || ^6.1",
+    "symfony/framework-bundle": "^5.4 || ^6.1",
+    "symfony/http-foundation": "^5.4 || ^6.1",
+    "symfony/messenger": "^5.4",
+    "symfony/polyfill-php80": "^1.24",
+    "symfony/serializer": "^5.4",
+    "symfony/service-contracts": "^2.2.0"
+  },
+  "config": {
+    "sort-packages": true
+  },
+  "extra": {
+    "phpstan": {
+      "includes": [
+        "extension.neon",
+        "rules.neon"
+      ]
+    }
+  },
   "autoload": {
     "psr-4": {
       "PHPStan\\": "src/"
     }
   },
   "autoload-dev": {
-    "classmap": ["tests/"]
-  }
+    "classmap": [
+      "tests/"
+    ]
+  },
+  "minimum-stability": "dev",
+  "prefer-stable": true
 }
diff --git a/extension.neon b/extension.neon
index 07424feb..ecc2ddf3 100644
--- a/extension.neon
+++ b/extension.neon
@@ -1,52 +1,364 @@
 parameters:
+	dynamicConstantNames:
+		- Symfony\Component\HttpKernel\Kernel::VERSION_ID
+	exceptions:
+		uncheckedExceptionClasses:
+			- 'Symfony\Component\Console\Exception\InvalidArgumentException'
 	symfony:
-		constant_hassers: true
+		containerXmlPath: null
+		constantHassers: true
+		consoleApplicationLoader: null
+	stubFiles:
+		- stubs/Psr/Cache/CacheException.stub
+		- stubs/Psr/Cache/CacheItemInterface.stub
+		- stubs/Psr/Cache/InvalidArgumentException.stub
+		- stubs/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.stub
+		- stubs/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.stub
+		- stubs/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FirewallListenerFactoryInterface.stub
+		- stubs/Symfony/Component/Console/Command.stub
+		- stubs/Symfony/Component/Console/Exception/ExceptionInterface.stub
+		- stubs/Symfony/Component/Console/Exception/InvalidArgumentException.stub
+		- stubs/Symfony/Component/Console/Exception/LogicException.stub
+		- stubs/Symfony/Component/Console/Helper/HelperInterface.stub
+		- stubs/Symfony/Component/Console/Output/OutputInterface.stub
+		- stubs/Symfony/Component/DependencyInjection/ContainerBuilder.stub
+		- stubs/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.stub
+		- stubs/Symfony/Component/EventDispatcher/EventDispatcherInterface.stub
+		- stubs/Symfony/Component/EventDispatcher/EventSubscriberInterface.stub
+		- stubs/Symfony/Component/EventDispatcher/GenericEvent.stub
+		- stubs/Symfony/Component/Form/AbstractType.stub
+		- stubs/Symfony/Component/Form/ChoiceList/Loader/ChoiceLoaderInterface.stub
+		- stubs/Symfony/Component/Form/Exception/ExceptionInterface.stub
+		- stubs/Symfony/Component/Form/Exception/RuntimeException.stub
+		- stubs/Symfony/Component/Form/Exception/TransformationFailedException.stub
+		- stubs/Symfony/Component/Form/DataTransformerInterface.stub
+		- stubs/Symfony/Component/Form/FormBuilderInterface.stub
+		- stubs/Symfony/Component/Form/FormConfigBuilderInterface.stub
+		- stubs/Symfony/Component/Form/FormConfigInterface.stub
+		- stubs/Symfony/Component/Form/FormInterface.stub
+		- stubs/Symfony/Component/Form/FormFactoryInterface.stub
+		- stubs/Symfony/Component/Form/FormTypeExtensionInterface.stub
+		- stubs/Symfony/Component/Form/FormTypeInterface.stub
+		- stubs/Symfony/Component/Form/FormView.stub
+		- stubs/Symfony/Component/HttpFoundation/Cookie.stub
+		- stubs/Symfony/Component/HttpFoundation/HeaderBag.stub
+		- stubs/Symfony/Component/HttpFoundation/ParameterBag.stub
+		- stubs/Symfony/Component/HttpFoundation/Session.stub
+		- stubs/Symfony/Component/Messenger/StampInterface.stub
+		- stubs/Symfony/Component/Messenger/Envelope.stub
+		- stubs/Symfony/Component/OptionsResolver/Exception/InvalidOptionsException.stub
+		- stubs/Symfony/Component/OptionsResolver/Options.stub
+		- stubs/Symfony/Component/Process/Exception/LogicException.stub
+		- stubs/Symfony/Component/Process/Process.stub
+		- stubs/Symfony/Component/PropertyAccess/Exception/AccessException.stub
+		- stubs/Symfony/Component/PropertyAccess/Exception/ExceptionInterface.stub
+		- stubs/Symfony/Component/PropertyAccess/Exception/InvalidArgumentException.stub
+		- stubs/Symfony/Component/PropertyAccess/Exception/RuntimeException.stub
+		- stubs/Symfony/Component/PropertyAccess/Exception/UnexpectedTypeException.stub
+		- stubs/Symfony/Component/PropertyAccess/PropertyAccessor.stub
+		- stubs/Symfony/Component/PropertyAccess/PropertyAccessorInterface.stub
+		- stubs/Symfony/Component/PropertyAccess/PropertyPathInterface.stub
+		- stubs/Symfony/Component/Security/Acl/Model/AclInterface.stub
+		- stubs/Symfony/Component/Security/Acl/Model/EntryInterface.stub
+		- stubs/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.stub
+		- stubs/Symfony/Component/Security/Core/Authorization/Voter/Voter.stub
+		- stubs/Symfony/Component/Security/Core/Authorization/Voter/VoterInterface.stub
+		- stubs/Symfony/Component/Serializer/Encoder/ContextAwareDecoderInterface.stub
+		- stubs/Symfony/Component/Serializer/Encoder/DecoderInterface.stub
+		- stubs/Symfony/Component/Serializer/Encoder/EncoderInterface.stub
+		- stubs/Symfony/Component/Serializer/Exception/BadMethodCallException.stub
+		- stubs/Symfony/Component/Serializer/Exception/CircularReferenceException.stub
+		- stubs/Symfony/Component/Serializer/Exception/ExceptionInterface.stub
+		- stubs/Symfony/Component/Serializer/Exception/ExtraAttributesException.stub
+		- stubs/Symfony/Component/Serializer/Exception/InvalidArgumentException.stub
+		- stubs/Symfony/Component/Serializer/Exception/LogicException.stub
+		- stubs/Symfony/Component/Serializer/Exception/RuntimeException.stub
+		- stubs/Symfony/Component/Serializer/Exception/UnexpectedValueException.stub
+		- stubs/Symfony/Component/Serializer/Normalizer/ContextAwareDenormalizerInterface.stub
+		- stubs/Symfony/Component/Serializer/Normalizer/ContextAwareNormalizerInterface.stub
+		- stubs/Symfony/Component/Serializer/Normalizer/DenormalizableInterface.stub
+		- stubs/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.stub
+		- stubs/Symfony/Component/Serializer/Normalizer/NormalizableInterface.stub
+		- stubs/Symfony/Component/Serializer/Normalizer/NormalizerInterface.stub
+		- stubs/Symfony/Component/Validator/Constraint.stub
+		- stubs/Symfony/Component/Validator/Constraints/Composite.stub
+		- stubs/Symfony/Component/Validator/Constraints/Compound.stub
+		- stubs/Symfony/Component/Validator/ConstraintViolationInterface.stub
+		- stubs/Symfony/Component/Validator/ConstraintViolationListInterface.stub
+		- stubs/Symfony/Contracts/Cache/CacheInterface.stub
+		- stubs/Symfony/Contracts/Cache/CallbackInterface.stub
+		- stubs/Symfony/Contracts/Cache/ItemInterface.stub
+		- stubs/Symfony/Contracts/Service/ServiceSubscriberInterface.stub
+		- stubs/Twig/Node/Node.stub
 
-rules:
-	- PHPStan\Rules\Symfony\ContainerInterfacePrivateServiceRule
-	- PHPStan\Rules\Symfony\ContainerInterfaceUnknownServiceRule
+parametersSchema:
+	symfony: structure([
+		containerXmlPath: schema(string(), nullable())
+		constantHassers: bool()
+		consoleApplicationLoader: schema(string(), nullable())
+	])
 
 services:
+	# console resolver
+	-
+		factory: PHPStan\Symfony\ConsoleApplicationResolver
+		arguments:
+			consoleApplicationLoader: %symfony.consoleApplicationLoader%
+
 	# service map
 	symfony.serviceMapFactory:
 		class: PHPStan\Symfony\ServiceMapFactory
-		factory: PHPStan\Symfony\XmlServiceMapFactory(%symfony.container_xml_path%)
+		factory: PHPStan\Symfony\XmlServiceMapFactory
+		arguments:
+			containerXmlPath: %symfony.containerXmlPath%
+	-
+		factory: @symfony.serviceMapFactory::create()
+
+	# parameter map
+	symfony.parameterMapFactory:
+		class: PHPStan\Symfony\ParameterMapFactory
+		factory: PHPStan\Symfony\XmlParameterMapFactory
+		arguments:
+			containerXmlPath: %symfony.containerXmlPath%
 	-
-		class: @symfony.serviceMapFactory::create()
+		factory: @symfony.parameterMapFactory::create()
+
+	# message map
+	symfony.messageMapFactory:
+		class: PHPStan\Symfony\MessageMapFactory
+		factory: PHPStan\Symfony\MessageMapFactory
+	-
+		factory: @symfony.messageMapFactory::create()
 
 	# ControllerTrait::get()/has() return type
 	-
-		class: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ContainerInterface, %symfony.constant_hassers%)
+		factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ContainerInterface, %symfony.constantHassers%)
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+	-
+		factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Psr\Container\ContainerInterface, %symfony.constantHassers%)
 		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
 	-
-		class: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller, %symfony.constant_hassers%)
+		factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller, %symfony.constantHassers%)
 		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
 	-
-		class: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController, %symfony.constant_hassers%)
+		factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController, %symfony.constantHassers%)
 		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
 
 	# ControllerTrait::has() type specification
 	-
-		class: PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension(Symfony\Component\DependencyInjection\ContainerInterface)
+		factory: PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension(Symfony\Component\DependencyInjection\ContainerInterface)
+		tags: [phpstan.typeSpecifier.methodTypeSpecifyingExtension]
+	-
+		factory: PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension(Psr\Container\ContainerInterface)
 		tags: [phpstan.typeSpecifier.methodTypeSpecifyingExtension]
 	-
-		class: PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller)
+		factory: PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller)
 		tags: [phpstan.typeSpecifier.methodTypeSpecifyingExtension]
 	-
-		class: PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController)
+		factory: PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController)
 		tags: [phpstan.typeSpecifier.methodTypeSpecifyingExtension]
 
 	# Request::getContent() return type
 	-
-		class: PHPStan\Type\Symfony\RequestDynamicReturnTypeExtension
+		factory: PHPStan\Type\Symfony\RequestDynamicReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	# Request::getSession() type specification
+	-
+		factory: PHPStan\Type\Symfony\RequestTypeSpecifyingExtension
+		tags: [phpstan.typeSpecifier.methodTypeSpecifyingExtension]
+
+	# InputBag::get() return type
+	-
+		factory: PHPStan\Type\Symfony\InputBagDynamicReturnTypeExtension
 		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
 
 	# HeaderBag::get() return type
 	-
-		class: PHPStan\Type\Symfony\HeaderBagDynamicReturnTypeExtension
+		factory: PHPStan\Type\Symfony\HeaderBagDynamicReturnTypeExtension
 		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
 
 	# SerializerInterface::deserialize() return type
 	-
-		class: PHPStan\Type\Symfony\SerializerInterfaceDynamicReturnTypeExtension
+		factory: PHPStan\Type\Symfony\SerializerDynamicReturnTypeExtension(Symfony\Component\Serializer\SerializerInterface, deserialize)
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	# DenormalizerInterface::denormalize() return type
+	-
+		factory: PHPStan\Type\Symfony\SerializerDynamicReturnTypeExtension(Symfony\Component\Serializer\Normalizer\DenormalizerInterface, denormalize)
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	# Envelope::all() return type
+	-
+		factory: PHPStan\Type\Symfony\EnvelopeReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	# Messenger HandleTrait::handle() return type
+	-
+		class: PHPStan\Type\Symfony\MessengerHandleTraitReturnTypeExtension
+		tags: [phpstan.broker.expressionTypeResolverExtension]
+
+	# InputInterface::getArgument() return type
+	-
+		factory: PHPStan\Type\Symfony\InputInterfaceGetArgumentDynamicReturnTypeExtension
 		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	# InputInterface::hasArgument() type specification
+	-
+		factory: PHPStan\Type\Symfony\ArgumentTypeSpecifyingExtension
+		tags: [phpstan.typeSpecifier.methodTypeSpecifyingExtension]
+
+	# InputInterface::hasArgument() return type
+	-
+		factory: PHPStan\Type\Symfony\InputInterfaceHasArgumentDynamicReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	# InputInterface::getOption() return type
+	-
+		factory: PHPStan\Type\Symfony\GetOptionTypeHelper
+	-
+		factory: PHPStan\Type\Symfony\InputInterfaceGetOptionDynamicReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	# InputInterface::getOptions() return type
+	-
+		factory: PHPStan\Type\Symfony\InputInterfaceGetOptionsDynamicReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	# InputInterface::hasOption() type specification
+	-
+		factory: PHPStan\Type\Symfony\OptionTypeSpecifyingExtension
+		tags: [phpstan.typeSpecifier.methodTypeSpecifyingExtension]
+
+	# InputInterface::hasOption() return type
+	-
+		factory: PHPStan\Type\Symfony\InputInterfaceHasOptionDynamicReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	# ArrayNodeDefinition::*prototype() return type
+	-
+		factory: PHPStan\Type\Symfony\Config\ArrayNodeDefinitionPrototypeDynamicReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	# ExprBuilder::end() return type
+	-
+		factory: PHPStan\Type\Symfony\Config\ReturnParentDynamicReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+		arguments:
+		    className: Symfony\Component\Config\Definition\Builder\ExprBuilder
+		    methods: [end]
+
+	# NodeBuilder::*node() return type
+	-
+		factory: PHPStan\Type\Symfony\Config\PassParentObjectDynamicReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+		arguments:
+		    className: Symfony\Component\Config\Definition\Builder\NodeBuilder
+		    methods: [arrayNode, scalarNode, booleanNode, integerNode, floatNode, enumNode, variableNode]
+
+	# NodeBuilder::end() return type
+	-
+		factory: PHPStan\Type\Symfony\Config\ReturnParentDynamicReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+		arguments:
+		    className: Symfony\Component\Config\Definition\Builder\NodeBuilder
+		    methods: [end]
+
+	# NodeDefinition::children() return type
+	-
+		factory: PHPStan\Type\Symfony\Config\PassParentObjectDynamicReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+		arguments:
+		    className: Symfony\Component\Config\Definition\Builder\NodeDefinition
+		    methods: [children, validate, beforeNormalization]
+
+	# NodeDefinition::end() return type
+	-
+		factory: PHPStan\Type\Symfony\Config\ReturnParentDynamicReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+		arguments:
+		    className: Symfony\Component\Config\Definition\Builder\NodeDefinition
+		    methods: [end]
+
+	# new TreeBuilder() return type
+	-
+		factory: PHPStan\Type\Symfony\Config\TreeBuilderDynamicReturnTypeExtension
+		tags: [phpstan.broker.dynamicStaticMethodReturnTypeExtension]
+
+	# TreeBuilder::getRootNode() return type
+	-
+		factory: PHPStan\Type\Symfony\Config\TreeBuilderGetRootNodeDynamicReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	# KernelInterface::locateResource() return type
+	-
+		class: PHPStan\Type\Symfony\KernelInterfaceDynamicReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	# ParameterBagInterface::get()/has() return type
+	-
+		factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface, 'get', 'has', %symfony.constantHassers%)
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	# ContainerInterface::getParameter()/hasParameter() return type
+	-
+		factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ContainerInterface, 'getParameter', 'hasParameter', %symfony.constantHassers%)
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	# (Abstract)Controller::getParameter() return type
+	-
+		factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController, 'getParameter', null, %symfony.constantHassers%)
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+	-
+		factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller, 'getParameter', null, %symfony.constantHassers%)
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+	-
+		class: PHPStan\Symfony\InputBagStubFilesExtension
+		tags:
+			- phpstan.stubFilesExtension
+	-
+		class: PHPStan\Symfony\SymfonyDiagnoseExtension
+		tags:
+			- phpstan.diagnoseExtension
+
+	# FormInterface::getErrors() return type
+	-
+		factory: PHPStan\Type\Symfony\Form\FormInterfaceDynamicReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	# Command::getHelper() return type
+	-
+		factory: PHPStan\Type\Symfony\CommandGetHelperDynamicReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	# ResponseHeaderBag::getCookies() return type
+	-
+		factory: PHPStan\Type\Symfony\ResponseHeaderBagDynamicReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	# InputBag::get() type specification
+	-
+		factory: PHPStan\Type\Symfony\InputBagTypeSpecifyingExtension
+		tags: [phpstan.typeSpecifier.methodTypeSpecifyingExtension]
+
+	# Additional constructors and initialization checks for @required autowiring
+	-
+		class: PHPStan\Symfony\RequiredAutowiringExtension
+		tags:
+			- phpstan.properties.readWriteExtension
+			- phpstan.additionalConstructorsExtension
+
+	# CacheInterface::get() return type
+	-
+		factory: PHPStan\Type\Symfony\CacheInterfaceGetDynamicReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	# Extension::getConfiguration() return type
+	-
+		factory: PHPStan\Type\Symfony\ExtensionGetConfigurationReturnTypeExtension
+		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	-
+		class: PHPStan\Symfony\SymfonyContainerResultCacheMetaExtension
+		tags:
+			- phpstan.resultCacheMetaExtension
diff --git a/phpcs.xml b/phpcs.xml
deleted file mode 100644
index 2ff108e3..00000000
--- a/phpcs.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0"?>
-<ruleset name="PHPStan PHPUnit extensions and rules">
-    <rule ref="vendor/consistence/coding-standard/Consistence/ruleset.xml">
-        <exclude name="Squiz.Functions.GlobalFunction.Found"/>
-        <exclude name="SlevomatCodingStandard.Namespaces.FullyQualifiedClassNameAfterKeyword"/>
-        <exclude name="SlevomatCodingStandard.Namespaces.FullyQualifiedClassNameInAnnotation"/>
-        <exclude name="SlevomatCodingStandard.Namespaces.FullyQualifiedExceptions"/>
-        <exclude name="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly"/>
-        <exclude name="Consistence.Exceptions.ExceptionDeclaration"/>
-        <exclude name="Squiz.Commenting.FunctionComment.MissingParamTag"/>
-    </rule>
-    <rule ref="SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses">
-        <properties>
-            <property name="caseSensitive" value="false"/>
-        </properties>
-    </rule>
-    <rule ref="SlevomatCodingStandard.TypeHints.DeclareStrictTypes">
-        <properties>
-            <property name="newlinesCountBetweenOpenTagAndDeclare" value="0"/>
-        </properties>
-    </rule>
-    <rule ref="SlevomatCodingStandard.TypeHints.TypeHintDeclaration">
-        <properties>
-            <property name="usefulAnnotations" type="array" value="
-                @dataProvider,
-                @requires
-            "/>
-        </properties>
-    </rule>
-    <rule ref="SlevomatCodingStandard.ControlStructures.AssignmentInCondition"/>
-    <rule ref="SlevomatCodingStandard.ControlStructures.DisallowEqualOperators"/>
-    <rule ref="SlevomatCodingStandard.ControlStructures.EarlyExit"/>
-    <rule ref="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming"/>
-    <rule ref="SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming"/>
-    <!-- <rule ref="SlevomatCodingStandard.ControlStructures.DisallowShortTernaryOperator"/>-->
-    <!-- <rule ref="SlevomatCodingStandard.Namespaces.RequireOneNamespaceInFile"/> -->
-    <!-- <rule ref="SlevomatCodingStandard.PHP.ShortList"/> -->
-    <rule ref="SlevomatCodingStandard.Files.TypeNameMatchesFileName">
-        <properties>
-            <property name="rootNamespaces" type="array" value="src=>PHPStan,tests=>PHPStan"/>
-        </properties>
-    </rule>
-    <exclude-pattern>tests/tmp</exclude-pattern>
-    <exclude-pattern>tests/Symfony/ExampleContainer.php</exclude-pattern>
-</ruleset>
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
new file mode 100644
index 00000000..79e87db9
--- /dev/null
+++ b/phpstan-baseline.neon
@@ -0,0 +1,79 @@
+parameters:
+	ignoreErrors:
+		-
+			message: '#^Call to internal method Symfony\\Component\\Console\\Command\\Command\:\:mergeApplicationDefinition\(\) from outside its root namespace Symfony\.$#'
+			identifier: method.internal
+			count: 1
+			path: src/Rules/Symfony/UndefinedArgumentRule.php
+
+		-
+			message: '#^Call to internal method Symfony\\Component\\Console\\Command\\Command\:\:mergeApplicationDefinition\(\) from outside its root namespace Symfony\.$#'
+			identifier: method.internal
+			count: 1
+			path: src/Rules/Symfony/UndefinedOptionRule.php
+
+		-
+			message: '#^Although PHPStan\\Reflection\\Php\\PhpPropertyReflection is covered by backward compatibility promise, this instanceof assumption might break because it''s not guaranteed to always stay the same\.$#'
+			identifier: phpstanApi.instanceofAssumption
+			count: 1
+			path: src/Symfony/RequiredAutowiringExtension.php
+
+		-
+			message: '#^Call to internal method Symfony\\Component\\Console\\Command\\Command\:\:mergeApplicationDefinition\(\) from outside its root namespace Symfony\.$#'
+			identifier: method.internal
+			count: 1
+			path: src/Type/Symfony/CommandGetHelperDynamicReturnTypeExtension.php
+
+		-
+			message: '#^Call to function method_exists\(\) with Symfony\\Component\\Console\\Input\\InputOption and ''isNegatable'' will always evaluate to true\.$#'
+			identifier: function.alreadyNarrowedType
+			count: 1
+			path: src/Type/Symfony/GetOptionTypeHelper.php
+
+		-
+			message: '#^Call to internal method Symfony\\Component\\Console\\Command\\Command\:\:mergeApplicationDefinition\(\) from outside its root namespace Symfony\.$#'
+			identifier: method.internal
+			count: 1
+			path: src/Type/Symfony/InputInterfaceGetArgumentDynamicReturnTypeExtension.php
+
+		-
+			message: '#^Call to internal method Symfony\\Component\\Console\\Command\\Command\:\:mergeApplicationDefinition\(\) from outside its root namespace Symfony\.$#'
+			identifier: method.internal
+			count: 1
+			path: src/Type/Symfony/InputInterfaceGetOptionDynamicReturnTypeExtension.php
+
+		-
+			message: '#^Call to internal method Symfony\\Component\\Console\\Command\\Command\:\:mergeApplicationDefinition\(\) from outside its root namespace Symfony\.$#'
+			identifier: method.internal
+			count: 1
+			path: src/Type/Symfony/InputInterfaceGetOptionsDynamicReturnTypeExtension.php
+
+		-
+			message: '#^Call to internal method Symfony\\Component\\Console\\Command\\Command\:\:mergeApplicationDefinition\(\) from outside its root namespace Symfony\.$#'
+			identifier: method.internal
+			count: 1
+			path: src/Type/Symfony/InputInterfaceHasArgumentDynamicReturnTypeExtension.php
+
+		-
+			message: '#^Call to internal method Symfony\\Component\\Console\\Command\\Command\:\:mergeApplicationDefinition\(\) from outside its root namespace Symfony\.$#'
+			identifier: method.internal
+			count: 1
+			path: src/Type/Symfony/InputInterfaceHasOptionDynamicReturnTypeExtension.php
+
+		-
+			message: '#^Accessing PHPStan\\Rules\\Methods\\CallMethodsRule\:\:class is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#'
+			identifier: phpstanApi.classConstant
+			count: 1
+			path: tests/Rules/NonexistentInputBagClassTest.php
+
+		-
+			message: '#^Accessing PHPStan\\Rules\\Properties\\UninitializedPropertyRule\:\:class is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#'
+			identifier: phpstanApi.classConstant
+			count: 1
+			path: tests/Symfony/RequiredAutowiringExtensionTest.php
+
+		-
+			message: '#^Accessing PHPStan\\Rules\\Comparison\\ImpossibleCheckTypeMethodCallRule\:\:class is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#'
+			identifier: phpstanApi.classConstant
+			count: 1
+			path: tests/Type/Symfony/ImpossibleCheckTypeMethodCallRuleTest.php
diff --git a/phpstan.neon b/phpstan.neon
index 66c99036..f13073e1 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -1,12 +1,15 @@
 includes:
+	- extension.neon
+	- rules.neon
 	- vendor/phpstan/phpstan-phpunit/extension.neon
 	- vendor/phpstan/phpstan-phpunit/rules.neon
 	- vendor/phpstan/phpstan-strict-rules/rules.neon
+	- phar://phpstan.phar/conf/bleedingEdge.neon
+	- phpstan-baseline.neon
 
 parameters:
-	excludes_analyse:
-		- */tests/tmp/*
-		- */tests/*/Example*.php
-		- */tests/*/header_bag_get.php
-		- */tests/*/request_get_content.php
-		- */tests/*/serializer.php
+	excludePaths:
+		- tests/tmp/*
+		- tests/*/Example*.php
+		- tests/*/console_application_loader.php
+		- tests/*/data/*
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 00000000..2e2f6167
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         bootstrap="tests/bootstrap.php"
+         colors="true"
+         backupGlobals="false"
+         backupStaticAttributes="false"
+         beStrictAboutChangesToGlobalState="true"
+         beStrictAboutOutputDuringTests="true"
+         beStrictAboutTestsThatDoNotTestAnything="true"
+         beStrictAboutTodoAnnotatedTests="true"
+         failOnRisky="true"
+         failOnWarning="true"
+         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xml"
+>
+    <coverage>
+        <include>
+            <directory suffix=".php">./src</directory>
+        </include>
+        <report>
+            <clover outputFile="tests/tmp/clover.xml"/>
+            <text
+                outputFile="php://stdout"
+                showUncoveredFiles="true"
+                showOnlySummary="true"
+            />
+        </report>
+    </coverage>
+
+    <testsuites>
+        <testsuite name="PHPStan for Symfony">
+            <directory suffix="Test.php">tests</directory>
+        </testsuite>
+    </testsuites>
+
+    <logging/>
+</phpunit>
diff --git a/rules.neon b/rules.neon
new file mode 100644
index 00000000..cedcea7a
--- /dev/null
+++ b/rules.neon
@@ -0,0 +1,8 @@
+rules:
+	- PHPStan\Rules\Symfony\ContainerInterfacePrivateServiceRule
+	- PHPStan\Rules\Symfony\ContainerInterfaceUnknownServiceRule
+	- PHPStan\Rules\Symfony\UndefinedArgumentRule
+	- PHPStan\Rules\Symfony\InvalidArgumentDefaultValueRule
+	- PHPStan\Rules\Symfony\UndefinedOptionRule
+	- PHPStan\Rules\Symfony\InvalidOptionDefaultValueRule
+
diff --git a/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php b/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php
index b9e978a6..c8ec11a8 100644
--- a/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php
+++ b/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php
@@ -6,16 +6,20 @@
 use PhpParser\Node\Expr\MethodCall;
 use PHPStan\Analyser\Scope;
 use PHPStan\Rules\Rule;
-use PHPStan\ShouldNotHappenException;
+use PHPStan\Rules\RuleErrorBuilder;
 use PHPStan\Symfony\ServiceMap;
+use PHPStan\TrinaryLogic;
 use PHPStan\Type\ObjectType;
+use PHPStan\Type\Type;
 use function sprintf;
 
+/**
+ * @implements Rule<MethodCall>
+ */
 final class ContainerInterfacePrivateServiceRule implements Rule
 {
 
-	/** @var ServiceMap */
-	private $serviceMap;
+	private ServiceMap $serviceMap;
 
 	public function __construct(ServiceMap $symfonyServiceMap)
 	{
@@ -27,50 +31,85 @@ public function getNodeType(): string
 		return MethodCall::class;
 	}
 
-	/**
-	 * @param \PhpParser\Node $node
-	 * @param \PHPStan\Analyser\Scope $scope
-	 * @return (string|\PHPStan\Rules\RuleError)[] errors
-	 */
 	public function processNode(Node $node, Scope $scope): array
 	{
-		if (!$node instanceof MethodCall) {
-			throw new ShouldNotHappenException();
-		}
-
 		if (!$node->name instanceof Node\Identifier) {
 			return [];
 		}
 
-		if ($node->name->name !== 'get' || !isset($node->args[0])) {
+		if ($node->name->name !== 'get' || !isset($node->getArgs()[0])) {
 			return [];
 		}
 
 		$argType = $scope->getType($node->var);
 
-		$isTestContainerType = (new ObjectType('Symfony\Bundle\FrameworkBundle\Test\TestContainer'))->isSuperTypeOf($argType);
+		$isTestContainer = $this->isTestContainer($argType, $scope);
 		$isOldServiceSubscriber = (new ObjectType('Symfony\Component\DependencyInjection\ServiceSubscriberInterface'))->isSuperTypeOf($argType);
-		$isServiceSubscriber = (new ObjectType('Symfony\Contracts\Service\ServiceSubscriberInterface'))->isSuperTypeOf($argType);
-		if ($isTestContainerType->yes() || $isOldServiceSubscriber->yes() || $isServiceSubscriber->yes()) {
+		$isServiceSubscriber = $this->isServiceSubscriber($argType, $scope);
+		$isServiceLocator = (new ObjectType('Symfony\Component\DependencyInjection\ServiceLocator'))->isSuperTypeOf($argType);
+		if ($isTestContainer->yes() || $isOldServiceSubscriber->yes() || $isServiceSubscriber->yes() || $isServiceLocator->yes()) {
 			return [];
 		}
 
 		$isControllerType = (new ObjectType('Symfony\Bundle\FrameworkBundle\Controller\Controller'))->isSuperTypeOf($argType);
 		$isAbstractControllerType = (new ObjectType('Symfony\Bundle\FrameworkBundle\Controller\AbstractController'))->isSuperTypeOf($argType);
 		$isContainerType = (new ObjectType('Symfony\Component\DependencyInjection\ContainerInterface'))->isSuperTypeOf($argType);
-		if (!$isControllerType->yes() && !$isAbstractControllerType->yes() && !$isContainerType->yes()) {
+		$isPsrContainerType = (new ObjectType('Psr\Container\ContainerInterface'))->isSuperTypeOf($argType);
+		if (
+			!$isControllerType->yes()
+			&& !$isAbstractControllerType->yes()
+			&& !$isContainerType->yes()
+			&& !$isPsrContainerType->yes()
+		) {
 			return [];
 		}
 
-		$serviceId = ServiceMap::getServiceIdFromNode($node->args[0]->value, $scope);
+		$serviceId = $this->serviceMap::getServiceIdFromNode($node->getArgs()[0]->value, $scope);
 		if ($serviceId !== null) {
 			$service = $this->serviceMap->getService($serviceId);
 			if ($service !== null && !$service->isPublic()) {
-				return [sprintf('Service "%s" is private.', $serviceId)];
+				return [
+					RuleErrorBuilder::message(sprintf('Service "%s" is private.', $serviceId))
+						->identifier('symfonyContainer.privateService')
+						->build(),
+				];
 			}
 		}
 
 		return [];
 	}
 
+	private function isServiceSubscriber(Type $containerType, Scope $scope): TrinaryLogic
+	{
+		$serviceSubscriberInterfaceType = new ObjectType('Symfony\Contracts\Service\ServiceSubscriberInterface');
+		$isContainerServiceSubscriber = $serviceSubscriberInterfaceType->isSuperTypeOf($containerType)->result;
+		$classReflection = $scope->getClassReflection();
+		if ($classReflection === null) {
+			return $isContainerServiceSubscriber;
+		}
+		$containedClassType = new ObjectType($classReflection->getName());
+		return $isContainerServiceSubscriber->or($serviceSubscriberInterfaceType->isSuperTypeOf($containedClassType)->result);
+	}
+
+	private function isTestContainer(Type $containerType, Scope $scope): TrinaryLogic
+	{
+		$testContainer = new ObjectType('Symfony\Bundle\FrameworkBundle\Test\TestContainer');
+		$isTestContainer = $testContainer->isSuperTypeOf($containerType)->result;
+
+		$classReflection = $scope->getClassReflection();
+		if ($classReflection === null) {
+			return $isTestContainer;
+		}
+
+		$containerInterface = new ObjectType('Symfony\Component\DependencyInjection\ContainerInterface');
+		$kernelTestCase = new ObjectType('Symfony\Bundle\FrameworkBundle\Test\KernelTestCase');
+		$containedClassType = new ObjectType($classReflection->getName());
+
+		return $isTestContainer->or(
+			$containerInterface->isSuperTypeOf($containerType)->result->and(
+				$kernelTestCase->isSuperTypeOf($containedClassType)->result,
+			),
+		);
+	}
+
 }
diff --git a/src/Rules/Symfony/ContainerInterfaceUnknownServiceRule.php b/src/Rules/Symfony/ContainerInterfaceUnknownServiceRule.php
index 198b931a..23444b6b 100644
--- a/src/Rules/Symfony/ContainerInterfaceUnknownServiceRule.php
+++ b/src/Rules/Symfony/ContainerInterfaceUnknownServiceRule.php
@@ -4,24 +4,26 @@
 
 use PhpParser\Node;
 use PhpParser\Node\Expr\MethodCall;
-use PhpParser\PrettyPrinter\Standard;
 use PHPStan\Analyser\Scope;
+use PHPStan\Node\Printer\Printer;
 use PHPStan\Rules\Rule;
-use PHPStan\ShouldNotHappenException;
+use PHPStan\Rules\RuleErrorBuilder;
 use PHPStan\Symfony\ServiceMap;
 use PHPStan\Type\ObjectType;
 use PHPStan\Type\Symfony\Helper;
+use function sprintf;
 
+/**
+ * @implements Rule<MethodCall>
+ */
 final class ContainerInterfaceUnknownServiceRule implements Rule
 {
 
-	/** @var ServiceMap */
-	private $serviceMap;
+	private ServiceMap $serviceMap;
 
-	/** @var \PhpParser\PrettyPrinter\Standard */
-	private $printer;
+	private Printer $printer;
 
-	public function __construct(ServiceMap $symfonyServiceMap, Standard $printer)
+	public function __construct(ServiceMap $symfonyServiceMap, Printer $printer)
 	{
 		$this->serviceMap = $symfonyServiceMap;
 		$this->printer = $printer;
@@ -32,38 +34,45 @@ public function getNodeType(): string
 		return MethodCall::class;
 	}
 
-	/**
-	 * @param \PhpParser\Node $node
-	 * @param \PHPStan\Analyser\Scope $scope
-	 * @return (string|\PHPStan\Rules\RuleError)[] errors
-	 */
 	public function processNode(Node $node, Scope $scope): array
 	{
-		if (!$node instanceof MethodCall) {
-			throw new ShouldNotHappenException();
-		}
-
 		if (!$node->name instanceof Node\Identifier) {
 			return [];
 		}
 
-		if ($node->name->name !== 'get' || !isset($node->args[0])) {
+		if ($node->name->name !== 'get' || !isset($node->getArgs()[0])) {
 			return [];
 		}
 
 		$argType = $scope->getType($node->var);
+		$isContainerBagType = (new ObjectType('Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface'))->isSuperTypeOf($argType);
+		if ($isContainerBagType->yes()) {
+			return [];
+		}
+
 		$isControllerType = (new ObjectType('Symfony\Bundle\FrameworkBundle\Controller\Controller'))->isSuperTypeOf($argType);
 		$isAbstractControllerType = (new ObjectType('Symfony\Bundle\FrameworkBundle\Controller\AbstractController'))->isSuperTypeOf($argType);
 		$isContainerType = (new ObjectType('Symfony\Component\DependencyInjection\ContainerInterface'))->isSuperTypeOf($argType);
-		if (!$isControllerType->yes() && !$isAbstractControllerType->yes() && !$isContainerType->yes()) {
+		$isPsrContainerType = (new ObjectType('Psr\Container\ContainerInterface'))->isSuperTypeOf($argType);
+		if (
+			!$isControllerType->yes()
+			&& !$isAbstractControllerType->yes()
+			&& !$isContainerType->yes()
+			&& !$isPsrContainerType->yes()
+		) {
 			return [];
 		}
 
-		$serviceId = ServiceMap::getServiceIdFromNode($node->args[0]->value, $scope);
+		$serviceId = $this->serviceMap::getServiceIdFromNode($node->getArgs()[0]->value, $scope);
 		if ($serviceId !== null) {
 			$service = $this->serviceMap->getService($serviceId);
-			if ($service === null && !$scope->isSpecified(Helper::createMarkerNode($node->var, $scope->getType($node->args[0]->value), $this->printer))) {
-				return [sprintf('Service "%s" is not registered in the container.', $serviceId)];
+			$serviceIdType = $scope->getType($node->getArgs()[0]->value);
+			if ($service === null && !$scope->getType(Helper::createMarkerNode($node->var, $serviceIdType, $this->printer))->equals($serviceIdType)) {
+				return [
+					RuleErrorBuilder::message(sprintf('Service "%s" is not registered in the container.', $serviceId))
+						->identifier('symfonyContainer.serviceNotFound')
+						->build(),
+				];
 			}
 		}
 
diff --git a/src/Rules/Symfony/InvalidArgumentDefaultValueRule.php b/src/Rules/Symfony/InvalidArgumentDefaultValueRule.php
new file mode 100644
index 00000000..5435d4c9
--- /dev/null
+++ b/src/Rules/Symfony/InvalidArgumentDefaultValueRule.php
@@ -0,0 +1,82 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Rules\Symfony;
+
+use PhpParser\Node;
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Rules\Rule;
+use PHPStan\Rules\RuleErrorBuilder;
+use PHPStan\Type\ArrayType;
+use PHPStan\Type\Constant\ConstantIntegerType;
+use PHPStan\Type\IntegerType;
+use PHPStan\Type\NullType;
+use PHPStan\Type\ObjectType;
+use PHPStan\Type\StringType;
+use PHPStan\Type\UnionType;
+use PHPStan\Type\VerbosityLevel;
+use function count;
+use function sprintf;
+
+/**
+ * @implements Rule<MethodCall>
+ */
+final class InvalidArgumentDefaultValueRule implements Rule
+{
+
+	public function getNodeType(): string
+	{
+		return MethodCall::class;
+	}
+
+	public function processNode(Node $node, Scope $scope): array
+	{
+		if (!(new ObjectType('Symfony\Component\Console\Command\Command'))->isSuperTypeOf($scope->getType($node->var))->yes()) {
+			return [];
+		}
+		if (!$node->name instanceof Node\Identifier || $node->name->name !== 'addArgument') {
+			return [];
+		}
+		if (!isset($node->getArgs()[3])) {
+			return [];
+		}
+
+		$modeType = isset($node->getArgs()[1]) ? $scope->getType($node->getArgs()[1]->value) : new NullType();
+		if ($modeType->isNull()->yes()) {
+			$modeType = new ConstantIntegerType(2); // InputArgument::OPTIONAL
+		}
+		$modeTypes = $modeType->getConstantScalarTypes();
+		if (count($modeTypes) !== 1) {
+			return [];
+		}
+		if (!$modeTypes[0] instanceof ConstantIntegerType) {
+			return [];
+		}
+		$mode = $modeTypes[0]->getValue();
+
+		$defaultType = $scope->getType($node->getArgs()[3]->value);
+
+		// not an array
+		if (($mode & 4) !== 4 && !(new UnionType([new StringType(), new NullType()]))->isSuperTypeOf($defaultType)->yes()) {
+			return [
+				RuleErrorBuilder::message(sprintf(
+					'Parameter #4 $default of method Symfony\Component\Console\Command\Command::addArgument() expects string|null, %s given.',
+					$defaultType->describe(VerbosityLevel::typeOnly()),
+				))->identifier('argument.type')->build(),
+			];
+		}
+
+		// is array
+		if (($mode & 4) === 4 && !(new UnionType([new ArrayType(new IntegerType(), new StringType()), new NullType()]))->isSuperTypeOf($defaultType)->yes()) {
+			return [
+				RuleErrorBuilder::message(sprintf(
+					'Parameter #4 $default of method Symfony\Component\Console\Command\Command::addArgument() expects array<int, string>|null, %s given.',
+					$defaultType->describe(VerbosityLevel::typeOnly()),
+				))->identifier('argument.type')->build(),
+			];
+		}
+
+		return [];
+	}
+
+}
diff --git a/src/Rules/Symfony/InvalidOptionDefaultValueRule.php b/src/Rules/Symfony/InvalidOptionDefaultValueRule.php
new file mode 100644
index 00000000..2e3dc0e9
--- /dev/null
+++ b/src/Rules/Symfony/InvalidOptionDefaultValueRule.php
@@ -0,0 +1,88 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Rules\Symfony;
+
+use PhpParser\Node;
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Rules\Rule;
+use PHPStan\Rules\RuleErrorBuilder;
+use PHPStan\Type\ArrayType;
+use PHPStan\Type\BooleanType;
+use PHPStan\Type\Constant\ConstantIntegerType;
+use PHPStan\Type\IntegerType;
+use PHPStan\Type\MixedType;
+use PHPStan\Type\NullType;
+use PHPStan\Type\ObjectType;
+use PHPStan\Type\StringType;
+use PHPStan\Type\UnionType;
+use PHPStan\Type\VerbosityLevel;
+use function count;
+use function sprintf;
+
+/**
+ * @implements Rule<MethodCall>
+ */
+final class InvalidOptionDefaultValueRule implements Rule
+{
+
+	public function getNodeType(): string
+	{
+		return MethodCall::class;
+	}
+
+	public function processNode(Node $node, Scope $scope): array
+	{
+		if (!(new ObjectType('Symfony\Component\Console\Command\Command'))->isSuperTypeOf($scope->getType($node->var))->yes()) {
+			return [];
+		}
+		if (!$node->name instanceof Node\Identifier || $node->name->name !== 'addOption') {
+			return [];
+		}
+		if (!isset($node->getArgs()[4])) {
+			return [];
+		}
+
+		$modeType = isset($node->getArgs()[2]) ? $scope->getType($node->getArgs()[2]->value) : new NullType();
+		if ($modeType->isNull()->yes()) {
+			$modeType = new ConstantIntegerType(1); // InputOption::VALUE_NONE
+		}
+		$modeTypes = $modeType->getConstantScalarTypes();
+		if (count($modeTypes) !== 1) {
+			return [];
+		}
+		if (!$modeTypes[0] instanceof ConstantIntegerType) {
+			return [];
+		}
+		$mode = $modeTypes[0]->getValue();
+
+		$defaultType = $scope->getType($node->getArgs()[4]->value);
+
+		// not an array
+		if (($mode & 8) !== 8) {
+			$checkType = new UnionType([new StringType(), new IntegerType(), new NullType(), new BooleanType()]);
+			if (!$checkType->isSuperTypeOf($defaultType)->yes()) {
+				return [
+					RuleErrorBuilder::message(sprintf(
+						'Parameter #5 $default of method Symfony\Component\Console\Command\Command::addOption() expects %s, %s given.',
+						$checkType->describe(VerbosityLevel::typeOnly()),
+						$defaultType->describe(VerbosityLevel::typeOnly()),
+					))->identifier('argument.type')->build(),
+				];
+			}
+		}
+
+		// is array
+		if (($mode & 8) === 8 && !(new UnionType([new ArrayType(new MixedType(), new StringType()), new NullType()]))->isSuperTypeOf($defaultType)->yes()) {
+			return [
+				RuleErrorBuilder::message(sprintf(
+					'Parameter #5 $default of method Symfony\Component\Console\Command\Command::addOption() expects array<string>|null, %s given.',
+					$defaultType->describe(VerbosityLevel::typeOnly()),
+				))->identifier('argument.type')->build(),
+			];
+		}
+
+		return [];
+	}
+
+}
diff --git a/src/Rules/Symfony/UndefinedArgumentRule.php b/src/Rules/Symfony/UndefinedArgumentRule.php
new file mode 100644
index 00000000..ee36a23c
--- /dev/null
+++ b/src/Rules/Symfony/UndefinedArgumentRule.php
@@ -0,0 +1,84 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Rules\Symfony;
+
+use InvalidArgumentException;
+use PhpParser\Node;
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Node\Printer\Printer;
+use PHPStan\Rules\Rule;
+use PHPStan\Rules\RuleErrorBuilder;
+use PHPStan\Symfony\ConsoleApplicationResolver;
+use PHPStan\Type\ObjectType;
+use PHPStan\Type\Symfony\Helper;
+use function count;
+use function sprintf;
+
+/**
+ * @implements Rule<MethodCall>
+ */
+final class UndefinedArgumentRule implements Rule
+{
+
+	private ConsoleApplicationResolver $consoleApplicationResolver;
+
+	private Printer $printer;
+
+	public function __construct(ConsoleApplicationResolver $consoleApplicationResolver, Printer $printer)
+	{
+		$this->consoleApplicationResolver = $consoleApplicationResolver;
+		$this->printer = $printer;
+	}
+
+	public function getNodeType(): string
+	{
+		return MethodCall::class;
+	}
+
+	public function processNode(Node $node, Scope $scope): array
+	{
+		$classReflection = $scope->getClassReflection();
+		if ($classReflection === null) {
+			return [];
+		}
+
+		if (!(new ObjectType('Symfony\Component\Console\Command\Command'))->isSuperTypeOf(new ObjectType($classReflection->getName()))->yes()) {
+			return [];
+		}
+		if (!(new ObjectType('Symfony\Component\Console\Input\InputInterface'))->isSuperTypeOf($scope->getType($node->var))->yes()) {
+			return [];
+		}
+		if (!$node->name instanceof Node\Identifier || $node->name->name !== 'getArgument') {
+			return [];
+		}
+		if (!isset($node->getArgs()[0])) {
+			return [];
+		}
+
+		$argType = $scope->getType($node->getArgs()[0]->value);
+		$argStrings = $argType->getConstantStrings();
+		if (count($argStrings) !== 1) {
+			return [];
+		}
+		$argName = $argStrings[0]->getValue();
+
+		$errors = [];
+		foreach ($this->consoleApplicationResolver->findCommands($classReflection) as $name => $command) {
+			try {
+				$command->mergeApplicationDefinition();
+				$command->getDefinition()->getArgument($argName);
+			} catch (InvalidArgumentException $e) {
+				if ($scope->getType(Helper::createMarkerNode($node->var, $argType, $this->printer))->equals($argType)) {
+					continue;
+				}
+				$errors[] = RuleErrorBuilder::message(sprintf('Command "%s" does not define argument "%s".', $name, $argName))
+					->identifier('symfonyConsole.argumentNotFound')
+					->build();
+			}
+		}
+
+		return $errors;
+	}
+
+}
diff --git a/src/Rules/Symfony/UndefinedOptionRule.php b/src/Rules/Symfony/UndefinedOptionRule.php
new file mode 100644
index 00000000..39a6a4ac
--- /dev/null
+++ b/src/Rules/Symfony/UndefinedOptionRule.php
@@ -0,0 +1,84 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Rules\Symfony;
+
+use InvalidArgumentException;
+use PhpParser\Node;
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Node\Printer\Printer;
+use PHPStan\Rules\Rule;
+use PHPStan\Rules\RuleErrorBuilder;
+use PHPStan\Symfony\ConsoleApplicationResolver;
+use PHPStan\Type\ObjectType;
+use PHPStan\Type\Symfony\Helper;
+use function count;
+use function sprintf;
+
+/**
+ * @implements Rule<MethodCall>
+ */
+final class UndefinedOptionRule implements Rule
+{
+
+	private ConsoleApplicationResolver $consoleApplicationResolver;
+
+	private Printer $printer;
+
+	public function __construct(ConsoleApplicationResolver $consoleApplicationResolver, Printer $printer)
+	{
+		$this->consoleApplicationResolver = $consoleApplicationResolver;
+		$this->printer = $printer;
+	}
+
+	public function getNodeType(): string
+	{
+		return MethodCall::class;
+	}
+
+	public function processNode(Node $node, Scope $scope): array
+	{
+		$classReflection = $scope->getClassReflection();
+		if ($classReflection === null) {
+			return [];
+		}
+
+		if (!(new ObjectType('Symfony\Component\Console\Command\Command'))->isSuperTypeOf(new ObjectType($classReflection->getName()))->yes()) {
+			return [];
+		}
+		if (!(new ObjectType('Symfony\Component\Console\Input\InputInterface'))->isSuperTypeOf($scope->getType($node->var))->yes()) {
+			return [];
+		}
+		if (!$node->name instanceof Node\Identifier || $node->name->name !== 'getOption') {
+			return [];
+		}
+		if (!isset($node->getArgs()[0])) {
+			return [];
+		}
+
+		$optType = $scope->getType($node->getArgs()[0]->value);
+		$optStrings = $optType->getConstantStrings();
+		if (count($optStrings) !== 1) {
+			return [];
+		}
+		$optName = $optStrings[0]->getValue();
+
+		$errors = [];
+		foreach ($this->consoleApplicationResolver->findCommands($classReflection) as $name => $command) {
+			try {
+				$command->mergeApplicationDefinition();
+				$command->getDefinition()->getOption($optName);
+			} catch (InvalidArgumentException $e) {
+				if ($scope->getType(Helper::createMarkerNode($node->var, $optType, $this->printer))->equals($optType)) {
+					continue;
+				}
+				$errors[] = RuleErrorBuilder::message(sprintf('Command "%s" does not define option "%s".', $name, $optName))
+					->identifier('symfonyConsole.optionNotFound')
+					->build();
+			}
+		}
+
+		return $errors;
+	}
+
+}
diff --git a/src/Symfony/ConsoleApplicationResolver.php b/src/Symfony/ConsoleApplicationResolver.php
new file mode 100644
index 00000000..13b24d26
--- /dev/null
+++ b/src/Symfony/ConsoleApplicationResolver.php
@@ -0,0 +1,90 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+use PHPStan\Reflection\ClassReflection;
+use PHPStan\ShouldNotHappenException;
+use PHPStan\Type\ObjectType;
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Command\Command;
+use function file_exists;
+use function get_class;
+use function is_readable;
+use function method_exists;
+use function sprintf;
+
+final class ConsoleApplicationResolver
+{
+
+	private ?string $consoleApplicationLoader = null;
+
+	private ?Application $consoleApplication = null;
+
+	public function __construct(?string $consoleApplicationLoader)
+	{
+		$this->consoleApplicationLoader = $consoleApplicationLoader;
+	}
+
+	public function hasConsoleApplicationLoader(): bool
+	{
+		return $this->consoleApplicationLoader !== null;
+	}
+
+	private function getConsoleApplication(): ?Application
+	{
+		if ($this->consoleApplicationLoader === null) {
+			return null;
+		}
+
+		if ($this->consoleApplication !== null) {
+			return $this->consoleApplication;
+		}
+
+		if (!file_exists($this->consoleApplicationLoader)
+			|| !is_readable($this->consoleApplicationLoader)
+		) {
+			throw new ShouldNotHappenException(sprintf('Cannot load console application. Check the parameters.symfony.consoleApplicationLoader setting in PHPStan\'s config. The offending value is "%s".', $this->consoleApplicationLoader));
+		}
+
+		return $this->consoleApplication = require $this->consoleApplicationLoader;
+	}
+
+	/**
+	 * @return Command[]
+	 */
+	public function findCommands(ClassReflection $classReflection): array
+	{
+		$consoleApplication = $this->getConsoleApplication();
+		if ($consoleApplication === null) {
+			return [];
+		}
+
+		$classType = new ObjectType($classReflection->getName());
+		if (!(new ObjectType('Symfony\Component\Console\Command\Command'))->isSuperTypeOf($classType)->yes()) {
+			return [];
+		}
+
+		$commands = [];
+		foreach ($consoleApplication->all() as $name => $command) {
+			$commandClass = new ObjectType(get_class($command));
+			$isLazyCommand = (new ObjectType('Symfony\Component\Console\Command\LazyCommand'))->isSuperTypeOf($commandClass)->yes();
+
+			if ($isLazyCommand && method_exists($command, 'getCommand')) {
+				/** @var Command $wrappedCommand */
+				$wrappedCommand = $command->getCommand();
+				if (!$classType->isSuperTypeOf(new ObjectType(get_class($wrappedCommand)))->yes()) {
+					continue;
+				}
+			}
+
+			if (!$isLazyCommand && !$classType->isSuperTypeOf($commandClass)->yes()) {
+				continue;
+			}
+
+			$commands[$name] = $command;
+		}
+
+		return $commands;
+	}
+
+}
diff --git a/src/Symfony/DefaultParameterMap.php b/src/Symfony/DefaultParameterMap.php
new file mode 100644
index 00000000..3149fd7d
--- /dev/null
+++ b/src/Symfony/DefaultParameterMap.php
@@ -0,0 +1,44 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+use PhpParser\Node\Expr;
+use PHPStan\Analyser\Scope;
+use PHPStan\Type\Type;
+use function array_map;
+
+final class DefaultParameterMap implements ParameterMap
+{
+
+	/** @var ParameterDefinition[] */
+	private array $parameters;
+
+	/**
+	 * @param ParameterDefinition[] $parameters
+	 */
+	public function __construct(array $parameters)
+	{
+		$this->parameters = $parameters;
+	}
+
+	/**
+	 * @return ParameterDefinition[]
+	 */
+	public function getParameters(): array
+	{
+		return $this->parameters;
+	}
+
+	public function getParameter(string $key): ?ParameterDefinition
+	{
+		return $this->parameters[$key] ?? null;
+	}
+
+	public static function getParameterKeysFromNode(Expr $node, Scope $scope): array
+	{
+		$strings = $scope->getType($node)->getConstantStrings();
+
+		return array_map(static fn (Type $type) => $type->getValue(), $strings);
+	}
+
+}
diff --git a/src/Symfony/DefaultServiceMap.php b/src/Symfony/DefaultServiceMap.php
new file mode 100644
index 00000000..5d3bccd0
--- /dev/null
+++ b/src/Symfony/DefaultServiceMap.php
@@ -0,0 +1,42 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+use PhpParser\Node\Expr;
+use PHPStan\Analyser\Scope;
+use function count;
+
+final class DefaultServiceMap implements ServiceMap
+{
+
+	/** @var ServiceDefinition[] */
+	private array $services;
+
+	/**
+	 * @param ServiceDefinition[] $services
+	 */
+	public function __construct(array $services)
+	{
+		$this->services = $services;
+	}
+
+	/**
+	 * @return ServiceDefinition[]
+	 */
+	public function getServices(): array
+	{
+		return $this->services;
+	}
+
+	public function getService(string $id): ?ServiceDefinition
+	{
+		return $this->services[$id] ?? null;
+	}
+
+	public static function getServiceIdFromNode(Expr $node, Scope $scope): ?string
+	{
+		$strings = $scope->getType($node)->getConstantStrings();
+		return count($strings) === 1 ? $strings[0]->getValue() : null;
+	}
+
+}
diff --git a/src/Symfony/FakeParameterMap.php b/src/Symfony/FakeParameterMap.php
new file mode 100644
index 00000000..53acdc3c
--- /dev/null
+++ b/src/Symfony/FakeParameterMap.php
@@ -0,0 +1,29 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+use PhpParser\Node\Expr;
+use PHPStan\Analyser\Scope;
+
+final class FakeParameterMap implements ParameterMap
+{
+
+	/**
+	 * @return ParameterDefinition[]
+	 */
+	public function getParameters(): array
+	{
+		return [];
+	}
+
+	public function getParameter(string $key): ?ParameterDefinition
+	{
+		return null;
+	}
+
+	public static function getParameterKeysFromNode(Expr $node, Scope $scope): array
+	{
+		return [];
+	}
+
+}
diff --git a/src/Symfony/FakeServiceMap.php b/src/Symfony/FakeServiceMap.php
new file mode 100644
index 00000000..88f9edac
--- /dev/null
+++ b/src/Symfony/FakeServiceMap.php
@@ -0,0 +1,29 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+use PhpParser\Node\Expr;
+use PHPStan\Analyser\Scope;
+
+final class FakeServiceMap implements ServiceMap
+{
+
+	/**
+	 * @return ServiceDefinition[]
+	 */
+	public function getServices(): array
+	{
+		return [];
+	}
+
+	public function getService(string $id): ?ServiceDefinition
+	{
+		return null;
+	}
+
+	public static function getServiceIdFromNode(Expr $node, Scope $scope): ?string
+	{
+		return null;
+	}
+
+}
diff --git a/src/Symfony/InputBagStubFilesExtension.php b/src/Symfony/InputBagStubFilesExtension.php
new file mode 100644
index 00000000..6ce36f4b
--- /dev/null
+++ b/src/Symfony/InputBagStubFilesExtension.php
@@ -0,0 +1,35 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound;
+use PHPStan\BetterReflection\Reflector\Reflector;
+use PHPStan\PhpDoc\StubFilesExtension;
+
+class InputBagStubFilesExtension implements StubFilesExtension
+{
+
+	private Reflector $reflector;
+
+	public function __construct(
+		Reflector $reflector
+	)
+	{
+		$this->reflector = $reflector;
+	}
+
+	public function getFiles(): array
+	{
+		try {
+			$this->reflector->reflectClass('Symfony\Component\HttpFoundation\InputBag');
+		} catch (IdentifierNotFound $e) {
+			return [];
+		}
+
+		return [
+			__DIR__ . '/../../stubs/Symfony/Component/HttpFoundation/InputBag.stub',
+			__DIR__ . '/../../stubs/Symfony/Component/HttpFoundation/Request.stub',
+		];
+	}
+
+}
diff --git a/src/Symfony/MessageMap.php b/src/Symfony/MessageMap.php
new file mode 100644
index 00000000..97bb8734
--- /dev/null
+++ b/src/Symfony/MessageMap.php
@@ -0,0 +1,24 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+use PHPStan\Type\Type;
+
+final class MessageMap
+{
+
+	/** @var array<string, Type> */
+	private array $messageMap;
+
+	/** @param array<string, Type> $messageMap */
+	public function __construct(array $messageMap)
+	{
+		$this->messageMap = $messageMap;
+	}
+
+	public function getTypeForClass(string $class): ?Type
+	{
+		return $this->messageMap[$class] ?? null;
+	}
+
+}
diff --git a/src/Symfony/MessageMapFactory.php b/src/Symfony/MessageMapFactory.php
new file mode 100644
index 00000000..3d7663ca
--- /dev/null
+++ b/src/Symfony/MessageMapFactory.php
@@ -0,0 +1,152 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+use PHPStan\Reflection\ClassReflection;
+use PHPStan\Reflection\ReflectionProvider;
+use Symfony\Component\Messenger\Handler\MessageSubscriberInterface;
+use function class_exists;
+use function count;
+use function is_array;
+use function is_int;
+use function is_string;
+
+final class MessageMapFactory
+{
+
+	private const MESSENGER_HANDLER_TAG = 'messenger.message_handler';
+	private const DEFAULT_HANDLER_METHOD = '__invoke';
+
+	private ReflectionProvider $reflectionProvider;
+
+	private ServiceMap $serviceMap;
+
+	public function __construct(ServiceMap $symfonyServiceMap, ReflectionProvider $reflectionProvider)
+	{
+		$this->serviceMap = $symfonyServiceMap;
+		$this->reflectionProvider = $reflectionProvider;
+	}
+
+	public function create(): MessageMap
+	{
+		$returnTypesMap = [];
+
+		foreach ($this->serviceMap->getServices() as $service) {
+			$serviceClass = $service->getClass();
+
+			if ($serviceClass === null) {
+				continue;
+			}
+
+			foreach ($service->getTags() as $tag) {
+				if ($tag->getName() !== self::MESSENGER_HANDLER_TAG) {
+					continue;
+				}
+
+				if (!$this->reflectionProvider->hasClass($serviceClass)) {
+					continue;
+				}
+
+				$reflectionClass = $this->reflectionProvider->getClass($serviceClass);
+
+				/** @var array{handles?: class-string, method?: string} $tagAttributes */
+				$tagAttributes = $tag->getAttributes();
+
+				if (isset($tagAttributes['handles'])) {
+					$handles = [$tagAttributes['handles'] => ['method' => $tagAttributes['method'] ?? self::DEFAULT_HANDLER_METHOD]];
+				} else {
+					$handles = $this->guessHandledMessages($reflectionClass);
+				}
+
+				foreach ($handles as $messageClassName => $options) {
+					$methodName = $options['method'] ?? self::DEFAULT_HANDLER_METHOD;
+
+					if (!$reflectionClass->hasNativeMethod($methodName)) {
+						continue;
+					}
+
+					$methodReflection = $reflectionClass->getNativeMethod($methodName);
+
+					foreach ($methodReflection->getVariants() as $variant) {
+						$returnTypesMap[$messageClassName][] = $variant->getReturnType();
+					}
+				}
+			}
+		}
+
+		$messageMap = [];
+		foreach ($returnTypesMap as $messageClassName => $returnTypes) {
+			if (count($returnTypes) !== 1) {
+				continue;
+			}
+
+			$messageMap[$messageClassName] = $returnTypes[0];
+		}
+
+		return new MessageMap($messageMap);
+	}
+
+	/** @return iterable<string, array<string, string>> */
+	private function guessHandledMessages(ClassReflection $reflectionClass): iterable
+	{
+		if ($reflectionClass->implementsInterface(MessageSubscriberInterface::class)) {
+			$className = $reflectionClass->getName();
+
+			foreach ($className::getHandledMessages() as $index => $value) {
+				$containOptions = self::containOptions($index, $value);
+				if ($containOptions === true) {
+					yield $index => $value;
+				} elseif ($containOptions === false) {
+					yield $value => ['method' => self::DEFAULT_HANDLER_METHOD];
+				}
+			}
+
+			return;
+		}
+
+		if (!$reflectionClass->hasNativeMethod(self::DEFAULT_HANDLER_METHOD)) {
+			return;
+		}
+
+		$methodReflection = $reflectionClass->getNativeMethod(self::DEFAULT_HANDLER_METHOD);
+
+		$variants = $methodReflection->getVariants();
+		if (count($variants) !== 1) {
+			return;
+		}
+
+		$parameters = $variants[0]->getParameters();
+
+		if (count($parameters) !== 1) {
+			return;
+		}
+
+		$classNames = $parameters[0]->getType()->getObjectClassNames();
+
+		if (count($classNames) !== 1) {
+			return;
+		}
+
+		yield $classNames[0] => ['method' => self::DEFAULT_HANDLER_METHOD];
+	}
+
+	/**
+	 * @param mixed $index
+	 * @param mixed $value
+	 * @phpstan-assert-if-true =class-string $index
+	 * @phpstan-assert-if-true =array<string, mixed> $value
+	 * @phpstan-assert-if-false =int $index
+	 * @phpstan-assert-if-false =class-string $value
+	 */
+	private static function containOptions($index, $value): ?bool
+	{
+		if (is_string($index) && class_exists($index) && is_array($value)) {
+			return true;
+		} elseif (is_int($index) && is_string($value) && class_exists($value)) {
+			return false;
+		}
+
+		return null;
+	}
+
+}
diff --git a/src/Symfony/Parameter.php b/src/Symfony/Parameter.php
new file mode 100644
index 00000000..53b53265
--- /dev/null
+++ b/src/Symfony/Parameter.php
@@ -0,0 +1,38 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+final class Parameter implements ParameterDefinition
+{
+
+	private string $key;
+
+	/** @var array<mixed>|bool|float|int|string */
+	private $value;
+
+	/**
+	 * @param array<mixed>|bool|float|int|string $value
+	 */
+	public function __construct(
+		string $key,
+		$value
+	)
+	{
+		$this->key = $key;
+		$this->value = $value;
+	}
+
+	public function getKey(): string
+	{
+		return $this->key;
+	}
+
+	/**
+	 * @return array<mixed>|bool|float|int|string
+	 */
+	public function getValue()
+	{
+		return $this->value;
+	}
+
+}
diff --git a/src/Symfony/ParameterDefinition.php b/src/Symfony/ParameterDefinition.php
new file mode 100644
index 00000000..1da7723b
--- /dev/null
+++ b/src/Symfony/ParameterDefinition.php
@@ -0,0 +1,18 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+/**
+ * @api
+ */
+interface ParameterDefinition
+{
+
+	public function getKey(): string;
+
+	/**
+	 * @return array<mixed>|bool|float|int|string
+	 */
+	public function getValue();
+
+}
diff --git a/src/Symfony/ParameterMap.php b/src/Symfony/ParameterMap.php
new file mode 100644
index 00000000..0c551635
--- /dev/null
+++ b/src/Symfony/ParameterMap.php
@@ -0,0 +1,26 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+use PhpParser\Node\Expr;
+use PHPStan\Analyser\Scope;
+
+/**
+ * @api
+ */
+interface ParameterMap
+{
+
+	/**
+	 * @return ParameterDefinition[]
+	 */
+	public function getParameters(): array;
+
+	public function getParameter(string $key): ?ParameterDefinition;
+
+	/**
+	 * @return array<string>
+	 */
+	public static function getParameterKeysFromNode(Expr $node, Scope $scope): array;
+
+}
diff --git a/src/Symfony/ParameterMapFactory.php b/src/Symfony/ParameterMapFactory.php
new file mode 100644
index 00000000..4e318540
--- /dev/null
+++ b/src/Symfony/ParameterMapFactory.php
@@ -0,0 +1,10 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+interface ParameterMapFactory
+{
+
+	public function create(): ParameterMap;
+
+}
diff --git a/src/Symfony/RequiredAutowiringExtension.php b/src/Symfony/RequiredAutowiringExtension.php
new file mode 100644
index 00000000..7d8d195d
--- /dev/null
+++ b/src/Symfony/RequiredAutowiringExtension.php
@@ -0,0 +1,90 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+use PHPStan\Reflection\AdditionalConstructorsExtension;
+use PHPStan\Reflection\ClassReflection;
+use PHPStan\Reflection\Php\PhpPropertyReflection;
+use PHPStan\Reflection\PropertyReflection;
+use PHPStan\Rules\Properties\ReadWritePropertiesExtension;
+use PHPStan\Type\FileTypeMapper;
+use function count;
+
+class RequiredAutowiringExtension implements ReadWritePropertiesExtension, AdditionalConstructorsExtension
+{
+
+	private FileTypeMapper $fileTypeMapper;
+
+	public function __construct(FileTypeMapper $fileTypeMapper)
+	{
+		$this->fileTypeMapper = $fileTypeMapper;
+	}
+
+	public function isAlwaysRead(PropertyReflection $property, string $propertyName): bool
+	{
+		return false;
+	}
+
+	public function isAlwaysWritten(PropertyReflection $property, string $propertyName): bool
+	{
+		return false;
+	}
+
+	public function isInitialized(PropertyReflection $property, string $propertyName): bool
+	{
+		// If the property is public, check for @required on the property itself
+		if (!$property->isPublic()) {
+			return false;
+		}
+
+		if ($property->getDocComment() !== null && $this->isRequiredFromDocComment($property->getDocComment())) {
+			return true;
+		}
+
+		// Check for the attribute version
+		if ($property instanceof PhpPropertyReflection && count($property->getNativeReflection()->getAttributes('Symfony\Contracts\Service\Attribute\Required')) > 0) {
+			return true;
+		}
+
+		return false;
+	}
+
+	public function getAdditionalConstructors(ClassReflection $classReflection): array
+	{
+		$additionalConstructors = [];
+		$nativeReflection = $classReflection->getNativeReflection();
+
+		foreach ($nativeReflection->getMethods() as $method) {
+			if (!$method->isPublic()) {
+				continue;
+			}
+
+			if ($method->getDocComment() !== false && $this->isRequiredFromDocComment($method->getDocComment())) {
+				$additionalConstructors[] = $method->getName();
+			}
+
+			if (count($method->getAttributes('Symfony\Contracts\Service\Attribute\Required')) === 0) {
+				continue;
+			}
+
+			$additionalConstructors[] = $method->getName();
+		}
+
+		return $additionalConstructors;
+	}
+
+	private function isRequiredFromDocComment(string $docComment): bool
+	{
+		$phpDoc = $this->fileTypeMapper->getResolvedPhpDoc(null, null, null, null, $docComment);
+
+		foreach ($phpDoc->getPhpDocNodes() as $node) {
+			// @required tag is available, meaning this property is always initialized
+			if (count($node->getTagsByName('@required')) > 0) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+}
diff --git a/src/Symfony/Service.php b/src/Symfony/Service.php
index c31324f5..44c0d1d7 100644
--- a/src/Symfony/Service.php
+++ b/src/Symfony/Service.php
@@ -5,27 +5,27 @@
 final class Service implements ServiceDefinition
 {
 
-	/** @var string */
-	private $id;
+	private string $id;
 
-	/** @var string|null */
-	private $class;
+	private ?string $class = null;
 
-	/** @var bool */
-	private $public;
+	private bool $public;
 
-	/** @var bool */
-	private $synthetic;
+	private bool $synthetic;
 
-	/** @var string|null */
-	private $alias;
+	private ?string $alias = null;
 
+	/** @var ServiceTag[] */
+	private array $tags;
+
+	/** @param ServiceTag[] $tags */
 	public function __construct(
 		string $id,
 		?string $class,
 		bool $public,
 		bool $synthetic,
-		?string $alias
+		?string $alias,
+		array $tags = []
 	)
 	{
 		$this->id = $id;
@@ -33,6 +33,7 @@ public function __construct(
 		$this->public = $public;
 		$this->synthetic = $synthetic;
 		$this->alias = $alias;
+		$this->tags = $tags;
 	}
 
 	public function getId(): string
@@ -60,4 +61,9 @@ public function getAlias(): ?string
 		return $this->alias;
 	}
 
+	public function getTags(): array
+	{
+		return $this->tags;
+	}
+
 }
diff --git a/src/Symfony/ServiceDefinition.php b/src/Symfony/ServiceDefinition.php
index c7cdcd18..3862fa8d 100644
--- a/src/Symfony/ServiceDefinition.php
+++ b/src/Symfony/ServiceDefinition.php
@@ -2,6 +2,9 @@
 
 namespace PHPStan\Symfony;
 
+/**
+ * @api
+ */
 interface ServiceDefinition
 {
 
@@ -15,4 +18,7 @@ public function isSynthetic(): bool;
 
 	public function getAlias(): ?string;
 
+	/** @return ServiceTag[] */
+	public function getTags(): array;
+
 }
diff --git a/src/Symfony/ServiceMap.php b/src/Symfony/ServiceMap.php
index 71da7e9a..bbd2d8a3 100644
--- a/src/Symfony/ServiceMap.php
+++ b/src/Symfony/ServiceMap.php
@@ -4,40 +4,20 @@
 
 use PhpParser\Node\Expr;
 use PHPStan\Analyser\Scope;
-use PHPStan\Type\TypeUtils;
-use function count;
 
-final class ServiceMap
+/**
+ * @api
+ */
+interface ServiceMap
 {
 
-	/** @var \PHPStan\Symfony\ServiceDefinition[] */
-	private $services;
-
-	/**
-	 * @param \PHPStan\Symfony\ServiceDefinition[] $services
-	 */
-	public function __construct(array $services)
-	{
-		$this->services = $services;
-	}
-
 	/**
-	 * @return \PHPStan\Symfony\ServiceDefinition[]
+	 * @return ServiceDefinition[]
 	 */
-	public function getServices(): array
-	{
-		return $this->services;
-	}
+	public function getServices(): array;
 
-	public function getService(string $id): ?ServiceDefinition
-	{
-		return $this->services[$id] ?? null;
-	}
+	public function getService(string $id): ?ServiceDefinition;
 
-	public static function getServiceIdFromNode(Expr $node, Scope $scope): ?string
-	{
-		$strings = TypeUtils::getConstantStrings($scope->getType($node));
-		return count($strings) === 1 ? $strings[0]->getValue() : null;
-	}
+	public static function getServiceIdFromNode(Expr $node, Scope $scope): ?string;
 
 }
diff --git a/src/Symfony/ServiceTag.php b/src/Symfony/ServiceTag.php
new file mode 100644
index 00000000..3b22ee34
--- /dev/null
+++ b/src/Symfony/ServiceTag.php
@@ -0,0 +1,30 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+final class ServiceTag implements ServiceTagDefinition
+{
+
+	private string $name;
+
+	/** @var array<string, string> */
+	private array $attributes;
+
+	/** @param array<string, string> $attributes */
+	public function __construct(string $name, array $attributes = [])
+	{
+		$this->name = $name;
+		$this->attributes = $attributes;
+	}
+
+	public function getName(): string
+	{
+		return $this->name;
+	}
+
+	public function getAttributes(): array
+	{
+		return $this->attributes;
+	}
+
+}
diff --git a/src/Symfony/ServiceTagDefinition.php b/src/Symfony/ServiceTagDefinition.php
new file mode 100644
index 00000000..b0f66d9c
--- /dev/null
+++ b/src/Symfony/ServiceTagDefinition.php
@@ -0,0 +1,13 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+interface ServiceTagDefinition
+{
+
+	public function getName(): string;
+
+	/** @return array<string, string> */
+	public function getAttributes(): array;
+
+}
diff --git a/src/Symfony/SymfonyContainerResultCacheMetaExtension.php b/src/Symfony/SymfonyContainerResultCacheMetaExtension.php
new file mode 100644
index 00000000..8e2f8028
--- /dev/null
+++ b/src/Symfony/SymfonyContainerResultCacheMetaExtension.php
@@ -0,0 +1,62 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+use PHPStan\Analyser\ResultCache\ResultCacheMetaExtension;
+use function array_map;
+use function hash;
+use function ksort;
+use function sort;
+use function var_export;
+
+final class SymfonyContainerResultCacheMetaExtension implements ResultCacheMetaExtension
+{
+
+	private ParameterMap $parameterMap;
+
+	private ServiceMap $serviceMap;
+
+	public function __construct(ParameterMap $parameterMap, ServiceMap $serviceMap)
+	{
+		$this->parameterMap = $parameterMap;
+		$this->serviceMap = $serviceMap;
+	}
+
+	public function getKey(): string
+	{
+		return 'symfonyDiContainer';
+	}
+
+	public function getHash(): string
+	{
+		$services = $parameters = [];
+
+		foreach ($this->parameterMap->getParameters() as $parameter) {
+			$parameters[$parameter->getKey()] = $parameter->getValue();
+		}
+		ksort($parameters);
+
+		foreach ($this->serviceMap->getServices() as $service) {
+			$serviceTags = array_map(
+				static fn (ServiceTag $tag) => [
+					'name' => $tag->getName(),
+					'attributes' => $tag->getAttributes(),
+				],
+				$service->getTags(),
+			);
+			sort($serviceTags);
+
+			$services[$service->getId()] = [
+				'class' => $service->getClass(),
+				'public' => $service->isPublic() ? 'yes' : 'no',
+				'synthetic' => $service->isSynthetic() ? 'yes' : 'no',
+				'alias' => $service->getAlias(),
+				'tags' => $serviceTags,
+			];
+		}
+		ksort($services);
+
+		return hash('sha256', var_export(['parameters' => $parameters, 'services' => $services], true));
+	}
+
+}
diff --git a/src/Symfony/SymfonyDiagnoseExtension.php b/src/Symfony/SymfonyDiagnoseExtension.php
new file mode 100644
index 00000000..38b19754
--- /dev/null
+++ b/src/Symfony/SymfonyDiagnoseExtension.php
@@ -0,0 +1,28 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+use PHPStan\Command\Output;
+use PHPStan\Diagnose\DiagnoseExtension;
+use function sprintf;
+
+class SymfonyDiagnoseExtension implements DiagnoseExtension
+{
+
+	private ConsoleApplicationResolver $consoleApplicationResolver;
+
+	public function __construct(ConsoleApplicationResolver $consoleApplicationResolver)
+	{
+		$this->consoleApplicationResolver = $consoleApplicationResolver;
+	}
+
+	public function print(Output $output): void
+	{
+		$output->writeLineFormatted(sprintf(
+			'<info>Symfony\'s consoleApplicationLoader:</info> %s',
+			$this->consoleApplicationResolver->hasConsoleApplicationLoader() ? 'In use' : 'No',
+		));
+		$output->writeLineFormatted('');
+	}
+
+}
diff --git a/src/Symfony/XmlContainerNotExistsException.php b/src/Symfony/XmlContainerNotExistsException.php
index 7238cc6e..59b600de 100644
--- a/src/Symfony/XmlContainerNotExistsException.php
+++ b/src/Symfony/XmlContainerNotExistsException.php
@@ -2,7 +2,9 @@
 
 namespace PHPStan\Symfony;
 
-final class XmlContainerNotExistsException extends \InvalidArgumentException
+use InvalidArgumentException;
+
+final class XmlContainerNotExistsException extends InvalidArgumentException
 {
 
 }
diff --git a/src/Symfony/XmlParameterMapFactory.php b/src/Symfony/XmlParameterMapFactory.php
new file mode 100644
index 00000000..4d3d3578
--- /dev/null
+++ b/src/Symfony/XmlParameterMapFactory.php
@@ -0,0 +1,124 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+use InvalidArgumentException;
+use PHPStan\ShouldNotHappenException;
+use SimpleXMLElement;
+use function base64_decode;
+use function count;
+use function file_get_contents;
+use function is_numeric;
+use function ksort;
+use function simplexml_load_string;
+use function sprintf;
+use function strpos;
+
+final class XmlParameterMapFactory implements ParameterMapFactory
+{
+
+	private ?string $containerXml = null;
+
+	public function __construct(?string $containerXmlPath)
+	{
+		$this->containerXml = $containerXmlPath;
+	}
+
+	public function create(): ParameterMap
+	{
+		if ($this->containerXml === null) {
+			return new FakeParameterMap();
+		}
+
+		$fileContents = file_get_contents($this->containerXml);
+		if ($fileContents === false) {
+			throw new XmlContainerNotExistsException(sprintf('Container %s does not exist', $this->containerXml));
+		}
+
+		$xml = @simplexml_load_string($fileContents);
+		if ($xml === false) {
+			throw new XmlContainerNotExistsException(sprintf('Container %s cannot be parsed', $this->containerXml));
+		}
+
+		/** @var Parameter[] $parameters */
+		$parameters = [];
+
+		if (count($xml->parameters) > 0) {
+			foreach ($xml->parameters->parameter as $def) {
+				/** @var SimpleXMLElement $attrs */
+				$attrs = $def->attributes();
+
+				$parameter = new Parameter(
+					(string) $attrs->key,
+					$this->getNodeValue($def),
+				);
+
+				$parameters[$parameter->getKey()] = $parameter;
+			}
+		}
+
+		ksort($parameters);
+
+		return new DefaultParameterMap($parameters);
+	}
+
+	/**
+	 * @return array<mixed>|bool|float|int|string
+	 */
+	private function getNodeValue(SimpleXMLElement $def)
+	{
+		/** @var SimpleXMLElement $attrs */
+		$attrs = $def->attributes();
+
+		$value = null;
+		switch ((string) $attrs->type) {
+			case 'collection':
+				$value = [];
+				$children = $def->children();
+				if ($children === null) {
+					throw new ShouldNotHappenException();
+				}
+				foreach ($children as $child) {
+					/** @var SimpleXMLElement $childAttrs */
+					$childAttrs = $child->attributes();
+
+					if (isset($childAttrs->key)) {
+						$value[(string) $childAttrs->key] = $this->getNodeValue($child);
+					} else {
+						$value[] = $this->getNodeValue($child);
+					}
+				}
+				break;
+
+			case 'string':
+				$value = (string) $def;
+				break;
+
+			case 'binary':
+				$value = base64_decode((string) $def, true);
+				if ($value === false) {
+					throw new InvalidArgumentException(sprintf('Parameter "%s" of binary type is not valid base64 encoded string.', (string) $attrs->key));
+				}
+
+				break;
+
+			default:
+				$value = (string) $def;
+
+				if (is_numeric($value)) {
+					if (strpos($value, '.') !== false) {
+						$value = (float) $value;
+					} else {
+						$value = (int) $value;
+					}
+				} elseif ($value === 'true') {
+					$value = true;
+				} elseif ($value === 'false') {
+					$value = false;
+				}
+		}
+
+		return $value;
+	}
+
+}
diff --git a/src/Symfony/XmlServiceMapFactory.php b/src/Symfony/XmlServiceMapFactory.php
index 858ea52a..ac79cb30 100644
--- a/src/Symfony/XmlServiceMapFactory.php
+++ b/src/Symfony/XmlServiceMapFactory.php
@@ -2,7 +2,11 @@
 
 namespace PHPStan\Symfony;
 
-use function simplexml_load_file;
+use SimpleXMLElement;
+use function count;
+use function file_get_contents;
+use function ksort;
+use function simplexml_load_string;
 use function sprintf;
 use function strpos;
 use function substr;
@@ -10,44 +14,65 @@
 final class XmlServiceMapFactory implements ServiceMapFactory
 {
 
-	/** @var string */
-	private $containerXml;
+	private ?string $containerXml = null;
 
-	public function __construct(string $containerXml)
+	public function __construct(?string $containerXmlPath)
 	{
-		$this->containerXml = $containerXml;
+		$this->containerXml = $containerXmlPath;
 	}
 
 	public function create(): ServiceMap
 	{
-		$xml = @simplexml_load_file($this->containerXml);
+		if ($this->containerXml === null) {
+			return new FakeServiceMap();
+		}
+
+		$fileContents = file_get_contents($this->containerXml);
+		if ($fileContents === false) {
+			throw new XmlContainerNotExistsException(sprintf('Container %s does not exist', $this->containerXml));
+		}
+
+		$xml = @simplexml_load_string($fileContents);
 		if ($xml === false) {
-			throw new XmlContainerNotExistsException(sprintf('Container %s does not exist or cannot be parsed', $this->containerXml));
+			throw new XmlContainerNotExistsException(sprintf('Container %s cannot be parsed', $this->containerXml));
 		}
 
-		/** @var \PHPStan\Symfony\Service[] $services */
+		/** @var Service[] $services */
 		$services = [];
-		/** @var \PHPStan\Symfony\Service[] $aliases */
+		/** @var Service[] $aliases */
 		$aliases = [];
-		foreach ($xml->services->service as $def) {
-			/** @var \SimpleXMLElement $attrs */
-			$attrs = $def->attributes();
-			if (!isset($attrs->id)) {
-				continue;
-			}
 
-			$service = new Service(
-				strpos((string) $attrs->id, '.') === 0 ? substr((string) $attrs->id, 1) : (string) $attrs->id,
-				isset($attrs->class) ? (string) $attrs->class : null,
-				!isset($attrs->public) || (string) $attrs->public !== 'false',
-				isset($attrs->synthetic) && (string) $attrs->synthetic === 'true',
-				isset($attrs->alias) ? (string) $attrs->alias : null
-			);
+		if (count($xml->services) > 0) {
+			foreach ($xml->services->service as $def) {
+				/** @var SimpleXMLElement $attrs */
+				$attrs = $def->attributes();
+				if (!isset($attrs->id)) {
+					continue;
+				}
 
-			if ($service->getAlias() !== null) {
-				$aliases[] = $service;
-			} else {
-				$services[$service->getId()] = $service;
+				$serviceTags = [];
+				foreach ($def->tag as $tag) {
+					$tagAttrs = ((array) $tag->attributes())['@attributes'] ?? [];
+					$tagName = $tagAttrs['name'];
+					unset($tagAttrs['name']);
+
+					$serviceTags[] = new ServiceTag($tagName, $tagAttrs);
+				}
+
+				$service = new Service(
+					$this->cleanServiceId((string) $attrs->id),
+					isset($attrs->class) ? (string) $attrs->class : null,
+					isset($attrs->public) && (string) $attrs->public === 'true',
+					isset($attrs->synthetic) && (string) $attrs->synthetic === 'true',
+					isset($attrs->alias) ? $this->cleanServiceId((string) $attrs->alias) : null,
+					$serviceTags,
+				);
+
+				if ($service->getAlias() !== null) {
+					$aliases[] = $service;
+				} else {
+					$services[$service->getId()] = $service;
+				}
 			}
 		}
 		foreach ($aliases as $service) {
@@ -61,11 +86,18 @@ public function create(): ServiceMap
 				$services[$alias]->getClass(),
 				$service->isPublic(),
 				$service->isSynthetic(),
-				$alias
+				$alias,
 			);
 		}
 
-		return new ServiceMap($services);
+		ksort($services);
+
+		return new DefaultServiceMap($services);
+	}
+
+	private function cleanServiceId(string $id): string
+	{
+		return strpos($id, '.') === 0 ? substr($id, 1) : $id;
 	}
 
 }
diff --git a/src/Type/Symfony/ArgumentTypeSpecifyingExtension.php b/src/Type/Symfony/ArgumentTypeSpecifyingExtension.php
new file mode 100644
index 00000000..5c21f021
--- /dev/null
+++ b/src/Type/Symfony/ArgumentTypeSpecifyingExtension.php
@@ -0,0 +1,56 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Analyser\SpecifiedTypes;
+use PHPStan\Analyser\TypeSpecifier;
+use PHPStan\Analyser\TypeSpecifierAwareExtension;
+use PHPStan\Analyser\TypeSpecifierContext;
+use PHPStan\Node\Printer\Printer;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Type\MethodTypeSpecifyingExtension;
+
+final class ArgumentTypeSpecifyingExtension implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension
+{
+
+	private Printer $printer;
+
+	private TypeSpecifier $typeSpecifier;
+
+	public function __construct(Printer $printer)
+	{
+		$this->printer = $printer;
+	}
+
+	public function getClass(): string
+	{
+		return 'Symfony\Component\Console\Input\InputInterface';
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection, MethodCall $node, TypeSpecifierContext $context): bool
+	{
+		return $methodReflection->getName() === 'hasArgument' && !$context->null();
+	}
+
+	public function specifyTypes(MethodReflection $methodReflection, MethodCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
+	{
+		if (!isset($node->getArgs()[0])) {
+			return new SpecifiedTypes();
+		}
+		$argType = $scope->getType($node->getArgs()[0]->value);
+		return $this->typeSpecifier->create(
+			Helper::createMarkerNode($node->var, $argType, $this->printer),
+			$argType,
+			$context,
+			$scope,
+		);
+	}
+
+	public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
+	{
+		$this->typeSpecifier = $typeSpecifier;
+	}
+
+}
diff --git a/src/Type/Symfony/CacheInterfaceGetDynamicReturnTypeExtension.php b/src/Type/Symfony/CacheInterfaceGetDynamicReturnTypeExtension.php
new file mode 100644
index 00000000..0862ce61
--- /dev/null
+++ b/src/Type/Symfony/CacheInterfaceGetDynamicReturnTypeExtension.php
@@ -0,0 +1,48 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Reflection\ParametersAcceptorSelector;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\GeneralizePrecision;
+use PHPStan\Type\Type;
+
+final class CacheInterfaceGetDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+
+	public function getClass(): string
+	{
+		return 'Symfony\Contracts\Cache\CacheInterface';
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection): bool
+	{
+		return $methodReflection->getName() === 'get';
+	}
+
+	public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
+	{
+		if (!isset($methodCall->getArgs()[1])) {
+			return null;
+		}
+
+		$callbackReturnType = $scope->getType($methodCall->getArgs()[1]->value);
+		if ($callbackReturnType->isCallable()->yes()) {
+			$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
+				$scope,
+				$methodCall->getArgs(),
+				$callbackReturnType->getCallableParametersAcceptors($scope),
+			);
+			$returnType = $parametersAcceptor->getReturnType();
+
+			// generalize template parameters
+			return $returnType->generalize(GeneralizePrecision::templateArgument());
+		}
+
+		return null;
+	}
+
+}
diff --git a/src/Type/Symfony/CommandGetHelperDynamicReturnTypeExtension.php b/src/Type/Symfony/CommandGetHelperDynamicReturnTypeExtension.php
new file mode 100644
index 00000000..fba70cfd
--- /dev/null
+++ b/src/Type/Symfony/CommandGetHelperDynamicReturnTypeExtension.php
@@ -0,0 +1,67 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Symfony\ConsoleApplicationResolver;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\ObjectType;
+use PHPStan\Type\Type;
+use PHPStan\Type\TypeCombinator;
+use Throwable;
+use function count;
+use function get_class;
+
+final class CommandGetHelperDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+
+	private ConsoleApplicationResolver $consoleApplicationResolver;
+
+	public function __construct(ConsoleApplicationResolver $consoleApplicationResolver)
+	{
+		$this->consoleApplicationResolver = $consoleApplicationResolver;
+	}
+
+	public function getClass(): string
+	{
+		return 'Symfony\Component\Console\Command\Command';
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection): bool
+	{
+		return $methodReflection->getName() === 'getHelper';
+	}
+
+	public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
+	{
+		if (!isset($methodCall->getArgs()[0])) {
+			return null;
+		}
+
+		$classReflection = $scope->getClassReflection();
+		if ($classReflection === null) {
+			return null;
+		}
+
+		$argStrings = $scope->getType($methodCall->getArgs()[0]->value)->getConstantStrings();
+		if (count($argStrings) !== 1) {
+			return null;
+		}
+		$argName = $argStrings[0]->getValue();
+
+		$returnTypes = [];
+		foreach ($this->consoleApplicationResolver->findCommands($classReflection) as $command) {
+			try {
+				$command->mergeApplicationDefinition();
+				$returnTypes[] = new ObjectType(get_class($command->getHelper($argName)));
+			} catch (Throwable $e) {
+				// no-op
+			}
+		}
+
+		return count($returnTypes) > 0 ? TypeCombinator::union(...$returnTypes) : null;
+	}
+
+}
diff --git a/src/Type/Symfony/Config/ArrayNodeDefinitionPrototypeDynamicReturnTypeExtension.php b/src/Type/Symfony/Config/ArrayNodeDefinitionPrototypeDynamicReturnTypeExtension.php
new file mode 100644
index 00000000..1dae22e2
--- /dev/null
+++ b/src/Type/Symfony/Config/ArrayNodeDefinitionPrototypeDynamicReturnTypeExtension.php
@@ -0,0 +1,82 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony\Config;
+
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Reflection\ParametersAcceptorSelector;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\Symfony\Config\ValueObject\ParentObjectType;
+use PHPStan\Type\Type;
+use PHPStan\Type\VerbosityLevel;
+use function count;
+use function in_array;
+
+final class ArrayNodeDefinitionPrototypeDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+
+	private const PROTOTYPE_METHODS = [
+		'arrayPrototype',
+		'scalarPrototype',
+		'booleanPrototype',
+		'integerPrototype',
+		'floatPrototype',
+		'enumPrototype',
+		'variablePrototype',
+	];
+
+	private const MAPPING = [
+		'variable' => 'Symfony\Component\Config\Definition\Builder\VariableNodeDefinition',
+		'scalar' => 'Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition',
+		'boolean' => 'Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition',
+		'integer' => 'Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition',
+		'float' => 'Symfony\Component\Config\Definition\Builder\FloatNodeDefinition',
+		'array' => 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition',
+		'enum' => 'Symfony\Component\Config\Definition\Builder\EnumNodeDefinition',
+	];
+
+	public function getClass(): string
+	{
+		return 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition';
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection): bool
+	{
+		return $methodReflection->getName() === 'prototype' || in_array($methodReflection->getName(), self::PROTOTYPE_METHODS, true);
+	}
+
+	public function getTypeFromMethodCall(
+		MethodReflection $methodReflection,
+		MethodCall $methodCall,
+		Scope $scope
+	): Type
+	{
+		$calledOnType = $scope->getType($methodCall->var);
+
+		$defaultType = ParametersAcceptorSelector::selectFromArgs(
+			$scope,
+			$methodCall->getArgs(),
+			$methodReflection->getVariants(),
+		)->getReturnType();
+
+		if ($methodReflection->getName() === 'prototype') {
+			if (!isset($methodCall->getArgs()[0])) {
+				return $defaultType;
+			}
+
+			$argStrings = $scope->getType($methodCall->getArgs()[0]->value)->getConstantStrings();
+			if (count($argStrings) === 1 && isset(self::MAPPING[$argStrings[0]->getValue()])) {
+				$type = $argStrings[0]->getValue();
+
+				return new ParentObjectType(self::MAPPING[$type], $calledOnType);
+			}
+		}
+
+		return new ParentObjectType(
+			$defaultType->describe(VerbosityLevel::typeOnly()),
+			$calledOnType,
+		);
+	}
+
+}
diff --git a/src/Type/Symfony/Config/PassParentObjectDynamicReturnTypeExtension.php b/src/Type/Symfony/Config/PassParentObjectDynamicReturnTypeExtension.php
new file mode 100644
index 00000000..800d9dbc
--- /dev/null
+++ b/src/Type/Symfony/Config/PassParentObjectDynamicReturnTypeExtension.php
@@ -0,0 +1,61 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony\Config;
+
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Reflection\ParametersAcceptorSelector;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\Symfony\Config\ValueObject\ParentObjectType;
+use PHPStan\Type\Type;
+use PHPStan\Type\VerbosityLevel;
+use function in_array;
+
+final class PassParentObjectDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+
+	/** @var class-string */
+	private string $className;
+
+	/** @var string[] */
+	private array $methods;
+
+	/**
+	 * @param class-string $className
+	 * @param string[] $methods
+	 */
+	public function __construct(string $className, array $methods)
+	{
+		$this->className = $className;
+		$this->methods = $methods;
+	}
+
+	public function getClass(): string
+	{
+		return $this->className;
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection): bool
+	{
+		return in_array($methodReflection->getName(), $this->methods, true);
+	}
+
+	public function getTypeFromMethodCall(
+		MethodReflection $methodReflection,
+		MethodCall $methodCall,
+		Scope $scope
+	): Type
+	{
+		$calledOnType = $scope->getType($methodCall->var);
+
+		$defaultType = ParametersAcceptorSelector::selectFromArgs(
+			$scope,
+			$methodCall->getArgs(),
+			$methodReflection->getVariants(),
+		)->getReturnType();
+
+		return new ParentObjectType($defaultType->describe(VerbosityLevel::typeOnly()), $calledOnType);
+	}
+
+}
diff --git a/src/Type/Symfony/Config/ReturnParentDynamicReturnTypeExtension.php b/src/Type/Symfony/Config/ReturnParentDynamicReturnTypeExtension.php
new file mode 100644
index 00000000..034d5d80
--- /dev/null
+++ b/src/Type/Symfony/Config/ReturnParentDynamicReturnTypeExtension.php
@@ -0,0 +1,56 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony\Config;
+
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\Symfony\Config\ValueObject\ParentObjectType;
+use PHPStan\Type\Type;
+use function in_array;
+
+final class ReturnParentDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+
+	/** @var class-string */
+	private string $className;
+
+	/** @var string[] */
+	private array $methods;
+
+	/**
+	 * @param class-string $className
+	 * @param string[] $methods
+	 */
+	public function __construct(string $className, array $methods)
+	{
+		$this->className = $className;
+		$this->methods = $methods;
+	}
+
+	public function getClass(): string
+	{
+		return $this->className;
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection): bool
+	{
+		return in_array($methodReflection->getName(), $this->methods, true);
+	}
+
+	public function getTypeFromMethodCall(
+		MethodReflection $methodReflection,
+		MethodCall $methodCall,
+		Scope $scope
+	): ?Type
+	{
+		$calledOnType = $scope->getType($methodCall->var);
+		if ($calledOnType instanceof ParentObjectType) {
+			return $calledOnType->getParent();
+		}
+
+		return null;
+	}
+
+}
diff --git a/src/Type/Symfony/Config/TreeBuilderDynamicReturnTypeExtension.php b/src/Type/Symfony/Config/TreeBuilderDynamicReturnTypeExtension.php
new file mode 100644
index 00000000..4f266c50
--- /dev/null
+++ b/src/Type/Symfony/Config/TreeBuilderDynamicReturnTypeExtension.php
@@ -0,0 +1,58 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony\Config;
+
+use PhpParser\Node\Expr\StaticCall;
+use PhpParser\Node\Name;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\ShouldNotHappenException;
+use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
+use PHPStan\Type\Symfony\Config\ValueObject\TreeBuilderType;
+use PHPStan\Type\Type;
+use function count;
+
+final class TreeBuilderDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
+{
+
+	private const MAPPING = [
+		'variable' => 'Symfony\Component\Config\Definition\Builder\VariableNodeDefinition',
+		'scalar' => 'Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition',
+		'boolean' => 'Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition',
+		'integer' => 'Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition',
+		'float' => 'Symfony\Component\Config\Definition\Builder\FloatNodeDefinition',
+		'array' => 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition',
+		'enum' => 'Symfony\Component\Config\Definition\Builder\EnumNodeDefinition',
+	];
+
+	public function getClass(): string
+	{
+		return 'Symfony\Component\Config\Definition\Builder\TreeBuilder';
+	}
+
+	public function isStaticMethodSupported(MethodReflection $methodReflection): bool
+	{
+		return $methodReflection->getName() === '__construct';
+	}
+
+	public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type
+	{
+		if (!$methodCall->class instanceof Name) {
+			throw new ShouldNotHappenException();
+		}
+
+		$className = $scope->resolveName($methodCall->class);
+
+		$type = 'array';
+
+		if (isset($methodCall->getArgs()[1])) {
+			$argStrings = $scope->getType($methodCall->getArgs()[1]->value)->getConstantStrings();
+			if (count($argStrings) === 1 && isset(self::MAPPING[$argStrings[0]->getValue()])) {
+				$type = $argStrings[0]->getValue();
+			}
+		}
+
+		return new TreeBuilderType($className, self::MAPPING[$type]);
+	}
+
+}
diff --git a/src/Type/Symfony/Config/TreeBuilderGetRootNodeDynamicReturnTypeExtension.php b/src/Type/Symfony/Config/TreeBuilderGetRootNodeDynamicReturnTypeExtension.php
new file mode 100644
index 00000000..2be2b574
--- /dev/null
+++ b/src/Type/Symfony/Config/TreeBuilderGetRootNodeDynamicReturnTypeExtension.php
@@ -0,0 +1,43 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony\Config;
+
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\Symfony\Config\ValueObject\ParentObjectType;
+use PHPStan\Type\Symfony\Config\ValueObject\TreeBuilderType;
+use PHPStan\Type\Type;
+
+final class TreeBuilderGetRootNodeDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+
+	public function getClass(): string
+	{
+		return 'Symfony\Component\Config\Definition\Builder\TreeBuilder';
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection): bool
+	{
+		return $methodReflection->getName() === 'getRootNode';
+	}
+
+	public function getTypeFromMethodCall(
+		MethodReflection $methodReflection,
+		MethodCall $methodCall,
+		Scope $scope
+	): ?Type
+	{
+		$calledOnType = $scope->getType($methodCall->var);
+		if ($calledOnType instanceof TreeBuilderType) {
+			return new ParentObjectType(
+				$calledOnType->getRootNodeClassName(),
+				$calledOnType,
+			);
+		}
+
+		return null;
+	}
+
+}
diff --git a/src/Type/Symfony/Config/ValueObject/ParentObjectType.php b/src/Type/Symfony/Config/ValueObject/ParentObjectType.php
new file mode 100644
index 00000000..19baf926
--- /dev/null
+++ b/src/Type/Symfony/Config/ValueObject/ParentObjectType.php
@@ -0,0 +1,31 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony\Config\ValueObject;
+
+use PHPStan\Type\ObjectType;
+use PHPStan\Type\Type;
+use PHPStan\Type\VerbosityLevel;
+
+class ParentObjectType extends ObjectType
+{
+
+	private Type $parent;
+
+	public function __construct(string $className, Type $parent)
+	{
+		parent::__construct($className);
+
+		$this->parent = $parent;
+	}
+
+	public function getParent(): Type
+	{
+		return $this->parent;
+	}
+
+	protected function describeAdditionalCacheKey(): string
+	{
+		return $this->parent->describe(VerbosityLevel::cache());
+	}
+
+}
diff --git a/src/Type/Symfony/Config/ValueObject/TreeBuilderType.php b/src/Type/Symfony/Config/ValueObject/TreeBuilderType.php
new file mode 100644
index 00000000..93c713f1
--- /dev/null
+++ b/src/Type/Symfony/Config/ValueObject/TreeBuilderType.php
@@ -0,0 +1,29 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony\Config\ValueObject;
+
+use PHPStan\Type\ObjectType;
+
+class TreeBuilderType extends ObjectType
+{
+
+	private string $rootNodeClassName;
+
+	public function __construct(string $className, string $rootNodeClassName)
+	{
+		parent::__construct($className);
+
+		$this->rootNodeClassName = $rootNodeClassName;
+	}
+
+	public function getRootNodeClassName(): string
+	{
+		return $this->rootNodeClassName;
+	}
+
+	protected function describeAdditionalCacheKey(): string
+	{
+		return $this->getRootNodeClassName();
+	}
+
+}
diff --git a/src/Type/Symfony/EnvelopeReturnTypeExtension.php b/src/Type/Symfony/EnvelopeReturnTypeExtension.php
new file mode 100644
index 00000000..06e08772
--- /dev/null
+++ b/src/Type/Symfony/EnvelopeReturnTypeExtension.php
@@ -0,0 +1,57 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Type\Accessory\AccessoryArrayListType;
+use PHPStan\Type\ArrayType;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\Generic\GenericClassStringType;
+use PHPStan\Type\IntegerType;
+use PHPStan\Type\ObjectType;
+use PHPStan\Type\Type;
+use PHPStan\Type\TypeCombinator;
+use function count;
+
+final class EnvelopeReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+
+	public function getClass(): string
+	{
+		return 'Symfony\Component\Messenger\Envelope';
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection): bool
+	{
+		return $methodReflection->getName() === 'all';
+	}
+
+	public function getTypeFromMethodCall(
+		MethodReflection $methodReflection,
+		MethodCall $methodCall,
+		Scope $scope
+	): Type
+	{
+		if (count($methodCall->getArgs()) === 0) {
+			return new ArrayType(
+				new GenericClassStringType(new ObjectType('Symfony\Component\Messenger\Stamp\StampInterface')),
+				TypeCombinator::intersect(new ArrayType(new IntegerType(), new ObjectType('Symfony\Component\Messenger\Stamp\StampInterface')), new AccessoryArrayListType()),
+			);
+		}
+
+		$argType = $scope->getType($methodCall->getArgs()[0]->value);
+		if (count($argType->getConstantStrings()) === 0) {
+			return TypeCombinator::intersect(new ArrayType(new IntegerType(), new ObjectType('Symfony\Component\Messenger\Stamp\StampInterface')), new AccessoryArrayListType());
+		}
+
+		$objectTypes = [];
+		foreach ($argType->getConstantStrings() as $constantString) {
+			$objectTypes[] = new ObjectType($constantString->getValue());
+		}
+
+		return TypeCombinator::intersect(new ArrayType(new IntegerType(), TypeCombinator::union(...$objectTypes)), new AccessoryArrayListType());
+	}
+
+}
diff --git a/src/Type/Symfony/ExtensionGetConfigurationReturnTypeExtension.php b/src/Type/Symfony/ExtensionGetConfigurationReturnTypeExtension.php
new file mode 100644
index 00000000..d975d9d1
--- /dev/null
+++ b/src/Type/Symfony/ExtensionGetConfigurationReturnTypeExtension.php
@@ -0,0 +1,104 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\ClassReflection;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Reflection\ReflectionProvider;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\NullType;
+use PHPStan\Type\ObjectType;
+use PHPStan\Type\Type;
+use PHPStan\Type\TypeCombinator;
+use function str_contains;
+use function strrpos;
+use function substr_replace;
+
+class ExtensionGetConfigurationReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+
+	private ReflectionProvider $reflectionProvider;
+
+	public function __construct(ReflectionProvider $reflectionProvider)
+	{
+		$this->reflectionProvider = $reflectionProvider;
+	}
+
+	public function getClass(): string
+	{
+		return 'Symfony\Component\DependencyInjection\Extension\Extension';
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection): bool
+	{
+		return $methodReflection->getName() === 'getConfiguration'
+			&& $methodReflection->getDeclaringClass()->getName() === 'Symfony\Component\DependencyInjection\Extension\Extension';
+	}
+
+	public function getTypeFromMethodCall(
+		MethodReflection $methodReflection,
+		MethodCall $methodCall,
+		Scope $scope
+	): ?Type
+	{
+		$types = [];
+		$extensionType = $scope->getType($methodCall->var);
+		$classes = $extensionType->getObjectClassNames();
+
+		foreach ($classes as $extensionName) {
+			if (str_contains($extensionName, "\0")) {
+				$types[] = new NullType();
+				continue;
+			}
+
+			$lastBackslash = strrpos($extensionName, '\\');
+			if ($lastBackslash === false) {
+				$types[] = new NullType();
+				continue;
+			}
+
+			$configurationName = substr_replace($extensionName, '\Configuration', $lastBackslash);
+			if (!$this->reflectionProvider->hasClass($configurationName)) {
+				$types[] = new NullType();
+				continue;
+			}
+
+			$reflection = $this->reflectionProvider->getClass($configurationName);
+			if ($this->hasRequiredConstructor($reflection)) {
+				$types[] = new NullType();
+				continue;
+			}
+
+			$types[] = new ObjectType($configurationName);
+		}
+
+		return TypeCombinator::union(...$types);
+	}
+
+	private function hasRequiredConstructor(ClassReflection $class): bool
+	{
+		if (!$class->hasConstructor()) {
+			return false;
+		}
+
+		$constructor = $class->getConstructor();
+		foreach ($constructor->getVariants() as $variant) {
+			$anyRequired = false;
+			foreach ($variant->getParameters() as $parameter) {
+				if (!$parameter->isOptional()) {
+					$anyRequired = true;
+					break;
+				}
+			}
+
+			if (!$anyRequired) {
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+}
diff --git a/src/Type/Symfony/Form/FormInterfaceDynamicReturnTypeExtension.php b/src/Type/Symfony/Form/FormInterfaceDynamicReturnTypeExtension.php
new file mode 100644
index 00000000..f80ddeb9
--- /dev/null
+++ b/src/Type/Symfony/Form/FormInterfaceDynamicReturnTypeExtension.php
@@ -0,0 +1,64 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony\Form;
+
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Type\Constant\ConstantBooleanType;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\Generic\GenericObjectType;
+use PHPStan\Type\ObjectType;
+use PHPStan\Type\Type;
+use PHPStan\Type\UnionType;
+use Symfony\Component\Form\FormError;
+use Symfony\Component\Form\FormErrorIterator;
+use Symfony\Component\Form\FormInterface;
+
+final class FormInterfaceDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+
+	public function getClass(): string
+	{
+		return FormInterface::class;
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection): bool
+	{
+		return $methodReflection->getName() === 'getErrors';
+	}
+
+	public function getTypeFromMethodCall(
+		MethodReflection $methodReflection,
+		MethodCall $methodCall,
+		Scope $scope
+	): Type
+	{
+		if (!isset($methodCall->getArgs()[1])) {
+			return new GenericObjectType(FormErrorIterator::class, [new ObjectType(FormError::class)]);
+		}
+
+		$firstArgType = $scope->getType($methodCall->getArgs()[0]->value);
+		$secondArgType = $scope->getType($methodCall->getArgs()[1]->value);
+
+		$firstIsTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($firstArgType)->result;
+		$firstIsFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($firstArgType)->result;
+		$secondIsTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($secondArgType)->result;
+		$secondIsFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($secondArgType)->result;
+
+		$firstCompareType = $firstIsTrueType->compareTo($firstIsFalseType);
+		$secondCompareType = $secondIsTrueType->compareTo($secondIsFalseType);
+
+		if ($firstCompareType === $firstIsTrueType && $secondCompareType === $secondIsFalseType) {
+			return new GenericObjectType(FormErrorIterator::class, [
+				new UnionType([
+					new ObjectType(FormError::class),
+					new ObjectType(FormErrorIterator::class),
+				]),
+			]);
+		}
+
+		return new GenericObjectType(FormErrorIterator::class, [new ObjectType(FormError::class)]);
+	}
+
+}
diff --git a/src/Type/Symfony/GetOptionTypeHelper.php b/src/Type/Symfony/GetOptionTypeHelper.php
new file mode 100644
index 00000000..6ddf87d8
--- /dev/null
+++ b/src/Type/Symfony/GetOptionTypeHelper.php
@@ -0,0 +1,41 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use PHPStan\Analyser\Scope;
+use PHPStan\Type\ArrayType;
+use PHPStan\Type\BooleanType;
+use PHPStan\Type\IntegerType;
+use PHPStan\Type\NullType;
+use PHPStan\Type\StringType;
+use PHPStan\Type\Type;
+use PHPStan\Type\TypeCombinator;
+use PHPStan\Type\UnionType;
+use Symfony\Component\Console\Input\InputOption;
+use function method_exists;
+
+class GetOptionTypeHelper
+{
+
+	public function getOptionType(Scope $scope, InputOption $option): Type
+	{
+		if (!$option->acceptValue()) {
+			if (method_exists($option, 'isNegatable') && $option->isNegatable()) {
+				return new UnionType([new BooleanType(), new NullType()]);
+			}
+
+			return new BooleanType();
+		}
+
+		$optType = TypeCombinator::union(new StringType(), new NullType());
+		if ($option->isValueRequired() && ($option->isArray() || $option->getDefault() !== null)) {
+			$optType = TypeCombinator::removeNull($optType);
+		}
+		if ($option->isArray()) {
+			$optType = new ArrayType(new IntegerType(), $optType);
+		}
+
+		return TypeCombinator::union($optType, $scope->getTypeFromValue($option->getDefault()));
+	}
+
+}
diff --git a/src/Type/Symfony/HeaderBagDynamicReturnTypeExtension.php b/src/Type/Symfony/HeaderBagDynamicReturnTypeExtension.php
index 42c6878a..f50dc4e8 100644
--- a/src/Type/Symfony/HeaderBagDynamicReturnTypeExtension.php
+++ b/src/Type/Symfony/HeaderBagDynamicReturnTypeExtension.php
@@ -5,7 +5,6 @@
 use PhpParser\Node\Expr\MethodCall;
 use PHPStan\Analyser\Scope;
 use PHPStan\Reflection\MethodReflection;
-use PHPStan\Reflection\ParametersAcceptorSelector;
 use PHPStan\Type\ArrayType;
 use PHPStan\Type\Constant\ConstantBooleanType;
 use PHPStan\Type\DynamicMethodReturnTypeExtension;
@@ -32,15 +31,15 @@ public function getTypeFromMethodCall(
 		MethodReflection $methodReflection,
 		MethodCall $methodCall,
 		Scope $scope
-	): Type
+	): ?Type
 	{
-		$firstArgType = isset($methodCall->args[2]) ? $scope->getType($methodCall->args[2]->value) : new ConstantBooleanType(true);
-		$isTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($firstArgType);
-		$isFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($firstArgType);
+		$firstArgType = isset($methodCall->getArgs()[2]) ? $scope->getType($methodCall->getArgs()[2]->value) : new ConstantBooleanType(true);
+		$isTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($firstArgType)->result;
+		$isFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($firstArgType)->result;
 		$compareTypes = $isTrueType->compareTo($isFalseType);
 
 		if ($compareTypes === $isTrueType) {
-			$defaultArgType = isset($methodCall->args[1]) ? $scope->getType($methodCall->args[1]->value) : new NullType();
+			$defaultArgType = isset($methodCall->getArgs()[1]) ? $scope->getType($methodCall->getArgs()[1]->value) : new NullType();
 
 			return TypeCombinator::union($defaultArgType, new StringType());
 		}
@@ -48,7 +47,7 @@ public function getTypeFromMethodCall(
 			return new ArrayType(new IntegerType(), new StringType());
 		}
 
-		return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
+		return null;
 	}
 
 }
diff --git a/src/Type/Symfony/Helper.php b/src/Type/Symfony/Helper.php
index a39af310..4aad820c 100644
--- a/src/Type/Symfony/Helper.php
+++ b/src/Type/Symfony/Helper.php
@@ -17,7 +17,7 @@ public static function createMarkerNode(Expr $expr, Type $type, PrettyPrinterAbs
 		return new Expr\Variable(md5(sprintf(
 			'%s::%s',
 			$printer->prettyPrintExpr($expr),
-			$type->describe(VerbosityLevel::value())
+			$type->describe(VerbosityLevel::precise()),
 		)));
 	}
 
diff --git a/src/Type/Symfony/InputBagDynamicReturnTypeExtension.php b/src/Type/Symfony/InputBagDynamicReturnTypeExtension.php
new file mode 100644
index 00000000..75e6d0bc
--- /dev/null
+++ b/src/Type/Symfony/InputBagDynamicReturnTypeExtension.php
@@ -0,0 +1,85 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Reflection\ParametersAcceptorSelector;
+use PHPStan\ShouldNotHappenException;
+use PHPStan\Type\ArrayType;
+use PHPStan\Type\BooleanType;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\FloatType;
+use PHPStan\Type\IntegerType;
+use PHPStan\Type\MixedType;
+use PHPStan\Type\NullType;
+use PHPStan\Type\StringType;
+use PHPStan\Type\Type;
+use PHPStan\Type\TypeCombinator;
+use PHPStan\Type\UnionType;
+use function in_array;
+
+final class InputBagDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+
+	public function getClass(): string
+	{
+		return 'Symfony\Component\HttpFoundation\InputBag';
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection): bool
+	{
+		return in_array($methodReflection->getName(), ['get', 'all'], true);
+	}
+
+	public function getTypeFromMethodCall(
+		MethodReflection $methodReflection,
+		MethodCall $methodCall,
+		Scope $scope
+	): ?Type
+	{
+		if ($methodReflection->getName() === 'get') {
+			return $this->getGetTypeFromMethodCall($methodReflection, $methodCall, $scope);
+		}
+
+		if ($methodReflection->getName() === 'all') {
+			return $this->getAllTypeFromMethodCall($methodCall);
+		}
+
+		throw new ShouldNotHappenException();
+	}
+
+	private function getGetTypeFromMethodCall(
+		MethodReflection $methodReflection,
+		MethodCall $methodCall,
+		Scope $scope
+	): ?Type
+	{
+		if (isset($methodCall->getArgs()[1])) {
+			$argType = $scope->getType($methodCall->getArgs()[1]->value);
+			$isNull = (new NullType())->isSuperTypeOf($argType);
+			if ($isNull->no()) {
+				return TypeCombinator::removeNull(ParametersAcceptorSelector::selectFromArgs(
+					$scope,
+					$methodCall->getArgs(),
+					$methodReflection->getVariants(),
+				)->getReturnType());
+			}
+		}
+
+		return null;
+	}
+
+	private function getAllTypeFromMethodCall(
+		MethodCall $methodCall
+	): Type
+	{
+		if (isset($methodCall->getArgs()[0])) {
+			return new ArrayType(new MixedType(), new MixedType(true));
+		}
+
+		return new ArrayType(new StringType(), new UnionType([new ArrayType(new MixedType(), new MixedType(true)), new BooleanType(), new FloatType(), new IntegerType(), new StringType()]));
+	}
+
+}
diff --git a/src/Type/Symfony/InputBagTypeSpecifyingExtension.php b/src/Type/Symfony/InputBagTypeSpecifyingExtension.php
new file mode 100644
index 00000000..11cd39ef
--- /dev/null
+++ b/src/Type/Symfony/InputBagTypeSpecifyingExtension.php
@@ -0,0 +1,50 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Analyser\SpecifiedTypes;
+use PHPStan\Analyser\TypeSpecifier;
+use PHPStan\Analyser\TypeSpecifierAwareExtension;
+use PHPStan\Analyser\TypeSpecifierContext;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Type\MethodTypeSpecifyingExtension;
+use PHPStan\Type\NullType;
+use Symfony\Component\HttpFoundation\InputBag;
+
+final class InputBagTypeSpecifyingExtension implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension
+{
+
+	private const INPUT_BAG_CLASS = InputBag::class;
+	private const HAS_METHOD_NAME = 'has';
+	private const GET_METHOD_NAME = 'get';
+
+	private TypeSpecifier $typeSpecifier;
+
+	public function getClass(): string
+	{
+		return self::INPUT_BAG_CLASS;
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection, MethodCall $node, TypeSpecifierContext $context): bool
+	{
+		return $methodReflection->getName() === self::HAS_METHOD_NAME && $context->false();
+	}
+
+	public function specifyTypes(MethodReflection $methodReflection, MethodCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
+	{
+		return $this->typeSpecifier->create(
+			new MethodCall($node->var, self::GET_METHOD_NAME, $node->getArgs()),
+			new NullType(),
+			$context->negate(),
+			$scope,
+		);
+	}
+
+	public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
+	{
+		$this->typeSpecifier = $typeSpecifier;
+	}
+
+}
diff --git a/src/Type/Symfony/InputInterfaceGetArgumentDynamicReturnTypeExtension.php b/src/Type/Symfony/InputInterfaceGetArgumentDynamicReturnTypeExtension.php
new file mode 100644
index 00000000..88bd7b0e
--- /dev/null
+++ b/src/Type/Symfony/InputInterfaceGetArgumentDynamicReturnTypeExtension.php
@@ -0,0 +1,99 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use InvalidArgumentException;
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Symfony\ConsoleApplicationResolver;
+use PHPStan\Type\ArrayType;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\IntegerType;
+use PHPStan\Type\NullType;
+use PHPStan\Type\StringType;
+use PHPStan\Type\Type;
+use PHPStan\Type\TypeCombinator;
+use function count;
+use function in_array;
+
+final class InputInterfaceGetArgumentDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+
+	private ConsoleApplicationResolver $consoleApplicationResolver;
+
+	public function __construct(ConsoleApplicationResolver $consoleApplicationResolver)
+	{
+		$this->consoleApplicationResolver = $consoleApplicationResolver;
+	}
+
+	public function getClass(): string
+	{
+		return 'Symfony\Component\Console\Input\InputInterface';
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection): bool
+	{
+		return $methodReflection->getName() === 'getArgument';
+	}
+
+	public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
+	{
+		if (!isset($methodCall->getArgs()[0])) {
+			return null;
+		}
+
+		$classReflection = $scope->getClassReflection();
+		if ($classReflection === null) {
+			return null;
+		}
+
+		$argStrings = $scope->getType($methodCall->getArgs()[0]->value)->getConstantStrings();
+		if (count($argStrings) !== 1) {
+			return null;
+		}
+		$argName = $argStrings[0]->getValue();
+
+		$argTypes = [];
+		$canBeNullInInteract = false;
+		foreach ($this->consoleApplicationResolver->findCommands($classReflection) as $command) {
+			try {
+				$command->mergeApplicationDefinition();
+				$argument = $command->getDefinition()->getArgument($argName);
+				if ($argument->isArray()) {
+					$argType = new ArrayType(new IntegerType(), new StringType());
+					if (!$argument->isRequired() && $argument->getDefault() !== []) {
+						$argType = TypeCombinator::union($argType, $scope->getTypeFromValue($argument->getDefault()));
+					}
+				} else {
+					$argType = new StringType();
+					if (!$argument->isRequired()) {
+						$argType = TypeCombinator::union($argType, $scope->getTypeFromValue($argument->getDefault()));
+					} else {
+						$canBeNullInInteract = true;
+					}
+				}
+				$argTypes[] = $argType;
+			} catch (InvalidArgumentException $e) {
+				// noop
+			}
+		}
+
+		if (count($argTypes) === 0) {
+			return null;
+		}
+
+		$method = $scope->getFunction();
+		if (
+			$canBeNullInInteract
+			&& $method instanceof MethodReflection
+			&& ($method->getName() === 'interact' || $method->getName() === 'initialize')
+			&& in_array('Symfony\Component\Console\Command\Command', $method->getDeclaringClass()->getParentClassesNames(), true)
+		) {
+			$argTypes[] = new NullType();
+		}
+
+		return TypeCombinator::union(...$argTypes);
+	}
+
+}
diff --git a/src/Type/Symfony/InputInterfaceGetOptionDynamicReturnTypeExtension.php b/src/Type/Symfony/InputInterfaceGetOptionDynamicReturnTypeExtension.php
new file mode 100644
index 00000000..6d0346cf
--- /dev/null
+++ b/src/Type/Symfony/InputInterfaceGetOptionDynamicReturnTypeExtension.php
@@ -0,0 +1,69 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use InvalidArgumentException;
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Symfony\ConsoleApplicationResolver;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\Type;
+use PHPStan\Type\TypeCombinator;
+use function count;
+
+final class InputInterfaceGetOptionDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+
+	private ConsoleApplicationResolver $consoleApplicationResolver;
+
+	private GetOptionTypeHelper $getOptionTypeHelper;
+
+	public function __construct(ConsoleApplicationResolver $consoleApplicationResolver, GetOptionTypeHelper $getOptionTypeHelper)
+	{
+		$this->consoleApplicationResolver = $consoleApplicationResolver;
+		$this->getOptionTypeHelper = $getOptionTypeHelper;
+	}
+
+	public function getClass(): string
+	{
+		return 'Symfony\Component\Console\Input\InputInterface';
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection): bool
+	{
+		return $methodReflection->getName() === 'getOption';
+	}
+
+	public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
+	{
+		if (!isset($methodCall->getArgs()[0])) {
+			return null;
+		}
+
+		$classReflection = $scope->getClassReflection();
+		if ($classReflection === null) {
+			return null;
+		}
+
+		$optStrings = $scope->getType($methodCall->getArgs()[0]->value)->getConstantStrings();
+		if (count($optStrings) !== 1) {
+			return null;
+		}
+		$optName = $optStrings[0]->getValue();
+
+		$optTypes = [];
+		foreach ($this->consoleApplicationResolver->findCommands($classReflection) as $command) {
+			try {
+				$command->mergeApplicationDefinition();
+				$option = $command->getDefinition()->getOption($optName);
+				$optTypes[] = $this->getOptionTypeHelper->getOptionType($scope, $option);
+			} catch (InvalidArgumentException $e) {
+				// noop
+			}
+		}
+
+		return count($optTypes) > 0 ? TypeCombinator::union(...$optTypes) : null;
+	}
+
+}
diff --git a/src/Type/Symfony/InputInterfaceGetOptionsDynamicReturnTypeExtension.php b/src/Type/Symfony/InputInterfaceGetOptionsDynamicReturnTypeExtension.php
new file mode 100644
index 00000000..3105621d
--- /dev/null
+++ b/src/Type/Symfony/InputInterfaceGetOptionsDynamicReturnTypeExtension.php
@@ -0,0 +1,67 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use InvalidArgumentException;
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Symfony\ConsoleApplicationResolver;
+use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
+use PHPStan\Type\Constant\ConstantStringType;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\Type;
+use PHPStan\Type\TypeCombinator;
+use function count;
+
+final class InputInterfaceGetOptionsDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+
+	private ConsoleApplicationResolver $consoleApplicationResolver;
+
+	private GetOptionTypeHelper $getOptionTypeHelper;
+
+	public function __construct(ConsoleApplicationResolver $consoleApplicationResolver, GetOptionTypeHelper $getOptionTypeHelper)
+	{
+		$this->consoleApplicationResolver = $consoleApplicationResolver;
+		$this->getOptionTypeHelper = $getOptionTypeHelper;
+	}
+
+	public function getClass(): string
+	{
+		return 'Symfony\Component\Console\Input\InputInterface';
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection): bool
+	{
+		return $methodReflection->getName() === 'getOptions';
+	}
+
+	public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
+	{
+		$classReflection = $scope->getClassReflection();
+		if ($classReflection === null) {
+			return null;
+		}
+
+		$optTypes = [];
+		foreach ($this->consoleApplicationResolver->findCommands($classReflection) as $command) {
+			try {
+				$command->mergeApplicationDefinition();
+				$options = $command->getDefinition()->getOptions();
+				$builder = ConstantArrayTypeBuilder::createEmpty();
+				foreach ($options as $name => $option) {
+					$optionType = $this->getOptionTypeHelper->getOptionType($scope, $option);
+					$builder->setOffsetValueType(new ConstantStringType($name), $optionType);
+				}
+
+				$optTypes[] = $builder->getArray();
+			} catch (InvalidArgumentException $e) {
+				// noop
+			}
+		}
+
+		return count($optTypes) > 0 ? TypeCombinator::union(...$optTypes) : null;
+	}
+
+}
diff --git a/src/Type/Symfony/InputInterfaceHasArgumentDynamicReturnTypeExtension.php b/src/Type/Symfony/InputInterfaceHasArgumentDynamicReturnTypeExtension.php
new file mode 100644
index 00000000..34bffcea
--- /dev/null
+++ b/src/Type/Symfony/InputInterfaceHasArgumentDynamicReturnTypeExtension.php
@@ -0,0 +1,84 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use InvalidArgumentException;
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Symfony\ConsoleApplicationResolver;
+use PHPStan\Type\Constant\ConstantBooleanType;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\Type;
+use function array_unique;
+use function count;
+use function in_array;
+
+final class InputInterfaceHasArgumentDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+
+	private ConsoleApplicationResolver $consoleApplicationResolver;
+
+	public function __construct(ConsoleApplicationResolver $consoleApplicationResolver)
+	{
+		$this->consoleApplicationResolver = $consoleApplicationResolver;
+	}
+
+	public function getClass(): string
+	{
+		return 'Symfony\Component\Console\Input\InputInterface';
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection): bool
+	{
+		return $methodReflection->getName() === 'hasArgument';
+	}
+
+	public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
+	{
+		if (!isset($methodCall->getArgs()[0])) {
+			return null;
+		}
+
+		$classReflection = $scope->getClassReflection();
+		if ($classReflection === null) {
+			return null;
+		}
+
+		$argStrings = $scope->getType($methodCall->getArgs()[0]->value)->getConstantStrings();
+		if (count($argStrings) !== 1) {
+			return null;
+		}
+		$argName = $argStrings[0]->getValue();
+
+		if ($argName === 'command') {
+			$method = $scope->getFunction();
+			if (
+				$method instanceof MethodReflection
+				&& ($method->getName() === 'interact' || $method->getName() === 'initialize')
+				&& in_array('Symfony\Component\Console\Command\Command', $method->getDeclaringClass()->getParentClassesNames(), true)
+			) {
+				return null;
+			}
+		}
+
+		$returnTypes = [];
+		foreach ($this->consoleApplicationResolver->findCommands($classReflection) as $command) {
+			try {
+				$command->mergeApplicationDefinition();
+				$command->getDefinition()->getArgument($argName);
+				$returnTypes[] = true;
+			} catch (InvalidArgumentException $e) {
+				$returnTypes[] = false;
+			}
+		}
+
+		if (count($returnTypes) === 0) {
+			return null;
+		}
+
+		$returnTypes = array_unique($returnTypes);
+		return count($returnTypes) === 1 ? new ConstantBooleanType($returnTypes[0]) : null;
+	}
+
+}
diff --git a/src/Type/Symfony/InputInterfaceHasOptionDynamicReturnTypeExtension.php b/src/Type/Symfony/InputInterfaceHasOptionDynamicReturnTypeExtension.php
new file mode 100644
index 00000000..e4f8b5b1
--- /dev/null
+++ b/src/Type/Symfony/InputInterfaceHasOptionDynamicReturnTypeExtension.php
@@ -0,0 +1,72 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use InvalidArgumentException;
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Symfony\ConsoleApplicationResolver;
+use PHPStan\Type\Constant\ConstantBooleanType;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\Type;
+use function array_unique;
+use function count;
+
+final class InputInterfaceHasOptionDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+
+	private ConsoleApplicationResolver $consoleApplicationResolver;
+
+	public function __construct(ConsoleApplicationResolver $consoleApplicationResolver)
+	{
+		$this->consoleApplicationResolver = $consoleApplicationResolver;
+	}
+
+	public function getClass(): string
+	{
+		return 'Symfony\Component\Console\Input\InputInterface';
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection): bool
+	{
+		return $methodReflection->getName() === 'hasOption';
+	}
+
+	public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
+	{
+		if (!isset($methodCall->getArgs()[0])) {
+			return null;
+		}
+
+		$classReflection = $scope->getClassReflection();
+		if ($classReflection === null) {
+			return null;
+		}
+
+		$optStrings = $scope->getType($methodCall->getArgs()[0]->value)->getConstantStrings();
+		if (count($optStrings) !== 1) {
+			return null;
+		}
+		$optName = $optStrings[0]->getValue();
+
+		$returnTypes = [];
+		foreach ($this->consoleApplicationResolver->findCommands($classReflection) as $command) {
+			try {
+				$command->mergeApplicationDefinition();
+				$command->getDefinition()->getOption($optName);
+				$returnTypes[] = true;
+			} catch (InvalidArgumentException $e) {
+				$returnTypes[] = false;
+			}
+		}
+
+		if (count($returnTypes) === 0) {
+			return null;
+		}
+
+		$returnTypes = array_unique($returnTypes);
+		return count($returnTypes) === 1 ? new ConstantBooleanType($returnTypes[0]) : null;
+	}
+
+}
diff --git a/src/Type/Symfony/KernelInterfaceDynamicReturnTypeExtension.php b/src/Type/Symfony/KernelInterfaceDynamicReturnTypeExtension.php
new file mode 100644
index 00000000..810de2f6
--- /dev/null
+++ b/src/Type/Symfony/KernelInterfaceDynamicReturnTypeExtension.php
@@ -0,0 +1,49 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Type\ArrayType;
+use PHPStan\Type\Constant\ConstantBooleanType;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\IntegerType;
+use PHPStan\Type\StringType;
+use PHPStan\Type\Type;
+
+final class KernelInterfaceDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+
+	public function getClass(): string
+	{
+		return 'Symfony\Component\HttpKernel\KernelInterface';
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection): bool
+	{
+		return $methodReflection->getName() === 'locateResource';
+	}
+
+	public function getTypeFromMethodCall(
+		MethodReflection $methodReflection,
+		MethodCall $methodCall,
+		Scope $scope
+	): ?Type
+	{
+		$firstArgType = isset($methodCall->getArgs()[2]) ? $scope->getType($methodCall->getArgs()[2]->value) : new ConstantBooleanType(true);
+		$isTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($firstArgType)->result;
+		$isFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($firstArgType)->result;
+		$compareTypes = $isTrueType->compareTo($isFalseType);
+
+		if ($compareTypes === $isTrueType) {
+			return new StringType();
+		}
+		if ($compareTypes === $isFalseType) {
+			return new ArrayType(new IntegerType(), new StringType());
+		}
+
+		return null;
+	}
+
+}
diff --git a/src/Type/Symfony/MessengerHandleTraitReturnTypeExtension.php b/src/Type/Symfony/MessengerHandleTraitReturnTypeExtension.php
new file mode 100644
index 00000000..2c7b1fbe
--- /dev/null
+++ b/src/Type/Symfony/MessengerHandleTraitReturnTypeExtension.php
@@ -0,0 +1,89 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use PhpParser\Node\Expr;
+use PhpParser\Node\Expr\MethodCall;
+use PhpParser\Node\Identifier;
+use PHPStan\Analyser\Scope;
+use PHPStan\Symfony\MessageMap;
+use PHPStan\Symfony\MessageMapFactory;
+use PHPStan\Type\ExpressionTypeResolverExtension;
+use PHPStan\Type\Type;
+use function count;
+use function is_null;
+
+final class MessengerHandleTraitReturnTypeExtension implements ExpressionTypeResolverExtension
+{
+
+	private const TRAIT_NAME = 'Symfony\Component\Messenger\HandleTrait';
+	private const TRAIT_METHOD_NAME = 'handle';
+
+	private MessageMapFactory $messageMapFactory;
+
+	private ?MessageMap $messageMap = null;
+
+	public function __construct(MessageMapFactory $symfonyMessageMapFactory)
+	{
+		$this->messageMapFactory = $symfonyMessageMapFactory;
+	}
+
+	public function getType(Expr $expr, Scope $scope): ?Type
+	{
+		if ($this->isSupported($expr, $scope)) {
+			$args = $expr->getArgs();
+			if (count($args) !== 1) {
+				return null;
+			}
+
+			$arg = $args[0]->value;
+			$argClassNames = $scope->getType($arg)->getObjectClassNames();
+
+			if (count($argClassNames) === 1) {
+				$messageMap = $this->getMessageMap();
+				$returnType = $messageMap->getTypeForClass($argClassNames[0]);
+
+				if (!is_null($returnType)) {
+					return $returnType;
+				}
+			}
+		}
+
+		return null;
+	}
+
+	private function getMessageMap(): MessageMap
+	{
+		if ($this->messageMap === null) {
+			$this->messageMap = $this->messageMapFactory->create();
+		}
+
+		return $this->messageMap;
+	}
+
+	/**
+	 * @phpstan-assert-if-true =MethodCall $expr
+	 */
+	private function isSupported(Expr $expr, Scope $scope): bool
+	{
+		if (!($expr instanceof MethodCall) || !($expr->name instanceof Identifier) || $expr->name->name !== self::TRAIT_METHOD_NAME) {
+			return false;
+		}
+
+		if (!$scope->isInClass()) {
+			return false;
+		}
+
+		$reflectionClass = $scope->getClassReflection()->getNativeReflection();
+
+		if (!$reflectionClass->hasMethod(self::TRAIT_METHOD_NAME)) {
+			return false;
+		}
+
+		$methodReflection = $reflectionClass->getMethod(self::TRAIT_METHOD_NAME);
+		$declaringClassReflection = $methodReflection->getBetterReflection()->getDeclaringClass();
+
+		return $declaringClassReflection->getName() === self::TRAIT_NAME;
+	}
+
+}
diff --git a/src/Type/Symfony/OptionTypeSpecifyingExtension.php b/src/Type/Symfony/OptionTypeSpecifyingExtension.php
new file mode 100644
index 00000000..8cd9dbd5
--- /dev/null
+++ b/src/Type/Symfony/OptionTypeSpecifyingExtension.php
@@ -0,0 +1,56 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Analyser\SpecifiedTypes;
+use PHPStan\Analyser\TypeSpecifier;
+use PHPStan\Analyser\TypeSpecifierAwareExtension;
+use PHPStan\Analyser\TypeSpecifierContext;
+use PHPStan\Node\Printer\Printer;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Type\MethodTypeSpecifyingExtension;
+
+final class OptionTypeSpecifyingExtension implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension
+{
+
+	private Printer $printer;
+
+	private TypeSpecifier $typeSpecifier;
+
+	public function __construct(Printer $printer)
+	{
+		$this->printer = $printer;
+	}
+
+	public function getClass(): string
+	{
+		return 'Symfony\Component\Console\Input\InputInterface';
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection, MethodCall $node, TypeSpecifierContext $context): bool
+	{
+		return $methodReflection->getName() === 'hasOption' && !$context->null();
+	}
+
+	public function specifyTypes(MethodReflection $methodReflection, MethodCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
+	{
+		if (!isset($node->getArgs()[0])) {
+			return new SpecifiedTypes();
+		}
+		$argType = $scope->getType($node->getArgs()[0]->value);
+		return $this->typeSpecifier->create(
+			Helper::createMarkerNode($node->var, $argType, $this->printer),
+			$argType,
+			$context,
+			$scope,
+		);
+	}
+
+	public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
+	{
+		$this->typeSpecifier = $typeSpecifier;
+	}
+
+}
diff --git a/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php
new file mode 100644
index 00000000..687b0c33
--- /dev/null
+++ b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php
@@ -0,0 +1,238 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\PhpDoc\TypeStringResolver;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\ShouldNotHappenException;
+use PHPStan\Symfony\ParameterMap;
+use PHPStan\Type\ArrayType;
+use PHPStan\Type\BooleanType;
+use PHPStan\Type\Constant\ConstantArrayType;
+use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
+use PHPStan\Type\Constant\ConstantBooleanType;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\FloatType;
+use PHPStan\Type\GeneralizePrecision;
+use PHPStan\Type\IntegerType;
+use PHPStan\Type\MixedType;
+use PHPStan\Type\NullType;
+use PHPStan\Type\StringType;
+use PHPStan\Type\Type;
+use PHPStan\Type\TypeCombinator;
+use PHPStan\Type\TypeTraverser;
+use PHPStan\Type\UnionType;
+use Symfony\Component\DependencyInjection\EnvVarProcessor;
+use function array_filter;
+use function array_keys;
+use function array_map;
+use function array_values;
+use function class_exists;
+use function count;
+use function in_array;
+use function is_array;
+use function is_int;
+use function is_string;
+use function preg_match;
+use function strlen;
+
+final class ParameterDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+
+	/** @var class-string */
+	private string $className;
+
+	private ?string $methodGet = null;
+
+	private ?string $methodHas = null;
+
+	private bool $constantHassers;
+
+	private ParameterMap $parameterMap;
+
+	private TypeStringResolver $typeStringResolver;
+
+	/**
+	 * @param class-string $className
+	 */
+	public function __construct(
+		string $className,
+		?string $methodGet,
+		?string $methodHas,
+		bool $constantHassers,
+		ParameterMap $symfonyParameterMap,
+		TypeStringResolver $typeStringResolver
+	)
+	{
+		$this->className = $className;
+		$this->methodGet = $methodGet;
+		$this->methodHas = $methodHas;
+		$this->constantHassers = $constantHassers;
+		$this->parameterMap = $symfonyParameterMap;
+		$this->typeStringResolver = $typeStringResolver;
+	}
+
+	public function getClass(): string
+	{
+		return $this->className;
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection): bool
+	{
+		$methods = array_filter([$this->methodGet, $this->methodHas], static fn (?string $method): bool => $method !== null);
+
+		return in_array($methodReflection->getName(), $methods, true);
+	}
+
+	public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
+	{
+		switch ($methodReflection->getName()) {
+			case $this->methodGet:
+				return $this->getGetTypeFromMethodCall($methodReflection, $methodCall, $scope);
+			case $this->methodHas:
+				return $this->getHasTypeFromMethodCall($methodReflection, $methodCall, $scope);
+		}
+		throw new ShouldNotHappenException();
+	}
+
+	private function getGetTypeFromMethodCall(
+		MethodReflection $methodReflection,
+		MethodCall $methodCall,
+		Scope $scope
+	): Type
+	{
+		// We don't use the method's return type because this won't work properly with lowest and
+		// highest versions of Symfony ("mixed" for lowest, "array|bool|float|integer|string|null" for highest).
+		$defaultReturnType = new UnionType([
+			new ArrayType(new MixedType(), new MixedType()),
+			new BooleanType(),
+			new FloatType(),
+			new IntegerType(),
+			new StringType(),
+			new NullType(),
+		]);
+		if (!isset($methodCall->getArgs()[0])) {
+			return $defaultReturnType;
+		}
+
+		$parameterKeys = $this->parameterMap::getParameterKeysFromNode($methodCall->getArgs()[0]->value, $scope);
+		if ($parameterKeys === []) {
+			return $defaultReturnType;
+		}
+
+		$returnTypes = [];
+		foreach ($parameterKeys as $parameterKey) {
+			$parameter = $this->parameterMap->getParameter($parameterKey);
+			if ($parameter === null) {
+				return $defaultReturnType;
+			}
+
+			$returnTypes[] = $this->generalizeTypeFromValue($scope, $parameter->getValue());
+		}
+
+		return TypeCombinator::union(...$returnTypes);
+	}
+
+	/**
+	 * @param array<mixed>|bool|float|int|string $value
+	 */
+	private function generalizeTypeFromValue(Scope $scope, $value): Type
+	{
+		if (is_array($value) && $value !== []) {
+			$hasOnlyStringKey = true;
+			foreach (array_keys($value) as $key) {
+				if (is_int($key)) {
+					$hasOnlyStringKey = false;
+					break;
+				}
+			}
+
+			if ($hasOnlyStringKey) {
+				$keyTypes = [];
+				$valueTypes = [];
+				foreach ($value as $key => $element) {
+					$keyType = $scope->getTypeFromValue($key);
+					$keyStringTypes = $keyType->getConstantStrings();
+					if (count($keyStringTypes) !== 1) {
+						throw new ShouldNotHappenException();
+					}
+					$keyTypes[] = $keyStringTypes[0];
+					$valueTypes[] = $this->generalizeTypeFromValue($scope, $element);
+				}
+
+				return ConstantArrayTypeBuilder::createFromConstantArray(
+					new ConstantArrayType($keyTypes, $valueTypes),
+				)->getArray();
+			}
+
+			return new ArrayType(
+				TypeCombinator::union(...array_map(fn ($item): Type => $this->generalizeTypeFromValue($scope, $item), array_keys($value))),
+				TypeCombinator::union(...array_map(fn ($item): Type => $this->generalizeTypeFromValue($scope, $item), array_values($value))),
+			);
+		}
+
+		if (
+			class_exists(EnvVarProcessor::class)
+			&& is_string($value)
+			&& preg_match('/%env\((.*)\:.*\)%/U', $value, $matches) === 1
+			&& strlen($matches[0]) === strlen($value)
+		) {
+			$providedTypes = EnvVarProcessor::getProvidedTypes();
+
+			return $this->typeStringResolver->resolve($providedTypes[$matches[1]] ?? 'bool|int|float|string|array');
+		}
+
+		return $this->generalizeType($scope->getTypeFromValue($value));
+	}
+
+	private function generalizeType(Type $type): Type
+	{
+		return TypeTraverser::map($type, function (Type $type, callable $traverse): Type {
+			if ($type instanceof ConstantArrayType) {
+				if (count($type->getValueTypes()) === 0) {
+					return new ArrayType(new MixedType(), new MixedType());
+				}
+				return new ArrayType($this->generalizeType($type->getKeyType()), $this->generalizeType($type->getItemType()));
+			}
+			if ($type->isConstantValue()->yes()) {
+				return $type->generalize(GeneralizePrecision::lessSpecific());
+			}
+			return $traverse($type);
+		});
+	}
+
+	private function getHasTypeFromMethodCall(
+		MethodReflection $methodReflection,
+		MethodCall $methodCall,
+		Scope $scope
+	): ?Type
+	{
+		if (!isset($methodCall->getArgs()[0]) || !$this->constantHassers) {
+			return null;
+		}
+
+		$parameterKeys = $this->parameterMap::getParameterKeysFromNode($methodCall->getArgs()[0]->value, $scope);
+		if ($parameterKeys === []) {
+			return null;
+		}
+
+		$has = null;
+		foreach ($parameterKeys as $parameterKey) {
+			$parameter = $this->parameterMap->getParameter($parameterKey);
+
+			if ($has === null) {
+				$has = $parameter !== null;
+			} elseif (
+				($has === true && $parameter === null)
+				|| ($has === false && $parameter !== null)
+			) {
+				return null;
+			}
+		}
+
+		return new ConstantBooleanType($has);
+	}
+
+}
diff --git a/src/Type/Symfony/RequestDynamicReturnTypeExtension.php b/src/Type/Symfony/RequestDynamicReturnTypeExtension.php
index 72991824..fd0a3a00 100644
--- a/src/Type/Symfony/RequestDynamicReturnTypeExtension.php
+++ b/src/Type/Symfony/RequestDynamicReturnTypeExtension.php
@@ -5,7 +5,6 @@
 use PhpParser\Node\Expr\MethodCall;
 use PHPStan\Analyser\Scope;
 use PHPStan\Reflection\MethodReflection;
-use PHPStan\Reflection\ParametersAcceptorSelector;
 use PHPStan\Type\Constant\ConstantBooleanType;
 use PHPStan\Type\DynamicMethodReturnTypeExtension;
 use PHPStan\Type\ResourceType;
@@ -29,15 +28,15 @@ public function getTypeFromMethodCall(
 		MethodReflection $methodReflection,
 		MethodCall $methodCall,
 		Scope $scope
-	): Type
+	): ?Type
 	{
-		if (!isset($methodCall->args[0])) {
+		if (!isset($methodCall->getArgs()[0])) {
 			return new StringType();
 		}
 
-		$argType = $scope->getType($methodCall->args[0]->value);
-		$isTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($argType);
-		$isFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($argType);
+		$argType = $scope->getType($methodCall->getArgs()[0]->value);
+		$isTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($argType)->result;
+		$isFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($argType)->result;
 		$compareTypes = $isTrueType->compareTo($isFalseType);
 		if ($compareTypes === $isTrueType) {
 			return new ResourceType();
@@ -46,7 +45,7 @@ public function getTypeFromMethodCall(
 			return new StringType();
 		}
 
-		return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
+		return null;
 	}
 
 }
diff --git a/src/Type/Symfony/RequestTypeSpecifyingExtension.php b/src/Type/Symfony/RequestTypeSpecifyingExtension.php
new file mode 100644
index 00000000..40d38493
--- /dev/null
+++ b/src/Type/Symfony/RequestTypeSpecifyingExtension.php
@@ -0,0 +1,57 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Analyser\SpecifiedTypes;
+use PHPStan\Analyser\TypeSpecifier;
+use PHPStan\Analyser\TypeSpecifierAwareExtension;
+use PHPStan\Analyser\TypeSpecifierContext;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Reflection\ParametersAcceptorSelector;
+use PHPStan\Type\MethodTypeSpecifyingExtension;
+use PHPStan\Type\TypeCombinator;
+
+final class RequestTypeSpecifyingExtension implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension
+{
+
+	private const REQUEST_CLASS = 'Symfony\Component\HttpFoundation\Request';
+	private const HAS_METHOD_NAME = 'hasSession';
+	private const GET_METHOD_NAME = 'getSession';
+
+	private TypeSpecifier $typeSpecifier;
+
+	public function getClass(): string
+	{
+		return self::REQUEST_CLASS;
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection, MethodCall $node, TypeSpecifierContext $context): bool
+	{
+		return $methodReflection->getName() === self::HAS_METHOD_NAME && !$context->null();
+	}
+
+	public function specifyTypes(MethodReflection $methodReflection, MethodCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
+	{
+		$methodVariants = $methodReflection->getDeclaringClass()->getNativeMethod(self::GET_METHOD_NAME)->getVariants();
+		$returnType = ParametersAcceptorSelector::selectFromArgs($scope, $node->getArgs(), $methodVariants)->getReturnType();
+
+		if (!TypeCombinator::containsNull($returnType)) {
+			return new SpecifiedTypes();
+		}
+
+		return $this->typeSpecifier->create(
+			new MethodCall($node->var, self::GET_METHOD_NAME),
+			TypeCombinator::removeNull($returnType),
+			$context,
+			$scope,
+		);
+	}
+
+	public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
+	{
+		$this->typeSpecifier = $typeSpecifier;
+	}
+
+}
diff --git a/src/Type/Symfony/ResponseHeaderBagDynamicReturnTypeExtension.php b/src/Type/Symfony/ResponseHeaderBagDynamicReturnTypeExtension.php
new file mode 100644
index 00000000..5a95bf98
--- /dev/null
+++ b/src/Type/Symfony/ResponseHeaderBagDynamicReturnTypeExtension.php
@@ -0,0 +1,65 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use PhpParser\Node\Expr\ClassConstFetch;
+use PhpParser\Node\Expr\MethodCall;
+use PhpParser\Node\Identifier;
+use PhpParser\Node\Name;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Type\ArrayType;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\IntegerType;
+use PHPStan\Type\ObjectType;
+use PHPStan\Type\StringType;
+use PHPStan\Type\Type;
+use Symfony\Component\HttpFoundation\Cookie;
+use Symfony\Component\HttpFoundation\ResponseHeaderBag;
+
+final class ResponseHeaderBagDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+
+	public function getClass(): string
+	{
+		return ResponseHeaderBag::class;
+	}
+
+	public function isMethodSupported(MethodReflection $methodReflection): bool
+	{
+		return $methodReflection->getName() === 'getCookies';
+	}
+
+	public function getTypeFromMethodCall(
+		MethodReflection $methodReflection,
+		MethodCall $methodCall,
+		Scope $scope
+	): Type
+	{
+		if (isset($methodCall->getArgs()[0])) {
+			$node = $methodCall->getArgs()[0]->value;
+
+			if (
+				$node instanceof ClassConstFetch &&
+				$node->class instanceof Name &&
+				$node->name instanceof Identifier &&
+				$node->class->toString() === ResponseHeaderBag::class &&
+				$node->name->name === 'COOKIES_ARRAY'
+			) {
+				return new ArrayType(
+					new StringType(),
+					new ArrayType(
+						new StringType(),
+						new ArrayType(
+							new StringType(),
+							new ObjectType(Cookie::class),
+						),
+					),
+				);
+			}
+		}
+
+		return new ArrayType(new IntegerType(), new ObjectType(Cookie::class));
+	}
+
+}
diff --git a/src/Type/Symfony/SerializerInterfaceDynamicReturnTypeExtension.php b/src/Type/Symfony/SerializerDynamicReturnTypeExtension.php
old mode 100644
new mode 100755
similarity index 51%
rename from src/Type/Symfony/SerializerInterfaceDynamicReturnTypeExtension.php
rename to src/Type/Symfony/SerializerDynamicReturnTypeExtension.php
index b6f7e33b..84f256e4
--- a/src/Type/Symfony/SerializerInterfaceDynamicReturnTypeExtension.php
+++ b/src/Type/Symfony/SerializerDynamicReturnTypeExtension.php
@@ -6,35 +6,58 @@
 use PHPStan\Analyser\Scope;
 use PHPStan\Reflection\MethodReflection;
 use PHPStan\Type\ArrayType;
-use PHPStan\Type\Constant\ConstantStringType;
 use PHPStan\Type\DynamicMethodReturnTypeExtension;
 use PHPStan\Type\MixedType;
 use PHPStan\Type\ObjectType;
 use PHPStan\Type\Type;
+use PHPStan\Type\TypeCombinator;
+use function count;
+use function substr;
 
-class SerializerInterfaceDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
+class SerializerDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
 {
 
+	/** @var class-string */
+	private string $class;
+
+	private string $method;
+
+	/**
+	 * @param class-string $class
+	 */
+	public function __construct(string $class, string $method)
+	{
+		$this->class = $class;
+		$this->method = $method;
+	}
+
 	public function getClass(): string
 	{
-		return 'Symfony\Component\Serializer\SerializerInterface';
+		return $this->class;
 	}
 
 	public function isMethodSupported(MethodReflection $methodReflection): bool
 	{
-		return $methodReflection->getName() === 'deserialize';
+		return $methodReflection->getName() === $this->method;
 	}
 
 	public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
 	{
-		$argType = $scope->getType($methodCall->args[1]->value);
-		if (!$argType instanceof ConstantStringType) {
+		if (!isset($methodCall->getArgs()[1])) {
 			return new MixedType();
 		}
 
-		$objectName = $argType->getValue();
+		$argType = $scope->getType($methodCall->getArgs()[1]->value);
+		if (count($argType->getConstantStrings()) === 0) {
+			return new MixedType();
+		}
+
+		$types = [];
+		foreach ($argType->getConstantStrings() as $constantString) {
+			$types[] = $this->getType($constantString->getValue());
+		}
 
-		return $this->getType($objectName);
+		return TypeCombinator::union(...$types);
 	}
 
 	private function getType(string $objectName): Type
diff --git a/src/Type/Symfony/ServiceDynamicReturnTypeExtension.php b/src/Type/Symfony/ServiceDynamicReturnTypeExtension.php
index 48e8aacf..0667d30c 100644
--- a/src/Type/Symfony/ServiceDynamicReturnTypeExtension.php
+++ b/src/Type/Symfony/ServiceDynamicReturnTypeExtension.php
@@ -5,32 +5,47 @@
 use PhpParser\Node\Expr\MethodCall;
 use PHPStan\Analyser\Scope;
 use PHPStan\Reflection\MethodReflection;
-use PHPStan\Reflection\ParametersAcceptorSelector;
 use PHPStan\ShouldNotHappenException;
+use PHPStan\Symfony\ParameterMap;
+use PHPStan\Symfony\ServiceDefinition;
 use PHPStan\Symfony\ServiceMap;
 use PHPStan\Type\Constant\ConstantBooleanType;
 use PHPStan\Type\DynamicMethodReturnTypeExtension;
 use PHPStan\Type\ObjectType;
 use PHPStan\Type\Type;
+use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
+use function class_exists;
 use function in_array;
+use function is_string;
 
 final class ServiceDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
 {
 
-	/** @var string */
-	private $className;
+	/** @var class-string */
+	private string $className;
 
-	/** @var bool */
-	private $constantHassers;
+	private bool $constantHassers;
 
-	/** @var \PHPStan\Symfony\ServiceMap */
-	private $symfonyServiceMap;
+	private ServiceMap $serviceMap;
 
-	public function __construct(string $className, bool $constantHassers, ServiceMap $symfonyServiceMap)
+	private ParameterMap $parameterMap;
+
+	private ?ParameterBag $parameterBag = null;
+
+	/**
+	 * @param class-string $className
+	 */
+	public function __construct(
+		string $className,
+		bool $constantHassers,
+		ServiceMap $symfonyServiceMap,
+		ParameterMap $symfonyParameterMap
+	)
 	{
 		$this->className = $className;
 		$this->constantHassers = $constantHassers;
-		$this->symfonyServiceMap = $symfonyServiceMap;
+		$this->serviceMap = $symfonyServiceMap;
+		$this->parameterMap = $symfonyParameterMap;
 	}
 
 	public function getClass(): string
@@ -43,7 +58,7 @@ public function isMethodSupported(MethodReflection $methodReflection): bool
 		return in_array($methodReflection->getName(), ['get', 'has'], true);
 	}
 
-	public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
+	public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
 	{
 		switch ($methodReflection->getName()) {
 			case 'get':
@@ -58,42 +73,84 @@ private function getGetTypeFromMethodCall(
 		MethodReflection $methodReflection,
 		MethodCall $methodCall,
 		Scope $scope
-	): Type
+	): ?Type
 	{
-		$returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
-		if (!isset($methodCall->args[0])) {
-			return $returnType;
+		if (!isset($methodCall->getArgs()[0])) {
+			return null;
+		}
+
+		$parameterBag = $this->tryGetParameterBag();
+		if ($parameterBag === null) {
+			return null;
 		}
 
-		$serviceId = ServiceMap::getServiceIdFromNode($methodCall->args[0]->value, $scope);
+		$serviceId = $this->serviceMap::getServiceIdFromNode($methodCall->getArgs()[0]->value, $scope);
 		if ($serviceId !== null) {
-			$service = $this->symfonyServiceMap->getService($serviceId);
-			if ($service !== null && !$service->isSynthetic()) {
-				return new ObjectType($service->getClass() ?? $serviceId);
+			$service = $this->serviceMap->getService($serviceId);
+			if ($service !== null && (!$service->isSynthetic() || $service->getClass() !== null)) {
+				return new ObjectType($this->determineServiceClass($parameterBag, $service) ?? $serviceId);
 			}
 		}
 
-		return $returnType;
+		return null;
+	}
+
+	private function tryGetParameterBag(): ?ParameterBag
+	{
+		if ($this->parameterBag !== null) {
+			return $this->parameterBag;
+		}
+
+		return $this->parameterBag = $this->tryCreateParameterBag();
+	}
+
+	private function tryCreateParameterBag(): ?ParameterBag
+	{
+		if (!class_exists(ParameterBag::class)) {
+			return null;
+		}
+
+		$parameters = [];
+
+		foreach ($this->parameterMap->getParameters() as $parameterDefinition) {
+			$parameters[$parameterDefinition->getKey()] = $parameterDefinition->getValue();
+		}
+
+		return new ParameterBag($parameters);
 	}
 
 	private function getHasTypeFromMethodCall(
 		MethodReflection $methodReflection,
 		MethodCall $methodCall,
 		Scope $scope
-	): Type
+	): ?Type
 	{
-		$returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
-		if (!isset($methodCall->args[0]) || !$this->constantHassers) {
-			return $returnType;
+		if (!isset($methodCall->getArgs()[0]) || !$this->constantHassers) {
+			return null;
 		}
 
-		$serviceId = ServiceMap::getServiceIdFromNode($methodCall->args[0]->value, $scope);
+		$serviceId = $this->serviceMap::getServiceIdFromNode($methodCall->getArgs()[0]->value, $scope);
 		if ($serviceId !== null) {
-			$service = $this->symfonyServiceMap->getService($serviceId);
+			$service = $this->serviceMap->getService($serviceId);
 			return new ConstantBooleanType($service !== null && $service->isPublic());
 		}
 
-		return $returnType;
+		return null;
+	}
+
+	private function determineServiceClass(ParameterBag $parameterBag, ServiceDefinition $service): ?string
+	{
+		$class = $service->getClass();
+		if ($class === null) {
+			return null;
+		}
+
+		$value = $parameterBag->resolveValue($class);
+		if (!is_string($value)) {
+			return null;
+		}
+
+		return $value;
 	}
 
 }
diff --git a/src/Type/Symfony/ServiceTypeSpecifyingExtension.php b/src/Type/Symfony/ServiceTypeSpecifyingExtension.php
index e9946387..dd767ccb 100644
--- a/src/Type/Symfony/ServiceTypeSpecifyingExtension.php
+++ b/src/Type/Symfony/ServiceTypeSpecifyingExtension.php
@@ -3,28 +3,29 @@
 namespace PHPStan\Type\Symfony;
 
 use PhpParser\Node\Expr\MethodCall;
-use PhpParser\PrettyPrinter\Standard;
 use PHPStan\Analyser\Scope;
 use PHPStan\Analyser\SpecifiedTypes;
 use PHPStan\Analyser\TypeSpecifier;
 use PHPStan\Analyser\TypeSpecifierAwareExtension;
 use PHPStan\Analyser\TypeSpecifierContext;
+use PHPStan\Node\Printer\Printer;
 use PHPStan\Reflection\MethodReflection;
 use PHPStan\Type\MethodTypeSpecifyingExtension;
 
 final class ServiceTypeSpecifyingExtension implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension
 {
 
-	/** @var string */
-	private $className;
+	/** @var class-string */
+	private string $className;
 
-	/** @var \PhpParser\PrettyPrinter\Standard */
-	private $printer;
+	private Printer $printer;
 
-	/** @var \PHPStan\Analyser\TypeSpecifier */
-	private $typeSpecifier;
+	private TypeSpecifier $typeSpecifier;
 
-	public function __construct(string $className, Standard $printer)
+	/**
+	 * @param class-string $className
+	 */
+	public function __construct(string $className, Printer $printer)
 	{
 		$this->className = $className;
 		$this->printer = $printer;
@@ -42,14 +43,15 @@ public function isMethodSupported(MethodReflection $methodReflection, MethodCall
 
 	public function specifyTypes(MethodReflection $methodReflection, MethodCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
 	{
-		if (!isset($node->args[0])) {
+		if (!isset($node->getArgs()[0])) {
 			return new SpecifiedTypes();
 		}
-		$argType = $scope->getType($node->args[0]->value);
+		$argType = $scope->getType($node->getArgs()[0]->value);
 		return $this->typeSpecifier->create(
 			Helper::createMarkerNode($node->var, $argType, $this->printer),
 			$argType,
-			$context
+			$context,
+			$scope,
 		);
 	}
 
diff --git a/stubs/Psr/Cache/CacheException.stub b/stubs/Psr/Cache/CacheException.stub
new file mode 100644
index 00000000..1be3e49b
--- /dev/null
+++ b/stubs/Psr/Cache/CacheException.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Psr\Cache;
+
+interface CacheException extends \Throwable
+{
+}
diff --git a/stubs/Psr/Cache/CacheItemInterface.stub b/stubs/Psr/Cache/CacheItemInterface.stub
new file mode 100644
index 00000000..f595213d
--- /dev/null
+++ b/stubs/Psr/Cache/CacheItemInterface.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Psr\Cache;
+
+interface CacheItemInterface
+{
+}
diff --git a/stubs/Psr/Cache/InvalidArgumentException.stub b/stubs/Psr/Cache/InvalidArgumentException.stub
new file mode 100644
index 00000000..b562bfc0
--- /dev/null
+++ b/stubs/Psr/Cache/InvalidArgumentException.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Psr\Cache;
+
+interface InvalidArgumentException extends CacheException
+{
+}
diff --git a/stubs/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.stub b/stubs/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.stub
new file mode 100644
index 00000000..075dce6d
--- /dev/null
+++ b/stubs/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.stub
@@ -0,0 +1,24 @@
+<?php
+
+namespace Symfony\Bundle\FrameworkBundle\Controller;
+
+use Symfony\Component\Form\FormInterface;
+use Symfony\Component\Form\FormTypeInterface;
+use Symfony\Contracts\Service\ServiceSubscriberInterface;
+
+abstract class AbstractController implements ServiceSubscriberInterface
+{
+	/**
+	 * @template TFormType of FormTypeInterface<TData>
+	 * @template TData
+	 *
+	 * @param class-string<TFormType> $type
+	 * @param TData                   $data
+	 * @param array<string, mixed>    $options
+	 *
+	 * @phpstan-return ($data is null ? FormInterface<null|TData> : FormInterface<TData>)
+	 */
+	protected function createForm(string $type, $data = null, array $options = []): FormInterface
+	{
+	}
+}
diff --git a/stubs/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.stub b/stubs/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.stub
new file mode 100644
index 00000000..8ca4b331
--- /dev/null
+++ b/stubs/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.stub
@@ -0,0 +1,16 @@
+<?php
+
+namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
+
+use Symfony\Component\Config\Definition\Builder\NodeDefinition;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+interface AuthenticatorFactoryInterface
+{
+    /**
+     * @param array<string, mixed> $config
+     *
+     * @return string|string[]
+     */
+    public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId);
+}
diff --git a/stubs/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FirewallListenerFactoryInterface.stub b/stubs/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FirewallListenerFactoryInterface.stub
new file mode 100644
index 00000000..5c6b54fd
--- /dev/null
+++ b/stubs/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FirewallListenerFactoryInterface.stub
@@ -0,0 +1,15 @@
+<?php
+
+namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+interface FirewallListenerFactoryInterface
+{
+    /**
+     * @param array<string, mixed> $config
+     *
+     * @return string[]
+     */
+    public function createListeners(ContainerBuilder $container, string $firewallName, array $config): array;
+}
diff --git a/stubs/Symfony/Component/Console/Command.stub b/stubs/Symfony/Component/Console/Command.stub
new file mode 100644
index 00000000..c656ef6a
--- /dev/null
+++ b/stubs/Symfony/Component/Console/Command.stub
@@ -0,0 +1,17 @@
+<?php
+
+namespace Symfony\Component\Console\Command;
+
+use Symfony\Component\Console\Exception\InvalidArgumentException;
+use Symfony\Component\Console\Exception\LogicException;
+
+class Command
+{
+    /**
+     * @return \Symfony\Component\Console\Helper\HelperInterface
+     *
+     * @throws LogicException
+     * @throws InvalidArgumentException
+     */
+    public function getHelper(string $name);
+}
diff --git a/stubs/Symfony/Component/Console/Exception/ExceptionInterface.stub b/stubs/Symfony/Component/Console/Exception/ExceptionInterface.stub
new file mode 100644
index 00000000..6e3c1226
--- /dev/null
+++ b/stubs/Symfony/Component/Console/Exception/ExceptionInterface.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\Console\Exception;
+
+interface ExceptionInterface extends \Throwable
+{
+}
diff --git a/stubs/Symfony/Component/Console/Exception/InvalidArgumentException.stub b/stubs/Symfony/Component/Console/Exception/InvalidArgumentException.stub
new file mode 100644
index 00000000..1fe7e7bd
--- /dev/null
+++ b/stubs/Symfony/Component/Console/Exception/InvalidArgumentException.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\Console\Exception;
+
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/stubs/Symfony/Component/Console/Exception/LogicException.stub b/stubs/Symfony/Component/Console/Exception/LogicException.stub
new file mode 100644
index 00000000..a959b8e9
--- /dev/null
+++ b/stubs/Symfony/Component/Console/Exception/LogicException.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\Console\Exception;
+
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/stubs/Symfony/Component/Console/Helper/HelperInterface.stub b/stubs/Symfony/Component/Console/Helper/HelperInterface.stub
new file mode 100644
index 00000000..757a68b0
--- /dev/null
+++ b/stubs/Symfony/Component/Console/Helper/HelperInterface.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\Console\Helper;
+
+interface HelperInterface
+{
+}
diff --git a/stubs/Symfony/Component/Console/Output/OutputInterface.stub b/stubs/Symfony/Component/Console/Output/OutputInterface.stub
new file mode 100644
index 00000000..6e4acd3b
--- /dev/null
+++ b/stubs/Symfony/Component/Console/Output/OutputInterface.stub
@@ -0,0 +1,28 @@
+<?php
+
+namespace Symfony\Component\Console\Output;
+
+interface OutputInterface
+{
+    public const VERBOSITY_QUIET = 16;
+    public const VERBOSITY_NORMAL = 32;
+    public const VERBOSITY_VERBOSE = 64;
+    public const VERBOSITY_VERY_VERBOSE = 128;
+    public const VERBOSITY_DEBUG = 256;
+
+    public const OUTPUT_NORMAL = 1;
+    public const OUTPUT_RAW = 2;
+    public const OUTPUT_PLAIN = 4;
+
+    /**
+     * @param string|iterable<string> $messages
+     * @param int-mask-of<self::VERBOSITY_*|self::OUTPUT_*> $options
+     */
+    public function write($messages, bool $newline = false, int $options = 0): void;
+
+    /**
+     * @param string|iterable<string> $messages
+     * @param int-mask-of<self::VERBOSITY_*|self::OUTPUT_*> $options
+     */
+    public function writeln($messages, int $options = 0): void;
+}
diff --git a/stubs/Symfony/Component/DependencyInjection/ContainerBuilder.stub b/stubs/Symfony/Component/DependencyInjection/ContainerBuilder.stub
new file mode 100644
index 00000000..457734fc
--- /dev/null
+++ b/stubs/Symfony/Component/DependencyInjection/ContainerBuilder.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\DependencyInjection;
+
+class ContainerBuilder
+{
+}
diff --git a/stubs/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.stub b/stubs/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.stub
new file mode 100644
index 00000000..b1229723
--- /dev/null
+++ b/stubs/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.stub
@@ -0,0 +1,15 @@
+<?php
+
+namespace Symfony\Component\DependencyInjection\Extension;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+interface ExtensionInterface
+{
+    /**
+     * @param array<mixed> $configs
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function load(array $configs, ContainerBuilder $container): void;
+}
diff --git a/stubs/Symfony/Component/EventDispatcher/EventDispatcherInterface.stub b/stubs/Symfony/Component/EventDispatcher/EventDispatcherInterface.stub
new file mode 100644
index 00000000..a58e43ca
--- /dev/null
+++ b/stubs/Symfony/Component/EventDispatcher/EventDispatcherInterface.stub
@@ -0,0 +1,14 @@
+<?php
+
+namespace Symfony\Contracts\EventDispatcher;
+
+interface EventDispatcherInterface
+{
+    /**
+     * @template TEvent of object
+     * @param TEvent $event
+     *
+     * @return TEvent
+     */
+    public function dispatch(object $event, ?string $eventName = null): object;
+}
diff --git a/stubs/Symfony/Component/EventDispatcher/EventSubscriberInterface.stub b/stubs/Symfony/Component/EventDispatcher/EventSubscriberInterface.stub
new file mode 100644
index 00000000..62474d10
--- /dev/null
+++ b/stubs/Symfony/Component/EventDispatcher/EventSubscriberInterface.stub
@@ -0,0 +1,11 @@
+<?php
+
+namespace Symfony\Component\EventDispatcher;
+
+interface EventSubscriberInterface
+{
+    /**
+     * @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
+     */
+    public static function getSubscribedEvents();
+}
diff --git a/stubs/Symfony/Component/EventDispatcher/GenericEvent.stub b/stubs/Symfony/Component/EventDispatcher/GenericEvent.stub
new file mode 100644
index 00000000..6e57a7df
--- /dev/null
+++ b/stubs/Symfony/Component/EventDispatcher/GenericEvent.stub
@@ -0,0 +1,11 @@
+<?php
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * @implements \IteratorAggregate<string, mixed>
+ */
+class GenericEvent implements \IteratorAggregate
+{
+
+}
diff --git a/stubs/Symfony/Component/Form/AbstractType.stub b/stubs/Symfony/Component/Form/AbstractType.stub
new file mode 100644
index 00000000..e99b746c
--- /dev/null
+++ b/stubs/Symfony/Component/Form/AbstractType.stub
@@ -0,0 +1,31 @@
+<?php
+
+namespace Symfony\Component\Form;
+
+/**
+ * @template TData
+ *
+ * @implements FormTypeInterface<TData>
+ */
+abstract class AbstractType implements FormTypeInterface
+{
+
+	/**
+	 * @param FormBuilderInterface<TData|null> $builder
+	 * @param array<string, mixed> $options
+	 */
+	public function buildForm(FormBuilderInterface $builder, array $options): void;
+
+	/**
+	 * @param FormInterface<TData> $form
+	 * @param array<string, mixed> $options
+	 */
+	public function buildView(FormView $view, FormInterface $form, array $options): void;
+
+	/**
+	 * @param FormInterface<TData> $form
+	 * @param array<string, mixed> $options
+	 */
+	public function finishView(FormView $view, FormInterface $form, array $options): void;
+
+}
diff --git a/stubs/Symfony/Component/Form/ChoiceList/Loader/ChoiceLoaderInterface.stub b/stubs/Symfony/Component/Form/ChoiceList/Loader/ChoiceLoaderInterface.stub
new file mode 100644
index 00000000..0388b898
--- /dev/null
+++ b/stubs/Symfony/Component/Form/ChoiceList/Loader/ChoiceLoaderInterface.stub
@@ -0,0 +1,22 @@
+<?php
+
+namespace Symfony\Component\Form\ChoiceList\Loader;
+
+interface ChoiceLoaderInterface
+{
+    /**
+     * @param array<string> $values
+     * @param callable|null $value
+     *
+     * @return array<mixed>
+     */
+    public function loadChoicesForValues(array $values, $value = null);
+
+    /**
+     * @param array<mixed> $choices
+     * @param callable|null $value
+     *
+     * @return array<string>
+     */
+    public function loadValuesForChoices(array $choices, $value = null);
+}
diff --git a/stubs/Symfony/Component/Form/DataTransformerInterface.stub b/stubs/Symfony/Component/Form/DataTransformerInterface.stub
new file mode 100644
index 00000000..393fa803
--- /dev/null
+++ b/stubs/Symfony/Component/Form/DataTransformerInterface.stub
@@ -0,0 +1,30 @@
+<?php
+
+namespace Symfony\Component\Form;
+
+use Symfony\Component\Form\Exception\TransformationFailedException;
+
+/**
+ * @phpstan-template T
+ * @phpstan-template R
+ */
+interface DataTransformerInterface
+{
+    /**
+     * @phpstan-param T|null $value The value in the original representation
+     *
+     * @phpstan-return R|null The value in the transformed representation
+     *
+     * @throws TransformationFailedException
+     */
+    public function transform($value);
+
+    /**
+     * @phpstan-param R|null $value The value in the transformed representation
+     *
+     * @phpstan-return T|null The value in the original representation
+     *
+     * @throws TransformationFailedException
+     */
+    public function reverseTransform($value);
+}
diff --git a/stubs/Symfony/Component/Form/Exception/ExceptionInterface.stub b/stubs/Symfony/Component/Form/Exception/ExceptionInterface.stub
new file mode 100644
index 00000000..306015dc
--- /dev/null
+++ b/stubs/Symfony/Component/Form/Exception/ExceptionInterface.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\Form\Exception;
+
+interface ExceptionInterface extends \Throwable
+{
+}
diff --git a/stubs/Symfony/Component/Form/Exception/RuntimeException.stub b/stubs/Symfony/Component/Form/Exception/RuntimeException.stub
new file mode 100644
index 00000000..8c37402d
--- /dev/null
+++ b/stubs/Symfony/Component/Form/Exception/RuntimeException.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\Form\Exception;
+
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/stubs/Symfony/Component/Form/Exception/TransformationFailedException.stub b/stubs/Symfony/Component/Form/Exception/TransformationFailedException.stub
new file mode 100644
index 00000000..8ec468c1
--- /dev/null
+++ b/stubs/Symfony/Component/Form/Exception/TransformationFailedException.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\Form\Exception;
+
+class TransformationFailedException extends RuntimeException
+{
+}
diff --git a/stubs/Symfony/Component/Form/FormBuilderInterface.stub b/stubs/Symfony/Component/Form/FormBuilderInterface.stub
new file mode 100644
index 00000000..fe578c50
--- /dev/null
+++ b/stubs/Symfony/Component/Form/FormBuilderInterface.stub
@@ -0,0 +1,19 @@
+<?php
+
+namespace Symfony\Component\Form;
+
+/**
+ * @template TData
+ *
+ * @extends \Traversable<int, \Symfony\Component\Form\FormBuilderInterface<mixed>>
+ * @extends FormConfigBuilderInterface<TData>
+ */
+interface FormBuilderInterface extends \Traversable, \Countable, FormConfigBuilderInterface
+{
+
+    /**
+     * @return FormInterface<TData|null>
+     */
+    public function getForm(): FormInterface;
+
+}
diff --git a/stubs/Symfony/Component/Form/FormConfigBuilderInterface.stub b/stubs/Symfony/Component/Form/FormConfigBuilderInterface.stub
new file mode 100644
index 00000000..a167ce43
--- /dev/null
+++ b/stubs/Symfony/Component/Form/FormConfigBuilderInterface.stub
@@ -0,0 +1,13 @@
+<?php
+
+namespace Symfony\Component\Form;
+
+/**
+ * @template TData
+ *
+ * @extends FormConfigInterface<TData>
+ */
+interface FormConfigBuilderInterface extends FormConfigInterface
+{
+
+}
diff --git a/stubs/Symfony/Component/Form/FormConfigInterface.stub b/stubs/Symfony/Component/Form/FormConfigInterface.stub
new file mode 100644
index 00000000..942d467b
--- /dev/null
+++ b/stubs/Symfony/Component/Form/FormConfigInterface.stub
@@ -0,0 +1,16 @@
+<?php
+
+namespace Symfony\Component\Form;
+
+/**
+ * @template TData
+ */
+interface FormConfigInterface
+{
+
+    /**
+     * @return TData
+     */
+    public function getData(): mixed;
+
+}
diff --git a/stubs/Symfony/Component/Form/FormFactoryInterface.stub b/stubs/Symfony/Component/Form/FormFactoryInterface.stub
new file mode 100644
index 00000000..a2221ec0
--- /dev/null
+++ b/stubs/Symfony/Component/Form/FormFactoryInterface.stub
@@ -0,0 +1,36 @@
+<?php
+
+namespace Symfony\Component\Form;
+
+use Symfony\Component\Form\Extension\Core\Type\FormType;
+
+interface FormFactoryInterface
+{
+	/**
+	 * @template TFormType of FormTypeInterface<TData>
+	 * @template TData
+	 *
+	 * @param class-string<TFormType> $type
+	 * @param TData                   $data
+	 * @param array<string, mixed>    $options
+	 *
+	 * @phpstan-return ($data is null ? FormInterface<null|TData> : FormInterface<TData>)
+	 *
+	 * @throws \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+	 */
+	public function create(string $type = FormType::class, $data = null, array $options = []): FormInterface;
+
+	/**
+	 * @template TFormType of FormTypeInterface<TData>
+	 * @template TData
+	 *
+	  * @param class-string<TFormType> $type
+	 * @param TData                   $data
+	 * @param array<string, mixed>    $options
+	 *
+	 * @phpstan-return ($data is null ? FormInterface<null|TData> : FormInterface<TData>)
+	 *
+	 * @throws \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+	 */
+	public function createNamed(string $name, string $type = FormType::class, $data = null, array $options = []): FormInterface;
+}
diff --git a/stubs/Symfony/Component/Form/FormInterface.stub b/stubs/Symfony/Component/Form/FormInterface.stub
new file mode 100644
index 00000000..4bd21229
--- /dev/null
+++ b/stubs/Symfony/Component/Form/FormInterface.stub
@@ -0,0 +1,24 @@
+<?php
+
+namespace Symfony\Component\Form;
+
+/**
+ * @template TData
+ *
+ * @extends \ArrayAccess<string, \Symfony\Component\Form\FormInterface<mixed>>
+ * @extends \Traversable<string, \Symfony\Component\Form\FormInterface<mixed>>
+ */
+interface FormInterface extends \ArrayAccess, \Traversable, \Countable
+{
+    /**
+     * @param TData $modelData
+     *
+     * @return $this
+     */
+    public function setData($modelData): FormInterface;
+
+    /**
+     * @return TData
+     */
+    public function getData();
+}
diff --git a/stubs/Symfony/Component/Form/FormTypeExtensionInterface.stub b/stubs/Symfony/Component/Form/FormTypeExtensionInterface.stub
new file mode 100644
index 00000000..a03d5e1c
--- /dev/null
+++ b/stubs/Symfony/Component/Form/FormTypeExtensionInterface.stub
@@ -0,0 +1,27 @@
+<?php
+
+namespace Symfony\Component\Form;
+
+/**
+ * @template TData
+ */
+interface FormTypeExtensionInterface
+{
+    /**
+     * @param FormBuilderInterface<TData|null> $builder
+     * @param array<string, mixed> $options
+     */
+    public function buildForm(FormBuilderInterface $builder, array $options): void;
+
+    /**
+     * @phpstan-param FormInterface<TData> $form
+     * @param array<string, mixed> $options
+     */
+    public function buildView(FormView $view, FormInterface $form, array $options): void;
+
+    /**
+     * @phpstan-param FormInterface<TData> $form
+     * @param array<string, mixed> $options
+     */
+    public function finishView(FormView $view, FormInterface $form, array $options): void;
+}
diff --git a/stubs/Symfony/Component/Form/FormTypeInterface.stub b/stubs/Symfony/Component/Form/FormTypeInterface.stub
new file mode 100644
index 00000000..8536656a
--- /dev/null
+++ b/stubs/Symfony/Component/Form/FormTypeInterface.stub
@@ -0,0 +1,27 @@
+<?php
+
+namespace Symfony\Component\Form;
+
+/**
+ * @template TData
+ */
+interface FormTypeInterface
+{
+    /**
+     * @param FormBuilderInterface<TData|null> $builder
+     * @param array<string, mixed> $options
+     */
+    public function buildForm(FormBuilderInterface $builder, array $options): void;
+
+    /**
+     * @param FormInterface<TData> $form
+     * @param array<string, mixed> $options
+     */
+    public function buildView(FormView $view, FormInterface $form, array $options): void;
+
+    /**
+     * @param FormInterface<TData> $form
+     * @param array<string, mixed> $options
+     */
+    public function finishView(FormView $view, FormInterface $form, array $options): void;
+}
diff --git a/stubs/Symfony/Component/Form/FormView.stub b/stubs/Symfony/Component/Form/FormView.stub
new file mode 100644
index 00000000..08c64752
--- /dev/null
+++ b/stubs/Symfony/Component/Form/FormView.stub
@@ -0,0 +1,22 @@
+<?php
+
+namespace Symfony\Component\Form;
+
+use ArrayAccess;
+use IteratorAggregate;
+
+/**
+ * @implements IteratorAggregate<int|string, self>
+ * @implements ArrayAccess<int|string, self>
+ */
+class FormView implements ArrayAccess, IteratorAggregate
+{
+
+    /**
+     * Returns an iterator to iterate over children (implements \IteratorAggregate).
+     *
+     * @return \ArrayIterator<int|string, FormView> The iterator
+     */
+    public function getIterator();
+
+}
diff --git a/stubs/Symfony/Component/HttpFoundation/Cookie.stub b/stubs/Symfony/Component/HttpFoundation/Cookie.stub
new file mode 100644
index 00000000..cfb45fa3
--- /dev/null
+++ b/stubs/Symfony/Component/HttpFoundation/Cookie.stub
@@ -0,0 +1,50 @@
+<?php
+
+namespace Symfony\Component\HttpFoundation;
+
+class Cookie
+{
+    public const SAMESITE_NONE = 'none';
+    public const SAMESITE_LAX = 'lax';
+    public const SAMESITE_STRICT = 'strict';
+
+    /**
+     * @param string                        $name     The name of the cookie
+     * @param string|null                   $value    The value of the cookie
+     * @param int|string|\DateTimeInterface $expire   The time the cookie expires
+     * @param string                        $path     The path on the server in which the cookie will be available on
+     * @param string|null                   $domain   The domain that the cookie is available to
+     * @param bool|null                     $secure   Whether the client should send back the cookie only over HTTPS or null to auto-enable this when the request is already using HTTPS
+     * @param bool                          $httpOnly Whether the cookie will be made accessible only through the HTTP protocol
+     * @param bool                          $raw      Whether the cookie value should be sent with no url encoding
+     * @param self::SAMESITE_*|''|null      $sameSite Whether the cookie will be available for cross-site requests
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function __construct(string $name, ?string $value = null, $expire = 0, ?string $path = '/', ?string $domain = null, ?bool $secure = false, bool $httpOnly = true, bool $raw = false, ?string $sameSite = null);
+
+    /**
+     * @param string                        $name     The name of the cookie
+     * @param string|null                   $value    The value of the cookie
+     * @param int|string|\DateTimeInterface $expire   The time the cookie expires
+     * @param string                        $path     The path on the server in which the cookie will be available on
+     * @param string|null                   $domain   The domain that the cookie is available to
+     * @param bool|null                     $secure   Whether the client should send back the cookie only over HTTPS or null to auto-enable this when the request is already using HTTPS
+     * @param bool                          $httpOnly Whether the cookie will be made accessible only through the HTTP protocol
+     * @param bool                          $raw      Whether the cookie value should be sent with no url encoding
+     * @param self::SAMESITE_*|''|null      $sameSite Whether the cookie will be available for cross-site requests
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function create(string $name, ?string $value = null, $expire = 0, ?string $path = '/', ?string $domain = null, ?bool $secure = false, bool $httpOnly = true, bool $raw = false, ?string $sameSite = null): self;
+
+    /**
+     * @return self::SAMESITE_*|null
+     */
+    public function getSameSite();
+
+    /**
+     * @param self::SAMESITE_*|''|null $sameSite
+     */
+    public function withSameSite(?string $sameSite): self;
+}
diff --git a/stubs/Symfony/Component/HttpFoundation/HeaderBag.stub b/stubs/Symfony/Component/HttpFoundation/HeaderBag.stub
new file mode 100644
index 00000000..cc3fe89a
--- /dev/null
+++ b/stubs/Symfony/Component/HttpFoundation/HeaderBag.stub
@@ -0,0 +1,16 @@
+<?php
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * @implements \IteratorAggregate<string, string|string[]>
+ */
+class HeaderBag implements \IteratorAggregate
+{
+
+	/**
+	 * @phpstan-return \Traversable<string, string|string[]>
+	 */
+	public function getIterator() {}
+
+}
diff --git a/stubs/Symfony/Component/HttpFoundation/InputBag.stub b/stubs/Symfony/Component/HttpFoundation/InputBag.stub
new file mode 100644
index 00000000..98223ce6
--- /dev/null
+++ b/stubs/Symfony/Component/HttpFoundation/InputBag.stub
@@ -0,0 +1,20 @@
+<?php
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * @template TValue of string|int|float|bool
+ */
+final class InputBag extends ParameterBag
+{
+
+	/**
+	 * @param TValue|null $default
+	 * @return TValue|null
+	 */
+	public function get(string $key, $default = null)
+	{
+
+	}
+
+}
diff --git a/stubs/Symfony/Component/HttpFoundation/ParameterBag.stub b/stubs/Symfony/Component/HttpFoundation/ParameterBag.stub
new file mode 100644
index 00000000..e412e9e0
--- /dev/null
+++ b/stubs/Symfony/Component/HttpFoundation/ParameterBag.stub
@@ -0,0 +1,16 @@
+<?php
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * @implements \IteratorAggregate<array-key, mixed>
+ */
+class ParameterBag implements \IteratorAggregate
+{
+    /**
+     * @return list<array-key>
+     */
+    public function keys(): array
+    {
+    }
+}
diff --git a/stubs/Symfony/Component/HttpFoundation/Request.stub b/stubs/Symfony/Component/HttpFoundation/Request.stub
new file mode 100644
index 00000000..0c2140cc
--- /dev/null
+++ b/stubs/Symfony/Component/HttpFoundation/Request.stub
@@ -0,0 +1,72 @@
+<?php
+
+namespace Symfony\Component\HttpFoundation;
+
+class Request
+{
+
+	/**
+	 * Request body parameters ($_POST).
+	 *
+	 * @var InputBag<string|int|float|bool>
+	 */
+	public $request;
+
+	/**
+	 * Query string parameters ($_GET).
+	 *
+	 * @var InputBag<string>
+	 */
+	public $query;
+
+	/**
+	 * Cookies ($_COOKIE).
+	 *
+	 * @var InputBag<string>
+	 */
+	public $cookies;
+
+    /**
+     * @return string[]
+     */
+    public static function getTrustedProxies(): array;
+
+    /**
+     * @return string[]
+     */
+    public static function getTrustedHosts(): array;
+
+    /**
+     * @param string $format
+     *
+     * @return string[]
+     */
+    public static function getMimeTypes($format): array;
+
+    /**
+     * @param string|null     $format
+     * @param string|string[] $mimeTypes
+     */
+    public function setFormat($format, $mimeTypes): void;
+
+    /**
+     * @return string[]
+     */
+    public function getLanguages(): array;
+
+    /**
+     * @return string[]
+     */
+    public function getCharsets(): array;
+
+    /**
+     * @return string[]
+     */
+    public function getEncodings(): array;
+
+    /**
+     * @return string[]
+     */
+    public function getAcceptableContentTypes(): array;
+
+}
diff --git a/stubs/Symfony/Component/HttpFoundation/Session.stub b/stubs/Symfony/Component/HttpFoundation/Session.stub
new file mode 100644
index 00000000..25485fe6
--- /dev/null
+++ b/stubs/Symfony/Component/HttpFoundation/Session.stub
@@ -0,0 +1,16 @@
+<?php
+
+namespace Symfony\Component\HttpFoundation\Session;
+
+/**
+ * @implements \IteratorAggregate<string, mixed>
+ */
+class Session implements \IteratorAggregate
+{
+
+	/**
+	 * @phpstan-return \Traversable<string, mixed>
+	 */
+	public function getIterator() {}
+
+}
diff --git a/stubs/Symfony/Component/Messenger/Envelope.stub b/stubs/Symfony/Component/Messenger/Envelope.stub
new file mode 100644
index 00000000..c40a5ee6
--- /dev/null
+++ b/stubs/Symfony/Component/Messenger/Envelope.stub
@@ -0,0 +1,17 @@
+<?php
+
+namespace Symfony\Component\Messenger;
+
+use Symfony\Component\Messenger\Stamp\StampInterface;
+
+final class Envelope
+{
+    /**
+     * @template T of \Symfony\Component\Messenger\Stamp\StampInterface
+     * @phpstan-param class-string<T> $stampFqcn
+     * @phpstan-return T|null
+     */
+    public function last(string $stampFqcn): ?StampInterface
+    {
+    }
+}
diff --git a/stubs/Symfony/Component/Messenger/StampInterface.stub b/stubs/Symfony/Component/Messenger/StampInterface.stub
new file mode 100644
index 00000000..2951ab45
--- /dev/null
+++ b/stubs/Symfony/Component/Messenger/StampInterface.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\Messenger\Stamp;
+
+interface StampInterface
+{
+}
diff --git a/stubs/Symfony/Component/OptionsResolver/Exception/InvalidOptionsException.stub b/stubs/Symfony/Component/OptionsResolver/Exception/InvalidOptionsException.stub
new file mode 100644
index 00000000..2856e32b
--- /dev/null
+++ b/stubs/Symfony/Component/OptionsResolver/Exception/InvalidOptionsException.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\OptionsResolver\Exception;
+
+class InvalidOptionsException extends \InvalidArgumentException
+{
+}
diff --git a/stubs/Symfony/Component/OptionsResolver/Options.stub b/stubs/Symfony/Component/OptionsResolver/Options.stub
new file mode 100644
index 00000000..31ca0207
--- /dev/null
+++ b/stubs/Symfony/Component/OptionsResolver/Options.stub
@@ -0,0 +1,41 @@
+<?php
+
+namespace Symfony\Component\OptionsResolver;
+
+/**
+ * @template TArray of array
+ * @extends \ArrayAccess<key-of<TArray>, value-of<TArray>>
+ */
+interface Options extends \ArrayAccess, \Countable
+{
+	/**
+	 * @param key-of<TArray> $offset
+	 *
+	 * @return bool
+	 */
+	public function offsetExists($offset);
+
+	/**
+	 * @template TOffset of key-of<TArray>
+	 * @param TOffset $offset
+	 * @return TArray[TOffset]
+	 */
+	public function offsetGet($offset);
+
+	/**
+	 * @template TOffset of key-of<TArray>
+	 * @param TOffset|null $offset
+	 * @param TArray[TOffset] $value
+	 *
+	 * @return void
+	 */
+	public function offsetSet($offset, $value);
+
+	/**
+	 * @template TOffset of key-of<TArray>
+	 * @param TOffset $offset
+	 *
+	 * @return void
+	 */
+	public function offsetUnset($offset);
+}
diff --git a/stubs/Symfony/Component/Process/Exception/LogicException.stub b/stubs/Symfony/Component/Process/Exception/LogicException.stub
new file mode 100644
index 00000000..cb781d6a
--- /dev/null
+++ b/stubs/Symfony/Component/Process/Exception/LogicException.stub
@@ -0,0 +1,8 @@
+<?php
+
+namespace Symfony\Component\Process\Exception;
+
+class LogicException extends \Exception
+{
+
+}
diff --git a/stubs/Symfony/Component/Process/Process.stub b/stubs/Symfony/Component/Process/Process.stub
new file mode 100644
index 00000000..b7bff236
--- /dev/null
+++ b/stubs/Symfony/Component/Process/Process.stub
@@ -0,0 +1,26 @@
+<?php
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\LogicException;
+
+/**
+ * @implements \IteratorAggregate<string, string>
+ */
+class Process implements \IteratorAggregate
+{
+
+    /**
+     * @param int $flags
+     *
+     * @return \Generator<string, string, mixed, mixed>
+     *
+     * @throws LogicException in case the output has been disabled
+     * @throws LogicException In case the process is not started
+     */
+    public function getIterator(int $flags = 0): \Generator
+    {
+
+    }
+
+}
diff --git a/stubs/Symfony/Component/PropertyAccess/Exception/AccessException.stub b/stubs/Symfony/Component/PropertyAccess/Exception/AccessException.stub
new file mode 100644
index 00000000..a763b784
--- /dev/null
+++ b/stubs/Symfony/Component/PropertyAccess/Exception/AccessException.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\PropertyAccess\Exception;
+
+class AccessException extends RuntimeException
+{
+}
diff --git a/stubs/Symfony/Component/PropertyAccess/Exception/ExceptionInterface.stub b/stubs/Symfony/Component/PropertyAccess/Exception/ExceptionInterface.stub
new file mode 100644
index 00000000..5028628d
--- /dev/null
+++ b/stubs/Symfony/Component/PropertyAccess/Exception/ExceptionInterface.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\PropertyAccess\Exception;
+
+interface ExceptionInterface extends \Throwable
+{
+}
diff --git a/stubs/Symfony/Component/PropertyAccess/Exception/InvalidArgumentException.stub b/stubs/Symfony/Component/PropertyAccess/Exception/InvalidArgumentException.stub
new file mode 100644
index 00000000..66ee13fb
--- /dev/null
+++ b/stubs/Symfony/Component/PropertyAccess/Exception/InvalidArgumentException.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\PropertyAccess\Exception;
+
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/stubs/Symfony/Component/PropertyAccess/Exception/RuntimeException.stub b/stubs/Symfony/Component/PropertyAccess/Exception/RuntimeException.stub
new file mode 100644
index 00000000..50827398
--- /dev/null
+++ b/stubs/Symfony/Component/PropertyAccess/Exception/RuntimeException.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\PropertyAccess\Exception;
+
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/stubs/Symfony/Component/PropertyAccess/Exception/UnexpectedTypeException.stub b/stubs/Symfony/Component/PropertyAccess/Exception/UnexpectedTypeException.stub
new file mode 100644
index 00000000..22bdaf62
--- /dev/null
+++ b/stubs/Symfony/Component/PropertyAccess/Exception/UnexpectedTypeException.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\PropertyAccess\Exception;
+
+class UnexpectedTypeException extends RuntimeException
+{
+}
diff --git a/stubs/Symfony/Component/PropertyAccess/PropertyAccessor.stub b/stubs/Symfony/Component/PropertyAccess/PropertyAccessor.stub
new file mode 100644
index 00000000..af5423f0
--- /dev/null
+++ b/stubs/Symfony/Component/PropertyAccess/PropertyAccessor.stub
@@ -0,0 +1,23 @@
+<?php
+
+namespace Symfony\Component\PropertyAccess;
+
+class PropertyAccessor implements PropertyAccessorInterface
+{
+
+    /**
+     * @phpstan-template T of object|array<mixed>
+     * @phpstan-param T &$objectOrArray
+     * @phpstan-param-out ($objectOrArray is object ? T : array<mixed>) $objectOrArray
+     * @phpstan-param string|PropertyPathInterface $propertyPath
+     * @phpstan-param mixed $value
+     *
+     * @return void
+     *
+     * @throws Exception\InvalidArgumentException If the property path is invalid
+     * @throws Exception\AccessException          If a property/index does not exist or is not public
+     * @throws Exception\UnexpectedTypeException  If a value within the path is neither object nor array
+     */
+    public function setValue(&$objectOrArray, $propertyPath, $value);
+
+}
diff --git a/stubs/Symfony/Component/PropertyAccess/PropertyAccessorInterface.stub b/stubs/Symfony/Component/PropertyAccess/PropertyAccessorInterface.stub
new file mode 100644
index 00000000..63a09a01
--- /dev/null
+++ b/stubs/Symfony/Component/PropertyAccess/PropertyAccessorInterface.stub
@@ -0,0 +1,23 @@
+<?php
+
+namespace Symfony\Component\PropertyAccess;
+
+interface PropertyAccessorInterface
+{
+
+    /**
+     * @phpstan-template T of object|array<mixed>
+     * @phpstan-param T &$objectOrArray
+     * @phpstan-param-out ($objectOrArray is object ? T : array<mixed>) $objectOrArray
+     * @phpstan-param string|PropertyPathInterface $propertyPath
+     * @phpstan-param mixed $value
+     *
+     * @return void
+     *
+     * @throws Exception\InvalidArgumentException If the property path is invalid
+     * @throws Exception\AccessException          If a property/index does not exist or is not public
+     * @throws Exception\UnexpectedTypeException  If a value within the path is neither object nor array
+     */
+    public function setValue(&$objectOrArray, $propertyPath, $value);
+
+}
diff --git a/stubs/Symfony/Component/PropertyAccess/PropertyPathInterface.stub b/stubs/Symfony/Component/PropertyAccess/PropertyPathInterface.stub
new file mode 100644
index 00000000..d687193e
--- /dev/null
+++ b/stubs/Symfony/Component/PropertyAccess/PropertyPathInterface.stub
@@ -0,0 +1,10 @@
+<?php
+
+namespace Symfony\Component\PropertyAccess;
+
+/**
+ * @extends \Traversable<int, string>
+ */
+interface PropertyPathInterface extends \Traversable
+{
+}
diff --git a/stubs/Symfony/Component/Security/Acl/Model/AclInterface.stub b/stubs/Symfony/Component/Security/Acl/Model/AclInterface.stub
new file mode 100644
index 00000000..2f509501
--- /dev/null
+++ b/stubs/Symfony/Component/Security/Acl/Model/AclInterface.stub
@@ -0,0 +1,40 @@
+<?php
+
+namespace Symfony\Component\Security\Acl\Model;
+
+interface AclInterface
+{
+
+    /**
+     * Returns all class-based ACEs associated with this ACL.
+     *
+     * @return array<int, EntryInterface>
+     */
+    public function getClassAces();
+
+    /**
+     * Returns all class-field-based ACEs associated with this ACL.
+     *
+     * @param string $field
+     *
+     * @return array<int, EntryInterface>
+     */
+    public function getClassFieldAces($field);
+
+    /**
+     * Returns all object-based ACEs associated with this ACL.
+     *
+     * @return array<int, EntryInterface>
+     */
+    public function getObjectAces();
+
+    /**
+     * Returns all object-field-based ACEs associated with this ACL.
+     *
+     * @param string $field
+     *
+     * @return array<int, EntryInterface>
+     */
+    public function getObjectFieldAces($field);
+
+}
diff --git a/stubs/Symfony/Component/Security/Acl/Model/EntryInterface.stub b/stubs/Symfony/Component/Security/Acl/Model/EntryInterface.stub
new file mode 100644
index 00000000..335e581d
--- /dev/null
+++ b/stubs/Symfony/Component/Security/Acl/Model/EntryInterface.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\Security\Acl\Model;
+
+interface EntryInterface
+{
+}
diff --git a/stubs/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.stub b/stubs/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.stub
new file mode 100644
index 00000000..204a9c40
--- /dev/null
+++ b/stubs/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\Security\Core\Authentication\Token;
+
+interface TokenInterface
+{
+}
diff --git a/stubs/Symfony/Component/Security/Core/Authorization/Voter/Voter.stub b/stubs/Symfony/Component/Security/Core/Authorization/Voter/Voter.stub
new file mode 100644
index 00000000..c2daae3b
--- /dev/null
+++ b/stubs/Symfony/Component/Security/Core/Authorization/Voter/Voter.stub
@@ -0,0 +1,36 @@
+<?php
+
+namespace Symfony\Component\Security\Core\Authorization\Voter;
+
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+
+/**
+ * @template TAttribute of string
+ * @template TSubject of mixed
+ */
+abstract class Voter implements VoterInterface
+{
+    /**
+     * Determines if the attribute and subject are supported by this voter.
+     *
+     * @param string $attribute
+     * @param mixed $subject
+     *
+     * @phpstan-assert-if-true TSubject $subject
+     * @phpstan-assert-if-true TAttribute $attribute
+     *
+     * @return bool
+     */
+    abstract protected function supports(string $attribute, $subject);
+
+    /**
+     * Perform a single access check operation on a given attribute, subject and token.
+     * It is safe to assume that $attribute and $subject already passed the "supports()" method check.
+     *
+     * @phpstan-param TAttribute $attribute
+     * @phpstan-param TSubject $subject
+     *
+     * @return bool
+     */
+    abstract protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token);
+}
diff --git a/stubs/Symfony/Component/Security/Core/Authorization/Voter/VoterInterface.stub b/stubs/Symfony/Component/Security/Core/Authorization/Voter/VoterInterface.stub
new file mode 100644
index 00000000..1e8d7c5d
--- /dev/null
+++ b/stubs/Symfony/Component/Security/Core/Authorization/Voter/VoterInterface.stub
@@ -0,0 +1,16 @@
+<?php
+
+namespace Symfony\Component\Security\Core\Authorization\Voter;
+
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+
+interface VoterInterface
+{
+	/**
+	 * @param mixed   $subject
+	 * @param mixed[] $attributes
+	 *
+	 * @return int
+	 */
+	public function vote(TokenInterface $token, $subject, array $attributes);
+}
diff --git a/stubs/Symfony/Component/Serializer/Encoder/ContextAwareDecoderInterface.stub b/stubs/Symfony/Component/Serializer/Encoder/ContextAwareDecoderInterface.stub
new file mode 100644
index 00000000..39e12d2f
--- /dev/null
+++ b/stubs/Symfony/Component/Serializer/Encoder/ContextAwareDecoderInterface.stub
@@ -0,0 +1,13 @@
+<?php
+
+namespace Symfony\Component\Serializer\Encoder;
+
+interface ContextAwareDecoderInterface extends DecoderInterface
+{
+    /**
+     * @param string $format
+     * @param array<mixed> $context
+     * @return bool
+     */
+    public function supportsDecoding($format, array $context = []);
+}
diff --git a/stubs/Symfony/Component/Serializer/Encoder/DecoderInterface.stub b/stubs/Symfony/Component/Serializer/Encoder/DecoderInterface.stub
new file mode 100644
index 00000000..e19f772e
--- /dev/null
+++ b/stubs/Symfony/Component/Serializer/Encoder/DecoderInterface.stub
@@ -0,0 +1,25 @@
+<?php
+
+namespace Symfony\Component\Serializer\Encoder;
+
+use Symfony\Component\Serializer\Exception\UnexpectedValueException;
+
+interface DecoderInterface
+{
+    /**
+     * @param string $data
+     * @param string $format
+     * @param array<mixed> $context
+     * @return mixed
+     *
+     * @throws UnexpectedValueException
+     */
+    public function decode($data, $format, array $context = []);
+
+    /**
+     * @param string $format Format name
+     * @param array<mixed> $context
+     * @return bool
+     */
+    public function supportsDecoding($format, array $context = []);
+}
diff --git a/stubs/Symfony/Component/Serializer/Encoder/EncoderInterface.stub b/stubs/Symfony/Component/Serializer/Encoder/EncoderInterface.stub
new file mode 100644
index 00000000..11e374eb
--- /dev/null
+++ b/stubs/Symfony/Component/Serializer/Encoder/EncoderInterface.stub
@@ -0,0 +1,25 @@
+<?php
+
+namespace Symfony\Component\Serializer\Encoder;
+
+use Symfony\Component\Serializer\Exception\UnexpectedValueException;
+
+interface EncoderInterface
+{
+    /**
+     * @param mixed $data
+     * @param string $format
+     * @param array<mixed> $context
+     * @return string
+     *
+     * @throws UnexpectedValueException
+     */
+    public function encode($data, $format, array $context = []);
+
+    /**
+     * @param string $format Format name
+     * @param array<mixed> $context
+     * @return bool
+     */
+    public function supportsEncoding($format, array $context = []);
+}
diff --git a/stubs/Symfony/Component/Serializer/Exception/BadMethodCallException.stub b/stubs/Symfony/Component/Serializer/Exception/BadMethodCallException.stub
new file mode 100644
index 00000000..3c569971
--- /dev/null
+++ b/stubs/Symfony/Component/Serializer/Exception/BadMethodCallException.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\Serializer\Exception;
+
+class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface
+{
+}
diff --git a/stubs/Symfony/Component/Serializer/Exception/CircularReferenceException.stub b/stubs/Symfony/Component/Serializer/Exception/CircularReferenceException.stub
new file mode 100644
index 00000000..30b5e55b
--- /dev/null
+++ b/stubs/Symfony/Component/Serializer/Exception/CircularReferenceException.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\Serializer\Exception;
+
+class CircularReferenceException extends RuntimeException
+{
+}
diff --git a/stubs/Symfony/Component/Serializer/Exception/ExceptionInterface.stub b/stubs/Symfony/Component/Serializer/Exception/ExceptionInterface.stub
new file mode 100644
index 00000000..d9563924
--- /dev/null
+++ b/stubs/Symfony/Component/Serializer/Exception/ExceptionInterface.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\Serializer\Exception;
+
+interface ExceptionInterface extends \Throwable
+{
+}
diff --git a/stubs/Symfony/Component/Serializer/Exception/ExtraAttributesException.stub b/stubs/Symfony/Component/Serializer/Exception/ExtraAttributesException.stub
new file mode 100644
index 00000000..b23f0d19
--- /dev/null
+++ b/stubs/Symfony/Component/Serializer/Exception/ExtraAttributesException.stub
@@ -0,0 +1,20 @@
+<?php
+
+namespace Symfony\Component\Serializer\Exception;
+
+class ExtraAttributesException extends RuntimeException
+{
+    /**
+     * @param string[] $extraAttributes
+     */
+    public function __construct(array $extraAttributes, ?\Throwable $previous = null)
+    {
+    }
+
+    /**
+     * @return string[]
+     */
+    public function getExtraAttributes()
+    {
+    }
+}
diff --git a/stubs/Symfony/Component/Serializer/Exception/InvalidArgumentException.stub b/stubs/Symfony/Component/Serializer/Exception/InvalidArgumentException.stub
new file mode 100644
index 00000000..045906c0
--- /dev/null
+++ b/stubs/Symfony/Component/Serializer/Exception/InvalidArgumentException.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\Serializer\Exception;
+
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/stubs/Symfony/Component/Serializer/Exception/LogicException.stub b/stubs/Symfony/Component/Serializer/Exception/LogicException.stub
new file mode 100644
index 00000000..101feb67
--- /dev/null
+++ b/stubs/Symfony/Component/Serializer/Exception/LogicException.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\Serializer\Exception;
+
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/stubs/Symfony/Component/Serializer/Exception/RuntimeException.stub b/stubs/Symfony/Component/Serializer/Exception/RuntimeException.stub
new file mode 100644
index 00000000..14a4e78e
--- /dev/null
+++ b/stubs/Symfony/Component/Serializer/Exception/RuntimeException.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\Serializer\Exception;
+
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/stubs/Symfony/Component/Serializer/Exception/UnexpectedValueException.stub b/stubs/Symfony/Component/Serializer/Exception/UnexpectedValueException.stub
new file mode 100644
index 00000000..5fbb9b6c
--- /dev/null
+++ b/stubs/Symfony/Component/Serializer/Exception/UnexpectedValueException.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\Serializer\Exception;
+
+class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface
+{
+}
diff --git a/stubs/Symfony/Component/Serializer/Normalizer/ContextAwareDenormalizerInterface.stub b/stubs/Symfony/Component/Serializer/Normalizer/ContextAwareDenormalizerInterface.stub
new file mode 100644
index 00000000..9d42b711
--- /dev/null
+++ b/stubs/Symfony/Component/Serializer/Normalizer/ContextAwareDenormalizerInterface.stub
@@ -0,0 +1,15 @@
+<?php
+
+namespace Symfony\Component\Serializer\Normalizer;
+
+interface ContextAwareDenormalizerInterface extends DenormalizerInterface
+{
+    /**
+     * @param mixed $data
+     * @param string $type
+     * @param string|null $format
+     * @param array<mixed> $context
+     * @return bool
+     */
+    public function supportsDenormalization($data, $type, $format = null, array $context = []);
+}
diff --git a/stubs/Symfony/Component/Serializer/Normalizer/ContextAwareNormalizerInterface.stub b/stubs/Symfony/Component/Serializer/Normalizer/ContextAwareNormalizerInterface.stub
new file mode 100644
index 00000000..aaf97be5
--- /dev/null
+++ b/stubs/Symfony/Component/Serializer/Normalizer/ContextAwareNormalizerInterface.stub
@@ -0,0 +1,14 @@
+<?php
+
+namespace Symfony\Component\Serializer\Normalizer;
+
+interface ContextAwareNormalizerInterface extends NormalizerInterface
+{
+    /**
+     * @param mixed $data
+     * @param string|null $format
+     * @param array<mixed> $context
+     * @return bool
+     */
+    public function supportsNormalization($data, $format = null, array $context = []);
+}
diff --git a/stubs/Symfony/Component/Serializer/Normalizer/DenormalizableInterface.stub b/stubs/Symfony/Component/Serializer/Normalizer/DenormalizableInterface.stub
new file mode 100644
index 00000000..7e8cb7e9
--- /dev/null
+++ b/stubs/Symfony/Component/Serializer/Normalizer/DenormalizableInterface.stub
@@ -0,0 +1,16 @@
+<?php
+
+namespace Symfony\Component\Serializer\Normalizer;
+
+interface DenormalizableInterface
+{
+    /**
+     * @param DenormalizerInterface $denormalizer
+     * @param array<mixed>|string|int|float|bool $data
+     * @param string|null $format
+     * @param array<mixed> $context
+     *
+     * @return void
+     */
+    public function denormalize($denormalizer, $data, $format = null, array $context = []);
+}
diff --git a/stubs/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.stub b/stubs/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.stub
new file mode 100644
index 00000000..b7e9968b
--- /dev/null
+++ b/stubs/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.stub
@@ -0,0 +1,40 @@
+<?php
+
+namespace Symfony\Component\Serializer\Normalizer;
+
+use Symfony\Component\Serializer\Exception\BadMethodCallException;
+use Symfony\Component\Serializer\Exception\ExceptionInterface;
+use Symfony\Component\Serializer\Exception\ExtraAttributesException;
+use Symfony\Component\Serializer\Exception\InvalidArgumentException;
+use Symfony\Component\Serializer\Exception\LogicException;
+use Symfony\Component\Serializer\Exception\RuntimeException;
+use Symfony\Component\Serializer\Exception\UnexpectedValueException;
+
+interface DenormalizerInterface
+{
+    /**
+     * @param mixed $data
+     * @param string $type
+     * @param string|null $format
+     * @param array<mixed> $context
+     * @return mixed
+     *
+     * @throws BadMethodCallException
+     * @throws InvalidArgumentException
+     * @throws UnexpectedValueException
+     * @throws ExtraAttributesException
+     * @throws LogicException
+     * @throws RuntimeException
+     * @throws ExceptionInterface
+     */
+    public function denormalize($data, $type, $format = null, array $context = []);
+
+    /**
+     * @param mixed $data
+     * @param string $type
+     * @param string|null $format
+     * @param array<mixed> $context
+     * @return bool
+     */
+    public function supportsDenormalization($data, $type, $format = null, array $context = []);
+}
diff --git a/stubs/Symfony/Component/Serializer/Normalizer/NormalizableInterface.stub b/stubs/Symfony/Component/Serializer/Normalizer/NormalizableInterface.stub
new file mode 100644
index 00000000..c7958c55
--- /dev/null
+++ b/stubs/Symfony/Component/Serializer/Normalizer/NormalizableInterface.stub
@@ -0,0 +1,14 @@
+<?php
+
+namespace Symfony\Component\Serializer\Normalizer;
+
+interface NormalizableInterface
+{
+    /**
+     * @param NormalizerInterface $normalizer
+     * @param string|null $format
+     * @param array<mixed> $context
+     * @return array<mixed>|string|int|float|bool
+     */
+    public function normalize(NormalizerInterface $normalizer, $format = null, array $context = []);
+}
diff --git a/stubs/Symfony/Component/Serializer/Normalizer/NormalizerInterface.stub b/stubs/Symfony/Component/Serializer/Normalizer/NormalizerInterface.stub
new file mode 100644
index 00000000..ba86b6b6
--- /dev/null
+++ b/stubs/Symfony/Component/Serializer/Normalizer/NormalizerInterface.stub
@@ -0,0 +1,35 @@
+<?php
+
+namespace Symfony\Component\Serializer\Normalizer;
+
+use ArrayObject;
+use Symfony\Component\Serializer\Exception\CircularReferenceException;
+use Symfony\Component\Serializer\Exception\ExceptionInterface;
+use Symfony\Component\Serializer\Exception\InvalidArgumentException;
+use Symfony\Component\Serializer\Exception\LogicException;
+
+interface NormalizerInterface
+{
+    /**
+     * @param mixed $object
+     * @param string|null $format
+     * @param array<mixed> $context
+     *
+     * @return array<mixed>|ArrayObject<array-key, mixed>|string|int|float|bool|null
+     *
+     * @throws InvalidArgumentException
+     * @throws CircularReferenceException
+     * @throws LogicException
+     * @throws ExceptionInterface
+     */
+    public function normalize($object, $format = null, array $context = []);
+
+    /**
+     * @param mixed $data
+     * @param string|null $format
+     * @param array<mixed> $context
+     *
+     * @return bool
+     */
+    public function supportsNormalization($data, $format = null, array $context = []);
+}
diff --git a/stubs/Symfony/Component/Validator/Constraint.stub b/stubs/Symfony/Component/Validator/Constraint.stub
new file mode 100644
index 00000000..e7a4b501
--- /dev/null
+++ b/stubs/Symfony/Component/Validator/Constraint.stub
@@ -0,0 +1,21 @@
+<?php
+
+namespace Symfony\Component\Validator;
+
+class Constraint
+{
+    /**
+     * @var array<string, string>
+     */
+    protected static $errorNames = [];
+
+    /**
+     * @return array<mixed>
+     */
+    public function getRequiredOptions();
+
+    /**
+     * @return string|array<string>
+     */
+    public function getTargets();
+}
diff --git a/stubs/Symfony/Component/Validator/ConstraintViolationInterface.stub b/stubs/Symfony/Component/Validator/ConstraintViolationInterface.stub
new file mode 100644
index 00000000..fd1c7b9b
--- /dev/null
+++ b/stubs/Symfony/Component/Validator/ConstraintViolationInterface.stub
@@ -0,0 +1,12 @@
+<?php
+
+namespace Symfony\Component\Validator;
+
+/**
+ * @method Constraint|null getConstraint() Returns the constraint whose validation caused the violation. Not implementing it is deprecated since Symfony 6.3.
+ * @method mixed           getCause()      Returns the cause of the violation. Not implementing it is deprecated since Symfony 6.2.
+ * @method string          __toString()    Converts the violation into a string for debugging purposes. Not implementing it is deprecated since Symfony 6.1.
+ */
+interface ConstraintViolationInterface
+{
+}
diff --git a/stubs/Symfony/Component/Validator/ConstraintViolationListInterface.stub b/stubs/Symfony/Component/Validator/ConstraintViolationListInterface.stub
new file mode 100644
index 00000000..00a2a9d5
--- /dev/null
+++ b/stubs/Symfony/Component/Validator/ConstraintViolationListInterface.stub
@@ -0,0 +1,13 @@
+<?php
+
+namespace Symfony\Component\Validator;
+
+/**
+ * @extends \ArrayAccess<int, ConstraintViolationInterface>
+ * @extends \Traversable<int, ConstraintViolationInterface>
+ *
+ * @method string __toString() Converts the violation into a string for debugging purposes. Not implementing it is deprecated since Symfony 6.1.
+ */
+interface ConstraintViolationListInterface extends \Traversable, \Countable, \ArrayAccess
+{
+}
diff --git a/stubs/Symfony/Component/Validator/Constraints/Composite.stub b/stubs/Symfony/Component/Validator/Constraints/Composite.stub
new file mode 100644
index 00000000..8344ea94
--- /dev/null
+++ b/stubs/Symfony/Component/Validator/Constraints/Composite.stub
@@ -0,0 +1,9 @@
+<?php
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+abstract class Composite extends Constraint
+{
+}
diff --git a/stubs/Symfony/Component/Validator/Constraints/Compound.stub b/stubs/Symfony/Component/Validator/Constraints/Compound.stub
new file mode 100644
index 00000000..6a36c6bf
--- /dev/null
+++ b/stubs/Symfony/Component/Validator/Constraints/Compound.stub
@@ -0,0 +1,14 @@
+<?php
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+abstract class Compound extends Composite
+{
+    /**
+     * @param array<string, mixed> $options
+     * @return array<Constraint>
+     */
+    abstract protected function getConstraints(array $options): array;
+}
diff --git a/stubs/Symfony/Contracts/Cache/CacheInterface.stub b/stubs/Symfony/Contracts/Cache/CacheInterface.stub
new file mode 100644
index 00000000..a361ead4
--- /dev/null
+++ b/stubs/Symfony/Contracts/Cache/CacheInterface.stub
@@ -0,0 +1,19 @@
+<?php
+
+namespace Symfony\Contracts\Cache;
+
+use Psr\Cache\InvalidArgumentException;
+
+interface CacheInterface
+{
+    /**
+     * @template T
+     *
+     * @param \Symfony\Contracts\Cache\CallbackInterface<T>|callable(\Symfony\Contracts\Cache\ItemInterface, bool): T $callback
+     * @param array<mixed> $metadata
+     * @return T
+     *
+     * @throws InvalidArgumentException
+     */
+    public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null);
+}
diff --git a/stubs/Symfony/Contracts/Cache/CallbackInterface.stub b/stubs/Symfony/Contracts/Cache/CallbackInterface.stub
new file mode 100644
index 00000000..9b5a6e1a
--- /dev/null
+++ b/stubs/Symfony/Contracts/Cache/CallbackInterface.stub
@@ -0,0 +1,16 @@
+<?php
+
+namespace Symfony\Contracts\Cache;
+
+use Psr\Cache\CacheItemInterface;
+
+/**
+ * @template T
+ */
+interface CallbackInterface
+{
+    /**
+     * @return T
+     */
+    public function __invoke(CacheItemInterface $item, bool &$save);
+}
diff --git a/stubs/Symfony/Contracts/Cache/ItemInterface.stub b/stubs/Symfony/Contracts/Cache/ItemInterface.stub
new file mode 100644
index 00000000..cb3982bb
--- /dev/null
+++ b/stubs/Symfony/Contracts/Cache/ItemInterface.stub
@@ -0,0 +1,9 @@
+<?php
+
+namespace Symfony\Contracts\Cache;
+
+use Psr\Cache\CacheItemInterface;
+
+interface ItemInterface extends CacheItemInterface
+{
+}
diff --git a/stubs/Symfony/Contracts/Service/ServiceSubscriberInterface.stub b/stubs/Symfony/Contracts/Service/ServiceSubscriberInterface.stub
new file mode 100644
index 00000000..8860e239
--- /dev/null
+++ b/stubs/Symfony/Contracts/Service/ServiceSubscriberInterface.stub
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Contracts\Service;
+
+interface ServiceSubscriberInterface
+{
+}
diff --git a/stubs/Twig/Node/Node.stub b/stubs/Twig/Node/Node.stub
new file mode 100644
index 00000000..692a0e95
--- /dev/null
+++ b/stubs/Twig/Node/Node.stub
@@ -0,0 +1,11 @@
+<?php
+
+namespace Twig\Node;
+
+/**
+ * @implements \IteratorAggregate<string, self>
+ */
+class Node implements \IteratorAggregate
+{
+
+}
diff --git a/tests/Rules/NonexistentInputBagClassTest.php b/tests/Rules/NonexistentInputBagClassTest.php
new file mode 100644
index 00000000..5dc674d4
--- /dev/null
+++ b/tests/Rules/NonexistentInputBagClassTest.php
@@ -0,0 +1,32 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Rules;
+
+use PHPStan\Rules\Methods\CallMethodsRule;
+use PHPStan\Testing\RuleTestCase;
+
+/**
+ * @extends RuleTestCase<CallMethodsRule>
+ */
+class NonexistentInputBagClassTest extends RuleTestCase
+{
+
+	protected function getRule(): Rule
+	{
+		return self::getContainer()->getByType(CallMethodsRule::class);
+	}
+
+	public function testInputBag(): void
+	{
+		$this->analyse([__DIR__ . '/data/input_bag.php'], []);
+	}
+
+	public static function getAdditionalConfigFiles(): array
+	{
+		return [
+			__DIR__ . '/../../extension.neon',
+			__DIR__ . '/../../rules.neon',
+		];
+	}
+
+}
diff --git a/tests/Rules/Symfony/ContainerInterfacePrivateServiceRuleFakeTest.php b/tests/Rules/Symfony/ContainerInterfacePrivateServiceRuleFakeTest.php
new file mode 100644
index 00000000..bbecb2e8
--- /dev/null
+++ b/tests/Rules/Symfony/ContainerInterfacePrivateServiceRuleFakeTest.php
@@ -0,0 +1,88 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Rules\Symfony;
+
+use PHPStan\Rules\Rule;
+use PHPStan\Symfony\XmlServiceMapFactory;
+use PHPStan\Testing\RuleTestCase;
+use function class_exists;
+use function interface_exists;
+
+/**
+ * @extends RuleTestCase<ContainerInterfacePrivateServiceRule>
+ */
+final class ContainerInterfacePrivateServiceRuleFakeTest extends RuleTestCase
+{
+
+	protected function getRule(): Rule
+	{
+		return new ContainerInterfacePrivateServiceRule((new XmlServiceMapFactory(null))->create());
+	}
+
+	public function testGetPrivateService(): void
+	{
+		if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\Controller')) {
+			self::markTestSkipped();
+		}
+		$this->analyse(
+			[
+				__DIR__ . '/ExampleController.php',
+			],
+			[],
+		);
+	}
+
+	public function testGetPrivateServiceInAbstractController(): void
+	{
+		if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\Controller')) {
+			self::markTestSkipped();
+		}
+		$this->analyse(
+			[
+				__DIR__ . '/ExampleAbstractController.php',
+			],
+			[],
+		);
+	}
+
+	public function testGetPrivateServiceInLegacyServiceSubscriber(): void
+	{
+		if (!interface_exists('Symfony\\Component\\DependencyInjection\\ServiceSubscriberInterface')) {
+			self::markTestSkipped('The test needs Symfony\Component\DependencyInjection\ServiceSubscriberInterface class.');
+		}
+
+		if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\Controller')) {
+			self::markTestSkipped();
+		}
+
+		$this->analyse(
+			[
+				__DIR__ . '/ExampleLegacyServiceSubscriber.php',
+				__DIR__ . '/ExampleLegacyServiceSubscriberFromAbstractController.php',
+				__DIR__ . '/ExampleLegacyServiceSubscriberFromLegacyController.php',
+			],
+			[],
+		);
+	}
+
+	public function testGetPrivateServiceInServiceSubscriber(): void
+	{
+		if (!interface_exists('Symfony\Contracts\Service\ServiceSubscriberInterface')) {
+			self::markTestSkipped('The test needs Symfony\Contracts\Service\ServiceSubscriberInterface class.');
+		}
+
+		if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\Controller')) {
+			self::markTestSkipped();
+		}
+
+		$this->analyse(
+			[
+				__DIR__ . '/ExampleServiceSubscriber.php',
+				__DIR__ . '/ExampleServiceSubscriberFromAbstractController.php',
+				__DIR__ . '/ExampleServiceSubscriberFromLegacyController.php',
+			],
+			[],
+		);
+	}
+
+}
diff --git a/tests/Rules/Symfony/ContainerInterfacePrivateServiceRuleTest.php b/tests/Rules/Symfony/ContainerInterfacePrivateServiceRuleTest.php
index abe71fed..31dd3680 100644
--- a/tests/Rules/Symfony/ContainerInterfacePrivateServiceRuleTest.php
+++ b/tests/Rules/Symfony/ContainerInterfacePrivateServiceRuleTest.php
@@ -5,7 +5,12 @@
 use PHPStan\Rules\Rule;
 use PHPStan\Symfony\XmlServiceMapFactory;
 use PHPStan\Testing\RuleTestCase;
+use function class_exists;
+use function interface_exists;
 
+/**
+ * @extends RuleTestCase<ContainerInterfacePrivateServiceRule>
+ */
 final class ContainerInterfacePrivateServiceRuleTest extends RuleTestCase
 {
 
@@ -16,6 +21,9 @@ protected function getRule(): Rule
 
 	public function testGetPrivateService(): void
 	{
+		if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\Controller')) {
+			self::markTestSkipped();
+		}
 		$this->analyse(
 			[
 				__DIR__ . '/ExampleController.php',
@@ -23,9 +31,9 @@ public function testGetPrivateService(): void
 			[
 				[
 					'Service "private" is private.',
-					12,
+					13,
 				],
-			]
+			],
 		);
 	}
 
@@ -35,13 +43,17 @@ public function testGetPrivateServiceInLegacyServiceSubscriber(): void
 			self::markTestSkipped('The test needs Symfony\Component\DependencyInjection\ServiceSubscriberInterface class.');
 		}
 
+		if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\Controller')) {
+			self::markTestSkipped();
+		}
+
 		$this->analyse(
 			[
 				__DIR__ . '/ExampleLegacyServiceSubscriber.php',
 				__DIR__ . '/ExampleLegacyServiceSubscriberFromAbstractController.php',
 				__DIR__ . '/ExampleLegacyServiceSubscriberFromLegacyController.php',
 			],
-			[]
+			[],
 		);
 	}
 
@@ -51,13 +63,27 @@ public function testGetPrivateServiceInServiceSubscriber(): void
 			self::markTestSkipped('The test needs Symfony\Contracts\Service\ServiceSubscriberInterface class.');
 		}
 
+		if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\Controller')) {
+			self::markTestSkipped();
+		}
+
 		$this->analyse(
 			[
 				__DIR__ . '/ExampleServiceSubscriber.php',
 				__DIR__ . '/ExampleServiceSubscriberFromAbstractController.php',
 				__DIR__ . '/ExampleServiceSubscriberFromLegacyController.php',
 			],
-			[]
+			[],
+		);
+	}
+
+	public function testGetPrivateServiceInTest(): void
+	{
+		$this->analyse(
+			[
+				__DIR__ . '/ExampleTest.php',
+			],
+			[],
 		);
 	}
 
diff --git a/tests/Rules/Symfony/ContainerInterfaceUnknownServiceRuleFakeTest.php b/tests/Rules/Symfony/ContainerInterfaceUnknownServiceRuleFakeTest.php
new file mode 100644
index 00000000..8d70f1c3
--- /dev/null
+++ b/tests/Rules/Symfony/ContainerInterfaceUnknownServiceRuleFakeTest.php
@@ -0,0 +1,62 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Rules\Symfony;
+
+use PHPStan\Node\Printer\Printer;
+use PHPStan\Rules\Rule;
+use PHPStan\Symfony\XmlServiceMapFactory;
+use PHPStan\Testing\RuleTestCase;
+use PHPStan\Type\MethodTypeSpecifyingExtension;
+use PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension;
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use function class_exists;
+
+/**
+ * @extends RuleTestCase<ContainerInterfaceUnknownServiceRule>
+ */
+final class ContainerInterfaceUnknownServiceRuleFakeTest extends RuleTestCase
+{
+
+	protected function getRule(): Rule
+	{
+		return new ContainerInterfaceUnknownServiceRule((new XmlServiceMapFactory(null))->create(), self::getContainer()->getByType(Printer::class));
+	}
+
+	/**
+	 * @return MethodTypeSpecifyingExtension[]
+	 */
+	protected function getMethodTypeSpecifyingExtensions(): array
+	{
+		return [
+			new ServiceTypeSpecifyingExtension(AbstractController::class, self::getContainer()->getByType(Printer::class)),
+		];
+	}
+
+	public function testGetPrivateService(): void
+	{
+		if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\Controller')) {
+			self::markTestSkipped();
+		}
+		$this->analyse(
+			[
+				__DIR__ . '/ExampleController.php',
+			],
+			[],
+		);
+	}
+
+	public function testGetPrivateServiceInAbstractController(): void
+	{
+		if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\Controller')) {
+			self::markTestSkipped();
+		}
+
+		$this->analyse(
+			[
+				__DIR__ . '/ExampleAbstractController.php',
+			],
+			[],
+		);
+	}
+
+}
diff --git a/tests/Rules/Symfony/ContainerInterfaceUnknownServiceRuleTest.php b/tests/Rules/Symfony/ContainerInterfaceUnknownServiceRuleTest.php
index 2fcc32ed..c975750f 100644
--- a/tests/Rules/Symfony/ContainerInterfaceUnknownServiceRuleTest.php
+++ b/tests/Rules/Symfony/ContainerInterfaceUnknownServiceRuleTest.php
@@ -2,44 +2,80 @@
 
 namespace PHPStan\Rules\Symfony;
 
-use PhpParser\PrettyPrinter\Standard;
+use PHPStan\Node\Printer\Printer;
 use PHPStan\Rules\Rule;
 use PHPStan\Symfony\XmlServiceMapFactory;
 use PHPStan\Testing\RuleTestCase;
-use PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension;
-use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use function class_exists;
+use function interface_exists;
 
+/**
+ * @extends RuleTestCase<ContainerInterfaceUnknownServiceRule>
+ */
 final class ContainerInterfaceUnknownServiceRuleTest extends RuleTestCase
 {
 
 	protected function getRule(): Rule
 	{
-		return new ContainerInterfaceUnknownServiceRule((new XmlServiceMapFactory(__DIR__ . '/container.xml'))->create(), new Standard());
+		return new ContainerInterfaceUnknownServiceRule((new XmlServiceMapFactory(__DIR__ . '/container.xml'))->create(), self::getContainer()->getByType(Printer::class));
 	}
 
-	/**
-	 * @return \PHPStan\Type\MethodTypeSpecifyingExtension[]
-	 */
-	protected function getMethodTypeSpecifyingExtensions(): array
+	public function testGetPrivateService(): void
 	{
-		return [
-			new ServiceTypeSpecifyingExtension(Controller::class, new Standard()),
-		];
+		if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\Controller')) {
+			self::markTestSkipped();
+		}
+		$this->analyse(
+			[
+				__DIR__ . '/ExampleController.php',
+			],
+			[
+				[
+					'Service "unknown" is not registered in the container.',
+					25,
+				],
+			],
+		);
 	}
 
-	public function testGetPrivateService(): void
+	public function testGetPrivateServiceInAbstractController(): void
 	{
+		if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\Controller')) {
+			self::markTestSkipped();
+		}
+
 		$this->analyse(
 			[
-				__DIR__ . '/ExampleController.php',
+				__DIR__ . '/ExampleAbstractController.php',
 			],
 			[
 				[
 					'Service "unknown" is not registered in the container.',
-					24,
+					25,
 				],
-			]
+			],
 		);
 	}
 
+	public function testGetPrivateServiceInLegacyServiceSubscriber(): void
+	{
+		if (!interface_exists('Symfony\Contracts\Service\ServiceSubscriberInterface')) {
+			self::markTestSkipped('The test needs Symfony\Contracts\Service\ServiceSubscriberInterface class.');
+		}
+
+		$this->analyse(
+			[
+				__DIR__ . '/ExampleServiceSubscriber.php',
+			],
+			[],
+		);
+	}
+
+	public static function getAdditionalConfigFiles(): array
+	{
+		return [
+			__DIR__ . '/../../../extension.neon',
+		];
+	}
+
 }
diff --git a/tests/Rules/Symfony/ExampleAbstractController.php b/tests/Rules/Symfony/ExampleAbstractController.php
new file mode 100644
index 00000000..22e5900e
--- /dev/null
+++ b/tests/Rules/Symfony/ExampleAbstractController.php
@@ -0,0 +1,43 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Rules\Symfony;
+
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Bundle\FrameworkBundle\Test\TestContainer;
+
+final class ExampleAbstractController extends AbstractController
+{
+
+	public function privateService(): void
+	{
+		$this->get('private');
+	}
+
+	public function privateServiceInTestContainer(): void
+	{
+		/** @var TestContainer $container */
+		$container = doFoo();
+		$container->get('private');
+	}
+
+	public function unknownService(): void
+	{
+		$this->get('unknown');
+	}
+
+	public function unknownGuardedServiceInsideContext(): void
+	{
+		if ($this->has('unknown')) { // phpcs:ignore
+			$this->get('unknown');
+		}
+	}
+
+	public function unknownGuardedServiceOutsideOfContext(): void
+	{
+		if (!$this->has('unknown')) {
+			return;
+		}
+		$this->get('unknown');
+	}
+
+}
diff --git a/tests/Rules/Symfony/ExampleCommand.php b/tests/Rules/Symfony/ExampleCommand.php
new file mode 100644
index 00000000..6dec4cbd
--- /dev/null
+++ b/tests/Rules/Symfony/ExampleCommand.php
@@ -0,0 +1,58 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Rules\Symfony;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+final class ExampleCommand extends Command
+{
+
+	protected function configure(): void
+	{
+		$this->setName('example-rule');
+
+		$this->addArgument('arg');
+
+		$this->addArgument('foo1', null, '', null);
+		$this->addArgument('bar1', null, '', '');
+		$this->addArgument('baz1', null, '', 1);
+		$this->addArgument('quz1', null, '', ['']);
+
+		$this->addArgument('quz2', InputArgument::IS_ARRAY, '', ['a' => 'b']);
+
+		$this->addOption('aaa');
+
+		$this->addOption('b', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, '', [1]);
+		$this->addOption('c', null, InputOption::VALUE_OPTIONAL, '', 1);
+		$this->addOption('d', null, InputOption::VALUE_OPTIONAL, '', false);
+		$this->addOption('f', null, InputOption::VALUE_REQUIRED, '', true);
+
+		/** @var string[] $defaults */
+		$defaults = [];
+		$this->addOption('e', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, '', $defaults);
+	}
+
+	protected function execute(InputInterface $input, OutputInterface $output): int
+	{
+		$input->getArgument('arg');
+		$input->getArgument('undefined');
+
+		if ($input->hasArgument('guarded')) {
+			$input->getArgument('guarded');
+		}
+
+		$input->getOption('aaa');
+		$input->getOption('bbb');
+
+		if ($input->hasOption('ccc')) {
+			$input->getOption('ccc');
+		}
+
+		return 0;
+	}
+
+}
diff --git a/tests/Rules/Symfony/ExampleController.php b/tests/Rules/Symfony/ExampleController.php
index 65d1359b..5b9e1ca4 100644
--- a/tests/Rules/Symfony/ExampleController.php
+++ b/tests/Rules/Symfony/ExampleController.php
@@ -3,6 +3,7 @@
 namespace PHPStan\Rules\Symfony;
 
 use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use Symfony\Bundle\FrameworkBundle\Test\TestContainer;
 
 final class ExampleController extends Controller
 {
@@ -14,7 +15,7 @@ public function privateService(): void
 
 	public function privateServiceInTestContainer(): void
 	{
-		/** @var \Symfony\Bundle\FrameworkBundle\Test\TestContainer $container */
+		/** @var TestContainer $container */
 		$container = doFoo();
 		$container->get('private');
 	}
@@ -39,4 +40,9 @@ public function unknownGuardedServiceOutsideOfContext(): void
 		$this->get('unknown');
 	}
 
+	public function privateServiceFromServiceLocator(): void
+	{
+		$this->get('service_locator')->get('private');
+	}
+
 }
diff --git a/tests/Rules/Symfony/ExampleServiceSubscriber.php b/tests/Rules/Symfony/ExampleServiceSubscriber.php
index c9a009d2..ec9c966d 100644
--- a/tests/Rules/Symfony/ExampleServiceSubscriber.php
+++ b/tests/Rules/Symfony/ExampleServiceSubscriber.php
@@ -2,14 +2,31 @@
 
 namespace PHPStan\Rules\Symfony;
 
+use Psr\Container\ContainerInterface;
+use Symfony\Component\DependencyInjection\ParameterBag\ContainerBag;
 use Symfony\Contracts\Service\ServiceSubscriberInterface;
 
 final class ExampleServiceSubscriber implements ServiceSubscriberInterface
 {
 
+	private ContainerInterface $locator;
+
+	public function __construct(ContainerInterface $locator)
+	{
+		$this->locator = $locator;
+	}
+
 	public function privateService(): void
 	{
 		$this->get('private');
+		$this->locator->get('private');
+	}
+
+	public function containerParameter(): void
+	{
+		/** @var ContainerBag $containerBag */
+		$containerBag = doFoo();
+		$containerBag->get('parameter_name');
 	}
 
 	/**
diff --git a/tests/Rules/Symfony/ExampleTest.php b/tests/Rules/Symfony/ExampleTest.php
new file mode 100644
index 00000000..10b06703
--- /dev/null
+++ b/tests/Rules/Symfony/ExampleTest.php
@@ -0,0 +1,23 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Rules\Symfony;
+
+use Symfony\Bundle\FrameworkBundle\KernelBrowser;
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+
+abstract class ExampleTest extends KernelTestCase
+{
+
+	public function bar(): void
+	{
+		$container = self::getContainer();
+		$container->get('private');
+	}
+
+	public function foo(KernelBrowser $browser): void
+	{
+		$container = $browser->getContainer();
+		$container->get('private');
+	}
+
+}
diff --git a/tests/Rules/Symfony/InvalidArgumentDefaultValueRuleTest.php b/tests/Rules/Symfony/InvalidArgumentDefaultValueRuleTest.php
new file mode 100644
index 00000000..bc6a6563
--- /dev/null
+++ b/tests/Rules/Symfony/InvalidArgumentDefaultValueRuleTest.php
@@ -0,0 +1,42 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Rules\Symfony;
+
+use PHPStan\Rules\Rule;
+use PHPStan\Testing\RuleTestCase;
+
+/**
+ * @extends RuleTestCase<InvalidArgumentDefaultValueRule>
+ */
+final class InvalidArgumentDefaultValueRuleTest extends RuleTestCase
+{
+
+	protected function getRule(): Rule
+	{
+		return new InvalidArgumentDefaultValueRule();
+	}
+
+	public function testGetArgument(): void
+	{
+		$this->analyse(
+			[
+				__DIR__ . '/ExampleCommand.php',
+			],
+			[
+				[
+					'Parameter #4 $default of method Symfony\Component\Console\Command\Command::addArgument() expects string|null, int given.',
+					22,
+				],
+				[
+					'Parameter #4 $default of method Symfony\Component\Console\Command\Command::addArgument() expects string|null, array<int, string> given.',
+					23,
+				],
+				[
+					'Parameter #4 $default of method Symfony\Component\Console\Command\Command::addArgument() expects array<int, string>|null, array<string, string> given.',
+					25,
+				],
+			],
+		);
+	}
+
+}
diff --git a/tests/Rules/Symfony/InvalidOptionDefaultValueRuleTest.php b/tests/Rules/Symfony/InvalidOptionDefaultValueRuleTest.php
new file mode 100644
index 00000000..2dcbbcd1
--- /dev/null
+++ b/tests/Rules/Symfony/InvalidOptionDefaultValueRuleTest.php
@@ -0,0 +1,34 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Rules\Symfony;
+
+use PHPStan\Rules\Rule;
+use PHPStan\Testing\RuleTestCase;
+
+/**
+ * @extends RuleTestCase<InvalidOptionDefaultValueRule>
+ */
+final class InvalidOptionDefaultValueRuleTest extends RuleTestCase
+{
+
+	protected function getRule(): Rule
+	{
+		return new InvalidOptionDefaultValueRule();
+	}
+
+	public function testGetArgument(): void
+	{
+		$this->analyse(
+			[
+				__DIR__ . '/ExampleCommand.php',
+			],
+			[
+				[
+					'Parameter #5 $default of method Symfony\Component\Console\Command\Command::addOption() expects array<string>|null, array<int, int> given.',
+					29,
+				],
+			],
+		);
+	}
+
+}
diff --git a/tests/Rules/Symfony/UndefinedArgumentRuleTest.php b/tests/Rules/Symfony/UndefinedArgumentRuleTest.php
new file mode 100644
index 00000000..d9970ef6
--- /dev/null
+++ b/tests/Rules/Symfony/UndefinedArgumentRuleTest.php
@@ -0,0 +1,43 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Rules\Symfony;
+
+use PHPStan\Node\Printer\Printer;
+use PHPStan\Rules\Rule;
+use PHPStan\Symfony\ConsoleApplicationResolver;
+use PHPStan\Testing\RuleTestCase;
+
+/**
+ * @extends RuleTestCase<UndefinedArgumentRule>
+ */
+final class UndefinedArgumentRuleTest extends RuleTestCase
+{
+
+	protected function getRule(): Rule
+	{
+		return new UndefinedArgumentRule(new ConsoleApplicationResolver(__DIR__ . '/console_application_loader.php'), self::getContainer()->getByType(Printer::class));
+	}
+
+	public function testGetArgument(): void
+	{
+		$this->analyse(
+			[
+				__DIR__ . '/ExampleCommand.php',
+			],
+			[
+				[
+					'Command "example-rule" does not define argument "undefined".',
+					42,
+				],
+			],
+		);
+	}
+
+	public static function getAdditionalConfigFiles(): array
+	{
+		return [
+			__DIR__ . '/argument.neon',
+		];
+	}
+
+}
diff --git a/tests/Rules/Symfony/UndefinedOptionRuleTest.php b/tests/Rules/Symfony/UndefinedOptionRuleTest.php
new file mode 100644
index 00000000..7f759213
--- /dev/null
+++ b/tests/Rules/Symfony/UndefinedOptionRuleTest.php
@@ -0,0 +1,43 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Rules\Symfony;
+
+use PHPStan\Node\Printer\Printer;
+use PHPStan\Rules\Rule;
+use PHPStan\Symfony\ConsoleApplicationResolver;
+use PHPStan\Testing\RuleTestCase;
+
+/**
+ * @extends RuleTestCase<UndefinedOptionRule>
+ */
+final class UndefinedOptionRuleTest extends RuleTestCase
+{
+
+	protected function getRule(): Rule
+	{
+		return new UndefinedOptionRule(new ConsoleApplicationResolver(__DIR__ . '/console_application_loader.php'), self::getContainer()->getByType(Printer::class));
+	}
+
+	public function testGetArgument(): void
+	{
+		$this->analyse(
+			[
+				__DIR__ . '/ExampleCommand.php',
+			],
+			[
+				[
+					'Command "example-rule" does not define option "bbb".',
+					49,
+				],
+			],
+		);
+	}
+
+	public static function getAdditionalConfigFiles(): array
+	{
+		return [
+			__DIR__ . '/option.neon',
+		];
+	}
+
+}
diff --git a/tests/Rules/Symfony/argument.neon b/tests/Rules/Symfony/argument.neon
new file mode 100644
index 00000000..86fa3f16
--- /dev/null
+++ b/tests/Rules/Symfony/argument.neon
@@ -0,0 +1,5 @@
+services:
+	-
+		class: PHPStan\Type\Symfony\ArgumentTypeSpecifyingExtension
+		tags:
+			- phpstan.typeSpecifier.methodTypeSpecifyingExtension
diff --git a/tests/Rules/Symfony/console_application_loader.php b/tests/Rules/Symfony/console_application_loader.php
new file mode 100644
index 00000000..05f8ed51
--- /dev/null
+++ b/tests/Rules/Symfony/console_application_loader.php
@@ -0,0 +1,10 @@
+<?php declare(strict_types = 1);
+
+use PHPStan\Rules\Symfony\ExampleCommand;
+use Symfony\Component\Console\Application;
+
+require_once __DIR__ . '/../../../vendor/autoload.php';
+
+$application = new Application();
+$application->add(new ExampleCommand());
+return $application;
diff --git a/tests/Rules/Symfony/container.xml b/tests/Rules/Symfony/container.xml
index f21aeae3..f3261e0a 100644
--- a/tests/Rules/Symfony/container.xml
+++ b/tests/Rules/Symfony/container.xml
@@ -3,5 +3,10 @@
     <services>
         <service id="private" class="Foo" public="false"></service>
         <service id="public" class="Foo"></service>
+        <service id="service_locator" class="Symfony\Component\DependencyInjection\ServiceLocator" public="true">
+            <argument type="collection">
+                <argument key="private" type="service" id="private"/>
+            </argument>
+        </service>
     </services>
 </container>
diff --git a/tests/Rules/Symfony/option.neon b/tests/Rules/Symfony/option.neon
new file mode 100644
index 00000000..30984a7a
--- /dev/null
+++ b/tests/Rules/Symfony/option.neon
@@ -0,0 +1,5 @@
+services:
+	-
+		class: PHPStan\Type\Symfony\OptionTypeSpecifyingExtension
+		tags:
+			- phpstan.typeSpecifier.methodTypeSpecifyingExtension
diff --git a/tests/Rules/data/input_bag.php b/tests/Rules/data/input_bag.php
new file mode 100644
index 00000000..03699f6b
--- /dev/null
+++ b/tests/Rules/data/input_bag.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace InputBagTest;
+
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Annotation\Route;
+
+class TestController extends AbstractController
+{
+	/**
+	 * @Route("/test", name="test")
+	 */
+	public function index(Request $request): Response
+	{
+		$foo = $request->query->get('foo');
+
+		return $this->render('test/index.html.twig', [
+			'controller_name' => 'TestController',
+		]);
+	}
+}
diff --git a/tests/Symfony/DefaultParameterMapTest.php b/tests/Symfony/DefaultParameterMapTest.php
new file mode 100644
index 00000000..018a68a9
--- /dev/null
+++ b/tests/Symfony/DefaultParameterMapTest.php
@@ -0,0 +1,144 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+use Iterator;
+use PHPUnit\Framework\TestCase;
+
+final class DefaultParameterMapTest extends TestCase
+{
+
+	/**
+	 * @dataProvider getParameterProvider
+	 */
+	public function testGetParameter(string $key, callable $validator): void
+	{
+		$factory = new XmlParameterMapFactory(__DIR__ . '/container.xml');
+		$validator($factory->create()->getParameter($key));
+	}
+
+	public function testGetParameterEscapedPath(): void
+	{
+		$factory = new XmlParameterMapFactory(__DIR__ . '/containers/bugfix%2Fcontainer.xml');
+		$serviceMap = $factory->create();
+
+		self::assertNotNull($serviceMap->getParameter('app.string'));
+	}
+
+	/**
+	 * @return Iterator<mixed>
+	 */
+	public function getParameterProvider(): Iterator
+	{
+		yield [
+			'unknown',
+			static function (?Parameter $parameter): void {
+				self::assertNull($parameter);
+			},
+		];
+		yield [
+			'app.string',
+			static function (?Parameter $parameter): void {
+				self::assertNotNull($parameter);
+				self::assertSame('app.string', $parameter->getKey());
+				self::assertSame('abcdef', $parameter->getValue());
+			},
+		];
+		yield [
+			'app.int',
+			static function (?Parameter $parameter): void {
+				self::assertNotNull($parameter);
+				self::assertSame('app.int', $parameter->getKey());
+				self::assertSame(123, $parameter->getValue());
+			},
+		];
+		yield [
+			'app.int_as_string',
+			static function (?Parameter $parameter): void {
+				self::assertNotNull($parameter);
+				self::assertSame('app.int_as_string', $parameter->getKey());
+				self::assertSame('123', $parameter->getValue());
+			},
+		];
+		yield [
+			'app.float',
+			static function (?Parameter $parameter): void {
+				self::assertNotNull($parameter);
+				self::assertSame('app.float', $parameter->getKey());
+				self::assertSame(123.45, $parameter->getValue());
+			},
+		];
+		yield [
+			'app.float_as_string',
+			static function (?Parameter $parameter): void {
+				self::assertNotNull($parameter);
+				self::assertSame('app.float_as_string', $parameter->getKey());
+				self::assertSame('123.45', $parameter->getValue());
+			},
+		];
+		yield [
+			'app.boolean',
+			static function (?Parameter $parameter): void {
+				self::assertNotNull($parameter);
+				self::assertSame('app.boolean', $parameter->getKey());
+				self::assertTrue($parameter->getValue());
+			},
+		];
+		yield [
+			'app.boolean_as_string',
+			static function (?Parameter $parameter): void {
+				self::assertNotNull($parameter);
+				self::assertSame('app.boolean_as_string', $parameter->getKey());
+				self::assertSame('true', $parameter->getValue());
+			},
+		];
+		yield [
+			'app.list',
+			static function (?Parameter $parameter): void {
+				self::assertNotNull($parameter);
+				self::assertSame('app.list', $parameter->getKey());
+				self::assertEquals(['en', 'es', 'fr'], $parameter->getValue());
+			},
+		];
+		yield [
+			'app.list_of_list',
+			static function (?Parameter $parameter): void {
+				self::assertNotNull($parameter);
+				self::assertSame('app.list_of_list', $parameter->getKey());
+				self::assertEquals([
+					['name' => 'the name', 'value' => 'the value'],
+					['name' => 'another name', 'value' => 'another value'],
+				], $parameter->getValue());
+			},
+		];
+		yield [
+			'app.map',
+			static function (?Parameter $parameter): void {
+				self::assertNotNull($parameter);
+				self::assertSame('app.map', $parameter->getKey());
+				self::assertEquals([
+					'a' => 'value of a',
+					'b' => 'value of b',
+					'c' => 'value of c',
+				], $parameter->getValue());
+			},
+		];
+		yield [
+			'app.binary',
+			static function (?Parameter $parameter): void {
+				self::assertNotNull($parameter);
+				self::assertSame('app.binary', $parameter->getKey());
+				self::assertSame('This is a Bell char ', $parameter->getValue());
+			},
+		];
+		yield [
+			'app.constant',
+			static function (?Parameter $parameter): void {
+				self::assertNotNull($parameter);
+				self::assertSame('app.constant', $parameter->getKey());
+				self::assertSame('Y-m-d\TH:i:sP', $parameter->getValue());
+			},
+		];
+	}
+
+}
diff --git a/tests/Symfony/ServiceMapTest.php b/tests/Symfony/DefaultServiceMapTest.php
similarity index 61%
rename from tests/Symfony/ServiceMapTest.php
rename to tests/Symfony/DefaultServiceMapTest.php
index 13ad2d9e..b43bee49 100644
--- a/tests/Symfony/ServiceMapTest.php
+++ b/tests/Symfony/DefaultServiceMapTest.php
@@ -5,7 +5,7 @@
 use Iterator;
 use PHPUnit\Framework\TestCase;
 
-final class ServiceMapTest extends TestCase
+final class DefaultServiceMapTest extends TestCase
 {
 
 	/**
@@ -17,91 +17,113 @@ public function testGetService(string $id, callable $validator): void
 		$validator($factory->create()->getService($id));
 	}
 
+	public function testGetContainerEscapedPath(): void
+	{
+		$factory = new XmlServiceMapFactory(__DIR__ . '/containers/bugfix%2Fcontainer.xml');
+		$serviceMap = $factory->create();
+
+		self::assertNotNull($serviceMap->getService('withClass'));
+	}
+
+	/**
+	 * @return Iterator<mixed>
+	 */
 	public function getServiceProvider(): Iterator
 	{
 		yield [
 			'unknown',
-			function (?Service $service): void {
+			static function (?Service $service): void {
 				self::assertNull($service);
 			},
 		];
 		yield [
 			'withoutClass',
-			function (?Service $service): void {
+			static function (?Service $service): void {
 				self::assertNotNull($service);
 				self::assertSame('withoutClass', $service->getId());
 				self::assertNull($service->getClass());
-				self::assertTrue($service->isPublic());
+				self::assertFalse($service->isPublic());
 				self::assertFalse($service->isSynthetic());
 				self::assertNull($service->getAlias());
 			},
 		];
 		yield [
 			'withClass',
-			function (?Service $service): void {
+			static function (?Service $service): void {
 				self::assertNotNull($service);
 				self::assertSame('withClass', $service->getId());
 				self::assertSame('Foo', $service->getClass());
-				self::assertTrue($service->isPublic());
+				self::assertFalse($service->isPublic());
 				self::assertFalse($service->isSynthetic());
 				self::assertNull($service->getAlias());
 			},
 		];
 		yield [
 			'withoutPublic',
-			function (?Service $service): void {
+			static function (?Service $service): void {
 				self::assertNotNull($service);
 				self::assertSame('withoutPublic', $service->getId());
 				self::assertSame('Foo', $service->getClass());
-				self::assertTrue($service->isPublic());
+				self::assertFalse($service->isPublic());
 				self::assertFalse($service->isSynthetic());
 				self::assertNull($service->getAlias());
 			},
 		];
 		yield [
-			'publicNotFalse',
-			function (?Service $service): void {
+			'publicNotTrue',
+			static function (?Service $service): void {
 				self::assertNotNull($service);
-				self::assertSame('publicNotFalse', $service->getId());
+				self::assertSame('publicNotTrue', $service->getId());
 				self::assertSame('Foo', $service->getClass());
-				self::assertTrue($service->isPublic());
+				self::assertFalse($service->isPublic());
 				self::assertFalse($service->isSynthetic());
 				self::assertNull($service->getAlias());
 			},
 		];
 		yield [
-			'private',
-			function (?Service $service): void {
+			'public',
+			static function (?Service $service): void {
 				self::assertNotNull($service);
-				self::assertSame('private', $service->getId());
+				self::assertSame('public', $service->getId());
 				self::assertSame('Foo', $service->getClass());
-				self::assertFalse($service->isPublic());
+				self::assertTrue($service->isPublic());
 				self::assertFalse($service->isSynthetic());
 				self::assertNull($service->getAlias());
 			},
 		];
 		yield [
 			'synthetic',
-			function (?Service $service): void {
+			static function (?Service $service): void {
 				self::assertNotNull($service);
 				self::assertSame('synthetic', $service->getId());
 				self::assertSame('Foo', $service->getClass());
-				self::assertTrue($service->isPublic());
+				self::assertFalse($service->isPublic());
 				self::assertTrue($service->isSynthetic());
 				self::assertNull($service->getAlias());
 			},
 		];
 		yield [
 			'alias',
-			function (?Service $service): void {
+			static function (?Service $service): void {
 				self::assertNotNull($service);
 				self::assertSame('alias', $service->getId());
 				self::assertSame('Foo', $service->getClass());
-				self::assertTrue($service->isPublic());
+				self::assertFalse($service->isPublic());
 				self::assertFalse($service->isSynthetic());
 				self::assertSame('withClass', $service->getAlias());
 			},
 		];
+		yield [
+			'aliasForInlined',
+			static function (?Service $service): void {
+				self::assertNotNull($service);
+				self::assertSame('aliasForInlined', $service->getId());
+				self::assertNull($service->getClass());
+				self::assertFalse($service->isPublic());
+				self::assertFalse($service->isSynthetic());
+				self::assertSame('inlined', $service->getAlias());
+			},
+		];
 	}
 
 }
diff --git a/tests/Symfony/NeonTest.php b/tests/Symfony/NeonTest.php
deleted file mode 100644
index e14ef8f2..00000000
--- a/tests/Symfony/NeonTest.php
+++ /dev/null
@@ -1,48 +0,0 @@
-<?php declare(strict_types = 1);
-
-namespace PHPStan\Symfony;
-
-use Nette\DI\Compiler;
-use Nette\DI\ContainerLoader;
-use PHPStan\DependencyInjection\RulesExtension;
-use PHPUnit\Framework\TestCase;
-use function sprintf;
-use function unlink;
-
-final class NeonTest extends TestCase
-{
-
-	public function testExtensionNeon(): void
-	{
-		$key = '';
-		$tmpDir = __DIR__ . '/../tmp';
-		$loader = new ContainerLoader($tmpDir, true);
-		$generatedContainer = sprintf('%s/%s.php', $tmpDir, $loader->getClassName($key));
-
-		@unlink($generatedContainer);
-		self::assertFileNotExists($generatedContainer);
-
-		$class = $loader->load(function (Compiler $compiler): void {
-			$compiler->addExtension('rules', new RulesExtension());
-			$compiler->addConfig(['parameters' => ['rootDir' => __DIR__]]);
-			$compiler->loadConfig(__DIR__ . '/config.neon');
-			$compiler->loadConfig(__DIR__ . '/../../extension.neon');
-		}, $key);
-		/** @var \Nette\DI\Container $container */
-		$container = new $class();
-
-		self::assertSame([
-			'rootDir' => __DIR__,
-			'symfony' => [
-				'container_xml_path' => __DIR__ . '/container.xml',
-				'constant_hassers' => true,
-			],
-		], $container->getParameters());
-
-		self::assertCount(2, $container->findByTag('phpstan.rules.rule'));
-		self::assertCount(6, $container->findByTag('phpstan.broker.dynamicMethodReturnTypeExtension'));
-		self::assertCount(3, $container->findByTag('phpstan.typeSpecifier.methodTypeSpecifyingExtension'));
-		self::assertInstanceOf(ServiceMap::class, $container->getByType(ServiceMap::class));
-	}
-
-}
diff --git a/tests/Symfony/RequiredAutowiringExtensionTest.php b/tests/Symfony/RequiredAutowiringExtensionTest.php
new file mode 100644
index 00000000..93fb3822
--- /dev/null
+++ b/tests/Symfony/RequiredAutowiringExtensionTest.php
@@ -0,0 +1,65 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+use PHPStan\Reflection\AdditionalConstructorsExtension;
+use PHPStan\Rules\Properties\UninitializedPropertyRule;
+use PHPStan\Rules\Rule;
+use PHPStan\Testing\RuleTestCase;
+use Symfony\Contracts\Service\Attribute\Required;
+use function class_exists;
+
+/**
+ * @extends RuleTestCase<UninitializedPropertyRule>
+ */
+final class RequiredAutowiringExtensionTest extends RuleTestCase
+{
+
+	protected function getRule(): Rule
+	{
+		$container = self::getContainer();
+		$container->getServicesByTag(AdditionalConstructorsExtension::EXTENSION_TAG);
+
+		return $container->getByType(UninitializedPropertyRule::class);
+	}
+
+	public function testRequiredAnnotations(): void
+	{
+		$this->analyse([__DIR__ . '/data/required-annotations.php'], [
+			[
+				'Class RequiredAnnotationTest\TestAnnotations has an uninitialized property $three. Give it default value or assign it in the constructor.',
+				12,
+			],
+			[
+				'Class RequiredAnnotationTest\TestAnnotations has an uninitialized property $four. Give it default value or assign it in the constructor.',
+				14,
+			],
+		]);
+	}
+
+	public function testRequiredAttributes(): void
+	{
+		if (!class_exists(Required::class)) {
+			self::markTestSkipped('Required symfony/service-contracts@3.2.1 or higher is not installed');
+		}
+
+		$this->analyse([__DIR__ . '/data/required-attributes.php'], [
+			[
+				'Class RequiredAttributesTest\TestAttributes has an uninitialized property $three. Give it default value or assign it in the constructor.',
+				14,
+			],
+			[
+				'Class RequiredAttributesTest\TestAttributes has an uninitialized property $four. Give it default value or assign it in the constructor.',
+				16,
+			],
+		]);
+	}
+
+	public static function getAdditionalConfigFiles(): array
+	{
+		return [
+			__DIR__ . '/required-autowiring-config.neon',
+		];
+	}
+
+}
diff --git a/tests/Symfony/SymfonyContainerResultCacheMetaExtensionTest.php b/tests/Symfony/SymfonyContainerResultCacheMetaExtensionTest.php
new file mode 100644
index 00000000..f5c8503f
--- /dev/null
+++ b/tests/Symfony/SymfonyContainerResultCacheMetaExtensionTest.php
@@ -0,0 +1,294 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+use PHPStan\Testing\PHPStanTestCase;
+use function count;
+
+/**
+ * @phpstan-type ContainerContents array{parameters?: ParameterMap, services?: ServiceMap}
+ */
+final class SymfonyContainerResultCacheMetaExtensionTest extends PHPStanTestCase
+{
+
+	/**
+	 * @param list<ContainerContents> $sameHashContents
+	 * @param ContainerContents $invalidatingContent
+	 *
+	 * @dataProvider provideContainerHashIsCalculatedCorrectlyCases
+	 */
+	public function testContainerHashIsCalculatedCorrectly(
+		array $sameHashContents,
+		array $invalidatingContent
+	): void
+	{
+		$hash = null;
+
+		self::assertGreaterThan(0, count($sameHashContents));
+
+		foreach ($sameHashContents as $content) {
+			$currentHash = (new SymfonyContainerResultCacheMetaExtension(
+				$content['parameters'] ?? new DefaultParameterMap([]),
+				$content['services'] ?? new DefaultServiceMap([]),
+			))->getHash();
+
+			if ($hash === null) {
+				$hash = $currentHash;
+			} else {
+				self::assertSame($hash, $currentHash);
+			}
+		}
+
+		self::assertNotSame(
+			$hash,
+			(new SymfonyContainerResultCacheMetaExtension(
+				$invalidatingContent['parameters'] ?? new DefaultParameterMap([]),
+				$invalidatingContent['services'] ?? new DefaultServiceMap([]),
+			))->getHash(),
+		);
+	}
+
+	/**
+	 * @return iterable<string, array{list<ContainerContents>, ContainerContents}>
+	 */
+	public static function provideContainerHashIsCalculatedCorrectlyCases(): iterable
+	{
+		yield 'service "class" changes' => [
+			[
+				[
+					'services' => new DefaultServiceMap([
+						new Service('Foo', 'Foo', true, false, null),
+						new Service('Bar', 'Bar', true, false, null),
+					]),
+				],
+				// Swapping services order in XML file does not affect the calculated hash
+				[
+					'services' => new DefaultServiceMap([
+						new Service('Bar', 'Bar', true, false, null),
+						new Service('Foo', 'Foo', true, false, null),
+					]),
+				],
+			],
+			[
+				'services' => new DefaultServiceMap([
+					new Service('Foo', 'Foo', true, false, null),
+					new Service('Bar', 'BarAdapter', true, false, null),
+				]),
+			],
+		];
+
+		yield 'service visibility changes' => [
+			[
+				[
+					'services' => new DefaultServiceMap([
+						new Service('Foo', 'Foo', true, false, null),
+					]),
+				],
+			],
+			[
+				'services' => new DefaultServiceMap([
+					new Service('Foo', 'Foo', false, false, null),
+				]),
+			],
+		];
+
+		yield 'service syntheticity changes' => [
+			[
+				[
+					'services' => new DefaultServiceMap([
+						new Service('Foo', 'Foo', true, false, null),
+					]),
+				],
+			],
+			[
+				'services' => new DefaultServiceMap([
+					new Service('Foo', 'Foo', true, true, null),
+				]),
+			],
+		];
+
+		yield 'service alias changes' => [
+			[
+				[
+					'services' => new DefaultServiceMap([
+						new Service('Foo', 'Foo', true, false, null),
+						new Service('Bar', 'Bar', true, false, null),
+						new Service('Baz', null, true, false, 'Foo'),
+					]),
+				],
+				// Swapping services order in XML file does not affect the calculated hash
+				[
+					'services' => new DefaultServiceMap([
+						new Service('Baz', null, true, false, 'Foo'),
+						new Service('Bar', 'Bar', true, false, null),
+						new Service('Foo', 'Foo', true, false, null),
+					]),
+				],
+			],
+			[
+				'services' => new DefaultServiceMap([
+					new Service('Foo', 'Foo', true, false, null),
+					new Service('Bar', 'Bar', true, false, null),
+					new Service('Baz', null, true, false, 'Bar'),
+				]),
+			],
+		];
+
+		yield 'service tag attributes changes' => [
+			[
+				[
+					'services' => new DefaultServiceMap([
+						new Service('Foo', 'Foo', true, false, null, [
+							new ServiceTag('foo.bar', ['baz' => 'bar']),
+							new ServiceTag('foo.baz', ['baz' => 'baz']),
+						]),
+					]),
+				],
+				[
+					'services' => new DefaultServiceMap([
+						new Service('Foo', 'Foo', true, false, null, [
+							new ServiceTag('foo.baz', ['baz' => 'baz']),
+							new ServiceTag('foo.bar', ['baz' => 'bar']),
+						]),
+					]),
+				],
+			],
+			[
+				'services' => new DefaultServiceMap([
+					new Service('Foo', 'Foo', true, false, null, [
+						new ServiceTag('foo.bar', ['baz' => 'bar']),
+						new ServiceTag('foo.baz', ['baz' => 'buzz']),
+					]),
+				]),
+			],
+		];
+
+		yield 'service tag added' => [
+			[
+				[
+					'services' => new DefaultServiceMap([
+						new Service('Foo', 'Foo', true, false, null, [
+							new ServiceTag('foo.bar', ['baz' => 'bar']),
+						]),
+					]),
+				],
+			],
+			[
+				'services' => new DefaultServiceMap([
+					new Service('Foo', 'Foo', true, false, null, [
+						new ServiceTag('foo.bar', ['baz' => 'bar']),
+						new ServiceTag('foo.baz', ['baz' => 'baz']),
+					]),
+				]),
+			],
+		];
+
+		yield 'service tag removed' => [
+			[
+				[
+					'services' => new DefaultServiceMap([
+						new Service('Foo', 'Foo', true, false, null, [
+							new ServiceTag('foo.bar', ['baz' => 'bar']),
+							new ServiceTag('foo.baz', ['baz' => 'baz']),
+						]),
+					]),
+				],
+			],
+			[
+				'services' => new DefaultServiceMap([
+					new Service('Foo', 'Foo', true, false, null, [
+						new ServiceTag('foo.bar', ['baz' => 'bar']),
+					]),
+				]),
+			],
+		];
+
+		yield 'new service added' => [
+			[
+				[
+					'services' => new DefaultServiceMap([
+						new Service('Foo', 'Foo', true, false, null),
+					]),
+				],
+			],
+			[
+				'services' => new DefaultServiceMap([
+					new Service('Foo', 'Foo', true, false, null),
+					new Service('Bar', 'Bar', true, false, null),
+				]),
+			],
+		];
+
+		yield 'service removed' => [
+			[
+				[
+					'services' => new DefaultServiceMap([
+						new Service('Foo', 'Foo', true, false, null),
+						new Service('Bar', 'Bar', true, false, null),
+					]),
+				],
+			],
+			[
+				'services' => new DefaultServiceMap([
+					new Service('Foo', 'Foo', true, false, null),
+				]),
+			],
+		];
+
+		yield 'parameter value changes' => [
+			[
+				[
+					'parameters' => new DefaultParameterMap([
+						new Parameter('foo', 'foo'),
+						new Parameter('bar', 'bar'),
+					]),
+				],
+				[
+					'parameters' => new DefaultParameterMap([
+						new Parameter('bar', 'bar'),
+						new Parameter('foo', 'foo'),
+					]),
+				],
+			],
+			[
+				'parameters' => new DefaultParameterMap([
+					new Parameter('foo', 'foo'),
+					new Parameter('bar', 'buzz'),
+				]),
+			],
+		];
+
+		yield 'new parameter added' => [
+			[
+				[
+					'parameters' => new DefaultParameterMap([
+						new Parameter('foo', 'foo'),
+					]),
+				],
+			],
+			[
+				'parameters' => new DefaultParameterMap([
+					new Parameter('foo', 'foo'),
+					new Parameter('bar', 'bar'),
+				]),
+			],
+		];
+
+		yield 'parameter removed' => [
+			[
+				[
+					'parameters' => new DefaultParameterMap([
+						new Parameter('foo', 'foo'),
+						new Parameter('bar', 'bar'),
+					]),
+				],
+			],
+			[
+				'parameters' => new DefaultParameterMap([
+					new Parameter('foo', 'foo'),
+				]),
+			],
+		];
+	}
+
+}
diff --git a/tests/Symfony/config.neon b/tests/Symfony/config.neon
deleted file mode 100644
index d5787020..00000000
--- a/tests/Symfony/config.neon
+++ /dev/null
@@ -1,6 +0,0 @@
-parameters:
-	symfony:
-		container_xml_path: %rootDir%/container.xml
-
-services:
-	- PhpParser\PrettyPrinter\Standard
diff --git a/tests/Symfony/container.xml b/tests/Symfony/container.xml
index 8c8fbd55..f456ab51 100644
--- a/tests/Symfony/container.xml
+++ b/tests/Symfony/container.xml
@@ -1,13 +1,47 @@
 <?xml version="1.0" encoding="utf-8"?>
 <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
+  <parameters>
+    <parameter key="app.string">abcdef</parameter>
+    <parameter key="app.int">123</parameter>
+    <parameter key="app.int_as_string" type="string">123</parameter>
+    <parameter key="app.float">123.45</parameter>
+    <parameter key="app.float_as_string" type="string">123.45</parameter>
+    <parameter key="app.boolean">true</parameter>
+    <parameter key="app.boolean_as_string" type="string">true</parameter>
+    <parameter key="app.list" type="collection">
+      <parameter>en</parameter>
+      <parameter>es</parameter>
+      <parameter>fr</parameter>
+    </parameter>
+    <parameter key="app.list_of_list" type="collection">
+      <parameter type="collection">
+        <parameter key="name">the name</parameter>
+        <parameter key="value">the value</parameter>
+      </parameter>
+      <parameter type="collection">
+        <parameter key="name">another name</parameter>
+        <parameter key="value">another value</parameter>
+      </parameter>
+    </parameter>
+    <parameter key="app.map" type="collection">
+      <parameter key="a">value of a</parameter>
+      <parameter key="b">value of b</parameter>
+      <parameter key="c">value of c</parameter>
+    </parameter>
+    <parameter key="app.binary" type="binary">VGhpcyBpcyBhIEJlbGwgY2hhciAH</parameter>
+    <parameter key="app.constant">Y-m-d\TH:i:sP</parameter>
+  </parameters>
+
   <services>
     <service></service><!-- without id -->
     <service id="withoutClass"></service>
     <service id="withClass" class="Foo"></service>
     <service id="withoutPublic" class="Foo"></service>
-    <service id="publicNotFalse" class="Foo" public="true"></service>
-    <service id="private" class="Foo" public="false"></service>
+    <service id="publicNotTrue" class="Foo" public="false"></service>
+    <service id="public" class="Foo" public="true"></service>
     <service id="synthetic" class="Foo" synthetic="true"></service>
     <service id="alias" class="Bar" alias="withClass"></service>
+    <service id=".inlined"></service>
+    <service id="aliasForInlined" alias=".inlined"></service>
   </services>
 </container>
diff --git a/tests/Symfony/containers/bugfix%2Fcontainer.xml b/tests/Symfony/containers/bugfix%2Fcontainer.xml
new file mode 100644
index 00000000..5bed7715
--- /dev/null
+++ b/tests/Symfony/containers/bugfix%2Fcontainer.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
+  <parameters>
+    <parameter key="app.string">abcdef</parameter>
+    <parameter key="app.int">123</parameter>
+    <parameter key="app.int_as_string" type="string">123</parameter>
+    <parameter key="app.float">123.45</parameter>
+    <parameter key="app.float_as_string" type="string">123.45</parameter>
+    <parameter key="app.boolean">true</parameter>
+    <parameter key="app.boolean_as_string" type="string">true</parameter>
+    <parameter key="app.list" type="collection">
+      <parameter>en</parameter>
+      <parameter>es</parameter>
+      <parameter>fr</parameter>
+    </parameter>
+    <parameter key="app.list_of_list" type="collection">
+      <parameter type="collection">
+        <parameter key="name">the name</parameter>
+        <parameter key="value">the value</parameter>
+      </parameter>
+      <parameter type="collection">
+        <parameter key="name">another name</parameter>
+        <parameter key="value">another value</parameter>
+      </parameter>
+    </parameter>
+    <parameter key="app.map" type="collection">
+      <parameter key="a">value of a</parameter>
+      <parameter key="b">value of b</parameter>
+      <parameter key="c">value of c</parameter>
+    </parameter>
+    <parameter key="app.binary" type="binary">VGhpcyBpcyBhIEJlbGwgY2hhciAH</parameter>
+    <parameter key="app.constant">Y-m-d\TH:i:sP</parameter>
+  </parameters>
+
+  <services>
+    <service></service><!-- without id -->
+    <service id="withoutClass"></service>
+    <service id="withClass" class="Foo"></service>
+    <service id="withoutPublic" class="Foo"></service>
+    <service id="publicNotFalse" class="Foo" public="true"></service>
+    <service id="private" class="Foo" public="false"></service>
+    <service id="synthetic" class="Foo" synthetic="true"></service>
+    <service id="alias" class="Bar" alias="withClass"></service>
+  </services>
+</container>
diff --git a/tests/Symfony/data/required-annotations.php b/tests/Symfony/data/required-annotations.php
new file mode 100644
index 00000000..e2085ab3
--- /dev/null
+++ b/tests/Symfony/data/required-annotations.php
@@ -0,0 +1,38 @@
+<?php // lint >= 7.4
+
+namespace RequiredAnnotationTest;
+
+class TestAnnotations
+{
+	/** @required */
+	public string $one;
+
+	private string $two;
+
+	public string $three;
+
+	private string $four;
+
+	/**
+	 * @required
+	 */
+	public function setTwo(int $two): void
+	{
+		$this->two = $two;
+	}
+
+	public function getTwo(): int
+	{
+		return $this->two;
+	}
+
+	public function setFour(int $four): void
+	{
+		$this->four = $four;
+	}
+
+	public function getFour(): int
+	{
+		return $this->four;
+	}
+}
diff --git a/tests/Symfony/data/required-attributes.php b/tests/Symfony/data/required-attributes.php
new file mode 100644
index 00000000..d847d276
--- /dev/null
+++ b/tests/Symfony/data/required-attributes.php
@@ -0,0 +1,38 @@
+<?php // lint >= 8.0
+
+namespace RequiredAttributesTest;
+
+use Symfony\Contracts\Service\Attribute\Required;
+
+class TestAttributes
+{
+	#[Required]
+	public string $one;
+
+	private string $two;
+
+	public string $three;
+
+	private string $four;
+
+	#[Required]
+	public function setTwo(int $two): void
+	{
+		$this->two = $two;
+	}
+
+	public function getTwo(): int
+	{
+		return $this->two;
+	}
+
+	public function setFour(int $four): void
+	{
+		$this->four = $four;
+	}
+
+	public function getFour(): int
+	{
+		return $this->four;
+	}
+}
diff --git a/tests/Symfony/required-autowiring-config.neon b/tests/Symfony/required-autowiring-config.neon
new file mode 100644
index 00000000..3ff4183c
--- /dev/null
+++ b/tests/Symfony/required-autowiring-config.neon
@@ -0,0 +1,6 @@
+services:
+	-
+		class: PHPStan\Symfony\RequiredAutowiringExtension
+		tags:
+		    - phpstan.properties.readWriteExtension
+		    - phpstan.additionalConstructorsExtension
diff --git a/tests/Type/Symfony/ExampleController.php b/tests/Type/Symfony/ExampleController.php
deleted file mode 100644
index 2a75eefb..00000000
--- a/tests/Type/Symfony/ExampleController.php
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php declare(strict_types = 1);
-
-namespace PHPStan\Type\Symfony;
-
-use Symfony\Bundle\FrameworkBundle\Controller\Controller;
-
-final class ExampleController extends Controller
-{
-
-	public function services(): void
-	{
-		$service1 = $this->get('foo');
-		$service2 = $this->get('bar');
-		$service3 = $this->get(doFoo());
-		$service4 = $this->get();
-
-		$has1 = $this->has('foo');
-		$has2 = $this->has('bar');
-		$has3 = $this->has(doFoo());
-		$has4 = $this->has();
-
-		die;
-	}
-
-}
diff --git a/tests/Type/Symfony/ExtensionTest.php b/tests/Type/Symfony/ExtensionTest.php
new file mode 100644
index 00000000..40420be0
--- /dev/null
+++ b/tests/Type/Symfony/ExtensionTest.php
@@ -0,0 +1,95 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use PHPStan\Testing\TypeInferenceTestCase;
+use ReflectionMethod;
+use Symfony\Component\HttpFoundation\Request;
+use function class_exists;
+use function strpos;
+
+class ExtensionTest extends TypeInferenceTestCase
+{
+
+	/** @return mixed[] */
+	public function dataFileAsserts(): iterable
+	{
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/messenger_handle_trait.php');
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/envelope_all.php');
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/header_bag_get.php');
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/response_header_bag_get_cookies.php');
+
+		if (class_exists('Symfony\Component\HttpFoundation\InputBag')) {
+			yield from $this->gatherAssertTypes(__DIR__ . '/data/input_bag.php');
+		}
+
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/tree_builder.php');
+
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/ExampleBaseCommand.php');
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/ExampleOptionCommand.php');
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/ExampleOptionLazyCommand.php');
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/kernel_interface.php');
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/property_accessor.php');
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/request_get_content.php');
+
+		$ref = new ReflectionMethod(Request::class, 'getSession');
+		$doc = (string) $ref->getDocComment();
+		if (strpos($doc, '@return SessionInterface|null') !== false) {
+			yield from $this->gatherAssertTypes(__DIR__ . '/data/request_get_session_null.php');
+		} else {
+			yield from $this->gatherAssertTypes(__DIR__ . '/data/request_get_session.php');
+		}
+
+		if (class_exists('Symfony\Bundle\FrameworkBundle\Controller\Controller')) {
+			yield from $this->gatherAssertTypes(__DIR__ . '/data/ExampleController.php');
+		}
+
+		if (class_exists('Symfony\Bundle\FrameworkBundle\Controller\AbstractController')) {
+			yield from $this->gatherAssertTypes(__DIR__ . '/data/ExampleAbstractController.php');
+		}
+
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/serializer.php');
+
+		if (class_exists('Symfony\Component\HttpFoundation\InputBag')) {
+			yield from $this->gatherAssertTypes(__DIR__ . '/data/input_bag_from_request.php');
+		}
+
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/denormalizer.php');
+
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/FormInterface_getErrors.php');
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/cache.php');
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/form_data_type.php');
+
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/extension/with-configuration/WithConfigurationExtension.php');
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/extension/without-configuration/WithoutConfigurationExtension.php');
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/extension/anonymous/AnonymousExtension.php');
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/extension/ignore-implemented/IgnoreImplementedExtension.php');
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/extension/multiple-types/MultipleTypes.php');
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/extension/with-configuration-with-constructor/WithConfigurationWithConstructorExtension.php');
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/extension/with-configuration-with-constructor-optional-params/WithConfigurationWithConstructorOptionalParamsExtension.php');
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/extension/with-configuration-with-constructor-required-params/WithConfigurationWithConstructorRequiredParamsExtension.php');
+	}
+
+	/**
+	 * @dataProvider dataFileAsserts
+	 * @param mixed ...$args
+	 */
+	public function testFileAsserts(
+		string $assertType,
+		string $file,
+		...$args
+	): void
+	{
+		$this->assertFileAsserts($assertType, $file, ...$args);
+	}
+
+	public static function getAdditionalConfigFiles(): array
+	{
+		return [
+			__DIR__ . '/../../../extension.neon',
+			__DIR__ . '/extension-test.neon',
+			'phar://' . __DIR__ . '/../../../vendor/phpstan/phpstan/phpstan.phar/conf/bleedingEdge.neon',
+		];
+	}
+
+}
diff --git a/tests/Type/Symfony/ExtensionTestCase.php b/tests/Type/Symfony/ExtensionTestCase.php
deleted file mode 100644
index c0c8ec93..00000000
--- a/tests/Type/Symfony/ExtensionTestCase.php
+++ /dev/null
@@ -1,76 +0,0 @@
-<?php declare(strict_types = 1);
-
-namespace PHPStan\Type\Symfony;
-
-use PhpParser\Node;
-use PhpParser\PrettyPrinter\Standard;
-use PHPStan\Analyser\NodeScopeResolver;
-use PHPStan\Analyser\Scope;
-use PHPStan\Analyser\ScopeContext;
-use PHPStan\Broker\AnonymousClassNameHelper;
-use PHPStan\Cache\Cache;
-use PHPStan\File\FileHelper;
-use PHPStan\Node\InClassMethodNode;
-use PHPStan\PhpDoc\PhpDocStringResolver;
-use PHPStan\PhpDoc\TypeNodeResolver;
-use PHPStan\Testing\TestCase;
-use PHPStan\Type\DynamicMethodReturnTypeExtension;
-use PHPStan\Type\FileTypeMapper;
-use PHPStan\Type\VerbosityLevel;
-
-abstract class ExtensionTestCase extends TestCase
-{
-
-	protected function processFile(
-		string $file,
-		string $expression,
-		string $type,
-		DynamicMethodReturnTypeExtension $extension
-	): void
-	{
-		$broker = $this->createBroker([$extension]);
-		$parser = $this->getParser();
-		$currentWorkingDirectory = $this->getCurrentWorkingDirectory();
-		$fileHelper = new FileHelper($currentWorkingDirectory);
-		$typeSpecifier = $this->createTypeSpecifier(new Standard(), $broker);
-		/** @var \PHPStan\PhpDoc\PhpDocStringResolver $phpDocStringResolver */
-		$phpDocStringResolver = self::getContainer()->getByType(PhpDocStringResolver::class);
-		/** @var \PHPStan\PhpDoc\TypeNodeResolver $typeNodeResolver */
-		$typeNodeResolver = self::getContainer()->getByType(TypeNodeResolver::class);
-		$resolver = new NodeScopeResolver(
-			$broker,
-			$parser,
-			new FileTypeMapper(...[ // PHPStan commit 7b23c31 broke the constructor so we have to use splat here
-				$parser,
-				$phpDocStringResolver,
-				$this->createMock(Cache::class),
-				$this->createMock(AnonymousClassNameHelper::class), // PHPStan commit 4fcdccc broke the helper so we have to use a mock here
-				$typeNodeResolver,
-			]),
-			$fileHelper,
-			$typeSpecifier,
-			true,
-			true,
-			true,
-			[]
-		);
-		$resolver->setAnalysedFiles([$fileHelper->normalizePath($file)]);
-
-		$run = false;
-		$resolver->processNodes(
-			$parser->parseFile($file),
-			$this->createScopeFactory($broker, $typeSpecifier)->create(ScopeContext::create($file)),
-			function (Node $node, Scope $scope) use ($expression, $type, &$run): void {
-				if ((new Standard())->prettyPrint([$node instanceof InClassMethodNode ? $node->getOriginalNode() : $node]) !== 'die') {
-					return;
-				}
-				/** @var \PhpParser\Node\Stmt\Expression $expNode */
-				$expNode = $this->getParser()->parseString(sprintf('<?php %s;', $expression))[0];
-				self::assertSame($type, $scope->getType($expNode->expr)->describe(VerbosityLevel::typeOnly()));
-				$run = true;
-			}
-		);
-		self::assertTrue($run);
-	}
-
-}
diff --git a/tests/Type/Symfony/ExtensionTestWithoutContainer.php b/tests/Type/Symfony/ExtensionTestWithoutContainer.php
new file mode 100644
index 00000000..fd1785c7
--- /dev/null
+++ b/tests/Type/Symfony/ExtensionTestWithoutContainer.php
@@ -0,0 +1,52 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use PHPStan\Testing\TypeInferenceTestCase;
+use function class_exists;
+
+class ExtensionTestWithoutContainer extends TypeInferenceTestCase
+{
+
+	/** @return mixed[] */
+	public function dataExampleController(): iterable
+	{
+		if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\Controller')) {
+			return;
+		}
+
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/ExampleController.php');
+	}
+
+	/** @return mixed[] */
+	public function dataAbstractController(): iterable
+	{
+		if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\AbstractController')) {
+			return;
+		}
+
+		yield from $this->gatherAssertTypes(__DIR__ . '/data/ExampleAbstractController.php');
+	}
+
+	/**
+	 * @dataProvider dataExampleController
+	 * @dataProvider dataAbstractController
+	 * @param mixed ...$args
+	 */
+	public function testFileAsserts(
+		string $assertType,
+		string $file,
+		...$args
+	): void
+	{
+		$this->assertFileAsserts($assertType, $file, ...$args);
+	}
+
+	public static function getAdditionalConfigFiles(): array
+	{
+		return [
+			__DIR__ . '/../../../extension.neon',
+		];
+	}
+
+}
diff --git a/tests/Type/Symfony/HeaderBagDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/HeaderBagDynamicReturnTypeExtensionTest.php
deleted file mode 100644
index 084f4aca..00000000
--- a/tests/Type/Symfony/HeaderBagDynamicReturnTypeExtensionTest.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php declare(strict_types = 1);
-
-namespace PHPStan\Type\Symfony;
-
-use Iterator;
-
-final class HeaderBagDynamicReturnTypeExtensionTest extends ExtensionTestCase
-{
-
-	/**
-	 * @dataProvider getProvider
-	 */
-	public function testGet(string $expression, string $type): void
-	{
-		$this->processFile(
-			__DIR__ . '/header_bag_get.php',
-			$expression,
-			$type,
-			new HeaderBagDynamicReturnTypeExtension()
-		);
-	}
-
-	public function getProvider(): Iterator
-	{
-		yield ['$test1', 'string|null'];
-		yield ['$test2', 'string|null'];
-		yield ['$test3', 'string'];
-		yield ['$test5', 'string|null'];
-		yield ['$test6', 'string'];
-		yield ['$test8', 'array<int, string>'];
-		yield ['$test9', 'array<int, string>'];
-	}
-
-}
diff --git a/tests/Type/Symfony/ImpossibleCheckTypeMethodCallRuleTest.php b/tests/Type/Symfony/ImpossibleCheckTypeMethodCallRuleTest.php
new file mode 100644
index 00000000..bde62b57
--- /dev/null
+++ b/tests/Type/Symfony/ImpossibleCheckTypeMethodCallRuleTest.php
@@ -0,0 +1,38 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use PHPStan\Rules\Comparison\ImpossibleCheckTypeMethodCallRule;
+use PHPStan\Rules\Rule;
+use PHPStan\Testing\RuleTestCase;
+
+/**
+ * @extends RuleTestCase<ImpossibleCheckTypeMethodCallRule>
+ */
+class ImpossibleCheckTypeMethodCallRuleTest extends RuleTestCase
+{
+
+	protected function getRule(): Rule
+	{
+		return self::getContainer()->getByType(ImpossibleCheckTypeMethodCallRule::class);
+	}
+
+	public function testExtension(): void
+	{
+		$this->analyse([__DIR__ . '/data/request_get_session.php'], []);
+	}
+
+	public function testBug178(): void
+	{
+		$this->analyse([__DIR__ . '/data/bug-178.php'], []);
+	}
+
+	public static function getAdditionalConfigFiles(): array
+	{
+		return [
+			__DIR__ . '/../../../extension.neon',
+			__DIR__ . '/../../../vendor/phpstan/phpstan-strict-rules/rules.neon',
+		];
+	}
+
+}
diff --git a/tests/Type/Symfony/RequestDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/RequestDynamicReturnTypeExtensionTest.php
deleted file mode 100644
index 2f697e4d..00000000
--- a/tests/Type/Symfony/RequestDynamicReturnTypeExtensionTest.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php declare(strict_types = 1);
-
-namespace PHPStan\Type\Symfony;
-
-use Iterator;
-
-final class RequestDynamicReturnTypeExtensionTest extends ExtensionTestCase
-{
-
-	/**
-	 * @dataProvider getContentProvider
-	 */
-	public function testGetContent(string $expression, string $type): void
-	{
-		$this->processFile(
-			__DIR__ . '/request_get_content.php',
-			$expression,
-			$type,
-			new RequestDynamicReturnTypeExtension()
-		);
-	}
-
-	public function getContentProvider(): Iterator
-	{
-		yield ['$content1', 'string'];
-		yield ['$content2', 'string'];
-		yield ['$content3', 'resource'];
-		yield ['$content4', 'resource|string'];
-	}
-
-}
diff --git a/tests/Type/Symfony/SerializerInterfaceDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/SerializerInterfaceDynamicReturnTypeExtensionTest.php
deleted file mode 100644
index fad62b3b..00000000
--- a/tests/Type/Symfony/SerializerInterfaceDynamicReturnTypeExtensionTest.php
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php declare(strict_types = 1);
-
-namespace PHPStan\Type\Symfony;
-
-use Iterator;
-
-final class SerializerInterfaceDynamicReturnTypeExtensionTest extends ExtensionTestCase
-{
-
-	/**
-	 * @dataProvider getContentProvider
-	 */
-	public function testGetContent(string $expression, string $type): void
-	{
-		$this->processFile(
-			__DIR__ . '/serializer.php',
-			$expression,
-			$type,
-			new SerializerInterfaceDynamicReturnTypeExtension()
-		);
-	}
-
-	public function getContentProvider(): Iterator
-	{
-		yield ['$first', 'Bar'];
-		yield ['$second', 'array<Bar>'];
-		yield ['$third', 'array<array<Bar>>'];
-	}
-
-}
diff --git a/tests/Type/Symfony/ServiceDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/ServiceDynamicReturnTypeExtensionTest.php
deleted file mode 100644
index a1ef5100..00000000
--- a/tests/Type/Symfony/ServiceDynamicReturnTypeExtensionTest.php
+++ /dev/null
@@ -1,56 +0,0 @@
-<?php declare(strict_types = 1);
-
-namespace PHPStan\Type\Symfony;
-
-use Iterator;
-use PHPStan\Symfony\XmlServiceMapFactory;
-use Symfony\Bundle\FrameworkBundle\Controller\Controller;
-
-final class ServiceDynamicReturnTypeExtensionTest extends ExtensionTestCase
-{
-
-	/**
-	 * @dataProvider servicesProvider
-	 */
-	public function testServices(string $expression, string $type): void
-	{
-		$this->processFile(
-			__DIR__ . '/ExampleController.php',
-			$expression,
-			$type,
-			new ServiceDynamicReturnTypeExtension(Controller::class, true, (new XmlServiceMapFactory(__DIR__ . '/container.xml'))->create())
-		);
-	}
-
-	public function servicesProvider(): Iterator
-	{
-		yield ['$service1', 'Foo'];
-		yield ['$service2', 'object'];
-		yield ['$service3', 'object'];
-		yield ['$service4', 'object'];
-		yield ['$has1', 'true'];
-		yield ['$has2', 'false'];
-		yield ['$has3', 'bool'];
-		yield ['$has4', 'bool'];
-	}
-
-	/**
-	 * @dataProvider constantHassersOffProvider
-	 */
-	public function testConstantHassersOff(string $expression, string $type): void
-	{
-		$this->processFile(
-			__DIR__ . '/ExampleController.php',
-			$expression,
-			$type,
-			new ServiceDynamicReturnTypeExtension(Controller::class, false, (new XmlServiceMapFactory(__DIR__ . '/container.xml'))->create())
-		);
-	}
-
-	public function constantHassersOffProvider(): Iterator
-	{
-		yield ['$has1', 'bool'];
-		yield ['$has2', 'bool'];
-	}
-
-}
diff --git a/tests/Type/Symfony/console_application_loader.php b/tests/Type/Symfony/console_application_loader.php
new file mode 100644
index 00000000..fb5459b5
--- /dev/null
+++ b/tests/Type/Symfony/console_application_loader.php
@@ -0,0 +1,23 @@
+<?php declare(strict_types = 1);
+
+use PHPStan\Type\Symfony\ExampleACommand;
+use PHPStan\Type\Symfony\ExampleBCommand;
+use PHPStan\Type\Symfony\ExampleOptionCommand;
+use PHPStan\Type\Symfony\ExampleOptionLazyCommand;
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Command\LazyCommand;
+
+require_once __DIR__ . '/../../../vendor/autoload.php';
+
+$application = new Application();
+$application->add(new ExampleACommand());
+$application->add(new ExampleBCommand());
+$application->add(new ExampleOptionCommand());
+
+if (class_exists(LazyCommand::class)) {
+	$application->add(new LazyCommand('lazy-example-option', [], '', false, static fn () => new ExampleOptionLazyCommand()));
+} else {
+	$application->add(new ExampleOptionLazyCommand());
+}
+
+return $application;
diff --git a/tests/Type/Symfony/container.xml b/tests/Type/Symfony/container.xml
index 978519d4..16d4b7fe 100644
--- a/tests/Type/Symfony/container.xml
+++ b/tests/Type/Symfony/container.xml
@@ -1,6 +1,376 @@
 <?xml version="1.0" encoding="utf-8"?>
 <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
+    <parameters>
+        <parameter key="app.class">Foo</parameter>
+        <parameter key="app.string">abcdef</parameter>
+        <parameter key="app.int">123</parameter>
+        <parameter key="app.int_as_string" type="string">123</parameter>
+        <parameter key="app.int_as_processor">%env(int:APP_INT)%</parameter>
+        <parameter key="app.float">123.45</parameter>
+        <parameter key="app.float_as_string" type="string">123.45</parameter>
+        <parameter key="app.float_as_processor">%env(float:APP_FLOAT)%</parameter>
+        <parameter key="app.boolean">true</parameter>
+        <parameter key="app.boolean_as_string" type="string">true</parameter>
+        <parameter key="app.boolean_as_processor">%env(bool:APP_BOOL)%</parameter>
+        <parameter key="app.list" type="collection">
+            <parameter>en</parameter>
+            <parameter>es</parameter>
+            <parameter>fr</parameter>
+        </parameter>
+        <parameter key="app.list_of_int" type="collection">
+            <parameter>123</parameter>
+            <parameter>456</parameter>
+            <parameter>789</parameter>
+        </parameter>
+        <parameter key="app.list_of_int_as_processor" type="collection">
+            <parameter>%env(int:APP_INT)%</parameter>
+            <parameter>%env(int:APP_INT)%</parameter>
+            <parameter>%env(int:APP_INT)%</parameter>
+        </parameter>
+        <parameter key="app.list_of_list" type="collection">
+            <parameter type="collection">
+                <parameter key="name">the name</parameter>
+                <parameter key="value">the value</parameter>
+            </parameter>
+            <parameter type="collection">
+                <parameter key="name">another name</parameter>
+                <parameter key="value">another value</parameter>
+            </parameter>
+        </parameter>
+        <parameter key="app.list_of_different_list" type="collection">
+            <parameter type="collection">
+                <parameter key="name">the name</parameter>
+                <parameter key="value">the value</parameter>
+            </parameter>
+            <parameter type="collection">
+                <parameter key="age">12</parameter>
+                <parameter key="size">32</parameter>
+            </parameter>
+        </parameter>
+        <parameter key="app.array_of_list" type="collection">
+            <parameter key="foo" type="collection">
+                <parameter key="name">the name</parameter>
+                <parameter key="value">the value</parameter>
+            </parameter>
+            <parameter type="collection">
+                <parameter key="name">another name</parameter>
+                <parameter key="value">another value</parameter>
+            </parameter>
+        </parameter>
+        <parameter key="app.list_of_things" type="collection">
+            <parameter key="url">%env(string:APP_STRING)%</parameter>
+            <parameter key="endpoint">%env(string:APP_STRING)%</parameter>
+            <parameter key="version">%env(string:APP_STRING)%</parameter>
+            <parameter key="payment" type="collection">
+                <parameter key="default" type="collection">
+                    <parameter key="username">%env(string:APP_STRING)%</parameter>
+                    <parameter key="password">%env(string:APP_STRING)%</parameter>
+                    <parameter key="signature">%env(string:APP_STRING)%</parameter>
+                </parameter>
+            </parameter>
+            <parameter key="api" type="collection">
+                <parameter key="mode">%env(string:APP_STRING)%</parameter>
+                <parameter key="default" type="collection">
+                    <parameter key="username">%env(string:APP_STRING)%</parameter>
+                    <parameter key="password">%env(string:APP_STRING)%</parameter>
+                    <parameter key="signature">%env(string:APP_STRING)%</parameter>
+                </parameter>
+            </parameter>
+        </parameter>
+        <parameter key="app.map" type="collection">
+            <parameter key="a">value of a</parameter>
+            <parameter key="b">value of b</parameter>
+            <parameter key="c">value of c</parameter>
+        </parameter>
+        <parameter key="app.hugemap" type="collection">
+            <parameter key="k1">v1</parameter>
+            <parameter key="k2">v2</parameter>
+            <parameter key="k3">v3</parameter>
+            <parameter key="k4">v4</parameter>
+            <parameter key="k5">v5</parameter>
+            <parameter key="k6">v6</parameter>
+            <parameter key="k7">v7</parameter>
+            <parameter key="k8">v8</parameter>
+            <parameter key="k9">v9</parameter>
+            <parameter key="k10">v10</parameter>
+            <parameter key="k11">v11</parameter>
+            <parameter key="k12">v12</parameter>
+            <parameter key="k13">v13</parameter>
+            <parameter key="k14">v14</parameter>
+            <parameter key="k15">v15</parameter>
+            <parameter key="k16">v16</parameter>
+            <parameter key="k17">v17</parameter>
+            <parameter key="k18">v18</parameter>
+            <parameter key="k19">v19</parameter>
+            <parameter key="k20">v20</parameter>
+            <parameter key="k21">v21</parameter>
+            <parameter key="k22">v22</parameter>
+            <parameter key="k23">v23</parameter>
+            <parameter key="k24">v24</parameter>
+            <parameter key="k25">v25</parameter>
+            <parameter key="k26">v26</parameter>
+            <parameter key="k27">v27</parameter>
+            <parameter key="k28">v28</parameter>
+            <parameter key="k29">v29</parameter>
+            <parameter key="k30">v30</parameter>
+            <parameter key="k31">v31</parameter>
+            <parameter key="k32">v32</parameter>
+            <parameter key="k33">v33</parameter>
+            <parameter key="k34">v34</parameter>
+            <parameter key="k35">v35</parameter>
+            <parameter key="k36">v36</parameter>
+            <parameter key="k37">v37</parameter>
+            <parameter key="k38">v38</parameter>
+            <parameter key="k39">v39</parameter>
+            <parameter key="k40">v40</parameter>
+            <parameter key="k41">v41</parameter>
+            <parameter key="k42">v42</parameter>
+            <parameter key="k43">v43</parameter>
+            <parameter key="k44">v44</parameter>
+            <parameter key="k45">v45</parameter>
+            <parameter key="k46">v46</parameter>
+            <parameter key="k47">v47</parameter>
+            <parameter key="k48">v48</parameter>
+            <parameter key="k49">v49</parameter>
+            <parameter key="k50">v50</parameter>
+            <parameter key="k51">v51</parameter>
+            <parameter key="k52">v52</parameter>
+            <parameter key="k53">v53</parameter>
+            <parameter key="k54">v54</parameter>
+            <parameter key="k55">v55</parameter>
+            <parameter key="k56">v56</parameter>
+            <parameter key="k57">v57</parameter>
+            <parameter key="k58">v58</parameter>
+            <parameter key="k59">v59</parameter>
+            <parameter key="k60">v60</parameter>
+            <parameter key="k61">v61</parameter>
+            <parameter key="k62">v62</parameter>
+            <parameter key="k63">v63</parameter>
+            <parameter key="k64">v64</parameter>
+            <parameter key="k65">v65</parameter>
+            <parameter key="k66">v66</parameter>
+            <parameter key="k67">v67</parameter>
+            <parameter key="k68">v68</parameter>
+            <parameter key="k69">v69</parameter>
+            <parameter key="k70">v70</parameter>
+            <parameter key="k71">v71</parameter>
+            <parameter key="k72">v72</parameter>
+            <parameter key="k73">v73</parameter>
+            <parameter key="k74">v74</parameter>
+            <parameter key="k75">v75</parameter>
+            <parameter key="k76">v76</parameter>
+            <parameter key="k77">v77</parameter>
+            <parameter key="k78">v78</parameter>
+            <parameter key="k79">v79</parameter>
+            <parameter key="k80">v80</parameter>
+            <parameter key="k81">v81</parameter>
+            <parameter key="k82">v82</parameter>
+            <parameter key="k83">v83</parameter>
+            <parameter key="k84">v84</parameter>
+            <parameter key="k85">v85</parameter>
+            <parameter key="k86">v86</parameter>
+            <parameter key="k87">v87</parameter>
+            <parameter key="k88">v88</parameter>
+            <parameter key="k89">v89</parameter>
+            <parameter key="k90">v90</parameter>
+            <parameter key="k91">v91</parameter>
+            <parameter key="k92">v92</parameter>
+            <parameter key="k93">v93</parameter>
+            <parameter key="k94">v94</parameter>
+            <parameter key="k95">v95</parameter>
+            <parameter key="k96">v96</parameter>
+            <parameter key="k97">v97</parameter>
+            <parameter key="k98">v98</parameter>
+            <parameter key="k99">v99</parameter>
+            <parameter key="k100">v100</parameter>
+            <parameter key="k101">v101</parameter>
+            <parameter key="k102">v102</parameter>
+            <parameter key="k103">v103</parameter>
+            <parameter key="k104">v104</parameter>
+            <parameter key="k105">v105</parameter>
+            <parameter key="k106">v106</parameter>
+            <parameter key="k107">v107</parameter>
+            <parameter key="k108">v108</parameter>
+            <parameter key="k109">v109</parameter>
+            <parameter key="k110">v110</parameter>
+            <parameter key="k111">v111</parameter>
+            <parameter key="k112">v112</parameter>
+            <parameter key="k113">v113</parameter>
+            <parameter key="k114">v114</parameter>
+            <parameter key="k115">v115</parameter>
+            <parameter key="k116">v116</parameter>
+            <parameter key="k117">v117</parameter>
+            <parameter key="k118">v118</parameter>
+            <parameter key="k119">v119</parameter>
+            <parameter key="k120">v120</parameter>
+            <parameter key="k121">v121</parameter>
+            <parameter key="k122">v122</parameter>
+            <parameter key="k123">v123</parameter>
+            <parameter key="k124">v124</parameter>
+            <parameter key="k125">v125</parameter>
+            <parameter key="k126">v126</parameter>
+            <parameter key="k127">v127</parameter>
+            <parameter key="k128">v128</parameter>
+            <parameter key="k129">v129</parameter>
+            <parameter key="k130">v130</parameter>
+            <parameter key="k131">v131</parameter>
+            <parameter key="k132">v132</parameter>
+            <parameter key="k133">v133</parameter>
+            <parameter key="k134">v134</parameter>
+            <parameter key="k135">v135</parameter>
+            <parameter key="k136">v136</parameter>
+            <parameter key="k137">v137</parameter>
+            <parameter key="k138">v138</parameter>
+            <parameter key="k139">v139</parameter>
+            <parameter key="k140">v140</parameter>
+            <parameter key="k141">v141</parameter>
+            <parameter key="k142">v142</parameter>
+            <parameter key="k143">v143</parameter>
+            <parameter key="k144">v144</parameter>
+            <parameter key="k145">v145</parameter>
+            <parameter key="k146">v146</parameter>
+            <parameter key="k147">v147</parameter>
+            <parameter key="k148">v148</parameter>
+            <parameter key="k149">v149</parameter>
+            <parameter key="k150">v150</parameter>
+            <parameter key="k151">v151</parameter>
+            <parameter key="k152">v152</parameter>
+            <parameter key="k153">v153</parameter>
+            <parameter key="k154">v154</parameter>
+            <parameter key="k155">v155</parameter>
+            <parameter key="k156">v156</parameter>
+            <parameter key="k157">v157</parameter>
+            <parameter key="k158">v158</parameter>
+            <parameter key="k159">v159</parameter>
+            <parameter key="k160">v160</parameter>
+            <parameter key="k161">v161</parameter>
+            <parameter key="k162">v162</parameter>
+            <parameter key="k163">v163</parameter>
+            <parameter key="k164">v164</parameter>
+            <parameter key="k165">v165</parameter>
+            <parameter key="k166">v166</parameter>
+            <parameter key="k167">v167</parameter>
+            <parameter key="k168">v168</parameter>
+            <parameter key="k169">v169</parameter>
+            <parameter key="k170">v170</parameter>
+            <parameter key="k171">v171</parameter>
+            <parameter key="k172">v172</parameter>
+            <parameter key="k173">v173</parameter>
+            <parameter key="k174">v174</parameter>
+            <parameter key="k175">v175</parameter>
+            <parameter key="k176">v176</parameter>
+            <parameter key="k177">v177</parameter>
+            <parameter key="k178">v178</parameter>
+            <parameter key="k179">v179</parameter>
+            <parameter key="k180">v180</parameter>
+            <parameter key="k181">v181</parameter>
+            <parameter key="k182">v182</parameter>
+            <parameter key="k183">v183</parameter>
+            <parameter key="k184">v184</parameter>
+            <parameter key="k185">v185</parameter>
+            <parameter key="k186">v186</parameter>
+            <parameter key="k187">v187</parameter>
+            <parameter key="k188">v188</parameter>
+            <parameter key="k189">v189</parameter>
+            <parameter key="k190">v190</parameter>
+            <parameter key="k191">v191</parameter>
+            <parameter key="k192">v192</parameter>
+            <parameter key="k193">v193</parameter>
+            <parameter key="k194">v194</parameter>
+            <parameter key="k195">v195</parameter>
+            <parameter key="k196">v196</parameter>
+            <parameter key="k197">v197</parameter>
+            <parameter key="k198">v198</parameter>
+            <parameter key="k199">v199</parameter>
+            <parameter key="k200">v200</parameter>
+            <parameter key="k201">v201</parameter>
+            <parameter key="k202">v202</parameter>
+            <parameter key="k203">v203</parameter>
+            <parameter key="k204">v204</parameter>
+            <parameter key="k205">v205</parameter>
+            <parameter key="k206">v206</parameter>
+            <parameter key="k207">v207</parameter>
+            <parameter key="k208">v208</parameter>
+            <parameter key="k209">v209</parameter>
+            <parameter key="k210">v210</parameter>
+            <parameter key="k211">v211</parameter>
+            <parameter key="k212">v212</parameter>
+            <parameter key="k213">v213</parameter>
+            <parameter key="k214">v214</parameter>
+            <parameter key="k215">v215</parameter>
+            <parameter key="k216">v216</parameter>
+            <parameter key="k217">v217</parameter>
+            <parameter key="k218">v218</parameter>
+            <parameter key="k219">v219</parameter>
+            <parameter key="k220">v220</parameter>
+            <parameter key="k221">v221</parameter>
+            <parameter key="k222">v222</parameter>
+            <parameter key="k223">v223</parameter>
+            <parameter key="k224">v224</parameter>
+            <parameter key="k225">v225</parameter>
+            <parameter key="k226">v226</parameter>
+            <parameter key="k227">v227</parameter>
+            <parameter key="k228">v228</parameter>
+            <parameter key="k229">v229</parameter>
+            <parameter key="k230">v230</parameter>
+            <parameter key="k231">v231</parameter>
+            <parameter key="k232">v232</parameter>
+            <parameter key="k233">v233</parameter>
+            <parameter key="k234">v234</parameter>
+            <parameter key="k235">v235</parameter>
+            <parameter key="k236">v236</parameter>
+            <parameter key="k237">v237</parameter>
+            <parameter key="k238">v238</parameter>
+            <parameter key="k239">v239</parameter>
+            <parameter key="k240">v240</parameter>
+            <parameter key="k241">v241</parameter>
+            <parameter key="k242">v242</parameter>
+            <parameter key="k243">v243</parameter>
+            <parameter key="k244">v244</parameter>
+            <parameter key="k245">v245</parameter>
+            <parameter key="k246">v246</parameter>
+            <parameter key="k247">v247</parameter>
+            <parameter key="k248">v248</parameter>
+            <parameter key="k249">v249</parameter>
+            <parameter key="k250">v250</parameter>
+            <parameter key="k251">v251</parameter>
+            <parameter key="k252">v252</parameter>
+            <parameter key="k253">v253</parameter>
+            <parameter key="k254">v254</parameter>
+            <parameter key="k255">v255</parameter>
+            <parameter key="k256">v256</parameter>
+            <parameter key="k257">v257</parameter>
+        </parameter>
+        <parameter key="app.binary" type="binary">VGhpcyBpcyBhIEJlbGwgY2hhciAH</parameter>
+        <parameter key="app.constant">Y-m-d\TH:i:sP</parameter>
+		<parameter key="test_collection" type="collection"/>
+		<parameter key="non_empty_collection" type="collection">
+			<parameter>value</parameter>
+		</parameter>
+    </parameters>
+
     <services>
-        <service id="foo" class="Foo"></service>
+        <service id="foo" class="Foo" public="true"></service>
+        <service id="parameterised_foo" class="%app.class%"></service>
+        <service id="parameterised_bar" class="%app.class%\Bar"></service>
+        <service id="synthetic" class="Synthetic" public="true" synthetic="true" />
+        <service id="regular_query_handler" class="MessengerHandleTrait\RegularQueryHandler">
+            <tag name="messenger.message_handler"/>
+        </service>
+        <service id="multi_query_handler" class="MessengerHandleTrait\MultiQueryHandler">
+            <tag name="messenger.message_handler"/>
+        </service>
+        <service id="tagged_handler" class="MessengerHandleTrait\TaggedHandler">
+            <tag name="messenger.message_handler" handles="MessengerHandleTrait\TaggedQuery" method="handle"/>
+        </service>
+        <service id="multi_handles_for_in_the_same_handler" class="MessengerHandleTrait\MultiHandlesForInTheSameHandler">
+            <tag name="messenger.message_handler"/>
+        </service>
+        <service id="multi_handlers_for_the_same_message_handler_1" class="MessengerHandleTrait\MultiHandlersForTheSameMessageHandler1">
+            <tag name="messenger.message_handler"/>
+        </service>
+        <service id="multi_handlers_for_the_same_message_handler_2" class="MessengerHandleTrait\MultiHandlersForTheSameMessageHandler2">
+            <tag name="messenger.message_handler"/>
+        </service>
     </services>
 </container>
diff --git a/tests/Type/Symfony/data/ExampleACommand.php b/tests/Type/Symfony/data/ExampleACommand.php
new file mode 100644
index 00000000..4ba27410
--- /dev/null
+++ b/tests/Type/Symfony/data/ExampleACommand.php
@@ -0,0 +1,21 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use Symfony\Component\Console\Input\InputArgument;
+
+final class ExampleACommand extends ExampleBaseCommand
+{
+
+	protected function configure(): void
+	{
+		parent::configure();
+		$this->setName('example-a');
+
+		$this->addArgument('aaa', null, '', 'aaa');
+		$this->addArgument('both');
+		$this->addArgument('diff', null, '', 'ddd');
+		$this->addArgument('arr', InputArgument::IS_ARRAY, '', ['arr']);
+	}
+
+}
diff --git a/tests/Type/Symfony/data/ExampleAbstractController.php b/tests/Type/Symfony/data/ExampleAbstractController.php
new file mode 100644
index 00000000..53b38066
--- /dev/null
+++ b/tests/Type/Symfony/data/ExampleAbstractController.php
@@ -0,0 +1,123 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
+use function PHPStan\Testing\assertType;
+
+final class ExampleAbstractController extends AbstractController
+{
+
+	public function services(): void
+	{
+		assertType('Foo', $this->get('foo'));
+		assertType('Foo', $this->get('parameterised_foo'));
+		assertType('Foo\Bar', $this->get('parameterised_bar'));
+		assertType('Synthetic', $this->get('synthetic'));
+		assertType('object', $this->get('bar'));
+		assertType('object', $this->get(doFoo()));
+		assertType('object', $this->get());
+
+		assertType('true', $this->has('foo'));
+		assertType('true', $this->has('synthetic'));
+		assertType('false', $this->has('bar'));
+		assertType('bool', $this->has(doFoo()));
+		assertType('bool', $this->has());
+	}
+
+	public function parameters(ContainerInterface $container, ParameterBagInterface $parameterBag): void
+	{
+		assertType('array|bool|float|int|string|null', $container->getParameter('unknown'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('unknown'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('unknown'));
+		assertType("string", $container->getParameter('app.string'));
+		assertType("string", $parameterBag->get('app.string'));
+		assertType("string", $this->getParameter('app.string'));
+		assertType('int', $container->getParameter('app.int'));
+		assertType('int', $parameterBag->get('app.int'));
+		assertType('int', $this->getParameter('app.int'));
+		assertType("string", $container->getParameter('app.int_as_string'));
+		assertType("string", $parameterBag->get('app.int_as_string'));
+		assertType("string", $this->getParameter('app.int_as_string'));
+		assertType('int', $container->getParameter('app.int_as_processor'));
+		assertType('int', $parameterBag->get('app.int_as_processor'));
+		assertType('int', $this->getParameter('app.int_as_processor'));
+		assertType('float', $container->getParameter('app.float'));
+		assertType('float', $parameterBag->get('app.float'));
+		assertType('float', $this->getParameter('app.float'));
+		assertType("string", $container->getParameter('app.float_as_string'));
+		assertType("string", $parameterBag->get('app.float_as_string'));
+		assertType("string", $this->getParameter('app.float_as_string'));
+		assertType('bool', $container->getParameter('app.boolean'));
+		assertType('bool', $parameterBag->get('app.boolean'));
+		assertType('bool', $this->getParameter('app.boolean'));
+		assertType("string", $container->getParameter('app.boolean_as_string'));
+		assertType("string", $parameterBag->get('app.boolean_as_string'));
+		assertType("string", $this->getParameter('app.boolean_as_string'));
+		assertType("array<int, string>", $container->getParameter('app.list'));
+		assertType("array<int, string>", $parameterBag->get('app.list'));
+		assertType("array<int, string>", $this->getParameter('app.list'));
+		assertType("array<int, int>", $container->getParameter('app.list_of_int'));
+		assertType("array<int, int>", $parameterBag->get('app.list_of_int'));
+		assertType("array<int, int>", $this->getParameter('app.list_of_int'));
+		assertType("array<int, int>", $container->getParameter('app.list_of_int_as_processor'));
+		assertType("array<int, int>", $parameterBag->get('app.list_of_int_as_processor'));
+		assertType("array<int, int>", $this->getParameter('app.list_of_int_as_processor'));
+		assertType("array<int, array{name: string, value: string}>", $container->getParameter('app.list_of_list'));
+		assertType("array<int, array{name: string, value: string}>", $parameterBag->get('app.list_of_list'));
+		assertType("array<int, array{name: string, value: string}>", $this->getParameter('app.list_of_list'));
+		assertType("array<int, array{age: int, size: int}|array{name: string, value: string}>", $container->getParameter('app.list_of_different_list'));
+		assertType("array<int, array{age: int, size: int}|array{name: string, value: string}>", $parameterBag->get('app.list_of_different_list'));
+		assertType("array<int, array{age: int, size: int}|array{name: string, value: string}>", $this->getParameter('app.list_of_different_list'));
+		assertType("array<int|string, array{name: string, value: string}>", $container->getParameter('app.array_of_list'));
+		assertType("array<int|string, array{name: string, value: string}>", $parameterBag->get('app.array_of_list'));
+		assertType("array<int|string, array{name: string, value: string}>", $this->getParameter('app.array_of_list'));
+		assertType("array{url: string, endpoint: string, version: string, payment: array{default: array{username: string, password: string, signature: string}}, api: array{mode: string, default: array{username: string, password: string, signature: string}}}", $container->getParameter('app.list_of_things'));
+		assertType("array{url: string, endpoint: string, version: string, payment: array{default: array{username: string, password: string, signature: string}}, api: array{mode: string, default: array{username: string, password: string, signature: string}}}", $parameterBag->get('app.list_of_things'));
+		assertType("array{url: string, endpoint: string, version: string, payment: array{default: array{username: string, password: string, signature: string}}, api: array{mode: string, default: array{username: string, password: string, signature: string}}}", $this->getParameter('app.list_of_things'));
+		assertType("array{a: string, b: string, c: string}", $container->getParameter('app.map'));
+		assertType("array{a: string, b: string, c: string}", $parameterBag->get('app.map'));
+		assertType("array{a: string, b: string, c: string}", $this->getParameter('app.map'));
+		assertType("non-falsy-string", implode(',', $this->getParameter('app.hugemap')));
+		assertType("string", $container->getParameter('app.binary'));
+		assertType("string", $parameterBag->get('app.binary'));
+		assertType("string", $this->getParameter('app.binary'));
+		assertType("string", $container->getParameter('app.constant'));
+		assertType("string", $parameterBag->get('app.constant'));
+		assertType("string", $this->getParameter('app.constant'));
+		assertType("array", $this->getParameter('test_collection'));
+		assertType("array<int, string>", $this->getParameter('non_empty_collection'));
+
+		assertType('false', $container->hasParameter('unknown'));
+		assertType('false', $parameterBag->has('unknown'));
+		assertType('true', $container->hasParameter('app.string'));
+		assertType('true', $parameterBag->has('app.string'));
+		assertType('true', $container->hasParameter('app.int'));
+		assertType('true', $parameterBag->has('app.int'));
+		assertType('true', $container->hasParameter('app.int_as_string'));
+		assertType('true', $parameterBag->has('app.int_as_string'));
+		assertType('true', $container->hasParameter('app.int_as_processor'));
+		assertType('true', $parameterBag->has('app.int_as_processor'));
+		assertType('true', $container->hasParameter('app.float'));
+		assertType('true', $parameterBag->has('app.float'));
+		assertType('true', $container->hasParameter('app.float_as_string'));
+		assertType('true', $parameterBag->has('app.float_as_string'));
+		assertType('true', $container->hasParameter('app.boolean'));
+		assertType('true', $parameterBag->has('app.boolean'));
+		assertType('true', $container->hasParameter('app.boolean_as_string'));
+		assertType('true', $parameterBag->has('app.boolean_as_string'));
+		assertType('true', $container->hasParameter('app.list'));
+		assertType('true', $parameterBag->has('app.list'));
+		assertType('true', $container->hasParameter('app.list_of_list'));
+		assertType('true', $parameterBag->has('app.list_of_list'));
+		assertType('true', $container->hasParameter('app.map'));
+		assertType('true', $parameterBag->has('app.map'));
+		assertType('true', $container->hasParameter('app.binary'));
+		assertType('true', $parameterBag->has('app.binary'));
+		assertType('true', $container->hasParameter('app.constant'));
+		assertType('true', $parameterBag->has('app.constant'));
+	}
+
+}
diff --git a/tests/Type/Symfony/data/ExampleAbstractControllerWithoutContainer.php b/tests/Type/Symfony/data/ExampleAbstractControllerWithoutContainer.php
new file mode 100644
index 00000000..edc6438a
--- /dev/null
+++ b/tests/Type/Symfony/data/ExampleAbstractControllerWithoutContainer.php
@@ -0,0 +1,115 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
+
+final class ExampleAbstractControllerWithoutContainer extends AbstractController
+{
+
+	public function services(): void
+	{
+		assertType('object', $this->get('foo'));
+		assertType('object', $this->get('synthetic'));
+		assertType('object', $this->get('bar'));
+		assertType('object', $this->get(doFoo()));
+		assertType('object', $this->get());
+
+		assertType('bool', $this->has('foo'));
+		assertType('bool', $this->has('synthetic'));
+		assertType('bool', $this->has('bar'));
+		assertType('bool', $this->has(doFoo()));
+		assertType('bool', $this->has());
+	}
+
+	public function parameters(ContainerInterface $container, ParameterBagInterface $parameterBag): void
+	{
+		assertType('array|bool|float|int|string|null', $container->getParameter('unknown'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('unknown'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('unknown'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.string'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.string'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.string'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.int'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.int'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.int'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.int_as_string'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.int_as_string'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.int_as_string'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.int_as_processor'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.int_as_processor'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.int_as_processor'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.float'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.float'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.float'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.float_as_string'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.float_as_string'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.float_as_string'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.float_as_processor'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.float_as_processor'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.float_as_processor'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.boolean'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.boolean'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.boolean'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.boolean_as_string'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.boolean_as_string'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.boolean_as_string'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.boolean_as_processor'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.boolean_as_processor'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.boolean_as_processor'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.list'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.list'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.list'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.list_of_list'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.list_of_list'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.list_of_list'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.array_of_list'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.array_of_list'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.array_of_list'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.map'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.map'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.map'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.binary'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.binary'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.binary'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.constant'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.constant'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.constant'));
+
+		assertType('bool', $container->hasParameter('unknown'));
+		assertType('bool', $parameterBag->has('unknown'));
+		assertType('bool', $container->hasParameter('app.string'));
+		assertType('bool', $parameterBag->has('app.string'));
+		assertType('bool', $container->hasParameter('app.int'));
+		assertType('bool', $parameterBag->has('app.int'));
+		assertType('bool', $container->hasParameter('app.int_as_string'));
+		assertType('bool', $parameterBag->has('app.int_as_string'));
+		assertType('bool', $container->hasParameter('app.int_as_processor'));
+		assertType('bool', $parameterBag->has('app.int_as_processor'));
+		assertType('bool', $container->hasParameter('app.float'));
+		assertType('bool', $parameterBag->has('app.float'));
+		assertType('bool', $container->hasParameter('app.float_as_string'));
+		assertType('bool', $parameterBag->has('app.float_as_string'));
+		assertType('bool', $container->hasParameter('app.float_as_processor'));
+		assertType('bool', $parameterBag->has('app.float_as_processor'));
+		assertType('bool', $container->hasParameter('app.boolean'));
+		assertType('bool', $parameterBag->has('app.boolean'));
+		assertType('bool', $container->hasParameter('app.boolean_as_string'));
+		assertType('bool', $parameterBag->has('app.boolean_as_string'));
+		assertType('bool', $container->hasParameter('app.boolean_as_processor'));
+		assertType('bool', $parameterBag->has('app.boolean_as_processor'));
+		assertType('bool', $container->hasParameter('app.list'));
+		assertType('bool', $parameterBag->has('app.list'));
+		assertType('bool', $container->hasParameter('app.list_of_list'));
+		assertType('bool', $parameterBag->has('app.list_of_list'));
+		assertType('bool', $container->hasParameter('app.map'));
+		assertType('bool', $parameterBag->has('app.map'));
+		assertType('bool', $container->hasParameter('app.binary'));
+		assertType('bool', $parameterBag->has('app.binary'));
+		assertType('bool', $container->hasParameter('app.constant'));
+		assertType('bool', $parameterBag->has('app.constant'));
+	}
+
+}
diff --git a/tests/Type/Symfony/data/ExampleBCommand.php b/tests/Type/Symfony/data/ExampleBCommand.php
new file mode 100644
index 00000000..b6b00dc2
--- /dev/null
+++ b/tests/Type/Symfony/data/ExampleBCommand.php
@@ -0,0 +1,20 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use Symfony\Component\Console\Input\InputArgument;
+
+final class ExampleBCommand extends ExampleBaseCommand
+{
+
+	protected function configure(): void
+	{
+		parent::configure();
+		$this->setName('example-b');
+
+		$this->addArgument('both');
+		$this->addArgument('bbb', null, '', 'bbb');
+		$this->addArgument('diff', InputArgument::IS_ARRAY, '', ['diff']);
+	}
+
+}
diff --git a/tests/Type/Symfony/data/ExampleBaseCommand.php b/tests/Type/Symfony/data/ExampleBaseCommand.php
new file mode 100644
index 00000000..0376429f
--- /dev/null
+++ b/tests/Type/Symfony/data/ExampleBaseCommand.php
@@ -0,0 +1,67 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use function PHPStan\Testing\assertType;
+
+abstract class ExampleBaseCommand extends Command
+{
+
+	protected function configure(): void
+	{
+		parent::configure();
+
+		$this->addArgument('required', InputArgument::REQUIRED);
+		$this->addArgument('base');
+	}
+
+	protected function initialize(InputInterface $input, OutputInterface $output): void
+	{
+		assertType('bool', $input->hasArgument('command'));
+		assertType('string|null', $input->getArgument('command'));
+
+		assertType('string|null', $input->getArgument('base'));
+		assertType('string', $input->getArgument('aaa'));
+		assertType('string', $input->getArgument('bbb'));
+		assertType('string|null', $input->getArgument('required'));
+		assertType('array<int, string>|string', $input->getArgument('diff'));
+		assertType('array<int, string>', $input->getArgument('arr'));
+		assertType('string|null', $input->getArgument('both'));
+		assertType('Symfony\Component\Console\Helper\QuestionHelper', $this->getHelper('question'));
+	}
+
+	protected function interact(InputInterface $input, OutputInterface $output): void
+	{
+		assertType('bool', $input->hasArgument('command'));
+		assertType('string|null', $input->getArgument('command'));
+
+		assertType('string|null', $input->getArgument('base'));
+		assertType('string', $input->getArgument('aaa'));
+		assertType('string', $input->getArgument('bbb'));
+		assertType('string|null', $input->getArgument('required'));
+		assertType('array<int, string>|string', $input->getArgument('diff'));
+		assertType('array<int, string>', $input->getArgument('arr'));
+		assertType('string|null', $input->getArgument('both'));
+		assertType('Symfony\Component\Console\Helper\QuestionHelper', $this->getHelper('question'));
+	}
+
+	protected function execute(InputInterface $input, OutputInterface $output): int
+	{
+		assertType('true', $input->hasArgument('command'));
+		assertType('string', $input->getArgument('command'));
+
+		assertType('string|null', $input->getArgument('base'));
+		assertType('string', $input->getArgument('aaa'));
+		assertType('string', $input->getArgument('bbb'));
+		assertType('string', $input->getArgument('required'));
+		assertType('array<int, string>|string', $input->getArgument('diff'));
+		assertType('array<int, string>', $input->getArgument('arr'));
+		assertType('string|null', $input->getArgument('both'));
+		assertType('Symfony\Component\Console\Helper\QuestionHelper', $this->getHelper('question'));
+	}
+
+}
diff --git a/tests/Type/Symfony/data/ExampleController.php b/tests/Type/Symfony/data/ExampleController.php
new file mode 100644
index 00000000..c7563537
--- /dev/null
+++ b/tests/Type/Symfony/data/ExampleController.php
@@ -0,0 +1,145 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
+use function PHPStan\Testing\assertType;
+
+final class ExampleController extends Controller
+{
+
+	public function services(): void
+	{
+		assertType('Foo', $this->get('foo'));
+		assertType('Foo', $this->get('parameterised_foo'));
+		assertType('Foo\Bar', $this->get('parameterised_bar'));
+		assertType('Synthetic', $this->get('synthetic'));
+		assertType('object', $this->get('bar'));
+		assertType('object', $this->get(doFoo()));
+		assertType('object', $this->get());
+
+		assertType('true', $this->has('foo'));
+		assertType('true', $this->has('synthetic'));
+		assertType('false', $this->has('bar'));
+		assertType('bool', $this->has(doFoo()));
+		assertType('bool', $this->has());
+	}
+
+	public function parameters(ContainerInterface $container, ParameterBagInterface $parameterBag): void
+	{
+		assertType('array|bool|float|int|string|null', $container->getParameter('unknown'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('unknown'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('unknown'));
+		assertType("string", $container->getParameter('app.string'));
+		assertType("string", $parameterBag->get('app.string'));
+		assertType("string", $this->getParameter('app.string'));
+		assertType('int', $container->getParameter('app.int'));
+		assertType('int', $parameterBag->get('app.int'));
+		assertType('int', $this->getParameter('app.int'));
+		assertType("string", $container->getParameter('app.int_as_string'));
+		assertType("string", $parameterBag->get('app.int_as_string'));
+		assertType("string", $this->getParameter('app.int_as_string'));
+		assertType('int', $container->getParameter('app.int_as_processor'));
+		assertType('int', $parameterBag->get('app.int_as_processor'));
+		assertType('int', $this->getParameter('app.int_as_processor'));
+		assertType('float', $container->getParameter('app.float'));
+		assertType('float', $parameterBag->get('app.float'));
+		assertType('float', $this->getParameter('app.float'));
+		assertType("string", $container->getParameter('app.float_as_string'));
+		assertType("string", $parameterBag->get('app.float_as_string'));
+		assertType("string", $this->getParameter('app.float_as_string'));
+		assertType('float', $container->getParameter('app.float_as_processor'));
+		assertType('float', $parameterBag->get('app.float_as_processor'));
+		assertType('float', $this->getParameter('app.float_as_processor'));
+		assertType('bool', $container->getParameter('app.boolean'));
+		assertType('bool', $parameterBag->get('app.boolean'));
+		assertType('bool', $this->getParameter('app.boolean'));
+		assertType("string", $container->getParameter('app.boolean_as_string'));
+		assertType("string", $parameterBag->get('app.boolean_as_string'));
+		assertType("string", $this->getParameter('app.boolean_as_string'));
+		assertType('bool', $container->getParameter('app.boolean_as_processor'));
+		assertType('bool', $parameterBag->get('app.boolean_as_processor'));
+		assertType('bool', $this->getParameter('app.boolean_as_processor'));
+		assertType("array<int, string>", $container->getParameter('app.list'));
+		assertType("array<int, string>", $parameterBag->get('app.list'));
+		assertType("array<int, string>", $this->getParameter('app.list'));
+		assertType("array<int, array{name: string, value: string}>", $container->getParameter('app.list_of_list'));
+		assertType("array<int, array{name: string, value: string}>", $parameterBag->get('app.list_of_list'));
+		assertType("array<int, array{name: string, value: string}>", $this->getParameter('app.list_of_list'));
+		assertType("array<int, array{age: int, size: int}|array{name: string, value: string}>", $container->getParameter('app.list_of_different_list'));
+		assertType("array<int, array{age: int, size: int}|array{name: string, value: string}>", $parameterBag->get('app.list_of_different_list'));
+		assertType("array<int, array{age: int, size: int}|array{name: string, value: string}>", $this->getParameter('app.list_of_different_list'));
+		assertType("array<int|string, array{name: string, value: string}>", $container->getParameter('app.array_of_list'));
+		assertType("array<int|string, array{name: string, value: string}>", $parameterBag->get('app.array_of_list'));
+		assertType("array<int|string, array{name: string, value: string}>", $this->getParameter('app.array_of_list'));
+		assertType("array{url: string, endpoint: string, version: string, payment: array{default: array{username: string, password: string, signature: string}}, api: array{mode: string, default: array{username: string, password: string, signature: string}}}", $container->getParameter('app.list_of_things'));
+		assertType("array{url: string, endpoint: string, version: string, payment: array{default: array{username: string, password: string, signature: string}}, api: array{mode: string, default: array{username: string, password: string, signature: string}}}", $parameterBag->get('app.list_of_things'));
+		assertType("array{url: string, endpoint: string, version: string, payment: array{default: array{username: string, password: string, signature: string}}, api: array{mode: string, default: array{username: string, password: string, signature: string}}}", $this->getParameter('app.list_of_things'));
+		assertType("array{a: string, b: string, c: string}", $container->getParameter('app.map'));
+		assertType("array{a: string, b: string, c: string}", $parameterBag->get('app.map'));
+		assertType("array{a: string, b: string, c: string}", $this->getParameter('app.map'));
+		assertType("string", $container->getParameter('app.binary'));
+		assertType("string", $parameterBag->get('app.binary'));
+		assertType("string", $this->getParameter('app.binary'));
+		assertType("string", $container->getParameter('app.constant'));
+		assertType("string", $parameterBag->get('app.constant'));
+		assertType("string", $this->getParameter('app.constant'));
+
+		assertType('false', $container->hasParameter('unknown'));
+		assertType('false', $parameterBag->has('unknown'));
+		assertType('true', $container->hasParameter('app.string'));
+		assertType('true', $parameterBag->has('app.string'));
+		assertType('true', $container->hasParameter('app.int'));
+		assertType('true', $parameterBag->has('app.int'));
+		assertType('true', $container->hasParameter('app.int_as_string'));
+		assertType('true', $parameterBag->has('app.int_as_string'));
+		assertType('true', $container->hasParameter('app.int_as_processor'));
+		assertType('true', $parameterBag->has('app.int_as_processor'));
+		assertType('true', $container->hasParameter('app.float'));
+		assertType('true', $parameterBag->has('app.float'));
+		assertType('true', $container->hasParameter('app.float_as_string'));
+		assertType('true', $parameterBag->has('app.float_as_string'));
+		assertType('true', $container->hasParameter('app.float_as_processor'));
+		assertType('true', $parameterBag->has('app.float_as_processor'));
+		assertType('true', $container->hasParameter('app.boolean'));
+		assertType('true', $parameterBag->has('app.boolean'));
+		assertType('true', $container->hasParameter('app.boolean_as_string'));
+		assertType('true', $parameterBag->has('app.boolean_as_string'));
+		assertType('true', $container->hasParameter('app.boolean_as_processor'));
+		assertType('true', $parameterBag->has('app.boolean_as_processor'));
+		assertType('true', $container->hasParameter('app.list'));
+		assertType('true', $parameterBag->has('app.list'));
+		assertType('true', $container->hasParameter('app.list_of_list'));
+		assertType('true', $parameterBag->has('app.list_of_list'));
+		assertType('true', $container->hasParameter('app.map'));
+		assertType('true', $parameterBag->has('app.map'));
+		assertType('true', $container->hasParameter('app.binary'));
+		assertType('true', $parameterBag->has('app.binary'));
+		assertType('true', $container->hasParameter('app.constant'));
+		assertType('true', $parameterBag->has('app.constant'));
+
+		$key = rand(0, 1) ? 'app.string' : 'app.int';
+		assertType("int|string", $container->getParameter($key));
+		assertType("int|string", $parameterBag->get($key));
+		assertType("int|string", $this->getParameter($key));
+		assertType('true', $container->hasParameter($key));
+		assertType('true', $parameterBag->has($key));
+
+		$key = rand(0, 1) ? 'app.string' : 'app.foo';
+		assertType("array|bool|float|int|string|null", $container->getParameter($key));
+		assertType("array|bool|float|int|string|null", $parameterBag->get($key));
+		assertType("array|bool|float|int|string|null", $this->getParameter($key));
+		assertType('bool', $container->hasParameter($key));
+		assertType('bool', $parameterBag->has($key));
+
+		$key = rand(0, 1) ? 'app.bar' : 'app.foo';
+		assertType("array|bool|float|int|string|null", $container->getParameter($key));
+		assertType("array|bool|float|int|string|null", $parameterBag->get($key));
+		assertType("array|bool|float|int|string|null", $this->getParameter($key));
+		assertType('false', $container->hasParameter($key));
+		assertType('false', $parameterBag->has($key));
+	}
+
+}
diff --git a/tests/Type/Symfony/data/ExampleControllerWithoutContainer.php b/tests/Type/Symfony/data/ExampleControllerWithoutContainer.php
new file mode 100644
index 00000000..2e48dc80
--- /dev/null
+++ b/tests/Type/Symfony/data/ExampleControllerWithoutContainer.php
@@ -0,0 +1,115 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
+
+final class ExampleControllerWithoutContainer extends Controller
+{
+
+	public function services(): void
+	{
+		assertType('object', $this->get('foo'));
+		assertType('object', $this->get('synthetic'));
+		assertType('object', $this->get('bar'));
+		assertType('object', $this->get(doFoo()));
+		assertType('object', $this->get());
+
+		assertType('bool', $this->has('foo'));
+		assertType('bool', $this->has('synthetic'));
+		assertType('bool', $this->has('bar'));
+		assertType('bool', $this->has(doFoo()));
+		assertType('bool', $this->has());
+	}
+
+	public function parameters(ContainerInterface $container, ParameterBagInterface $parameterBag): void
+	{
+		assertType('array|bool|float|int|string|null', $container->getParameter('unknown'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('unknown'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('unknown'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.string'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.string'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.string'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.int'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.int'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.int'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.int_as_string'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.int_as_string'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.int_as_string'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.int_as_processor'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.int_as_processor'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.int_as_processor'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.float'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.float'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.float'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.float_as_string'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.float_as_string'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.float_as_string'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.float_as_processor'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.float_as_processor'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.float_as_processor'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.boolean'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.boolean'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.boolean'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.boolean_as_string'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.boolean_as_string'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.boolean_as_string'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.boolean_as_processor'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.boolean_as_processor'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.boolean_as_processor'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.list'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.list'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.list'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.list_of_list'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.list_of_list'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.list_of_list'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.array_of_list'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.array_of_list'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.array_of_list'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.map'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.map'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.map'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.binary'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.binary'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.binary'));
+		assertType('array|bool|float|int|string|null', $container->getParameter('app.constant'));
+		assertType('array|bool|float|int|string|null', $parameterBag->get('app.constant'));
+		assertType('array|bool|float|int|string|null', $this->getParameter('app.constant'));
+
+		assertType('bool', $container->hasParameter('unknown'));
+		assertType('bool', $parameterBag->has('unknown'));
+		assertType('bool', $container->hasParameter('app.string'));
+		assertType('bool', $parameterBag->has('app.string'));
+		assertType('bool', $container->hasParameter('app.int'));
+		assertType('bool', $parameterBag->has('app.int'));
+		assertType('bool', $container->hasParameter('app.int_as_string'));
+		assertType('bool', $parameterBag->has('app.int_as_string'));
+		assertType('bool', $container->hasParameter('app.int_as_processor'));
+		assertType('bool', $parameterBag->has('app.int_as_processor'));
+		assertType('bool', $container->hasParameter('app.float'));
+		assertType('bool', $parameterBag->has('app.float'));
+		assertType('bool', $container->hasParameter('app.float_as_string'));
+		assertType('bool', $parameterBag->has('app.float_as_string'));
+		assertType('bool', $container->hasParameter('app.float_as_processor'));
+		assertType('bool', $parameterBag->has('app.float_as_processor'));
+		assertType('bool', $container->hasParameter('app.boolean'));
+		assertType('bool', $parameterBag->has('app.boolean'));
+		assertType('bool', $container->hasParameter('app.boolean_as_string'));
+		assertType('bool', $parameterBag->has('app.boolean_as_string'));
+		assertType('bool', $container->hasParameter('app.boolean_as_processor'));
+		assertType('bool', $parameterBag->has('app.boolean_as_processor'));
+		assertType('bool', $container->hasParameter('app.list'));
+		assertType('bool', $parameterBag->has('app.list'));
+		assertType('bool', $container->hasParameter('app.list_of_list'));
+		assertType('bool', $parameterBag->has('app.list_of_list'));
+		assertType('bool', $container->hasParameter('app.map'));
+		assertType('bool', $parameterBag->has('app.map'));
+		assertType('bool', $container->hasParameter('app.binary'));
+		assertType('bool', $parameterBag->has('app.binary'));
+		assertType('bool', $container->hasParameter('app.constant'));
+		assertType('bool', $parameterBag->has('app.constant'));
+	}
+
+}
diff --git a/tests/Type/Symfony/data/ExampleOptionCommand.php b/tests/Type/Symfony/data/ExampleOptionCommand.php
new file mode 100644
index 00000000..b880173d
--- /dev/null
+++ b/tests/Type/Symfony/data/ExampleOptionCommand.php
@@ -0,0 +1,49 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use function PHPStan\Testing\assertType;
+
+final class ExampleOptionCommand extends Command
+{
+
+	protected function configure(): void
+	{
+		parent::configure();
+		$this->setName('example-option');
+
+		$this->addOption('a', null, InputOption::VALUE_NONE);
+		$this->addOption('b', null, InputOption::VALUE_OPTIONAL);
+		$this->addOption('c', null, InputOption::VALUE_REQUIRED);
+		$this->addOption('d', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL);
+		$this->addOption('e', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED);
+		$this->addOption('f', null, InputOption::VALUE_NEGATABLE);
+
+		$this->addOption('bb', null, InputOption::VALUE_OPTIONAL, '', 1);
+		$this->addOption('cc', null, InputOption::VALUE_REQUIRED, '', 1);
+		$this->addOption('dd', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, '', [1]);
+		$this->addOption('ee', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, '', [1]);
+	}
+
+	protected function execute(InputInterface $input, OutputInterface $output): int
+	{
+		assertType('bool', $input->getOption('a'));
+		assertType('string|null', $input->getOption('b'));
+		assertType('string|null', $input->getOption('c'));
+		assertType('array<int, string|null>', $input->getOption('d'));
+		assertType('array<int, string>', $input->getOption('e'));
+		assertType('bool|null', $input->getOption('f'));
+
+		assertType('1|string|null', $input->getOption('bb'));
+		assertType('1|string', $input->getOption('cc'));
+		assertType('array<int, 1|string|null>', $input->getOption('dd'));
+		assertType('array<int, 1|string>', $input->getOption('ee'));
+
+		assertType('array{a: bool, b: string|null, c: string|null, d: array<int, string|null>, e: array<int, string>, f: bool|null, bb: 1|string|null, cc: 1|string, dd: array<int, 1|string|null>, ee: array<int, 1|string>, help: bool, quiet: bool, verbose: bool, version: bool, ansi: bool|null, no-interaction: bool}', $input->getOptions());
+	}
+
+}
diff --git a/tests/Type/Symfony/data/ExampleOptionLazyCommand.php b/tests/Type/Symfony/data/ExampleOptionLazyCommand.php
new file mode 100644
index 00000000..433e1cfe
--- /dev/null
+++ b/tests/Type/Symfony/data/ExampleOptionLazyCommand.php
@@ -0,0 +1,51 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use function PHPStan\Testing\assertType;
+
+final class ExampleOptionLazyCommand extends Command
+{
+
+	protected static $defaultName = 'lazy-example-option';
+	protected static $defaultDescription = 'lazy example description';
+
+	protected function configure(): void
+	{
+		parent::configure();
+
+		$this->addOption('a', null, InputOption::VALUE_NONE);
+		$this->addOption('b', null, InputOption::VALUE_OPTIONAL);
+		$this->addOption('c', null, InputOption::VALUE_REQUIRED);
+		$this->addOption('d', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL);
+		$this->addOption('e', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED);
+		$this->addOption('f', null, InputOption::VALUE_NEGATABLE);
+
+		$this->addOption('bb', null, InputOption::VALUE_OPTIONAL, '', 1);
+		$this->addOption('cc', null, InputOption::VALUE_REQUIRED, '', 1);
+		$this->addOption('dd', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, '', [1]);
+		$this->addOption('ee', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, '', [1]);
+	}
+
+	protected function execute(InputInterface $input, OutputInterface $output): int
+	{
+		assertType('bool', $input->getOption('a'));
+		assertType('string|null', $input->getOption('b'));
+		assertType('string|null', $input->getOption('c'));
+		assertType('array<int, string|null>', $input->getOption('d'));
+		assertType('array<int, string>', $input->getOption('e'));
+		assertType('bool|null', $input->getOption('f'));
+
+		assertType('1|string|null', $input->getOption('bb'));
+		assertType('1|string', $input->getOption('cc'));
+		assertType('array<int, 1|string|null>', $input->getOption('dd'));
+		assertType('array<int, 1|string>', $input->getOption('ee'));
+
+		assertType('array{a: bool, b: string|null, c: string|null, d: array<int, string|null>, e: array<int, string>, f: bool|null, bb: 1|string|null, cc: 1|string, dd: array<int, 1|string|null>, ee: array<int, 1|string>, help: bool, quiet: bool, verbose: bool, version: bool, ansi: bool|null, no-interaction: bool}', $input->getOptions());
+	}
+
+}
diff --git a/tests/Type/Symfony/data/FormInterface_getErrors.php b/tests/Type/Symfony/data/FormInterface_getErrors.php
new file mode 100644
index 00000000..a360a21d
--- /dev/null
+++ b/tests/Type/Symfony/data/FormInterface_getErrors.php
@@ -0,0 +1,20 @@
+<?php declare(strict_types=1);
+
+use Symfony\Component\Form\FormError;
+use Symfony\Component\Form\FormErrorIterator;
+use Symfony\Component\Form\FormInterface;
+use function PHPStan\Testing\assertType;
+
+/** @var FormInterface $form */
+$form = new stdClass();
+
+assertType(FormErrorIterator::class . '<'. FormError::class .'>', $form->getErrors());
+assertType(FormErrorIterator::class . '<'. FormError::class .'>', $form->getErrors(false));
+assertType(FormErrorIterator::class . '<'. FormError::class .'>', $form->getErrors(false, true));
+
+assertType(FormErrorIterator::class . '<'. FormError::class .'>', $form->getErrors(true));
+assertType(FormErrorIterator::class . '<'. FormError::class .'>', $form->getErrors(true, true));
+
+assertType(FormErrorIterator::class . '<'. FormError::class .'>', $form->getErrors(false, false));
+
+assertType(FormErrorIterator::class . '<'. FormError::class .'|'. FormErrorIterator::class . '>', $form->getErrors(true, false));
diff --git a/tests/Type/Symfony/data/bug-178.php b/tests/Type/Symfony/data/bug-178.php
new file mode 100644
index 00000000..9dbd5137
--- /dev/null
+++ b/tests/Type/Symfony/data/bug-178.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Bug178;
+
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+
+class Foo extends AbstractController
+{
+
+	public function doFoo(): void
+	{
+		if ($this->has('sonata.media.manager.category') && $this->has('sonata.media.manager.context')) {
+			// do stuff that requires both managers.
+		}
+	}
+
+}
diff --git a/tests/Type/Symfony/data/cache.php b/tests/Type/Symfony/data/cache.php
new file mode 100644
index 00000000..a8862177
--- /dev/null
+++ b/tests/Type/Symfony/data/cache.php
@@ -0,0 +1,42 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Symfony\cache;
+
+use function PHPStan\Testing\assertType;
+
+function testCacheCallable(\Symfony\Contracts\Cache\CacheInterface  $cache): void {
+	$result = $cache->get('foo', function (): string {
+		return '';
+	});
+
+	assertType('string', $result);
+};
+
+/**
+ * @param callable():string $fn
+ */
+function testNonScalarCacheCallable(\Symfony\Contracts\Cache\CacheInterface $cache, callable $fn): void {
+	$result = $cache->get('foo', $fn);
+
+	assertType('string', $result);
+};
+
+
+/**
+ * @param callable():non-empty-string $fn
+ */
+function testCacheCallableReturnTypeGeneralization(\Symfony\Contracts\Cache\CacheInterface $cache, callable $fn): void {
+	$result = $cache->get('foo', $fn);
+
+	assertType('string', $result);
+};
+
+
+/**
+ * @param \Symfony\Contracts\Cache\CallbackInterface<\stdClass> $cb
+ */
+ function testCacheCallbackInterface(\Symfony\Contracts\Cache\CacheInterface  $cache, \Symfony\Contracts\Cache\CallbackInterface $cb): void {
+	$result = $cache->get('foo',$cb);
+
+	assertType('stdClass', $result);
+};
diff --git a/tests/Type/Symfony/data/denormalizer.php b/tests/Type/Symfony/data/denormalizer.php
new file mode 100644
index 00000000..ccbb8fc2
--- /dev/null
+++ b/tests/Type/Symfony/data/denormalizer.php
@@ -0,0 +1,10 @@
+<?php declare(strict_types = 1);
+
+use function PHPStan\Testing\assertType;
+
+$serializer = new \Symfony\Component\Serializer\Serializer();
+
+assertType('Bar', $serializer->denormalize('bar', 'Bar', 'format'));
+assertType('array<Bar>', $serializer->denormalize('bar', 'Bar[]', 'format'));
+assertType('array<array<Bar>>', $serializer->denormalize('bar', 'Bar[][]', 'format'));
+assertType('mixed', $serializer->denormalize('bar'));
diff --git a/tests/Type/Symfony/data/envelope_all.php b/tests/Type/Symfony/data/envelope_all.php
new file mode 100644
index 00000000..aac9d583
--- /dev/null
+++ b/tests/Type/Symfony/data/envelope_all.php
@@ -0,0 +1,9 @@
+<?php declare(strict_types = 1);
+
+use function PHPStan\Testing\assertType;
+
+$envelope = new \Symfony\Component\Messenger\Envelope(new stdClass());
+
+assertType('list<Symfony\Component\Messenger\Stamp\ReceivedStamp>', $envelope->all(\Symfony\Component\Messenger\Stamp\ReceivedStamp::class));
+assertType('list<Symfony\Component\Messenger\Stamp\StampInterface>', $envelope->all(random_bytes(1)));
+assertType('array<class-string<Symfony\Component\Messenger\Stamp\StampInterface>, list<Symfony\Component\Messenger\Stamp\StampInterface>>', $envelope->all());
diff --git a/tests/Type/Symfony/data/extension/anonymous/AnonymousExtension.php b/tests/Type/Symfony/data/extension/anonymous/AnonymousExtension.php
new file mode 100644
index 00000000..1e33cb37
--- /dev/null
+++ b/tests/Type/Symfony/data/extension/anonymous/AnonymousExtension.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace PHPStan\Type\Symfony\Extension\Anonymous;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Extension\Extension;
+
+new class extends Extension
+{
+	public function load(array $configs, ContainerBuilder $container)
+	{
+		\PHPStan\Testing\assertType(
+			'null',
+			$this->getConfiguration($configs, $container)
+		);
+	}
+};
diff --git a/tests/Type/Symfony/data/extension/ignore-implemented/IgnoreImplementedExtension.php b/tests/Type/Symfony/data/extension/ignore-implemented/IgnoreImplementedExtension.php
new file mode 100644
index 00000000..614431f2
--- /dev/null
+++ b/tests/Type/Symfony/data/extension/ignore-implemented/IgnoreImplementedExtension.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace PHPStan\Type\Symfony\Extension\IgnoreImplemented;
+
+use Symfony\Component\Config\Definition\ConfigurationInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use \Symfony\Component\DependencyInjection\Extension\Extension;
+
+class IgnoreImplementedExtension extends Extension
+{
+	public function load(array $configs, ContainerBuilder $container): void
+	{
+		\PHPStan\Testing\assertType(
+			'Symfony\Component\Config\Definition\ConfigurationInterface|null',
+			$this->getConfiguration($configs, $container)
+		);
+	}
+
+	public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface
+	{
+		return null;
+	}
+}
diff --git a/tests/Type/Symfony/data/extension/multiple-types/MultipleTypes.php b/tests/Type/Symfony/data/extension/multiple-types/MultipleTypes.php
new file mode 100644
index 00000000..77c44003
--- /dev/null
+++ b/tests/Type/Symfony/data/extension/multiple-types/MultipleTypes.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace PHPStan\Type\Symfony\Extension\MultipleTypes;
+
+use PHPStan\Type\Symfony\Extension\WithConfiguration\WithConfigurationExtension;
+use PHPStan\Type\Symfony\Extension\WithoutConfiguration\WithoutConfigurationExtension;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * @param WithConfigurationExtension|WithoutConfigurationExtension $extension
+ */
+function test($extension, array $configs, ContainerBuilder $container)
+{
+	\PHPStan\Testing\assertType(
+		'PHPStan\Type\Symfony\Extension\WithConfiguration\Configuration|null',
+		$extension->getConfiguration($configs, $container)
+	);
+}
diff --git a/tests/Type/Symfony/data/extension/with-configuration-with-constructor-optional-params/Configuration.php b/tests/Type/Symfony/data/extension/with-configuration-with-constructor-optional-params/Configuration.php
new file mode 100644
index 00000000..4ff16c39
--- /dev/null
+++ b/tests/Type/Symfony/data/extension/with-configuration-with-constructor-optional-params/Configuration.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace PHPStan\Type\Symfony\Extension\WithConfigurationWithConstructorOptionalParams;
+
+use Symfony\Component\Config\Definition\ConfigurationInterface;
+
+class Configuration implements ConfigurationInterface
+{
+	public function __construct($foo = null)
+	{
+	}
+
+	public function getConfigTreeBuilder()
+	{
+	}
+}
diff --git a/tests/Type/Symfony/data/extension/with-configuration-with-constructor-optional-params/WithConfigurationWithConstructorOptionalParamsExtension.php b/tests/Type/Symfony/data/extension/with-configuration-with-constructor-optional-params/WithConfigurationWithConstructorOptionalParamsExtension.php
new file mode 100644
index 00000000..56545350
--- /dev/null
+++ b/tests/Type/Symfony/data/extension/with-configuration-with-constructor-optional-params/WithConfigurationWithConstructorOptionalParamsExtension.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace PHPStan\Type\Symfony\Extension\WithConfigurationWithConstructorOptionalParams;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use \Symfony\Component\DependencyInjection\Extension\Extension;
+
+class WithConfigurationWithConstructorOptionalParamsExtension extends Extension
+{
+	public function load(array $configs, ContainerBuilder $container): void
+	{
+		\PHPStan\Testing\assertType(
+			Configuration::class,
+			$this->getConfiguration($configs, $container)
+		);
+	}
+}
diff --git a/tests/Type/Symfony/data/extension/with-configuration-with-constructor-required-params/Configuration.php b/tests/Type/Symfony/data/extension/with-configuration-with-constructor-required-params/Configuration.php
new file mode 100644
index 00000000..b9d5bcc1
--- /dev/null
+++ b/tests/Type/Symfony/data/extension/with-configuration-with-constructor-required-params/Configuration.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace PHPStan\Type\Symfony\Extension\WithConfigurationWithConstructorRequiredParams;
+
+use Symfony\Component\Config\Definition\ConfigurationInterface;
+
+class Configuration implements ConfigurationInterface
+{
+	public function __construct($foo)
+	{
+	}
+
+	public function getConfigTreeBuilder()
+	{
+	}
+}
diff --git a/tests/Type/Symfony/data/extension/with-configuration-with-constructor-required-params/WithConfigurationWithConstructorRequiredParamsExtension.php b/tests/Type/Symfony/data/extension/with-configuration-with-constructor-required-params/WithConfigurationWithConstructorRequiredParamsExtension.php
new file mode 100644
index 00000000..ccc8b992
--- /dev/null
+++ b/tests/Type/Symfony/data/extension/with-configuration-with-constructor-required-params/WithConfigurationWithConstructorRequiredParamsExtension.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace PHPStan\Type\Symfony\Extension\WithConfigurationWithConstructorRequiredParams;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use \Symfony\Component\DependencyInjection\Extension\Extension;
+
+class WithConfigurationWithConstructorRequiredParamsExtension extends Extension
+{
+	public function load(array $configs, ContainerBuilder $container): void
+	{
+		\PHPStan\Testing\assertType(
+			'null',
+			$this->getConfiguration($configs, $container)
+		);
+	}
+}
diff --git a/tests/Type/Symfony/data/extension/with-configuration-with-constructor/Configuration.php b/tests/Type/Symfony/data/extension/with-configuration-with-constructor/Configuration.php
new file mode 100644
index 00000000..8eea9eb9
--- /dev/null
+++ b/tests/Type/Symfony/data/extension/with-configuration-with-constructor/Configuration.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace PHPStan\Type\Symfony\Extension\WithConfigurationWithConstructor;
+
+use Symfony\Component\Config\Definition\ConfigurationInterface;
+
+class Configuration implements ConfigurationInterface
+{
+	public function __construct()
+	{
+	}
+
+	public function getConfigTreeBuilder()
+	{
+	}
+}
diff --git a/tests/Type/Symfony/data/extension/with-configuration-with-constructor/WithConfigurationWithConstructorExtension.php b/tests/Type/Symfony/data/extension/with-configuration-with-constructor/WithConfigurationWithConstructorExtension.php
new file mode 100644
index 00000000..7b5a2daf
--- /dev/null
+++ b/tests/Type/Symfony/data/extension/with-configuration-with-constructor/WithConfigurationWithConstructorExtension.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace PHPStan\Type\Symfony\Extension\WithConfigurationWithConstructor;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use \Symfony\Component\DependencyInjection\Extension\Extension;
+
+class WithConfigurationWithConstructorExtension extends Extension
+{
+	public function load(array $configs, ContainerBuilder $container): void
+	{
+		\PHPStan\Testing\assertType(
+			Configuration::class,
+			$this->getConfiguration($configs, $container)
+		);
+	}
+}
diff --git a/tests/Type/Symfony/data/extension/with-configuration/Configuration.php b/tests/Type/Symfony/data/extension/with-configuration/Configuration.php
new file mode 100644
index 00000000..4e8c51b5
--- /dev/null
+++ b/tests/Type/Symfony/data/extension/with-configuration/Configuration.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace PHPStan\Type\Symfony\Extension\WithConfiguration;
+
+use Symfony\Component\Config\Definition\ConfigurationInterface;
+
+class Configuration implements ConfigurationInterface
+{
+	public function getConfigTreeBuilder()
+	{
+	}
+}
diff --git a/tests/Type/Symfony/data/extension/with-configuration/WithConfigurationExtension.php b/tests/Type/Symfony/data/extension/with-configuration/WithConfigurationExtension.php
new file mode 100644
index 00000000..9b809841
--- /dev/null
+++ b/tests/Type/Symfony/data/extension/with-configuration/WithConfigurationExtension.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace PHPStan\Type\Symfony\Extension\WithConfiguration;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use \Symfony\Component\DependencyInjection\Extension\Extension;
+
+class WithConfigurationExtension extends Extension
+{
+	public function load(array $configs, ContainerBuilder $container): void
+	{
+		\PHPStan\Testing\assertType(
+			Configuration::class,
+			$this->getConfiguration($configs, $container)
+		);
+	}
+}
diff --git a/tests/Type/Symfony/data/extension/without-configuration/WithoutConfigurationExtension.php b/tests/Type/Symfony/data/extension/without-configuration/WithoutConfigurationExtension.php
new file mode 100644
index 00000000..dccec3e2
--- /dev/null
+++ b/tests/Type/Symfony/data/extension/without-configuration/WithoutConfigurationExtension.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace PHPStan\Type\Symfony\Extension\WithoutConfiguration;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use \Symfony\Component\DependencyInjection\Extension\Extension;
+
+class WithoutConfigurationExtension extends Extension
+{
+	public function load(array $configs, ContainerBuilder $container): void
+	{
+		\PHPStan\Testing\assertType(
+			'null',
+			$this->getConfiguration($configs, $container)
+		);
+	}
+}
diff --git a/tests/Type/Symfony/data/form_data_type.php b/tests/Type/Symfony/data/form_data_type.php
new file mode 100644
index 00000000..34a673a4
--- /dev/null
+++ b/tests/Type/Symfony/data/form_data_type.php
@@ -0,0 +1,93 @@
+<?php declare(strict_types = 1);
+
+namespace GenericFormDataType;
+
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\Extension\Core\Type\NumberType;
+use Symfony\Component\Form\Extension\Core\Type\TextType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\Form\FormFactoryInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use function PHPStan\Testing\assertType;
+
+class DataClass
+{
+
+	/** @var int */
+	public $foo;
+
+	/** @var string */
+	public $bar;
+
+}
+
+/**
+ * @extends AbstractType<DataClass>
+ */
+class DataClassType extends AbstractType
+{
+
+	public function buildForm(FormBuilderInterface $builder, array $options): void
+	{
+		assertType('GenericFormDataType\DataClass|null', $builder->getData());
+		assertType('GenericFormDataType\DataClass|null', $builder->getForm()->getData());
+
+		$builder
+			->add('foo', NumberType::class)
+			->add('bar', TextType::class)
+		;
+	}
+
+	public function configureOptions(OptionsResolver $resolver): void
+	{
+		$resolver
+			->setDefaults([
+				'data_class' => DataClass::class,
+			])
+		;
+	}
+
+}
+
+class FormFactoryAwareClass
+{
+
+	/** @var FormFactoryInterface */
+	private $formFactory;
+
+	public function __construct(FormFactoryInterface $formFactory)
+	{
+		$this->formFactory = $formFactory;
+	}
+
+	public function doSomething(): void
+	{
+		$form = $this->formFactory->create(DataClassType::class, new DataClass());
+		assertType('GenericFormDataType\DataClass', $form->getData());
+	}
+
+	public function doSomethingNullable(): void
+	{
+		$form = $this->formFactory->create(DataClassType::class);
+		assertType('GenericFormDataType\DataClass|null', $form->getData());
+	}
+
+}
+
+class FormController extends AbstractController
+{
+
+	public function doSomething(): void
+	{
+		$form = $this->createForm(DataClassType::class, new DataClass());
+		assertType('GenericFormDataType\DataClass', $form->getData());
+	}
+
+	public function doSomethingNullable(): void
+	{
+		$form = $this->createForm(DataClassType::class);
+		assertType('GenericFormDataType\DataClass|null', $form->getData());
+	}
+
+}
diff --git a/tests/Type/Symfony/data/header_bag_get.php b/tests/Type/Symfony/data/header_bag_get.php
new file mode 100644
index 00000000..e3f4c561
--- /dev/null
+++ b/tests/Type/Symfony/data/header_bag_get.php
@@ -0,0 +1,13 @@
+<?php declare(strict_types = 1);
+
+use function PHPStan\Testing\assertType;
+
+$bag = new \Symfony\Component\HttpFoundation\HeaderBag(['foo' => ['bar']]);
+
+assertType('string|null', $bag->get('foo'));
+assertType('string|null', $bag->get('foo', null));
+assertType('string', $bag->get('foo', 'baz'));
+assertType('string|null', $bag->get('foo', null, true));
+assertType('string', $bag->get('foo', 'baz', true));
+assertType('array<int, string>', $bag->get('foo', null, false));
+assertType('array<int, string>', $bag->get('foo', 'baz', false));
diff --git a/tests/Type/Symfony/data/input_bag.php b/tests/Type/Symfony/data/input_bag.php
new file mode 100644
index 00000000..77b58821
--- /dev/null
+++ b/tests/Type/Symfony/data/input_bag.php
@@ -0,0 +1,22 @@
+<?php declare(strict_types = 1);
+
+use function PHPStan\Testing\assertType;
+
+$bag = new \Symfony\Component\HttpFoundation\InputBag(['foo' => 'bar', 'bar' => ['x']]);
+
+assertType('bool|float|int|string|null', $bag->get('foo'));
+
+if ($bag->has('foo')) {
+	// Because `has` rely on `array_key_exists` we can still have set the NULL value.
+	assertType('bool|float|int|string|null', $bag->get('foo'));
+	assertType('bool|float|int|string|null', $bag->get('bar'));
+} else {
+	assertType('null', $bag->get('foo'));
+	assertType('bool|float|int|string|null', $bag->get('bar'));
+}
+
+assertType('bool|float|int|string|null', $bag->get('foo', null));
+assertType('bool|float|int|string', $bag->get('foo', ''));
+assertType('bool|float|int|string', $bag->get('foo', 'baz'));
+assertType('array<string, array<mixed>|bool|float|int|string>', $bag->all());
+assertType('array<mixed>', $bag->all('bar'));
diff --git a/tests/Type/Symfony/data/input_bag_from_request.php b/tests/Type/Symfony/data/input_bag_from_request.php
new file mode 100644
index 00000000..7aa6057c
--- /dev/null
+++ b/tests/Type/Symfony/data/input_bag_from_request.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace InputBag;
+
+use Symfony\Component\HttpFoundation\Request;
+use function PHPStan\Testing\assertType;
+
+class Foo
+{
+
+	public function doFoo(Request $request): void
+	{
+		assertType('bool|float|int|string|null', $request->request->get('foo'));
+		assertType('string|null', $request->query->get('foo'));
+		assertType('string|null', $request->cookies->get('foo'));
+
+		assertType('bool|float|int|string', $request->request->get('foo', 'foo'));
+		assertType('string', $request->query->get('foo', 'foo'));
+		assertType('string', $request->cookies->get('foo', 'foo'));
+	}
+
+}
diff --git a/tests/Type/Symfony/data/kernel_interface.php b/tests/Type/Symfony/data/kernel_interface.php
new file mode 100644
index 00000000..e2239754
--- /dev/null
+++ b/tests/Type/Symfony/data/kernel_interface.php
@@ -0,0 +1,19 @@
+<?php declare(strict_types = 1);
+
+use function PHPStan\Testing\assertType;
+
+$kernel = new class ('dev', true) extends \Symfony\Component\HttpKernel\Kernel {
+
+	public function registerBundles(): void
+	{
+	}
+
+	public function registerContainerConfiguration(\Symfony\Component\Config\Loader\LoaderInterface $loader): void
+	{
+	}
+
+};
+
+assertType('string', $kernel->locateResource(''));
+assertType('string', $kernel->locateResource('', null, true));
+assertType('array<int, string>', $kernel->locateResource('', null, false));
diff --git a/tests/Type/Symfony/data/messenger_handle_trait.php b/tests/Type/Symfony/data/messenger_handle_trait.php
new file mode 100644
index 00000000..7a86d482
--- /dev/null
+++ b/tests/Type/Symfony/data/messenger_handle_trait.php
@@ -0,0 +1,113 @@
+<?php declare(strict_types = 1);
+
+namespace MessengerHandleTrait;
+
+use Symfony\Component\Messenger\Handler\MessageSubscriberInterface;
+use Symfony\Component\Messenger\HandleTrait;
+use function PHPStan\Testing\assertType;
+
+class RegularQuery {}
+class RegularQueryResult {}
+class RegularQueryHandler
+{
+    public function __invoke(RegularQuery $query): RegularQueryResult
+    {
+        return new RegularQueryResult();
+    }
+}
+
+class BooleanQuery {}
+class StringQuery {}
+class IntQuery {}
+class FloatQuery {}
+class MultiQueryHandler implements MessageSubscriberInterface
+{
+    public static function getHandledMessages(): iterable
+    {
+        yield BooleanQuery::class;
+        yield IntQuery::class => ['method' => 'handleInt'];
+        yield FloatQuery::class => ['method' => 'handleFloat'];
+        yield StringQuery::class => ['method' => 'handleString'];
+    }
+
+    public function __invoke(BooleanQuery $query): bool
+    {
+        return true;
+    }
+
+    public function handleInt(IntQuery $query): int
+    {
+        return 0;
+    }
+
+    public function handleFloat(FloatQuery $query): float
+    {
+        return 0.0;
+    }
+
+    public function handleString(StringQuery $query): string
+    {
+        return 'string result';
+    }
+}
+
+class TaggedQuery {}
+class TaggedResult {}
+class TaggedHandler
+{
+    public function handle(TaggedQuery $query): TaggedResult
+    {
+        return new TaggedResult();
+    }
+}
+
+class MultiHandlesForInTheSameHandlerQuery {}
+class MultiHandlesForInTheSameHandler implements MessageSubscriberInterface
+{
+    public static function getHandledMessages(): iterable
+    {
+        yield MultiHandlesForInTheSameHandlerQuery::class;
+        yield MultiHandlesForInTheSameHandlerQuery::class => ['priority' => '0'];
+    }
+
+    public function __invoke(MultiHandlesForInTheSameHandlerQuery $query): bool
+    {
+        return true;
+    }
+}
+
+class MultiHandlersForTheSameMessageQuery {}
+class MultiHandlersForTheSameMessageHandler1
+{
+    public function __invoke(MultiHandlersForTheSameMessageQuery $query): bool
+    {
+        return true;
+    }
+}
+class MultiHandlersForTheSameMessageHandler2
+{
+    public function __invoke(MultiHandlersForTheSameMessageQuery $query): bool
+    {
+        return false;
+    }
+}
+
+class HandleTraitClass {
+    use HandleTrait;
+
+    public function __invoke()
+    {
+        assertType(RegularQueryResult::class, $this->handle(new RegularQuery()));
+
+        assertType('bool', $this->handle(new BooleanQuery()));
+        assertType('int', $this->handle(new IntQuery()));
+        assertType('float', $this->handle(new FloatQuery()));
+        assertType('string', $this->handle(new StringQuery()));
+
+        assertType(TaggedResult::class, $this->handle(new TaggedQuery()));
+
+        // HandleTrait will throw exception in fact due to multiple handle methods/handlers per single query
+        assertType('mixed', $this->handle(new MultiHandlesForInTheSameHandlerQuery()));
+        assertType('mixed', $this->handle(new MultiHandlersForTheSameMessageQuery()));
+    }
+}
diff --git a/tests/Type/Symfony/data/property_accessor.php b/tests/Type/Symfony/data/property_accessor.php
new file mode 100644
index 00000000..8d5e95f0
--- /dev/null
+++ b/tests/Type/Symfony/data/property_accessor.php
@@ -0,0 +1,13 @@
+<?php declare(strict_types = 1);
+
+use function PHPStan\Testing\assertType;
+
+$propertyAccessor = new \Symfony\Component\PropertyAccess\PropertyAccessor();
+
+$array = [1 => 'ea'];
+$propertyAccessor->setValue($array, 'foo', 'bar');
+assertType('array<mixed>', $array);
+
+$object = new \stdClass();
+$propertyAccessor->setValue($object, 'foo', 'bar');
+assertType('stdClass', $object);
diff --git a/tests/Type/Symfony/data/request_get_content.php b/tests/Type/Symfony/data/request_get_content.php
new file mode 100644
index 00000000..4038e3fa
--- /dev/null
+++ b/tests/Type/Symfony/data/request_get_content.php
@@ -0,0 +1,11 @@
+<?php declare(strict_types = 1);
+
+use function PHPStan\Testing\assertType;
+
+/** @var \Symfony\Component\HttpFoundation\Request $request */
+$request = doFoo();
+
+assertType('string', $request->getContent());
+assertType('string', $request->getContent(false));
+assertType('resource', $request->getContent(true));
+assertType('resource|string', $request->getContent(doBar()));
diff --git a/tests/Type/Symfony/data/request_get_session.php b/tests/Type/Symfony/data/request_get_session.php
new file mode 100644
index 00000000..9a0335e5
--- /dev/null
+++ b/tests/Type/Symfony/data/request_get_session.php
@@ -0,0 +1,14 @@
+<?php declare(strict_types = 1);
+
+use Symfony\Component\HttpFoundation\Session\SessionInterface;
+use function PHPStan\Testing\assertType;
+
+/** @var \Symfony\Component\HttpFoundation\Request $request */
+$request = doRequest();
+
+$session1 = $request->getSession();
+assertType(SessionInterface::class, $request->getSession());
+
+if ($request->hasSession()) {
+	assertType(SessionInterface::class, $request->getSession());
+}
diff --git a/tests/Type/Symfony/data/request_get_session_null.php b/tests/Type/Symfony/data/request_get_session_null.php
new file mode 100644
index 00000000..9c37979d
--- /dev/null
+++ b/tests/Type/Symfony/data/request_get_session_null.php
@@ -0,0 +1,14 @@
+<?php declare(strict_types = 1);
+
+use Symfony\Component\HttpFoundation\Session\SessionInterface;
+use function PHPStan\Testing\assertType;
+
+/** @var \Symfony\Component\HttpFoundation\Request $request */
+$request = doRequest();
+
+$session1 = $request->getSession();
+assertType(SessionInterface::class . '|null', $request->getSession());
+
+if ($request->hasSession()) {
+	assertType(SessionInterface::class, $request->getSession());
+}
diff --git a/tests/Type/Symfony/data/response_header_bag_get_cookies.php b/tests/Type/Symfony/data/response_header_bag_get_cookies.php
new file mode 100644
index 00000000..f07d5e5b
--- /dev/null
+++ b/tests/Type/Symfony/data/response_header_bag_get_cookies.php
@@ -0,0 +1,12 @@
+<?php declare(strict_types = 1);
+
+use Symfony\Component\HttpFoundation\Cookie;
+use Symfony\Component\HttpFoundation\ResponseHeaderBag;
+use function PHPStan\Testing\assertType;
+
+$headerBag = new ResponseHeaderBag();
+$headerBag->setCookie(Cookie::create('cookie_name'));
+
+assertType('array<int, Symfony\Component\HttpFoundation\Cookie>', $headerBag->getCookies());
+assertType('array<int, Symfony\Component\HttpFoundation\Cookie>', $headerBag->getCookies(ResponseHeaderBag::COOKIES_FLAT));
+assertType('array<string, array<string, array<string, Symfony\Component\HttpFoundation\Cookie>>>', $headerBag->getCookies(ResponseHeaderBag::COOKIES_ARRAY));
diff --git a/tests/Type/Symfony/data/serializer.php b/tests/Type/Symfony/data/serializer.php
new file mode 100644
index 00000000..8b75f575
--- /dev/null
+++ b/tests/Type/Symfony/data/serializer.php
@@ -0,0 +1,10 @@
+<?php declare(strict_types = 1);
+
+use function PHPStan\Testing\assertType;
+
+$serializer = new \Symfony\Component\Serializer\Serializer();
+
+assertType('Bar', $serializer->deserialize('bar', 'Bar', 'format'));
+assertType('array<Bar>', $serializer->deserialize('bar', 'Bar[]', 'format'));
+assertType('array<array<Bar>>', $serializer->deserialize('bar', 'Bar[][]', 'format'));
+assertType('mixed', $serializer->deserialize('bar'));
diff --git a/tests/Type/Symfony/data/tree_builder.php b/tests/Type/Symfony/data/tree_builder.php
new file mode 100644
index 00000000..8c3c3270
--- /dev/null
+++ b/tests/Type/Symfony/data/tree_builder.php
@@ -0,0 +1,183 @@
+<?php declare(strict_types = 1);
+
+use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+use function PHPStan\Testing\assertType;
+
+$treeBuilder = new TreeBuilder('my_tree');
+$treeRootNode = $treeBuilder->getRootNode();
+assertType('Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition', $treeRootNode);
+
+assertType('Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition', $treeRootNode
+	->children()
+	->end());
+
+assertType('Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition', $treeRootNode
+	->children()
+	->scalarNode("protocol")
+	->end()
+	->end());
+
+assertType('Symfony\Component\Config\Definition\Builder\NodeBuilder', $treeRootNode
+	->children());
+
+assertType('Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition', $treeRootNode
+	->children()
+	->arrayNode("protocols"));
+
+assertType('Symfony\Component\Config\Definition\Builder\NodeBuilder', $treeRootNode
+	->children()
+	->arrayNode("protocols")
+	->children());
+
+assertType('Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition', $treeRootNode
+	->children()
+	->arrayNode("protocols")
+	->children()
+	->scalarNode("protocol"));
+
+assertType('Symfony\Component\Config\Definition\Builder\NodeBuilder', $treeRootNode
+	->children()
+	->arrayNode("protocols")
+	->children()
+	->scalarNode("protocol")
+	->end());
+
+assertType('Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition', $treeRootNode
+	->children()
+	->arrayNode("protocols")
+	->children()
+	->scalarNode("protocol")
+	->end()
+	->end());
+
+assertType('Symfony\Component\Config\Definition\Builder\NodeBuilder', $treeRootNode
+	->children()
+	->arrayNode("protocols")
+	->children()
+	->scalarNode("protocol")
+	->end()
+	->end()
+	->end());
+
+assertType('Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition', $treeRootNode
+	->children()
+	->arrayNode("protocols")
+	->children()
+	->scalarNode("protocol")
+	->end()
+	->end()
+	->end()
+	->end());
+
+assertType('Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition', $treeRootNode
+	->children()
+	->arrayNode("protocols")
+	->children()
+	->booleanNode("auto_connect")
+	->defaultTrue()
+	->end()
+	->scalarNode("default_connection")
+	->defaultValue("default")
+	->end()
+	->integerNode("positive_value")
+	->min(0)
+	->end()
+	->floatNode("big_value")
+	->max(5E45)
+	->end()
+	->enumNode("delivery")
+	->values(["standard", "expedited", "priority"])
+	->end()
+	->end()
+	->end()
+	->end());
+
+$arrayTreeBuilder = new TreeBuilder('my_tree', 'array');
+$arrayRootNode = $arrayTreeBuilder->getRootNode();
+
+assertType('Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition', $arrayRootNode);
+assertType('Symfony\Component\Config\Definition\Builder\TreeBuilder', $arrayRootNode->end());
+assertType('Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition', $arrayRootNode
+	->children()
+	->arrayNode("methods")
+	->prototype("scalar")
+	->defaultNull()
+	->end()
+	->end()
+	->end());
+
+assertType('Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition', $arrayRootNode
+	->children()
+	->arrayNode("methods")
+	->scalarPrototype()
+	->defaultNull()
+	->end()
+	->end()
+	->end());
+
+assertType('Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition', $arrayRootNode
+	->children()
+	->arrayNode("methods")
+	->prototype("scalar")
+	->validate()
+	->ifNotInArray(["one", "two"])
+	->thenInvalid("%s is not a valid method.")
+	->end()
+	->end()
+	->end()
+	->end());
+
+assertType('Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition', $arrayRootNode
+	->children()
+	->arrayNode("methods")
+	->prototype("array")
+	->beforeNormalization()
+	->ifString()
+	->then(static function ($v) {
+		return [$v];
+	})
+	->end()
+	->end()
+	->end()
+	->end());
+
+$variableTreeBuilder = new TreeBuilder('my_tree', 'variable');
+$variableRootNode = $variableTreeBuilder->getRootNode();
+
+assertType('Symfony\Component\Config\Definition\Builder\VariableNodeDefinition', $variableRootNode);
+assertType('Symfony\Component\Config\Definition\Builder\TreeBuilder', $variableRootNode->end());
+
+$scalarTreeBuilder = new TreeBuilder('my_tree', 'scalar');
+$scalarRootNode = $scalarTreeBuilder->getRootNode();
+
+assertType('Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition', $scalarRootNode);
+assertType('Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition', $scalarRootNode->defaultValue("default"));
+assertType('Symfony\Component\Config\Definition\Builder\TreeBuilder', $scalarRootNode->defaultValue("default")->end());
+
+$booleanTreeBuilder = new TreeBuilder('my_tree', 'boolean');
+$booleanRootNode = $booleanTreeBuilder->getRootNode();
+
+assertType('Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition', $booleanRootNode);
+assertType('Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition', $booleanRootNode->defaultTrue());
+assertType('Symfony\Component\Config\Definition\Builder\TreeBuilder', $booleanRootNode->defaultTrue()->end());
+
+$integerTreeBuilder = new TreeBuilder('my_tree', 'integer');
+$integerRootNode = $integerTreeBuilder->getRootNode();
+
+assertType('Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition', $integerRootNode);
+assertType('Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition', $integerRootNode->min(0));
+assertType('Symfony\Component\Config\Definition\Builder\TreeBuilder', $integerRootNode->min(0)->end());
+
+$floatTreeBuilder = new TreeBuilder('my_tree', 'float');
+$floatRootNode = $floatTreeBuilder->getRootNode();
+
+assertType('Symfony\Component\Config\Definition\Builder\FloatNodeDefinition', $floatRootNode);
+assertType('Symfony\Component\Config\Definition\Builder\FloatNodeDefinition', $floatRootNode->max(5E45));
+assertType('Symfony\Component\Config\Definition\Builder\TreeBuilder', $floatRootNode->max(5E45)->end());
+
+$enumTreeBuilder = new TreeBuilder('my_tree', 'enum');
+$enumRootNode = $enumTreeBuilder->getRootNode();
+
+assertType('Symfony\Component\Config\Definition\Builder\EnumNodeDefinition', $enumRootNode);
+assertType('Symfony\Component\Config\Definition\Builder\EnumNodeDefinition', $enumRootNode->values(["standard", "expedited", "priority"]));
+assertType('Symfony\Component\Config\Definition\Builder\TreeBuilder', $enumRootNode->values(["standard", "expedited", "priority"])->end());
diff --git a/tests/Type/Symfony/extension-test.neon b/tests/Type/Symfony/extension-test.neon
new file mode 100644
index 00000000..0f1d9522
--- /dev/null
+++ b/tests/Type/Symfony/extension-test.neon
@@ -0,0 +1,4 @@
+parameters:
+	symfony:
+		consoleApplicationLoader: console_application_loader.php
+		containerXmlPath: container.xml
diff --git a/tests/Type/Symfony/header_bag_get.php b/tests/Type/Symfony/header_bag_get.php
deleted file mode 100644
index ed2d36aa..00000000
--- a/tests/Type/Symfony/header_bag_get.php
+++ /dev/null
@@ -1,15 +0,0 @@
-<?php declare(strict_types = 1);
-
-$bag = new \Symfony\Component\HttpFoundation\HeaderBag(['foo' => ['bar']]);
-
-$test1 = $bag->get('foo');
-$test2 = $bag->get('foo', null);
-$test3 = $bag->get('foo', 'baz');
-
-$test5 = $bag->get('foo', null, true);
-$test6 = $bag->get('foo', 'baz', true);
-
-$test8 = $bag->get('foo', null, false);
-$test9 = $bag->get('foo', 'baz', false);
-
-die;
diff --git a/tests/Type/Symfony/request_get_content.php b/tests/Type/Symfony/request_get_content.php
deleted file mode 100644
index 3fbacaab..00000000
--- a/tests/Type/Symfony/request_get_content.php
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php declare(strict_types = 1);
-
-/** @var \Symfony\Component\HttpFoundation\Request $request */
-$request = doFoo();
-
-$content1 = $request->getContent();
-$content2 = $request->getContent(false);
-$content3 = $request->getContent(true);
-$content4 = $request->getContent(doBar());
-
-die;
diff --git a/tests/Type/Symfony/serializer.php b/tests/Type/Symfony/serializer.php
deleted file mode 100644
index a19065be..00000000
--- a/tests/Type/Symfony/serializer.php
+++ /dev/null
@@ -1,9 +0,0 @@
-<?php declare(strict_types = 1);
-
-$serializer = new \Symfony\Component\Serializer\Serializer();
-
-$first = $serializer->deserialize('bar', 'Bar', 'format');
-$second = $serializer->deserialize('bar', 'Bar[]', 'format');
-$third = $serializer->deserialize('bar', 'Bar[][]', 'format');
-
-die;
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
deleted file mode 100644
index 738b4ac6..00000000
--- a/tests/phpunit.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<phpunit
-        bootstrap="bootstrap.php"
-        colors="true"
-        backupGlobals="false"
-        backupStaticAttributes="false"
-        beStrictAboutChangesToGlobalState="true"
-        beStrictAboutOutputDuringTests="true"
-        beStrictAboutTestsThatDoNotTestAnything="true"
-        beStrictAboutTodoAnnotatedTests="true"
-        failOnRisky="true"
-        failOnWarning="true"
->
-    <filter>
-        <whitelist>
-            <directory suffix=".php">../src</directory>
-        </whitelist>
-    </filter>
-    <logging>
-        <log
-                type="coverage-text"
-                target="php://stdout"
-                showUncoveredFiles="true"
-                showOnlySummary="true"
-        />
-        <log type="coverage-clover" target="tmp/clover.xml"/>
-    </logging>
-</phpunit>
diff --git a/tmp/.gitignore b/tmp/.gitignore
new file mode 100644
index 00000000..37890cae
--- /dev/null
+++ b/tmp/.gitignore
@@ -0,0 +1,3 @@
+*
+!cache
+!.*
diff --git a/tmp/cache/.gitignore b/tmp/cache/.gitignore
new file mode 100644
index 00000000..125e3429
--- /dev/null
+++ b/tmp/cache/.gitignore
@@ -0,0 +1,2 @@
+*
+!.*
