diff --git a/.gitattributes b/.gitattributes
index 823b5a81..1545ee73 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,10 +1,12 @@
*.php text eol=lf
tests export-ignore
+tmp export-ignore
.coveralls.yml 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/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index 15b77335..00000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-version: 2
-updates:
- - package-ecosystem: composer
- directory: "/"
- schedule:
- interval: monthly
- open-pull-requests-limit: 10
- - package-ecosystem: composer
- directory: "/build-cs"
- schedule:
- interval: monthly
- open-pull-requests-limit: 10
- - package-ecosystem: github-actions
- directory: "/"
- schedule:
- interval: monthly
- open-pull-requests-limit: 10
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
index 83f79354..88543fb5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -6,7 +6,7 @@ on:
pull_request:
push:
branches:
- - "master"
+ - "2.0.x"
jobs:
lint:
@@ -16,15 +16,16 @@ jobs:
strategy:
matrix:
php-version:
- - "7.1"
- - "7.2"
- - "7.3"
- "7.4"
- "8.0"
+ - "8.1"
+ - "8.2"
+ - "8.3"
+ - "8.4"
steps:
- name: "Checkout"
- uses: "actions/checkout@v2"
+ uses: actions/checkout@v4
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
@@ -36,42 +37,48 @@ jobs:
run: "composer validate"
- name: "Install dependencies"
- run: "composer install --no-interaction --no-progress --no-suggest"
-
- - name: "Update PHPUnit"
- if: matrix.php-version == '7.4' || matrix.php-version == '8.0'
- run: "composer require --dev phpunit/phpunit:'^9.5' --update-with-dependencies"
-
+ run: "composer install --no-interaction --no-progress"
- name: "Lint"
- run: "vendor/bin/phing lint"
+ run: "make lint"
- coding-standards:
+ coding-standard:
name: "Coding Standard"
runs-on: "ubuntu-latest"
steps:
- name: "Checkout"
- uses: "actions/checkout@v2"
+ uses: actions/checkout@v4
+
+ - name: "Checkout build-cs"
+ uses: actions/checkout@v4
+ with:
+ repository: "phpstan/build-cs"
+ path: "build-cs"
+ ref: "2.x"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
- php-version: "7.4"
+ php-version: "8.2"
- name: "Validate Composer"
run: "composer validate"
- name: "Install dependencies"
- run: "composer install --no-interaction --no-progress --no-suggest"
+ 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: "vendor/bin/phing lint"
+ run: "make lint"
- name: "Coding Standard"
- run: "vendor/bin/phing cs"
+ run: "make cs"
tests:
name: "Tests"
@@ -81,21 +88,19 @@ jobs:
fail-fast: false
matrix:
php-version:
- - "7.1"
- - "7.2"
- - "7.3"
- "7.4"
- "8.0"
+ - "8.1"
+ - "8.2"
+ - "8.3"
+ - "8.4"
dependencies:
- "lowest"
- "highest"
- exclude:
- - php-version: "7.1"
- dependencies: "highest"
steps:
- name: "Checkout"
- uses: "actions/checkout@v2"
+ uses: actions/checkout@v4
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
@@ -105,18 +110,14 @@ jobs:
- name: "Install lowest dependencies"
if: ${{ matrix.dependencies == 'lowest' }}
- run: "composer update --prefer-lowest --no-interaction --no-progress --no-suggest"
+ run: "composer update --prefer-lowest --no-interaction --no-progress"
- name: "Install highest dependencies"
if: ${{ matrix.dependencies == 'highest' }}
- run: "composer update --no-interaction --no-progress --no-suggest"
-
- - name: "Update PHPUnit"
- if: matrix.php-version == '7.4' || matrix.php-version == '8.0'
- run: "composer require --dev phpunit/phpunit:'^9.5' --update-with-dependencies"
+ run: "composer update --no-interaction --no-progress"
- name: "Tests"
- run: "vendor/bin/phing tests"
+ run: "make tests"
static-analysis:
name: "PHPStan"
@@ -126,18 +127,19 @@ jobs:
fail-fast: false
matrix:
php-version:
- - "7.1"
- - "7.2"
- - "7.3"
- "7.4"
- "8.0"
+ - "8.1"
+ - "8.2"
+ - "8.3"
+ - "8.4"
dependencies:
- "lowest"
- "highest"
steps:
- name: "Checkout"
- uses: "actions/checkout@v2"
+ uses: actions/checkout@v4
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
@@ -149,15 +151,11 @@ jobs:
- name: "Install lowest dependencies"
if: ${{ matrix.dependencies == 'lowest' }}
- run: "composer update --prefer-lowest --no-interaction --no-progress --no-suggest"
+ run: "composer update --prefer-lowest --no-interaction --no-progress"
- name: "Install highest dependencies"
if: ${{ matrix.dependencies == 'highest' }}
- run: "composer update --no-interaction --no-progress --no-suggest"
-
- - name: "Update PHPUnit"
- if: matrix.php-version == '7.4' || matrix.php-version == '8.0'
- run: "composer require --dev phpunit/phpunit:'^9.5' --update-with-dependencies"
+ run: "composer update --no-interaction --no-progress"
- name: "PHPStan"
- run: "vendor/bin/phing phpstan"
+ run: "make phpstan"
diff --git a/.github/workflows/create-tag.yml b/.github/workflows/create-tag.yml
new file mode 100644
index 00000000..a8535014
--- /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@v4
+ 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..09b39ded
--- /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@v1
+ 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
index 225470a6..b8c96d48 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -14,19 +14,19 @@ jobs:
steps:
- name: "Checkout"
- uses: "actions/checkout@v2"
+ uses: actions/checkout@v4
- name: Generate changelog
id: changelog
- uses: metcalfc/changelog-generator@v1.0.0
+ uses: metcalfc/changelog-generator@v4.6.2
with:
- myToken: ${{ secrets.GITHUB_TOKEN }}
+ myToken: ${{ secrets.PHPSTAN_BOT_TOKEN }}
- name: "Create release"
id: create-release
uses: actions/create-release@v1
env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.PHPSTAN_BOT_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
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/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 0081f2ad..25a155c1 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,11 @@ 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.
@@ -56,11 +61,18 @@ You have to provide a path to `srcDevDebugProjectContainer.xml` or similar XML f
```yaml
parameters:
symfony:
- container_xml_path: var/cache/dev/srcDevDebugProjectContainer.xml
- # or with Symfony 4.2+
- container_xml_path: var/cache/dev/srcApp_KernelDevDebugContainer.xml
+ containerXmlPath: var/cache/dev/srcDevDebugProjectContainer.xml
+ # or with Symfony 4.2+
+ containerXmlPath: var/cache/dev/srcApp_KernelDevDebugContainer.xml
# or with Symfony 5+
- container_xml_path: var/cache/dev/App_KernelDevDebugContainer.xml
+ 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
@@ -78,35 +90,72 @@ In that case, you can disable the `::has()` method return type resolving like th
```
parameters:
symfony:
- constant_hassers: false
+ constantHassers: false
```
Be aware that it may hide genuine errors in your application.
-## Console command analysis
+## Analysis of Symfony Console Commands
-You can opt in for more advanced analysis 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()`.
+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:
- console_application_loader: tests/console-application.php
+ consoleApplicationLoader: tests/console-application.php
```
-For example, in a Symfony project, `console-application.php` would look something like this:
+Symfony 4:
```php
-require __DIR__.'/../config/bootstrap.php';
-$kernel = new \App\Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
-return new \Symfony\Bundle\FrameworkBundle\Console\Application($kernel);
+// 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);
```
-You may then encounter an error with PhpParser:
+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;
-```bash
-Compile Error: Cannot Declare interface PhpParser\NodeVisitor, because the name is already in use
+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
diff --git a/build-cs/.gitignore b/build-cs/.gitignore
deleted file mode 100644
index ff72e2d0..00000000
--- a/build-cs/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/composer.lock
-/vendor
diff --git a/build-cs/composer.json b/build-cs/composer.json
deleted file mode 100644
index 9acd0275..00000000
--- a/build-cs/composer.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "require-dev": {
- "consistence/coding-standard": "^3.10",
- "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
- "slevomat/coding-standard": "^6.4"
- }
-}
diff --git a/build.xml b/build.xml
deleted file mode 100644
index 75b98df1..00000000
--- a/build.xml
+++ /dev/null
@@ -1,108 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/composer.json b/composer.json
index 46dbf2d5..c03d2c99 100644
--- a/composer.json
+++ b/composer.json
@@ -13,36 +13,34 @@
}
],
"require": {
- "php": "^7.1 || ^8.0",
+ "php": "^7.4 || ^8.0",
"ext-simplexml": "*",
- "phpstan/phpstan": "^0.12.83"
+ "phpstan/phpstan": "^2.1.13"
},
"conflict": {
"symfony/framework-bundle": "<3.0"
},
"require-dev": {
- "phing/phing": "^2.16.3",
"php-parallel-lint/php-parallel-lint": "^1.2",
- "phpstan/phpstan-phpunit": "^0.12.16",
- "phpstan/phpstan-strict-rules": "^0.12.5",
- "phpunit/phpunit": "^7.5.20",
- "symfony/console": "^4.0",
- "symfony/config": "^4.2",
- "symfony/framework-bundle": "^4.0",
- "symfony/http-foundation": "^4.0",
- "symfony/messenger": "^4.2",
- "symfony/serializer": "^4.0"
+ "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": {
- "platform": {
- "php": "7.4.6"
- },
"sort-packages": true
},
"extra": {
- "branch-alias": {
- "dev-master": "0.12-dev"
- },
"phpstan": {
"includes": [
"extension.neon",
diff --git a/extension.neon b/extension.neon
index 51ef297f..0803248f 100644
--- a/extension.neon
+++ b/extension.neon
@@ -1,18 +1,45 @@
parameters:
dynamicConstantNames:
- Symfony\Component\HttpKernel\Kernel::VERSION_ID
+ exceptions:
+ uncheckedExceptionClasses:
+ - 'Symfony\Component\Console\Exception\InvalidArgumentException'
symfony:
- container_xml_path: null
- constant_hassers: true
- console_application_loader: null
+ containerXmlPath: null
+ constantHassers: true
+ consoleApplicationLoader: null
stubFiles:
- - stubs/Symfony/Component/Form/ChoiceList/Loader/ChoiceLoaderInterface.stub
+ - 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/FrameworkBundle/KernelBrowser.stub
+ - stubs/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.stub
+ - stubs/Symfony/Bundle/FrameworkBundle/Test/TestContainer.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
@@ -20,17 +47,35 @@ parameters:
- 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/PropertyAccessorInterface.stub
- stubs/Symfony/Component/PropertyAccess/PropertyPathInterface.stub
- stubs/Symfony/Component/Security/Acl/Model/AclInterface.stub
- - stubs/Symfony/Component/Security/Acl/Model/AclProviderInterface.stub
- - stubs/Symfony/Component/Security/Acl/Model/MutableAclInterface.stub
- - stubs/Symfony/Component/Security/Acl/Model/MutableAclProviderInterface.stub
- - stubs/Symfony/Component/Security/Acl/Model/ObjectIdentityInterface.stub
- - stubs/Symfony/Component/Security/Acl/Model/SecurityIdentityInterface.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
@@ -38,15 +83,21 @@ parameters:
- 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
parametersSchema:
symfony: structure([
- container_xml_path: schema(string(), nullable())
- constant_hassers: bool()
- console_application_loader: schema(string(), nullable())
+ containerXmlPath: schema(string(), nullable())
+ constantHassers: bool()
+ consoleApplicationLoader: schema(string(), nullable())
])
services:
@@ -54,27 +105,45 @@ services:
-
factory: PHPStan\Symfony\ConsoleApplicationResolver
arguments:
- consoleApplicationLoader: %symfony.console_application_loader%
+ 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%
+ -
+ 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
-
- factory: 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.constant_hassers%)
+ factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Psr\Container\ContainerInterface, %symfony.constantHassers%)
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
-
- factory: 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]
-
- factory: 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
@@ -126,6 +195,11 @@ services:
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
@@ -142,10 +216,17 @@ services:
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
@@ -210,3 +291,76 @@ services:
-
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 feb4d827..00000000
--- a/phpcs.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- tests/tmp
- tests/Symfony/ExampleContainer.php
-
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 bc33def1..f13073e1 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -5,17 +5,11 @@ includes:
- 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:
+ excludePaths:
- tests/tmp/*
- tests/*/Example*.php
- tests/*/console_application_loader.php
- - tests/*/envelope_all.php
- - tests/*/header_bag_get.php
- - tests/*/input_bag.php
- - tests/*/kernel_interface.php
- - tests/*/request_get_content.php
- - tests/*/request_get_session.php
- - tests/*/serializer.php
- - tests/*/denormalizer.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 @@
+
+
+
+
+ ./src
+
+
+
+
+
+
+
+
+
+ tests
+
+
+
+
+
diff --git a/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php b/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php
index f97633c0..96f1efea 100644
--- a/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php
+++ b/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php
@@ -6,9 +6,11 @@
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;
/**
@@ -17,8 +19,7 @@
final class ContainerInterfacePrivateServiceRule implements Rule
{
- /** @var ServiceMap */
- private $serviceMap;
+ private ServiceMap $serviceMap;
public function __construct(ServiceMap $symfonyServiceMap)
{
@@ -30,22 +31,13 @@ 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 [];
}
@@ -53,8 +45,9 @@ public function processNode(Node $node, Scope $scope): array
$isTestContainerType = (new ObjectType('Symfony\Bundle\FrameworkBundle\Test\TestContainer'))->isSuperTypeOf($argType);
$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 ($isTestContainerType->yes() || $isOldServiceSubscriber->yes() || $isServiceSubscriber->yes() || $isServiceLocator->yes()) {
return [];
}
@@ -71,15 +64,31 @@ public function processNode(Node $node, Scope $scope): array
return [];
}
- $serviceId = $this->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);
+ }
+
}
diff --git a/src/Rules/Symfony/ContainerInterfaceUnknownServiceRule.php b/src/Rules/Symfony/ContainerInterfaceUnknownServiceRule.php
index 2719bb4e..23444b6b 100644
--- a/src/Rules/Symfony/ContainerInterfaceUnknownServiceRule.php
+++ b/src/Rules/Symfony/ContainerInterfaceUnknownServiceRule.php
@@ -4,13 +4,14 @@
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
@@ -18,13 +19,11 @@
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;
@@ -35,26 +34,22 @@ 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);
@@ -68,12 +63,16 @@ public function processNode(Node $node, Scope $scope): array
return [];
}
- $serviceId = $this->serviceMap::getServiceIdFromNode($node->args[0]->value, $scope);
+ $serviceId = $this->serviceMap::getServiceIdFromNode($node->getArgs()[0]->value, $scope);
if ($serviceId !== null) {
$service = $this->serviceMap->getService($serviceId);
- $serviceIdType = $scope->getType($node->args[0]->value);
+ $serviceIdType = $scope->getType($node->getArgs()[0]->value);
if ($service === null && !$scope->getType(Helper::createMarkerNode($node->var, $serviceIdType, $this->printer))->equals($serviceIdType)) {
- return [sprintf('Service "%s" is not registered in the container.', $serviceId)];
+ 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
index b735dbfe..5435d4c9 100644
--- a/src/Rules/Symfony/InvalidArgumentDefaultValueRule.php
+++ b/src/Rules/Symfony/InvalidArgumentDefaultValueRule.php
@@ -6,16 +6,16 @@
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
-use PHPStan\ShouldNotHappenException;
+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\TypeUtils;
use PHPStan\Type\UnionType;
use PHPStan\Type\VerbosityLevel;
+use function count;
use function sprintf;
/**
@@ -29,32 +29,23 @@ 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 (!(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->args[3])) {
+ if (!isset($node->getArgs()[3])) {
return [];
}
- $modeType = isset($node->args[1]) ? $scope->getType($node->args[1]->value) : new NullType();
- if ($modeType instanceof NullType) {
+ $modeType = isset($node->getArgs()[1]) ? $scope->getType($node->getArgs()[1]->value) : new NullType();
+ if ($modeType->isNull()->yes()) {
$modeType = new ConstantIntegerType(2); // InputArgument::OPTIONAL
}
- $modeTypes = TypeUtils::getConstantScalars($modeType);
+ $modeTypes = $modeType->getConstantScalarTypes();
if (count($modeTypes) !== 1) {
return [];
}
@@ -63,16 +54,26 @@ public function processNode(Node $node, Scope $scope): array
}
$mode = $modeTypes[0]->getValue();
- $defaultType = $scope->getType($node->args[3]->value);
+ $defaultType = $scope->getType($node->getArgs()[3]->value);
// not an array
if (($mode & 4) !== 4 && !(new UnionType([new StringType(), new NullType()]))->isSuperTypeOf($defaultType)->yes()) {
- return [sprintf('Parameter #4 $default of method Symfony\Component\Console\Command\Command::addArgument() expects string|null, %s given.', $defaultType->describe(VerbosityLevel::typeOnly()))];
+ 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 [sprintf('Parameter #4 $default of method Symfony\Component\Console\Command\Command::addArgument() expects array|null, %s given.', $defaultType->describe(VerbosityLevel::typeOnly()))];
+ return [
+ RuleErrorBuilder::message(sprintf(
+ 'Parameter #4 $default of method Symfony\Component\Console\Command\Command::addArgument() expects array|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
index 42d40ee0..2e3dc0e9 100644
--- a/src/Rules/Symfony/InvalidOptionDefaultValueRule.php
+++ b/src/Rules/Symfony/InvalidOptionDefaultValueRule.php
@@ -6,19 +6,18 @@
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
-use PHPStan\ShouldNotHappenException;
+use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\ArrayType;
-use PHPStan\Type\Constant\ConstantBooleanType;
+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\TypeCombinator;
-use PHPStan\Type\TypeUtils;
use PHPStan\Type\UnionType;
use PHPStan\Type\VerbosityLevel;
+use function count;
use function sprintf;
/**
@@ -32,32 +31,23 @@ 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 (!(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->args[4])) {
+ if (!isset($node->getArgs()[4])) {
return [];
}
- $modeType = isset($node->args[2]) ? $scope->getType($node->args[2]->value) : new NullType();
- if ($modeType instanceof NullType) {
+ $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 = TypeUtils::getConstantScalars($modeType);
+ $modeTypes = $modeType->getConstantScalarTypes();
if (count($modeTypes) !== 1) {
return [];
}
@@ -66,22 +56,30 @@ public function processNode(Node $node, Scope $scope): array
}
$mode = $modeTypes[0]->getValue();
- $defaultType = $scope->getType($node->args[4]->value);
+ $defaultType = $scope->getType($node->getArgs()[4]->value);
// not an array
if (($mode & 8) !== 8) {
- $checkType = new UnionType([new StringType(), new IntegerType(), new NullType()]);
- if (($mode & 4) === 4) { // https://symfony.com/doc/current/console/input.html#options-with-optional-arguments
- $checkType = TypeCombinator::union($checkType, new ConstantBooleanType(false));
- }
+ $checkType = new UnionType([new StringType(), new IntegerType(), new NullType(), new BooleanType()]);
if (!$checkType->isSuperTypeOf($defaultType)->yes()) {
- return [sprintf('Parameter #5 $default of method Symfony\Component\Console\Command\Command::addOption() expects %s, %s given.', $checkType->describe(VerbosityLevel::typeOnly()), $defaultType->describe(VerbosityLevel::typeOnly()))];
+ 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 [sprintf('Parameter #5 $default of method Symfony\Component\Console\Command\Command::addOption() expects array|null, %s given.', $defaultType->describe(VerbosityLevel::typeOnly()))];
+ return [
+ RuleErrorBuilder::message(sprintf(
+ 'Parameter #5 $default of method Symfony\Component\Console\Command\Command::addOption() expects array|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
index e6de90b7..ee36a23c 100644
--- a/src/Rules/Symfony/UndefinedArgumentRule.php
+++ b/src/Rules/Symfony/UndefinedArgumentRule.php
@@ -5,14 +5,13 @@
use InvalidArgumentException;
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\ConsoleApplicationResolver;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Symfony\Helper;
-use PHPStan\Type\TypeUtils;
use function count;
use function sprintf;
@@ -22,13 +21,11 @@
final class UndefinedArgumentRule implements Rule
{
- /** @var \PHPStan\Symfony\ConsoleApplicationResolver */
- private $consoleApplicationResolver;
+ private ConsoleApplicationResolver $consoleApplicationResolver;
- /** @var \PhpParser\PrettyPrinter\Standard */
- private $printer;
+ private Printer $printer;
- public function __construct(ConsoleApplicationResolver $consoleApplicationResolver, Standard $printer)
+ public function __construct(ConsoleApplicationResolver $consoleApplicationResolver, Printer $printer)
{
$this->consoleApplicationResolver = $consoleApplicationResolver;
$this->printer = $printer;
@@ -39,17 +36,8 @@ 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();
- };
-
$classReflection = $scope->getClassReflection();
if ($classReflection === null) {
return [];
@@ -64,12 +52,12 @@ public function processNode(Node $node, Scope $scope): array
if (!$node->name instanceof Node\Identifier || $node->name->name !== 'getArgument') {
return [];
}
- if (!isset($node->args[0])) {
+ if (!isset($node->getArgs()[0])) {
return [];
}
- $argType = $scope->getType($node->args[0]->value);
- $argStrings = TypeUtils::getConstantStrings($argType);
+ $argType = $scope->getType($node->getArgs()[0]->value);
+ $argStrings = $argType->getConstantStrings();
if (count($argStrings) !== 1) {
return [];
}
@@ -84,7 +72,9 @@ public function processNode(Node $node, Scope $scope): array
if ($scope->getType(Helper::createMarkerNode($node->var, $argType, $this->printer))->equals($argType)) {
continue;
}
- $errors[] = sprintf('Command "%s" does not define argument "%s".', $name, $argName);
+ $errors[] = RuleErrorBuilder::message(sprintf('Command "%s" does not define argument "%s".', $name, $argName))
+ ->identifier('symfonyConsole.argumentNotFound')
+ ->build();
}
}
diff --git a/src/Rules/Symfony/UndefinedOptionRule.php b/src/Rules/Symfony/UndefinedOptionRule.php
index ee041929..39a6a4ac 100644
--- a/src/Rules/Symfony/UndefinedOptionRule.php
+++ b/src/Rules/Symfony/UndefinedOptionRule.php
@@ -5,14 +5,13 @@
use InvalidArgumentException;
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\ConsoleApplicationResolver;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Symfony\Helper;
-use PHPStan\Type\TypeUtils;
use function count;
use function sprintf;
@@ -22,13 +21,11 @@
final class UndefinedOptionRule implements Rule
{
- /** @var \PHPStan\Symfony\ConsoleApplicationResolver */
- private $consoleApplicationResolver;
+ private ConsoleApplicationResolver $consoleApplicationResolver;
- /** @var \PhpParser\PrettyPrinter\Standard */
- private $printer;
+ private Printer $printer;
- public function __construct(ConsoleApplicationResolver $consoleApplicationResolver, Standard $printer)
+ public function __construct(ConsoleApplicationResolver $consoleApplicationResolver, Printer $printer)
{
$this->consoleApplicationResolver = $consoleApplicationResolver;
$this->printer = $printer;
@@ -39,17 +36,8 @@ 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();
- };
-
$classReflection = $scope->getClassReflection();
if ($classReflection === null) {
return [];
@@ -64,12 +52,12 @@ public function processNode(Node $node, Scope $scope): array
if (!$node->name instanceof Node\Identifier || $node->name->name !== 'getOption') {
return [];
}
- if (!isset($node->args[0])) {
+ if (!isset($node->getArgs()[0])) {
return [];
}
- $optType = $scope->getType($node->args[0]->value);
- $optStrings = TypeUtils::getConstantStrings($optType);
+ $optType = $scope->getType($node->getArgs()[0]->value);
+ $optStrings = $optType->getConstantStrings();
if (count($optStrings) !== 1) {
return [];
}
@@ -84,7 +72,9 @@ public function processNode(Node $node, Scope $scope): array
if ($scope->getType(Helper::createMarkerNode($node->var, $optType, $this->printer))->equals($optType)) {
continue;
}
- $errors[] = sprintf('Command "%s" does not define option "%s".', $name, $optName);
+ $errors[] = RuleErrorBuilder::message(sprintf('Command "%s" does not define option "%s".', $name, $optName))
+ ->identifier('symfonyConsole.optionNotFound')
+ ->build();
}
}
diff --git a/src/Symfony/ConsoleApplicationResolver.php b/src/Symfony/ConsoleApplicationResolver.php
index 88ee908f..13b24d26 100644
--- a/src/Symfony/ConsoleApplicationResolver.php
+++ b/src/Symfony/ConsoleApplicationResolver.php
@@ -5,45 +5,57 @@
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
{
- /** @var \Symfony\Component\Console\Application|null */
- private $consoleApplication;
+ private ?string $consoleApplicationLoader = null;
+
+ private ?Application $consoleApplication = null;
public function __construct(?string $consoleApplicationLoader)
{
- if ($consoleApplicationLoader === null) {
- return;
- }
- $this->consoleApplication = $this->loadConsoleApplication($consoleApplicationLoader);
+ $this->consoleApplicationLoader = $consoleApplicationLoader;
}
- /**
- * @return \Symfony\Component\Console\Application|null
- * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint
- */
- private function loadConsoleApplication(string $consoleApplicationLoader)
+ public function hasConsoleApplicationLoader(): bool
{
- if (!file_exists($consoleApplicationLoader)
- || !is_readable($consoleApplicationLoader)
+ 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();
+ 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 require $consoleApplicationLoader;
+ return $this->consoleApplication = require $this->consoleApplicationLoader;
}
/**
- * @return \Symfony\Component\Console\Command\Command[]
+ * @return Command[]
*/
public function findCommands(ClassReflection $classReflection): array
{
- if ($this->consoleApplication === null) {
+ $consoleApplication = $this->getConsoleApplication();
+ if ($consoleApplication === null) {
return [];
}
@@ -53,10 +65,22 @@ public function findCommands(ClassReflection $classReflection): array
}
$commands = [];
- foreach ($this->consoleApplication->all() as $name => $command) {
- if (!$classType->isSuperTypeOf(new ObjectType(get_class($command)))->yes()) {
+ 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;
}
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 @@
+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
index 6339b450..5d3bccd0 100644
--- a/src/Symfony/DefaultServiceMap.php
+++ b/src/Symfony/DefaultServiceMap.php
@@ -4,17 +4,16 @@
use PhpParser\Node\Expr;
use PHPStan\Analyser\Scope;
-use PHPStan\Type\TypeUtils;
use function count;
final class DefaultServiceMap implements ServiceMap
{
- /** @var \PHPStan\Symfony\ServiceDefinition[] */
- private $services;
+ /** @var ServiceDefinition[] */
+ private array $services;
/**
- * @param \PHPStan\Symfony\ServiceDefinition[] $services
+ * @param ServiceDefinition[] $services
*/
public function __construct(array $services)
{
@@ -22,7 +21,7 @@ public function __construct(array $services)
}
/**
- * @return \PHPStan\Symfony\ServiceDefinition[]
+ * @return ServiceDefinition[]
*/
public function getServices(): array
{
@@ -36,7 +35,7 @@ public function getService(string $id): ?ServiceDefinition
public static function getServiceIdFromNode(Expr $node, Scope $scope): ?string
{
- $strings = TypeUtils::getConstantStrings($scope->getType($node));
+ $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 @@
+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 @@
+ */
+ private array $messageMap;
+
+ /** @param array $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 @@
+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> */
+ 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 $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 @@
+|bool|float|int|string */
+ private $value;
+
+ /**
+ * @param array|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|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 @@
+|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 @@
+
+ */
+ 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 @@
+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 b954dc72..bbd2d8a3 100644
--- a/src/Symfony/ServiceMap.php
+++ b/src/Symfony/ServiceMap.php
@@ -5,11 +5,14 @@
use PhpParser\Node\Expr;
use PHPStan\Analyser\Scope;
+/**
+ * @api
+ */
interface ServiceMap
{
/**
- * @return \PHPStan\Symfony\ServiceDefinition[]
+ * @return ServiceDefinition[]
*/
public function getServices(): array;
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 @@
+ */
+ private array $attributes;
+
+ /** @param array $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 @@
+ */
+ 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 @@
+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 @@
+consoleApplicationResolver = $consoleApplicationResolver;
+ }
+
+ public function print(Output $output): void
+ {
+ $output->writeLineFormatted(sprintf(
+ 'Symfony\'s consoleApplicationLoader: %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 @@
+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|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 47aa1bc9..ac79cb30 100644
--- a/src/Symfony/XmlServiceMapFactory.php
+++ b/src/Symfony/XmlServiceMapFactory.php
@@ -2,6 +2,10 @@
namespace PHPStan\Symfony;
+use SimpleXMLElement;
+use function count;
+use function file_get_contents;
+use function ksort;
use function simplexml_load_string;
use function sprintf;
use function strpos;
@@ -10,12 +14,11 @@
final class XmlServiceMapFactory implements ServiceMapFactory
{
- /** @var string|null */
- 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
@@ -34,29 +37,42 @@ public function create(): ServiceMap
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;
+ }
+
+ $serviceTags = [];
+ foreach ($def->tag as $tag) {
+ $tagAttrs = ((array) $tag->attributes())['@attributes'] ?? [];
+ $tagName = $tagAttrs['name'];
+ unset($tagAttrs['name']);
+
+ $serviceTags[] = new ServiceTag($tagName, $tagAttrs);
+ }
- if ($service->getAlias() !== null) {
- $aliases[] = $service;
- } else {
- $services[$service->getId()] = $service;
+ $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) {
@@ -70,11 +86,18 @@ public function create(): ServiceMap
$services[$alias]->getClass(),
$service->isPublic(),
$service->isSynthetic(),
- $alias
+ $alias,
);
}
+ 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
index edf5574c..5c21f021 100644
--- a/src/Type/Symfony/ArgumentTypeSpecifyingExtension.php
+++ b/src/Type/Symfony/ArgumentTypeSpecifyingExtension.php
@@ -3,25 +3,23 @@
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 ArgumentTypeSpecifyingExtension implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension
{
- /** @var \PhpParser\PrettyPrinter\Standard */
- private $printer;
+ private Printer $printer;
- /** @var \PHPStan\Analyser\TypeSpecifier */
- private $typeSpecifier;
+ private TypeSpecifier $typeSpecifier;
- public function __construct(Standard $printer)
+ public function __construct(Printer $printer)
{
$this->printer = $printer;
}
@@ -38,14 +36,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/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 @@
+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 @@
+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
index 71e4fb17..1dae22e2 100644
--- a/src/Type/Symfony/Config/ArrayNodeDefinitionPrototypeDynamicReturnTypeExtension.php
+++ b/src/Type/Symfony/Config/ArrayNodeDefinitionPrototypeDynamicReturnTypeExtension.php
@@ -9,8 +9,9 @@
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Symfony\Config\ValueObject\ParentObjectType;
use PHPStan\Type\Type;
-use PHPStan\Type\TypeUtils;
use PHPStan\Type\VerbosityLevel;
+use function count;
+use function in_array;
final class ArrayNodeDefinitionPrototypeDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
@@ -53,14 +54,18 @@ public function getTypeFromMethodCall(
{
$calledOnType = $scope->getType($methodCall->var);
- $defaultType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
+ $defaultType = ParametersAcceptorSelector::selectFromArgs(
+ $scope,
+ $methodCall->getArgs(),
+ $methodReflection->getVariants(),
+ )->getReturnType();
if ($methodReflection->getName() === 'prototype') {
- if (!isset($methodCall->args[0])) {
+ if (!isset($methodCall->getArgs()[0])) {
return $defaultType;
}
- $argStrings = TypeUtils::getConstantStrings($scope->getType($methodCall->args[0]->value));
+ $argStrings = $scope->getType($methodCall->getArgs()[0]->value)->getConstantStrings();
if (count($argStrings) === 1 && isset(self::MAPPING[$argStrings[0]->getValue()])) {
$type = $argStrings[0]->getValue();
@@ -70,7 +75,7 @@ public function getTypeFromMethodCall(
return new ParentObjectType(
$defaultType->describe(VerbosityLevel::typeOnly()),
- $calledOnType
+ $calledOnType,
);
}
diff --git a/src/Type/Symfony/Config/PassParentObjectDynamicReturnTypeExtension.php b/src/Type/Symfony/Config/PassParentObjectDynamicReturnTypeExtension.php
index 5d19b60f..800d9dbc 100644
--- a/src/Type/Symfony/Config/PassParentObjectDynamicReturnTypeExtension.php
+++ b/src/Type/Symfony/Config/PassParentObjectDynamicReturnTypeExtension.php
@@ -10,18 +10,19 @@
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 string */
- private $className;
+ /** @var class-string */
+ private string $className;
/** @var string[] */
- private $methods;
+ private array $methods;
/**
- * @param string $className
+ * @param class-string $className
* @param string[] $methods
*/
public function __construct(string $className, array $methods)
@@ -48,7 +49,11 @@ public function getTypeFromMethodCall(
{
$calledOnType = $scope->getType($methodCall->var);
- $defaultType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
+ $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
index 8dd47a90..034d5d80 100644
--- a/src/Type/Symfony/Config/ReturnParentDynamicReturnTypeExtension.php
+++ b/src/Type/Symfony/Config/ReturnParentDynamicReturnTypeExtension.php
@@ -5,22 +5,22 @@
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 function in_array;
final class ReturnParentDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
- /** @var string */
- private $className;
+ /** @var class-string */
+ private string $className;
/** @var string[] */
- private $methods;
+ private array $methods;
/**
- * @param string $className
+ * @param class-string $className
* @param string[] $methods
*/
public function __construct(string $className, array $methods)
@@ -43,14 +43,14 @@ public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
- ): Type
+ ): ?Type
{
$calledOnType = $scope->getType($methodCall->var);
if ($calledOnType instanceof ParentObjectType) {
return $calledOnType->getParent();
}
- return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
+ return null;
}
}
diff --git a/src/Type/Symfony/Config/TreeBuilderDynamicReturnTypeExtension.php b/src/Type/Symfony/Config/TreeBuilderDynamicReturnTypeExtension.php
index 25cd12dc..4f266c50 100644
--- a/src/Type/Symfony/Config/TreeBuilderDynamicReturnTypeExtension.php
+++ b/src/Type/Symfony/Config/TreeBuilderDynamicReturnTypeExtension.php
@@ -6,10 +6,11 @@
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 PHPStan\Type\TypeUtils;
+use function count;
final class TreeBuilderDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
{
@@ -37,15 +38,15 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo
public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type
{
if (!$methodCall->class instanceof Name) {
- throw new \PHPStan\ShouldNotHappenException();
+ throw new ShouldNotHappenException();
}
$className = $scope->resolveName($methodCall->class);
$type = 'array';
- if (isset($methodCall->args[1])) {
- $argStrings = TypeUtils::getConstantStrings($scope->getType($methodCall->args[1]->value));
+ 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();
}
diff --git a/src/Type/Symfony/Config/TreeBuilderGetRootNodeDynamicReturnTypeExtension.php b/src/Type/Symfony/Config/TreeBuilderGetRootNodeDynamicReturnTypeExtension.php
index 4b7c1b3a..2be2b574 100644
--- a/src/Type/Symfony/Config/TreeBuilderGetRootNodeDynamicReturnTypeExtension.php
+++ b/src/Type/Symfony/Config/TreeBuilderGetRootNodeDynamicReturnTypeExtension.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\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Symfony\Config\ValueObject\ParentObjectType;
use PHPStan\Type\Symfony\Config\ValueObject\TreeBuilderType;
@@ -28,20 +27,17 @@ public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
- ): Type
+ ): ?Type
{
$calledOnType = $scope->getType($methodCall->var);
-
- $defaultType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
-
if ($calledOnType instanceof TreeBuilderType) {
return new ParentObjectType(
$calledOnType->getRootNodeClassName(),
- $calledOnType
+ $calledOnType,
);
}
- return $defaultType;
+ return null;
}
}
diff --git a/src/Type/Symfony/Config/ValueObject/ParentObjectType.php b/src/Type/Symfony/Config/ValueObject/ParentObjectType.php
index 56aec24f..19baf926 100644
--- a/src/Type/Symfony/Config/ValueObject/ParentObjectType.php
+++ b/src/Type/Symfony/Config/ValueObject/ParentObjectType.php
@@ -9,8 +9,7 @@
class ParentObjectType extends ObjectType
{
- /** @var Type */
- private $parent;
+ private Type $parent;
public function __construct(string $className, Type $parent)
{
diff --git a/src/Type/Symfony/Config/ValueObject/TreeBuilderType.php b/src/Type/Symfony/Config/ValueObject/TreeBuilderType.php
index 71bc4fc2..93c713f1 100644
--- a/src/Type/Symfony/Config/ValueObject/TreeBuilderType.php
+++ b/src/Type/Symfony/Config/ValueObject/TreeBuilderType.php
@@ -7,8 +7,7 @@
class TreeBuilderType extends ObjectType
{
- /** @var string */
- private $rootNodeClassName;
+ private string $rootNodeClassName;
public function __construct(string $className, string $rootNodeClassName)
{
diff --git a/src/Type/Symfony/EnvelopeReturnTypeExtension.php b/src/Type/Symfony/EnvelopeReturnTypeExtension.php
index bf0712fc..06e08772 100644
--- a/src/Type/Symfony/EnvelopeReturnTypeExtension.php
+++ b/src/Type/Symfony/EnvelopeReturnTypeExtension.php
@@ -5,12 +5,15 @@
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\Constant\ConstantStringType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
-use PHPStan\Type\MixedType;
+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
{
@@ -31,16 +34,24 @@ public function getTypeFromMethodCall(
Scope $scope
): Type
{
- if (count($methodCall->args) === 0) {
- return new ArrayType(new MixedType(), new ArrayType(new MixedType(), new ObjectType('Symfony\Component\Messenger\Stamp\StampInterface')));
+ 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->args[0]->value);
- if (!$argType instanceof ConstantStringType) {
- return new ArrayType(new MixedType(), new ObjectType('Symfony\Component\Messenger\Stamp\StampInterface'));
+ $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());
}
- return new ArrayType(new MixedType(), new ObjectType($argType->getValue()));
+ $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 @@
+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 @@
+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 @@
+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
index 51e5ff19..75e6d0bc 100644
--- a/src/Type/Symfony/InputBagDynamicReturnTypeExtension.php
+++ b/src/Type/Symfony/InputBagDynamicReturnTypeExtension.php
@@ -8,12 +8,17 @@
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
{
@@ -32,7 +37,7 @@ public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
- ): Type
+ ): ?Type
{
if ($methodReflection->getName() === 'get') {
return $this->getGetTypeFromMethodCall($methodReflection, $methodCall, $scope);
@@ -49,30 +54,32 @@ private function getGetTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
- ): Type
+ ): ?Type
{
- if (isset($methodCall->args[1])) {
- $argType = $scope->getType($methodCall->args[1]->value);
+ if (isset($methodCall->getArgs()[1])) {
+ $argType = $scope->getType($methodCall->getArgs()[1]->value);
$isNull = (new NullType())->isSuperTypeOf($argType);
- $isString = (new StringType())->isSuperTypeOf($argType);
- $compare = $isNull->compareTo($isString);
- if ($compare === $isString) {
- return new StringType();
+ if ($isNull->no()) {
+ return TypeCombinator::removeNull(ParametersAcceptorSelector::selectFromArgs(
+ $scope,
+ $methodCall->getArgs(),
+ $methodReflection->getVariants(),
+ )->getReturnType());
}
}
- return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
+ return null;
}
private function getAllTypeFromMethodCall(
MethodCall $methodCall
): Type
{
- if (isset($methodCall->args[0])) {
- return new ArrayType(new MixedType(), new StringType());
+ if (isset($methodCall->getArgs()[0])) {
+ return new ArrayType(new MixedType(), new MixedType(true));
}
- return new ArrayType(new StringType(), new UnionType([new StringType(), new ArrayType(new MixedType(), new StringType())]));
+ 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 @@
+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
index 24cccc40..88bd7b0e 100644
--- a/src/Type/Symfony/InputInterfaceGetArgumentDynamicReturnTypeExtension.php
+++ b/src/Type/Symfony/InputInterfaceGetArgumentDynamicReturnTypeExtension.php
@@ -6,22 +6,21 @@
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
-use PHPStan\Reflection\ParametersAcceptorSelector;
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 PHPStan\Type\TypeUtils;
use function count;
+use function in_array;
final class InputInterfaceGetArgumentDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
- /** @var \PHPStan\Symfony\ConsoleApplicationResolver */
- private $consoleApplicationResolver;
+ private ConsoleApplicationResolver $consoleApplicationResolver;
public function __construct(ConsoleApplicationResolver $consoleApplicationResolver)
{
@@ -38,26 +37,25 @@ public function isMethodSupported(MethodReflection $methodReflection): bool
return $methodReflection->getName() === 'getArgument';
}
- public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
+ public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
{
- $defaultReturnType = ParametersAcceptorSelector::selectFromArgs($scope, $methodCall->args, $methodReflection->getVariants())->getReturnType();
-
- if (!isset($methodCall->args[0])) {
- return $defaultReturnType;
+ if (!isset($methodCall->getArgs()[0])) {
+ return null;
}
$classReflection = $scope->getClassReflection();
if ($classReflection === null) {
- return $defaultReturnType;
+ return null;
}
- $argStrings = TypeUtils::getConstantStrings($scope->getType($methodCall->args[0]->value));
+ $argStrings = $scope->getType($methodCall->getArgs()[0]->value)->getConstantStrings();
if (count($argStrings) !== 1) {
- return $defaultReturnType;
+ return null;
}
$argName = $argStrings[0]->getValue();
$argTypes = [];
+ $canBeNullInInteract = false;
foreach ($this->consoleApplicationResolver->findCommands($classReflection) as $command) {
try {
$command->mergeApplicationDefinition();
@@ -71,6 +69,8 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
$argType = new StringType();
if (!$argument->isRequired()) {
$argType = TypeCombinator::union($argType, $scope->getTypeFromValue($argument->getDefault()));
+ } else {
+ $canBeNullInInteract = true;
}
}
$argTypes[] = $argType;
@@ -79,7 +79,21 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
}
}
- return count($argTypes) > 0 ? TypeCombinator::union(...$argTypes) : $defaultReturnType;
+ 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
index 40a3993f..6d0346cf 100644
--- a/src/Type/Symfony/InputInterfaceGetOptionDynamicReturnTypeExtension.php
+++ b/src/Type/Symfony/InputInterfaceGetOptionDynamicReturnTypeExtension.php
@@ -6,28 +6,23 @@
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
-use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Symfony\ConsoleApplicationResolver;
-use PHPStan\Type\ArrayType;
-use PHPStan\Type\BooleanType;
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 PHPStan\Type\TypeUtils;
use function count;
final class InputInterfaceGetOptionDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
- /** @var \PHPStan\Symfony\ConsoleApplicationResolver */
- private $consoleApplicationResolver;
+ private ConsoleApplicationResolver $consoleApplicationResolver;
- public function __construct(ConsoleApplicationResolver $consoleApplicationResolver)
+ private GetOptionTypeHelper $getOptionTypeHelper;
+
+ public function __construct(ConsoleApplicationResolver $consoleApplicationResolver, GetOptionTypeHelper $getOptionTypeHelper)
{
$this->consoleApplicationResolver = $consoleApplicationResolver;
+ $this->getOptionTypeHelper = $getOptionTypeHelper;
}
public function getClass(): string
@@ -40,22 +35,20 @@ public function isMethodSupported(MethodReflection $methodReflection): bool
return $methodReflection->getName() === 'getOption';
}
- public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
+ public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
{
- $defaultReturnType = ParametersAcceptorSelector::selectFromArgs($scope, $methodCall->args, $methodReflection->getVariants())->getReturnType();
-
- if (!isset($methodCall->args[0])) {
- return $defaultReturnType;
+ if (!isset($methodCall->getArgs()[0])) {
+ return null;
}
$classReflection = $scope->getClassReflection();
if ($classReflection === null) {
- return $defaultReturnType;
+ return null;
}
- $optStrings = TypeUtils::getConstantStrings($scope->getType($methodCall->args[0]->value));
+ $optStrings = $scope->getType($methodCall->getArgs()[0]->value)->getConstantStrings();
if (count($optStrings) !== 1) {
- return $defaultReturnType;
+ return null;
}
$optName = $optStrings[0]->getValue();
@@ -64,25 +57,13 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
try {
$command->mergeApplicationDefinition();
$option = $command->getDefinition()->getOption($optName);
- if (!$option->acceptValue()) {
- $optType = new BooleanType();
- } else {
- $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);
- }
- $optType = TypeCombinator::union($optType, $scope->getTypeFromValue($option->getDefault()));
- }
- $optTypes[] = $optType;
+ $optTypes[] = $this->getOptionTypeHelper->getOptionType($scope, $option);
} catch (InvalidArgumentException $e) {
// noop
}
}
- return count($optTypes) > 0 ? TypeCombinator::union(...$optTypes) : $defaultReturnType;
+ 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 @@
+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
index f69908fe..34bffcea 100644
--- a/src/Type/Symfony/InputInterfaceHasArgumentDynamicReturnTypeExtension.php
+++ b/src/Type/Symfony/InputInterfaceHasArgumentDynamicReturnTypeExtension.php
@@ -7,19 +7,17 @@
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Symfony\ConsoleApplicationResolver;
-use PHPStan\Type\BooleanType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Type;
-use PHPStan\Type\TypeUtils;
use function array_unique;
use function count;
+use function in_array;
final class InputInterfaceHasArgumentDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
- /** @var \PHPStan\Symfony\ConsoleApplicationResolver */
- private $consoleApplicationResolver;
+ private ConsoleApplicationResolver $consoleApplicationResolver;
public function __construct(ConsoleApplicationResolver $consoleApplicationResolver)
{
@@ -36,25 +34,34 @@ public function isMethodSupported(MethodReflection $methodReflection): bool
return $methodReflection->getName() === 'hasArgument';
}
- public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
+ public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
{
- $defaultReturnType = new BooleanType();
-
- if (!isset($methodCall->args[0])) {
- return $defaultReturnType;
+ if (!isset($methodCall->getArgs()[0])) {
+ return null;
}
$classReflection = $scope->getClassReflection();
if ($classReflection === null) {
- return $defaultReturnType;
+ return null;
}
- $argStrings = TypeUtils::getConstantStrings($scope->getType($methodCall->args[0]->value));
+ $argStrings = $scope->getType($methodCall->getArgs()[0]->value)->getConstantStrings();
if (count($argStrings) !== 1) {
- return $defaultReturnType;
+ 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 {
@@ -67,11 +74,11 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
}
if (count($returnTypes) === 0) {
- return $defaultReturnType;
+ return null;
}
$returnTypes = array_unique($returnTypes);
- return count($returnTypes) === 1 ? new ConstantBooleanType($returnTypes[0]) : $defaultReturnType;
+ return count($returnTypes) === 1 ? new ConstantBooleanType($returnTypes[0]) : null;
}
}
diff --git a/src/Type/Symfony/InputInterfaceHasOptionDynamicReturnTypeExtension.php b/src/Type/Symfony/InputInterfaceHasOptionDynamicReturnTypeExtension.php
index c60e2644..e4f8b5b1 100644
--- a/src/Type/Symfony/InputInterfaceHasOptionDynamicReturnTypeExtension.php
+++ b/src/Type/Symfony/InputInterfaceHasOptionDynamicReturnTypeExtension.php
@@ -7,19 +7,16 @@
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Symfony\ConsoleApplicationResolver;
-use PHPStan\Type\BooleanType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Type;
-use PHPStan\Type\TypeUtils;
use function array_unique;
use function count;
final class InputInterfaceHasOptionDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
- /** @var \PHPStan\Symfony\ConsoleApplicationResolver */
- private $consoleApplicationResolver;
+ private ConsoleApplicationResolver $consoleApplicationResolver;
public function __construct(ConsoleApplicationResolver $consoleApplicationResolver)
{
@@ -36,22 +33,20 @@ public function isMethodSupported(MethodReflection $methodReflection): bool
return $methodReflection->getName() === 'hasOption';
}
- public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
+ public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
{
- $defaultReturnType = new BooleanType();
-
- if (!isset($methodCall->args[0])) {
- return $defaultReturnType;
+ if (!isset($methodCall->getArgs()[0])) {
+ return null;
}
$classReflection = $scope->getClassReflection();
if ($classReflection === null) {
- return $defaultReturnType;
+ return null;
}
- $optStrings = TypeUtils::getConstantStrings($scope->getType($methodCall->args[0]->value));
+ $optStrings = $scope->getType($methodCall->getArgs()[0]->value)->getConstantStrings();
if (count($optStrings) !== 1) {
- return $defaultReturnType;
+ return null;
}
$optName = $optStrings[0]->getValue();
@@ -67,11 +62,11 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
}
if (count($returnTypes) === 0) {
- return $defaultReturnType;
+ return null;
}
$returnTypes = array_unique($returnTypes);
- return count($returnTypes) === 1 ? new ConstantBooleanType($returnTypes[0]) : $defaultReturnType;
+ return count($returnTypes) === 1 ? new ConstantBooleanType($returnTypes[0]) : null;
}
}
diff --git a/src/Type/Symfony/KernelInterfaceDynamicReturnTypeExtension.php b/src/Type/Symfony/KernelInterfaceDynamicReturnTypeExtension.php
index f74f114e..810de2f6 100644
--- a/src/Type/Symfony/KernelInterfaceDynamicReturnTypeExtension.php
+++ b/src/Type/Symfony/KernelInterfaceDynamicReturnTypeExtension.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;
@@ -30,11 +29,11 @@ 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) {
@@ -44,7 +43,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/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 @@
+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
index aaff84c0..8cd9dbd5 100644
--- a/src/Type/Symfony/OptionTypeSpecifyingExtension.php
+++ b/src/Type/Symfony/OptionTypeSpecifyingExtension.php
@@ -3,25 +3,23 @@
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 OptionTypeSpecifyingExtension implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension
{
- /** @var \PhpParser\PrettyPrinter\Standard */
- private $printer;
+ private Printer $printer;
- /** @var \PHPStan\Analyser\TypeSpecifier */
- private $typeSpecifier;
+ private TypeSpecifier $typeSpecifier;
- public function __construct(Standard $printer)
+ public function __construct(Printer $printer)
{
$this->printer = $printer;
}
@@ -38,14 +36,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/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 @@
+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|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
index 295d37e4..40d38493 100644
--- a/src/Type/Symfony/RequestTypeSpecifyingExtension.php
+++ b/src/Type/Symfony/RequestTypeSpecifyingExtension.php
@@ -8,7 +8,6 @@
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierAwareExtension;
use PHPStan\Analyser\TypeSpecifierContext;
-use PHPStan\Broker\Broker;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\MethodTypeSpecifyingExtension;
@@ -21,16 +20,7 @@ final class RequestTypeSpecifyingExtension implements MethodTypeSpecifyingExtens
private const HAS_METHOD_NAME = 'hasSession';
private const GET_METHOD_NAME = 'getSession';
- /** @var Broker */
- private $broker;
-
- /** @var TypeSpecifier */
- private $typeSpecifier;
-
- public function __construct(Broker $broker)
- {
- $this->broker = $broker;
- }
+ private TypeSpecifier $typeSpecifier;
public function getClass(): string
{
@@ -44,13 +34,18 @@ public function isMethodSupported(MethodReflection $methodReflection, MethodCall
public function specifyTypes(MethodReflection $methodReflection, MethodCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
- $classReflection = $this->broker->getClass(self::REQUEST_CLASS);
- $methodVariants = $classReflection->getNativeMethod(self::GET_METHOD_NAME)->getVariants();
+ $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(ParametersAcceptorSelector::selectSingle($methodVariants)->getReturnType()),
- $context
+ TypeCombinator::removeNull($returnType),
+ $context,
+ $scope,
);
}
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 @@
+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/SerializerDynamicReturnTypeExtension.php b/src/Type/Symfony/SerializerDynamicReturnTypeExtension.php
index c6c237ee..84f256e4 100755
--- a/src/Type/Symfony/SerializerDynamicReturnTypeExtension.php
+++ b/src/Type/Symfony/SerializerDynamicReturnTypeExtension.php
@@ -6,21 +6,25 @@
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 SerializerDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
- /** @var string */
- private $class;
+ /** @var class-string */
+ private string $class;
- /** @var string */
- private $method;
+ private string $method;
+ /**
+ * @param class-string $class
+ */
public function __construct(string $class, string $method)
{
$this->class = $class;
@@ -39,18 +43,21 @@ public function isMethodSupported(MethodReflection $methodReflection): bool
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
{
- if (!isset($methodCall->args[1])) {
+ if (!isset($methodCall->getArgs()[1])) {
return new MixedType();
}
- $argType = $scope->getType($methodCall->args[1]->value);
- if (!$argType instanceof ConstantStringType) {
+ $argType = $scope->getType($methodCall->getArgs()[1]->value);
+ if (count($argType->getConstantStrings()) === 0) {
return new MixedType();
}
- $objectName = $argType->getValue();
+ $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 6b2f6f84..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 $serviceMap;
+ 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->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 = $this->serviceMap::getServiceIdFromNode($methodCall->args[0]->value, $scope);
+ $serviceId = $this->serviceMap::getServiceIdFromNode($methodCall->getArgs()[0]->value, $scope);
if ($serviceId !== null) {
$service = $this->serviceMap->getService($serviceId);
- if ($service !== null && !$service->isSynthetic()) {
- return new ObjectType($service->getClass() ?? $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 = $this->serviceMap::getServiceIdFromNode($methodCall->args[0]->value, $scope);
+ $serviceId = $this->serviceMap::getServiceIdFromNode($methodCall->getArgs()[0]->value, $scope);
if ($serviceId !== null) {
$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 @@
+
+ * @template TData
+ *
+ * @param class-string $type
+ * @param TData $data
+ * @param array $options
+ *
+ * @phpstan-return ($data is null ? FormInterface : FormInterface)
+ */
+ protected function createForm(string $type, $data = null, array $options = []): FormInterface
+ {
+ }
+}
diff --git a/stubs/Symfony/Bundle/FrameworkBundle/KernelBrowser.stub b/stubs/Symfony/Bundle/FrameworkBundle/KernelBrowser.stub
new file mode 100644
index 00000000..ec54f1eb
--- /dev/null
+++ b/stubs/Symfony/Bundle/FrameworkBundle/KernelBrowser.stub
@@ -0,0 +1,13 @@
+ $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 @@
+ $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 @@
+ $messages
+ * @param int-mask-of $options
+ */
+ public function write($messages, bool $newline = false, int $options = 0): void;
+
+ /**
+ * @param string|iterable $messages
+ * @param int-mask-of $options
+ */
+ public function writeln($messages, int $options = 0): void;
+}
diff --git a/stubs/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.stub b/stubs/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.stub
index d0b04792..b1229723 100644
--- a/stubs/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.stub
+++ b/stubs/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.stub
@@ -8,6 +8,8 @@ interface ExtensionInterface
{
/**
* @param array $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 @@
+|array|array>>
+ * @return array>
*/
public static function getSubscribedEvents();
}
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 @@
+
+ */
+abstract class AbstractType implements FormTypeInterface
+{
+
+ /**
+ * @param FormBuilderInterface $builder
+ * @param array $options
+ */
+ public function buildForm(FormBuilderInterface $builder, array $options): void;
+
+ /**
+ * @param FormInterface $form
+ * @param array $options
+ */
+ public function buildView(FormView $view, FormInterface $form, array $options): void;
+
+ /**
+ * @param FormInterface $form
+ * @param array $options
+ */
+ public function finishView(FormView $view, FormInterface $form, array $options): void;
+
+}
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 @@
+
+ * @template TData
+ *
+ * @extends \Traversable>
+ * @extends FormConfigBuilderInterface
*/
-interface FormBuilderInterface extends \Traversable
+interface FormBuilderInterface extends \Traversable, \Countable, FormConfigBuilderInterface
{
+ /**
+ * @return FormInterface
+ */
+ 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 @@
+
+ */
+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 @@
+
+ * @template TData
+ *
+ * @param class-string $type
+ * @param TData $data
+ * @param array $options
+ *
+ * @phpstan-return ($data is null ? FormInterface : FormInterface)
+ *
+ * @throws \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ */
+ public function create(string $type = FormType::class, $data = null, array $options = []): FormInterface;
+
+ /**
+ * @template TFormType of FormTypeInterface
+ * @template TData
+ *
+ * @param class-string $type
+ * @param TData $data
+ * @param array $options
+ *
+ * @phpstan-return ($data is null ? FormInterface : FormInterface)
+ *
+ * @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
index 868c4ae2..4bd21229 100644
--- a/stubs/Symfony/Component/Form/FormInterface.stub
+++ b/stubs/Symfony/Component/Form/FormInterface.stub
@@ -3,9 +3,22 @@
namespace Symfony\Component\Form;
/**
- * @extends \Traversable
+ * @template TData
+ *
+ * @extends \ArrayAccess>
+ * @extends \Traversable>
*/
-interface FormInterface extends \Traversable
+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
index 25b6f25d..a03d5e1c 100644
--- a/stubs/Symfony/Component/Form/FormTypeExtensionInterface.stub
+++ b/stubs/Symfony/Component/Form/FormTypeExtensionInterface.stub
@@ -2,19 +2,25 @@
namespace Symfony\Component\Form;
+/**
+ * @template TData
+ */
interface FormTypeExtensionInterface
{
/**
+ * @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options): void;
/**
+ * @phpstan-param FormInterface $form
* @param array $options
*/
public function buildView(FormView $view, FormInterface $form, array $options): void;
/**
+ * @phpstan-param FormInterface $form
* @param array $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
index cebbc1c2..8536656a 100644
--- a/stubs/Symfony/Component/Form/FormTypeInterface.stub
+++ b/stubs/Symfony/Component/Form/FormTypeInterface.stub
@@ -2,19 +2,25 @@
namespace Symfony\Component\Form;
+/**
+ * @template TData
+ */
interface FormTypeInterface
{
/**
+ * @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options): void;
/**
+ * @param FormInterface $form
* @param array $options
*/
public function buildView(FormView $view, FormInterface $form, array $options): void;
/**
+ * @param FormInterface $form
* @param array $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
index e7646acf..08c64752 100644
--- a/stubs/Symfony/Component/Form/FormView.stub
+++ b/stubs/Symfony/Component/Form/FormView.stub
@@ -6,9 +6,17 @@ use ArrayAccess;
use IteratorAggregate;
/**
- * @implements IteratorAggregate
- * @implements ArrayAccess
+ * @implements IteratorAggregate
+ * @implements ArrayAccess
*/
class FormView implements ArrayAccess, IteratorAggregate
{
+
+ /**
+ * Returns an iterator to iterate over children (implements \IteratorAggregate).
+ *
+ * @return \ArrayIterator The iterator
+ */
+ public function getIterator();
+
}
diff --git a/stubs/Symfony/Component/HttpFoundation/Cookie.stub b/stubs/Symfony/Component/HttpFoundation/Cookie.stub
index 40ee45ab..cfb45fa3 100644
--- a/stubs/Symfony/Component/HttpFoundation/Cookie.stub
+++ b/stubs/Symfony/Component/HttpFoundation/Cookie.stub
@@ -21,7 +21,7 @@ class Cookie
*
* @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);
+ 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
@@ -36,7 +36,7 @@ class Cookie
*
* @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;
+ 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
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 @@
+
+ */
+ 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 @@
+
+ */
+ public $request;
+
+ /**
+ * Query string parameters ($_GET).
+ *
+ * @var InputBag
+ */
+ public $query;
+
+ /**
+ * Cookies ($_COOKIE).
+ *
+ * @var InputBag
+ */
+ 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/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 @@
+ $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 @@
+, value-of>
+ */
+interface Options extends \ArrayAccess, \Countable
+{
+ /**
+ * @param key-of $offset
+ *
+ * @return bool
+ */
+ public function offsetExists($offset);
+
+ /**
+ * @template TOffset of key-of
+ * @param TOffset $offset
+ * @return TArray[TOffset]
+ */
+ public function offsetGet($offset);
+
+ /**
+ * @template TOffset of key-of
+ * @param TOffset|null $offset
+ * @param TArray[TOffset] $value
+ *
+ * @return void
+ */
+ public function offsetSet($offset, $value);
+
+ /**
+ * @template TOffset of key-of
+ * @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 @@
+
*/
class Process implements \IteratorAggregate
{
+ /**
+ * @param int $flags
+ *
+ * @return \Generator
+ *
+ * @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 @@
+
+ * @phpstan-param T &$objectOrArray
+ * @phpstan-param-out ($objectOrArray is object ? T : array) $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/Security/Acl/Model/AclInterface.stub b/stubs/Symfony/Component/Security/Acl/Model/AclInterface.stub
index b2ce3e58..2f509501 100644
--- a/stubs/Symfony/Component/Security/Acl/Model/AclInterface.stub
+++ b/stubs/Symfony/Component/Security/Acl/Model/AclInterface.stub
@@ -5,4 +5,36 @@ namespace Symfony\Component\Security\Acl\Model;
interface AclInterface
{
+ /**
+ * Returns all class-based ACEs associated with this ACL.
+ *
+ * @return array
+ */
+ public function getClassAces();
+
+ /**
+ * Returns all class-field-based ACEs associated with this ACL.
+ *
+ * @param string $field
+ *
+ * @return array
+ */
+ public function getClassFieldAces($field);
+
+ /**
+ * Returns all object-based ACEs associated with this ACL.
+ *
+ * @return array
+ */
+ public function getObjectAces();
+
+ /**
+ * Returns all object-field-based ACEs associated with this ACL.
+ *
+ * @param string $field
+ *
+ * @return array
+ */
+ public function getObjectFieldAces($field);
+
}
diff --git a/stubs/Symfony/Component/Security/Acl/Model/AclProviderInterface.stub b/stubs/Symfony/Component/Security/Acl/Model/AclProviderInterface.stub
deleted file mode 100644
index 97cb6880..00000000
--- a/stubs/Symfony/Component/Security/Acl/Model/AclProviderInterface.stub
+++ /dev/null
@@ -1,19 +0,0 @@
- $sids
- * @phpstan-return AclInterface
- */
- public function findAcl(ObjectIdentityInterface $oid, array $sids = []);
-
- /**
- * @phpstan-param array $oids
- * @phpstan-param array $sids
- * @phpstan-return \SplObjectStorage
- */
- public function findAcls(array $oids, array $sids = []);
-}
diff --git a/stubs/Symfony/Component/Security/Acl/Model/ObjectIdentityInterface.stub b/stubs/Symfony/Component/Security/Acl/Model/EntryInterface.stub
similarity index 63%
rename from stubs/Symfony/Component/Security/Acl/Model/ObjectIdentityInterface.stub
rename to stubs/Symfony/Component/Security/Acl/Model/EntryInterface.stub
index 8c03dabc..335e581d 100644
--- a/stubs/Symfony/Component/Security/Acl/Model/ObjectIdentityInterface.stub
+++ b/stubs/Symfony/Component/Security/Acl/Model/EntryInterface.stub
@@ -2,7 +2,6 @@
namespace Symfony\Component\Security\Acl\Model;
-interface ObjectIdentityInterface
+interface EntryInterface
{
-
}
diff --git a/stubs/Symfony/Component/Security/Acl/Model/MutableAclInterface.stub b/stubs/Symfony/Component/Security/Acl/Model/MutableAclInterface.stub
deleted file mode 100644
index 75df80b3..00000000
--- a/stubs/Symfony/Component/Security/Acl/Model/MutableAclInterface.stub
+++ /dev/null
@@ -1,8 +0,0 @@
- $sids
- * @phpstan-return MutableAclInterface
- */
- public function findAcl(ObjectIdentityInterface $oid, array $sids = []);
-
- /**
- * @phpstan-param array $oids
- * @phpstan-param array $sids
- * @phpstan-return \SplObjectStorage
- */
- public function findAcls(array $oids, array $sids = []);
-}
diff --git a/stubs/Symfony/Component/Security/Acl/Model/SecurityIdentityInterface.stub b/stubs/Symfony/Component/Security/Acl/Model/SecurityIdentityInterface.stub
deleted file mode 100644
index d60731dc..00000000
--- a/stubs/Symfony/Component/Security/Acl/Model/SecurityIdentityInterface.stub
+++ /dev/null
@@ -1,8 +0,0 @@
- $context
* @return mixed
+ *
+ * @throws UnexpectedValueException
*/
public function decode($data, $format, array $context = []);
/**
* @param string $format Format name
+ * @param array $context
* @return bool
*/
- public function supportsDecoding($format);
+ public function supportsDecoding($format, array $context = []);
}
diff --git a/stubs/Symfony/Component/Serializer/Encoder/EncoderInterface.stub b/stubs/Symfony/Component/Serializer/Encoder/EncoderInterface.stub
index eb6fc97c..11e374eb 100644
--- a/stubs/Symfony/Component/Serializer/Encoder/EncoderInterface.stub
+++ b/stubs/Symfony/Component/Serializer/Encoder/EncoderInterface.stub
@@ -2,6 +2,8 @@
namespace Symfony\Component\Serializer\Encoder;
+use Symfony\Component\Serializer\Exception\UnexpectedValueException;
+
interface EncoderInterface
{
/**
@@ -9,12 +11,15 @@ interface EncoderInterface
* @param string $format
* @param array $context
* @return string
+ *
+ * @throws UnexpectedValueException
*/
public function encode($data, $format, array $context = []);
/**
* @param string $format Format name
+ * @param array $context
* @return bool
*/
- public function supportsEncoding($format);
+ 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 @@
+ $context
*
- * @return object|object[]
+ * @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
index 83c6ddff..b7e9968b 100644
--- a/stubs/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.stub
+++ b/stubs/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.stub
@@ -2,6 +2,14 @@
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
{
/**
@@ -9,7 +17,15 @@ interface DenormalizerInterface
* @param string $type
* @param string|null $format
* @param array $context
- * @return object|array
+ * @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 = []);
@@ -17,7 +33,8 @@ interface DenormalizerInterface
* @param mixed $data
* @param string $type
* @param string|null $format
+ * @param array $context
* @return bool
*/
- public function supportsDenormalization($data, $type, $format = null);
+ public function supportsDenormalization($data, $type, $format = null, array $context = []);
}
diff --git a/stubs/Symfony/Component/Serializer/Normalizer/NormalizerInterface.stub b/stubs/Symfony/Component/Serializer/Normalizer/NormalizerInterface.stub
index a65b5bed..ba86b6b6 100644
--- a/stubs/Symfony/Component/Serializer/Normalizer/NormalizerInterface.stub
+++ b/stubs/Symfony/Component/Serializer/Normalizer/NormalizerInterface.stub
@@ -2,20 +2,34 @@
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 $context
- * @return array|string|int|float|bool|null
+ *
+ * @return array|ArrayObject|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 $context
+ *
* @return bool
*/
- public function supportsNormalization($data, $format = null);
+ public function supportsNormalization($data, $format = null, array $context = []);
}
diff --git a/stubs/Symfony/Component/Validator/Constraint.stub b/stubs/Symfony/Component/Validator/Constraint.stub
index 7d875f2a..e7a4b501 100644
--- a/stubs/Symfony/Component/Validator/Constraint.stub
+++ b/stubs/Symfony/Component/Validator/Constraint.stub
@@ -4,6 +4,11 @@ namespace Symfony\Component\Validator;
class Constraint
{
+ /**
+ * @var array
+ */
+ protected static $errorNames = [];
+
/**
* @return array
*/
diff --git a/stubs/Symfony/Component/Validator/ConstraintViolationInterface.stub b/stubs/Symfony/Component/Validator/ConstraintViolationInterface.stub
index ef3312bb..fd1c7b9b 100644
--- a/stubs/Symfony/Component/Validator/ConstraintViolationInterface.stub
+++ b/stubs/Symfony/Component/Validator/ConstraintViolationInterface.stub
@@ -2,6 +2,11 @@
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
index 423bdfb0..00a2a9d5 100644
--- a/stubs/Symfony/Component/Validator/ConstraintViolationListInterface.stub
+++ b/stubs/Symfony/Component/Validator/ConstraintViolationListInterface.stub
@@ -3,8 +3,11 @@
namespace Symfony\Component\Validator;
/**
+ * @extends \ArrayAccess
* @extends \Traversable
+ *
+ * @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
+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 @@
+ $options
+ * @return array
+ */
+ 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 @@
+|callable(\Symfony\Contracts\Cache\ItemInterface, bool): T $callback
+ * @param array $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 @@
+
+ */
+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
index 6aa4ae47..bbecb2e8 100644
--- a/tests/Rules/Symfony/ContainerInterfacePrivateServiceRuleFakeTest.php
+++ b/tests/Rules/Symfony/ContainerInterfacePrivateServiceRuleFakeTest.php
@@ -5,6 +5,8 @@
use PHPStan\Rules\Rule;
use PHPStan\Symfony\XmlServiceMapFactory;
use PHPStan\Testing\RuleTestCase;
+use function class_exists;
+use function interface_exists;
/**
* @extends RuleTestCase
@@ -19,21 +21,27 @@ protected function getRule(): Rule
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',
],
- []
+ [],
);
}
@@ -43,13 +51,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',
],
- []
+ [],
);
}
@@ -59,13 +71,17 @@ 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',
],
- []
+ [],
);
}
diff --git a/tests/Rules/Symfony/ContainerInterfacePrivateServiceRuleTest.php b/tests/Rules/Symfony/ContainerInterfacePrivateServiceRuleTest.php
index 3704818e..dfa3d2b7 100644
--- a/tests/Rules/Symfony/ContainerInterfacePrivateServiceRuleTest.php
+++ b/tests/Rules/Symfony/ContainerInterfacePrivateServiceRuleTest.php
@@ -5,6 +5,8 @@
use PHPStan\Rules\Rule;
use PHPStan\Symfony\XmlServiceMapFactory;
use PHPStan\Testing\RuleTestCase;
+use function class_exists;
+use function interface_exists;
/**
* @extends RuleTestCase
@@ -19,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',
@@ -26,9 +31,9 @@ public function testGetPrivateService(): void
[
[
'Service "private" is private.',
- 12,
+ 13,
],
- ]
+ ],
);
}
@@ -38,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',
],
- []
+ [],
);
}
@@ -54,13 +63,17 @@ 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',
],
- []
+ [],
);
}
diff --git a/tests/Rules/Symfony/ContainerInterfaceUnknownServiceRuleFakeTest.php b/tests/Rules/Symfony/ContainerInterfaceUnknownServiceRuleFakeTest.php
index 87404430..8d70f1c3 100644
--- a/tests/Rules/Symfony/ContainerInterfaceUnknownServiceRuleFakeTest.php
+++ b/tests/Rules/Symfony/ContainerInterfaceUnknownServiceRuleFakeTest.php
@@ -2,12 +2,14 @@
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\MethodTypeSpecifyingExtension;
use PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension;
-use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use function class_exists;
/**
* @extends RuleTestCase
@@ -17,36 +19,43 @@ final class ContainerInterfaceUnknownServiceRuleFakeTest extends RuleTestCase
protected function getRule(): Rule
{
- return new ContainerInterfaceUnknownServiceRule((new XmlServiceMapFactory(null))->create(), new Standard());
+ return new ContainerInterfaceUnknownServiceRule((new XmlServiceMapFactory(null))->create(), self::getContainer()->getByType(Printer::class));
}
/**
- * @return \PHPStan\Type\MethodTypeSpecifyingExtension[]
+ * @return MethodTypeSpecifyingExtension[]
*/
protected function getMethodTypeSpecifyingExtensions(): array
{
return [
- new ServiceTypeSpecifyingExtension(Controller::class, new Standard()),
+ 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 776812af..c975750f 100644
--- a/tests/Rules/Symfony/ContainerInterfaceUnknownServiceRuleTest.php
+++ b/tests/Rules/Symfony/ContainerInterfaceUnknownServiceRuleTest.php
@@ -2,13 +2,12 @@
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\AbstractController;
-use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use function class_exists;
+use function interface_exists;
/**
* @extends RuleTestCase
@@ -18,22 +17,14 @@ final class ContainerInterfaceUnknownServiceRuleTest extends RuleTestCase
protected function getRule(): Rule
{
- return new ContainerInterfaceUnknownServiceRule((new XmlServiceMapFactory(__DIR__ . '/container.xml'))->create(), new Standard());
- }
-
- /**
- * @return \PHPStan\Type\MethodTypeSpecifyingExtension[]
- */
- protected function getMethodTypeSpecifyingExtensions(): array
- {
- return [
- new ServiceTypeSpecifyingExtension(Controller::class, new Standard()),
- new ServiceTypeSpecifyingExtension(AbstractController::class, new Standard()),
- ];
+ return new ContainerInterfaceUnknownServiceRule((new XmlServiceMapFactory(__DIR__ . '/container.xml'))->create(), self::getContainer()->getByType(Printer::class));
}
public function testGetPrivateService(): void
{
+ if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\Controller')) {
+ self::markTestSkipped();
+ }
$this->analyse(
[
__DIR__ . '/ExampleController.php',
@@ -41,14 +32,18 @@ public function testGetPrivateService(): void
[
[
'Service "unknown" is not registered in the container.',
- 24,
+ 25,
],
- ]
+ ],
);
}
public function testGetPrivateServiceInAbstractController(): void
{
+ if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\Controller')) {
+ self::markTestSkipped();
+ }
+
$this->analyse(
[
__DIR__ . '/ExampleAbstractController.php',
@@ -56,10 +51,31 @@ public function testGetPrivateServiceInAbstractController(): void
[
[
'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
index 4f0d2abd..22e5900e 100644
--- a/tests/Rules/Symfony/ExampleAbstractController.php
+++ b/tests/Rules/Symfony/ExampleAbstractController.php
@@ -3,6 +3,7 @@
namespace PHPStan\Rules\Symfony;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Bundle\FrameworkBundle\Test\TestContainer;
final class ExampleAbstractController extends AbstractController
{
@@ -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');
}
diff --git a/tests/Rules/Symfony/ExampleCommand.php b/tests/Rules/Symfony/ExampleCommand.php
index c376d875..6dec4cbd 100644
--- a/tests/Rules/Symfony/ExampleCommand.php
+++ b/tests/Rules/Symfony/ExampleCommand.php
@@ -29,6 +29,7 @@ protected function configure(): void
$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 = [];
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/InvalidArgumentDefaultValueRuleTest.php b/tests/Rules/Symfony/InvalidArgumentDefaultValueRuleTest.php
index 5e4f2ee6..bc6a6563 100644
--- a/tests/Rules/Symfony/InvalidArgumentDefaultValueRuleTest.php
+++ b/tests/Rules/Symfony/InvalidArgumentDefaultValueRuleTest.php
@@ -35,7 +35,7 @@ public function testGetArgument(): void
'Parameter #4 $default of method Symfony\Component\Console\Command\Command::addArgument() expects array|null, array given.',
25,
],
- ]
+ ],
);
}
diff --git a/tests/Rules/Symfony/InvalidOptionDefaultValueRuleTest.php b/tests/Rules/Symfony/InvalidOptionDefaultValueRuleTest.php
index 3e5e03d4..2dcbbcd1 100644
--- a/tests/Rules/Symfony/InvalidOptionDefaultValueRuleTest.php
+++ b/tests/Rules/Symfony/InvalidOptionDefaultValueRuleTest.php
@@ -27,7 +27,7 @@ public function testGetArgument(): void
'Parameter #5 $default of method Symfony\Component\Console\Command\Command::addOption() expects array|null, array given.',
29,
],
- ]
+ ],
);
}
diff --git a/tests/Rules/Symfony/UndefinedArgumentRuleTest.php b/tests/Rules/Symfony/UndefinedArgumentRuleTest.php
index 22fee153..d9970ef6 100644
--- a/tests/Rules/Symfony/UndefinedArgumentRuleTest.php
+++ b/tests/Rules/Symfony/UndefinedArgumentRuleTest.php
@@ -2,11 +2,10 @@
namespace PHPStan\Rules\Symfony;
-use PhpParser\PrettyPrinter\Standard;
+use PHPStan\Node\Printer\Printer;
use PHPStan\Rules\Rule;
use PHPStan\Symfony\ConsoleApplicationResolver;
use PHPStan\Testing\RuleTestCase;
-use PHPStan\Type\Symfony\ArgumentTypeSpecifyingExtension;
/**
* @extends RuleTestCase
@@ -16,17 +15,7 @@ final class UndefinedArgumentRuleTest extends RuleTestCase
protected function getRule(): Rule
{
- return new UndefinedArgumentRule(new ConsoleApplicationResolver(__DIR__ . '/console_application_loader.php'), new Standard());
- }
-
- /**
- * @return \PHPStan\Type\MethodTypeSpecifyingExtension[]
- */
- protected function getMethodTypeSpecifyingExtensions(): array
- {
- return [
- new ArgumentTypeSpecifyingExtension(new Standard()),
- ];
+ return new UndefinedArgumentRule(new ConsoleApplicationResolver(__DIR__ . '/console_application_loader.php'), self::getContainer()->getByType(Printer::class));
}
public function testGetArgument(): void
@@ -38,10 +27,17 @@ public function testGetArgument(): void
[
[
'Command "example-rule" does not define argument "undefined".',
- 41,
+ 42,
],
- ]
+ ],
);
}
+ public static function getAdditionalConfigFiles(): array
+ {
+ return [
+ __DIR__ . '/argument.neon',
+ ];
+ }
+
}
diff --git a/tests/Rules/Symfony/UndefinedOptionRuleTest.php b/tests/Rules/Symfony/UndefinedOptionRuleTest.php
index 1ca949de..7f759213 100644
--- a/tests/Rules/Symfony/UndefinedOptionRuleTest.php
+++ b/tests/Rules/Symfony/UndefinedOptionRuleTest.php
@@ -2,11 +2,10 @@
namespace PHPStan\Rules\Symfony;
-use PhpParser\PrettyPrinter\Standard;
+use PHPStan\Node\Printer\Printer;
use PHPStan\Rules\Rule;
use PHPStan\Symfony\ConsoleApplicationResolver;
use PHPStan\Testing\RuleTestCase;
-use PHPStan\Type\Symfony\OptionTypeSpecifyingExtension;
/**
* @extends RuleTestCase
@@ -16,17 +15,7 @@ final class UndefinedOptionRuleTest extends RuleTestCase
protected function getRule(): Rule
{
- return new UndefinedOptionRule(new ConsoleApplicationResolver(__DIR__ . '/console_application_loader.php'), new Standard());
- }
-
- /**
- * @return \PHPStan\Type\MethodTypeSpecifyingExtension[]
- */
- protected function getMethodTypeSpecifyingExtensions(): array
- {
- return [
- new OptionTypeSpecifyingExtension(new Standard()),
- ];
+ return new UndefinedOptionRule(new ConsoleApplicationResolver(__DIR__ . '/console_application_loader.php'), self::getContainer()->getByType(Printer::class));
}
public function testGetArgument(): void
@@ -38,10 +27,17 @@ public function testGetArgument(): void
[
[
'Command "example-rule" does not define option "bbb".',
- 48,
+ 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/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 @@
+
+
+
+
+
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 @@
+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 @@
+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
+ */
+ 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/DefaultServiceMapTest.php b/tests/Symfony/DefaultServiceMapTest.php
index 35d6b35d..b43bee49 100644
--- a/tests/Symfony/DefaultServiceMapTest.php
+++ b/tests/Symfony/DefaultServiceMapTest.php
@@ -26,93 +26,104 @@ public function testGetContainerEscapedPath(): void
}
/**
- * @return \Iterator
+ * @return Iterator
*/
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/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 @@
+
+ */
+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 @@
+ $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, 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 e4f42c64..00000000
--- a/tests/Symfony/config.neon
+++ /dev/null
@@ -1,3 +0,0 @@
-parameters:
- symfony:
- container_xml_path: container.xml
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 @@
+
+ abcdef
+ 123
+ 123
+ 123.45
+ 123.45
+ true
+ true
+
+ en
+ es
+ fr
+
+
+
+ the name
+ the value
+
+
+ another name
+ another value
+
+
+
+ value of a
+ value of b
+ value of c
+
+ VGhpcyBpcyBhIEJlbGwgY2hhciAH
+ Y-m-d\TH:i:sP
+
+
-
-
+
+
+
+
diff --git a/tests/Symfony/containers/bugfix%2Fcontainer.xml b/tests/Symfony/containers/bugfix%2Fcontainer.xml
index 8c8fbd55..5bed7715 100644
--- a/tests/Symfony/containers/bugfix%2Fcontainer.xml
+++ b/tests/Symfony/containers/bugfix%2Fcontainer.xml
@@ -1,5 +1,37 @@
+
+ abcdef
+ 123
+ 123
+ 123.45
+ 123.45
+ true
+ true
+
+ en
+ es
+ fr
+
+
+
+ the name
+ the value
+
+
+ another name
+ another value
+
+
+
+ value of a
+ value of b
+ value of c
+
+ VGhpcyBpcyBhIEJlbGwgY2hhciAH
+ Y-m-d\TH:i:sP
+
+
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 @@
+= 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 @@
+= 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/Config/TreeBuilderTest.php b/tests/Type/Symfony/Config/TreeBuilderTest.php
deleted file mode 100644
index 67f77917..00000000
--- a/tests/Type/Symfony/Config/TreeBuilderTest.php
+++ /dev/null
@@ -1,211 +0,0 @@
-processFile(
- __DIR__ . '/tree_builder.php',
- $expression,
- $type,
- [
- new ArrayNodeDefinitionPrototypeDynamicReturnTypeExtension(),
- new ReturnParentDynamicReturnTypeExtension('Symfony\Component\Config\Definition\Builder\ExprBuilder', ['end']),
- new ReturnParentDynamicReturnTypeExtension('Symfony\Component\Config\Definition\Builder\NodeBuilder', ['end']),
- new ReturnParentDynamicReturnTypeExtension('Symfony\Component\Config\Definition\Builder\NodeDefinition', ['end']),
- new PassParentObjectDynamicReturnTypeExtension('Symfony\Component\Config\Definition\Builder\NodeBuilder', ['arrayNode', 'scalarNode', 'booleanNode', 'integerNode', 'floatNode', 'enumNode', 'variableNode']),
- new PassParentObjectDynamicReturnTypeExtension('Symfony\Component\Config\Definition\Builder\NodeDefinition', ['children', 'validate', 'beforeNormalization']),
- new TreeBuilderGetRootNodeDynamicReturnTypeExtension(),
- ],
- [new TreeBuilderDynamicReturnTypeExtension()]
- );
- }
-
- /**
- * @return \Iterator
- */
- public function getProvider(): Iterator
- {
- yield ['$treeRootNode', 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition'];
- yield ['
- $treeRootNode
- ->children()
- ->end()
- ', 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition'];
- yield ['
- $treeRootNode
- ->children()
- ->scalarNode("protocol")
- ->end()
- ->end()
- ', 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition'];
- yield ['
- $treeRootNode
- ->children()
- ', 'Symfony\Component\Config\Definition\Builder\NodeBuilder'];
- yield ['
- $treeRootNode
- ->children()
- ->arrayNode("protocols")
- ', 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition'];
- yield ['
- $treeRootNode
- ->children()
- ->arrayNode("protocols")
- ->children()
- ', 'Symfony\Component\Config\Definition\Builder\NodeBuilder'];
- yield ['
- $treeRootNode
- ->children()
- ->arrayNode("protocols")
- ->children()
- ->scalarNode("protocol")
- ', 'Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition'];
- yield ['
- $treeRootNode
- ->children()
- ->arrayNode("protocols")
- ->children()
- ->scalarNode("protocol")
- ->end()
- ', 'Symfony\Component\Config\Definition\Builder\NodeBuilder'];
- yield ['
- $treeRootNode
- ->children()
- ->arrayNode("protocols")
- ->children()
- ->scalarNode("protocol")
- ->end()
- ->end()
- ', 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition'];
- yield ['
- $treeRootNode
- ->children()
- ->arrayNode("protocols")
- ->children()
- ->scalarNode("protocol")
- ->end()
- ->end()
- ->end()
- ', 'Symfony\Component\Config\Definition\Builder\NodeBuilder'];
- yield ['
- $treeRootNode
- ->children()
- ->arrayNode("protocols")
- ->children()
- ->scalarNode("protocol")
- ->end()
- ->end()
- ->end()
- ->end()
- ', 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition'];
- yield ['
- $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()
- ', 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition'];
-
- yield ['$arrayRootNode', 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition'];
- yield ['$arrayRootNode->end()', 'Symfony\Component\Config\Definition\Builder\TreeBuilder'];
- yield ['
- $arrayRootNode
- ->children()
- ->arrayNode("methods")
- ->prototype("scalar")
- ->defaultNull()
- ->end()
- ->end()
- ->end()
- ', 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition'];
- yield ['
- $arrayRootNode
- ->children()
- ->arrayNode("methods")
- ->scalarPrototype()
- ->defaultNull()
- ->end()
- ->end()
- ->end()
- ', 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition'];
- yield ['
- $arrayRootNode
- ->children()
- ->arrayNode("methods")
- ->prototype("scalar")
- ->validate()
- ->ifNotInArray(["one", "two"])
- ->thenInvalid("%s is not a valid method.")
- ->end()
- ->end()
- ->end()
- ->end()
- ', 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition'];
- yield ['
- $arrayRootNode
- ->children()
- ->arrayNode("methods")
- ->prototype("array")
- ->beforeNormalization()
- ->ifString()
- ->then(static function ($v) {
- return [$v];
- })
- ->end()
- ->end()
- ->end()
- ->end()
- ', 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition'];
-
- yield ['$variableRootNode', 'Symfony\Component\Config\Definition\Builder\VariableNodeDefinition'];
- yield ['$variableRootNode->end()', 'Symfony\Component\Config\Definition\Builder\TreeBuilder'];
-
- yield ['$scalarRootNode', 'Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition'];
- yield ['$scalarRootNode->defaultValue("default")', 'Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition'];
- yield ['$scalarRootNode->defaultValue("default")->end()', 'Symfony\Component\Config\Definition\Builder\TreeBuilder'];
-
- yield ['$booleanRootNode', 'Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition'];
- yield ['$booleanRootNode->defaultTrue()', 'Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition'];
- yield ['$booleanRootNode->defaultTrue()->end()', 'Symfony\Component\Config\Definition\Builder\TreeBuilder'];
-
- yield ['$integerRootNode', 'Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition'];
- yield ['$integerRootNode->min(0)', 'Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition'];
- yield ['$integerRootNode->min(0)->end()', 'Symfony\Component\Config\Definition\Builder\TreeBuilder'];
-
- yield ['$floatRootNode', 'Symfony\Component\Config\Definition\Builder\FloatNodeDefinition'];
- yield ['$floatRootNode->max(5E45)', 'Symfony\Component\Config\Definition\Builder\FloatNodeDefinition'];
- yield ['$floatRootNode->max(5E45)->end()', 'Symfony\Component\Config\Definition\Builder\TreeBuilder'];
-
- yield ['$enumRootNode', 'Symfony\Component\Config\Definition\Builder\EnumNodeDefinition'];
- yield ['$enumRootNode->values(["standard", "expedited", "priority"])', 'Symfony\Component\Config\Definition\Builder\EnumNodeDefinition'];
- yield ['$enumRootNode->values(["standard", "expedited", "priority"])->end()', 'Symfony\Component\Config\Definition\Builder\TreeBuilder'];
- }
-
-}
diff --git a/tests/Type/Symfony/Config/tree_builder.php b/tests/Type/Symfony/Config/tree_builder.php
deleted file mode 100644
index f1126608..00000000
--- a/tests/Type/Symfony/Config/tree_builder.php
+++ /dev/null
@@ -1,29 +0,0 @@
-getRootNode();
-
-$arrayTreeBuilder = new TreeBuilder('my_tree', 'array');
-$arrayRootNode = $arrayTreeBuilder->getRootNode();
-
-$variableTreeBuilder = new TreeBuilder('my_tree', 'variable');
-$variableRootNode = $variableTreeBuilder->getRootNode();
-
-$scalarTreeBuilder = new TreeBuilder('my_tree', 'scalar');
-$scalarRootNode = $scalarTreeBuilder->getRootNode();
-
-$booleanTreeBuilder = new TreeBuilder('my_tree', 'boolean');
-$booleanRootNode = $booleanTreeBuilder->getRootNode();
-
-$integerTreeBuilder = new TreeBuilder('my_tree', 'integer');
-$integerRootNode = $integerTreeBuilder->getRootNode();
-
-$floatTreeBuilder = new TreeBuilder('my_tree', 'float');
-$floatRootNode = $floatTreeBuilder->getRootNode();
-
-$enumTreeBuilder = new TreeBuilder('my_tree', 'enum');
-$enumRootNode = $enumTreeBuilder->getRootNode();
-
-die;
diff --git a/tests/Type/Symfony/EnvelopeReturnTypeExtensionTest.php b/tests/Type/Symfony/EnvelopeReturnTypeExtensionTest.php
deleted file mode 100644
index ef59ccc4..00000000
--- a/tests/Type/Symfony/EnvelopeReturnTypeExtensionTest.php
+++ /dev/null
@@ -1,35 +0,0 @@
-processFile(
- __DIR__ . '/envelope_all.php',
- $expression,
- $type,
- [new EnvelopeReturnTypeExtension()]
- );
- }
-
- /**
- * @return \Iterator
- */
- public function getProvider(): Iterator
- {
- yield ['$test1', 'array<' . ReceivedStamp::class . '>'];
- yield ['$test2', 'array<' . StampInterface::class . '>'];
- yield ['$test3', 'array>'];
- }
-
-}
diff --git a/tests/Type/Symfony/ExampleBaseCommand.php b/tests/Type/Symfony/ExampleBaseCommand.php
deleted file mode 100644
index b058ee2c..00000000
--- a/tests/Type/Symfony/ExampleBaseCommand.php
+++ /dev/null
@@ -1,31 +0,0 @@
-addArgument('base');
- }
-
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $base = $input->getArgument('base');
- $aaa = $input->getArgument('aaa');
- $bbb = $input->getArgument('bbb');
- $diff = $input->getArgument('diff');
- $arr = $input->getArgument('arr');
- $both = $input->getArgument('both');
-
- die;
- }
-
-}
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 @@
-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 @@
+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 a69c6e59..00000000
--- a/tests/Type/Symfony/ExtensionTestCase.php
+++ /dev/null
@@ -1,96 +0,0 @@
-createBroker($dynamicMethodReturnTypeExtensions, $dynamicStaticMethodReturnTypeExtensions);
- $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);
- $fileTypeMapper = new FileTypeMapper(
- new DirectReflectionProviderProvider($broker),
- $parser,
- $phpDocStringResolver,
- self::getContainer()->getByType(PhpDocNodeResolver::class),
- $this->createMock(Cache::class),
- $this->createMock(AnonymousClassNameHelper::class)
- );
- $resolver = new NodeScopeResolver(
- $broker,
- self::getReflectors()[0],
- $this->getClassReflectionExtensionRegistryProvider(),
- $parser,
- $fileTypeMapper,
- new PhpVersion(PHP_VERSION_ID),
- new PhpDocInheritanceResolver($fileTypeMapper),
- $fileHelper,
- $typeSpecifier,
- true,
- true,
- 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 ($node instanceof VirtualNode) {
- return;
- }
- if ((new Standard())->prettyPrint([$node]) !== 'die') {
- return;
- }
- /** @var \PhpParser\Node\Stmt\Expression $expNode */
- $expNode = $this->getParser()->parseString(sprintf('getType($expNode->expr)->describe(VerbosityLevel::typeOnly()), sprintf('Expression "%s"', $expression));
- $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 @@
+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 f84f3032..00000000
--- a/tests/Type/Symfony/HeaderBagDynamicReturnTypeExtensionTest.php
+++ /dev/null
@@ -1,37 +0,0 @@
-processFile(
- __DIR__ . '/header_bag_get.php',
- $expression,
- $type,
- [new HeaderBagDynamicReturnTypeExtension()]
- );
- }
-
- /**
- * @return \Iterator
- */
- 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'];
- yield ['$test9', 'array'];
- }
-
-}
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 @@
+
+ */
+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/InputBagDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/InputBagDynamicReturnTypeExtensionTest.php
deleted file mode 100644
index f1a2a520..00000000
--- a/tests/Type/Symfony/InputBagDynamicReturnTypeExtensionTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-processFile(
- __DIR__ . '/input_bag.php',
- $expression,
- $type,
- [new InputBagDynamicReturnTypeExtension()]
- );
- }
-
- /**
- * @return \Iterator
- */
- public function inputBagProvider(): Iterator
- {
- yield ['$test1', 'string|null'];
- yield ['$test2', 'string|null'];
- yield ['$test3', 'string'];
- yield ['$test4', 'string'];
- yield ['$test5', 'array|string>'];
- yield ['$test6', 'array'];
- }
-
-}
diff --git a/tests/Type/Symfony/InputInterfaceGetArgumentDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/InputInterfaceGetArgumentDynamicReturnTypeExtensionTest.php
deleted file mode 100644
index 00d85e00..00000000
--- a/tests/Type/Symfony/InputInterfaceGetArgumentDynamicReturnTypeExtensionTest.php
+++ /dev/null
@@ -1,37 +0,0 @@
-processFile(
- __DIR__ . '/ExampleBaseCommand.php',
- $expression,
- $type,
- [new InputInterfaceGetArgumentDynamicReturnTypeExtension(new ConsoleApplicationResolver(__DiR__ . '/console_application_loader.php'))]
- );
- }
-
- /**
- * @return \Iterator
- */
- public function argumentTypesProvider(): Iterator
- {
- yield ['$base', 'string|null'];
- yield ['$aaa', 'string'];
- yield ['$bbb', 'string'];
- yield ['$diff', 'array|string'];
- yield ['$arr', 'array'];
- yield ['$both', 'string|null'];
- }
-
-}
diff --git a/tests/Type/Symfony/InputInterfaceGetOptionDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/InputInterfaceGetOptionDynamicReturnTypeExtensionTest.php
deleted file mode 100644
index ede5f68d..00000000
--- a/tests/Type/Symfony/InputInterfaceGetOptionDynamicReturnTypeExtensionTest.php
+++ /dev/null
@@ -1,41 +0,0 @@
-processFile(
- __DIR__ . '/ExampleOptionCommand.php',
- $expression,
- $type,
- [new InputInterfaceGetOptionDynamicReturnTypeExtension(new ConsoleApplicationResolver(__DiR__ . '/console_application_loader.php'))]
- );
- }
-
- /**
- * @return \Iterator
- */
- public function argumentTypesProvider(): Iterator
- {
- yield ['$a', 'bool'];
- yield ['$b', 'string|null'];
- yield ['$c', 'string|null'];
- yield ['$d', 'array'];
- yield ['$e', 'array'];
-
- yield ['$bb', 'int|string|null'];
- yield ['$cc', 'int|string'];
- yield ['$dd', 'array'];
- yield ['$ee', 'array'];
- }
-
-}
diff --git a/tests/Type/Symfony/KernelInterfaceDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/KernelInterfaceDynamicReturnTypeExtensionTest.php
deleted file mode 100644
index 40bef996..00000000
--- a/tests/Type/Symfony/KernelInterfaceDynamicReturnTypeExtensionTest.php
+++ /dev/null
@@ -1,33 +0,0 @@
-processFile(
- __DIR__ . '/kernel_interface.php',
- $expression,
- $type,
- [new KernelInterfaceDynamicReturnTypeExtension()]
- );
- }
-
- /**
- * @return Iterator
- */
- public function getProvider(): Iterator
- {
- yield ['$foo', 'string'];
- yield ['$bar', 'string'];
- yield ['$baz', 'array'];
- }
-
-}
diff --git a/tests/Type/Symfony/RequestDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/RequestDynamicReturnTypeExtensionTest.php
deleted file mode 100644
index f0834674..00000000
--- a/tests/Type/Symfony/RequestDynamicReturnTypeExtensionTest.php
+++ /dev/null
@@ -1,34 +0,0 @@
-processFile(
- __DIR__ . '/request_get_content.php',
- $expression,
- $type,
- [new RequestDynamicReturnTypeExtension()]
- );
- }
-
- /**
- * @return Iterator
- */
- public function getContentProvider(): Iterator
- {
- yield ['$content1', 'string'];
- yield ['$content2', 'string'];
- yield ['$content3', 'resource'];
- yield ['$content4', 'resource|string'];
- }
-
-}
diff --git a/tests/Type/Symfony/RequestTypeSpecifyingExtensionTest.php b/tests/Type/Symfony/RequestTypeSpecifyingExtensionTest.php
deleted file mode 100644
index ac6f80c1..00000000
--- a/tests/Type/Symfony/RequestTypeSpecifyingExtensionTest.php
+++ /dev/null
@@ -1,54 +0,0 @@
-
- */
-final class RequestTypeSpecifyingExtensionTest extends RuleTestCase
-{
-
- protected function getRule(): Rule
- {
- return new VariableTypeReportingRule();
- }
-
- /** @return MethodTypeSpecifyingExtension[] */
- protected function getMethodTypeSpecifyingExtensions(): array
- {
- return [
- new RequestTypeSpecifyingExtension($this->createBroker()),
- ];
- }
-
- public function testGetSession(): void
- {
- $ref = new ReflectionMethod(Request::class, 'getSession');
- $doc = (string) $ref->getDocComment();
-
- $checkedTypeString = SessionInterface::class;
- if (strpos($doc, '@return SessionInterface|null') !== false) {
- $checkedTypeString .= '|null';
- }
-
- $this->analyse([__DIR__ . '/request_get_session.php'], [
- [
- 'Variable $session1 is: ' . $checkedTypeString,
- 7,
- ],
- [
- 'Variable $session2 is: ' . SessionInterface::class,
- 11,
- ],
- ]);
- }
-
-}
diff --git a/tests/Type/Symfony/SerializerDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/SerializerDynamicReturnTypeExtensionTest.php
deleted file mode 100644
index 0857127f..00000000
--- a/tests/Type/Symfony/SerializerDynamicReturnTypeExtensionTest.php
+++ /dev/null
@@ -1,52 +0,0 @@
-processFile(
- __DIR__ . '/serializer.php',
- $expression,
- $type,
- [new SerializerDynamicReturnTypeExtension(
- 'Symfony\Component\Serializer\SerializerInterface',
- 'deserialize'
- )]
- );
- }
-
- /**
- * @dataProvider getContentProvider
- */
- public function testDenormalizerInterface(string $expression, string $type): void
- {
- $this->processFile(
- __DIR__ . '/denormalizer.php',
- $expression,
- $type,
- [new SerializerDynamicReturnTypeExtension(
- 'Symfony\Component\Serializer\Normalizer\DenormalizerInterface',
- 'denormalize'
- )]
- );
- }
-
- /**
- * @return Iterator
- */
- public function getContentProvider(): Iterator
- {
- yield ['$first', 'Bar'];
- yield ['$second', 'array'];
- yield ['$third', 'array>'];
- }
-
-}
diff --git a/tests/Type/Symfony/ServiceDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/ServiceDynamicReturnTypeExtensionTest.php
deleted file mode 100644
index 8cfaa65d..00000000
--- a/tests/Type/Symfony/ServiceDynamicReturnTypeExtensionTest.php
+++ /dev/null
@@ -1,74 +0,0 @@
-processFile(
- __DIR__ . '/ExampleController.php',
- $expression,
- $type,
- [new ServiceDynamicReturnTypeExtension(Controller::class, true, (new XmlServiceMapFactory($container))->create())]
- );
- }
-
- /**
- * @return Iterator
- */
- public function servicesProvider(): Iterator
- {
- yield ['$service1', 'Foo', __DIR__ . '/container.xml'];
- yield ['$service2', 'object', __DIR__ . '/container.xml'];
- yield ['$service3', 'object', __DIR__ . '/container.xml'];
- yield ['$service4', 'object', __DIR__ . '/container.xml'];
- yield ['$has1', 'true', __DIR__ . '/container.xml'];
- yield ['$has2', 'false', __DIR__ . '/container.xml'];
- yield ['$has3', 'bool', __DIR__ . '/container.xml'];
- yield ['$has4', 'bool', __DIR__ . '/container.xml'];
-
- yield ['$service1', 'object', null];
- yield ['$service2', 'object', null];
- yield ['$service3', 'object', null];
- yield ['$service4', 'object', null];
- yield ['$has1', 'bool', null];
- yield ['$has2', 'bool', null];
- yield ['$has3', 'bool', null];
- yield ['$has4', 'bool', null];
- }
-
- /**
- * @dataProvider constantHassersOffProvider
- */
- public function testConstantHassersOff(string $expression, string $type, ?string $container): void
- {
- $this->processFile(
- __DIR__ . '/ExampleController.php',
- $expression,
- $type,
- [new ServiceDynamicReturnTypeExtension(Controller::class, false, (new XmlServiceMapFactory($container))->create())]
- );
- }
-
- /**
- * @return Iterator
- */
- public function constantHassersOffProvider(): Iterator
- {
- yield ['$has1', 'bool', __DIR__ . '/container.xml'];
- yield ['$has2', 'bool', __DIR__ . '/container.xml'];
-
- yield ['$has1', 'bool', null];
- yield ['$has2', 'bool', null];
- }
-
-}
diff --git a/tests/Type/Symfony/VariableTypeReportingRule.php b/tests/Type/Symfony/VariableTypeReportingRule.php
deleted file mode 100644
index cbac8c06..00000000
--- a/tests/Type/Symfony/VariableTypeReportingRule.php
+++ /dev/null
@@ -1,42 +0,0 @@
-
- */
-final class VariableTypeReportingRule implements Rule
-{
-
- public function getNodeType(): string
- {
- return Variable::class;
- }
-
- public function processNode(Node $node, Scope $scope): array
- {
- if (!is_string($node->name)) {
- return [];
- }
- if (!$scope->isInFirstLevelStatement()) {
- return [];
- };
- if ($scope->isInExpressionAssign($node)) {
- return [];
- }
- return [
- sprintf(
- 'Variable $%s is: %s',
- $node->name,
- $scope->getType($node)->describe(VerbosityLevel::value())
- ),
- ];
- }
-
-}
diff --git a/tests/Type/Symfony/console_application_loader.php b/tests/Type/Symfony/console_application_loader.php
index 524bc159..fb5459b5 100644
--- a/tests/Type/Symfony/console_application_loader.php
+++ b/tests/Type/Symfony/console_application_loader.php
@@ -3,7 +3,9 @@
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';
@@ -11,4 +13,11 @@
$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 @@
+
+ Foo
+ abcdef
+ 123
+ 123
+ %env(int:APP_INT)%
+ 123.45
+ 123.45
+ %env(float:APP_FLOAT)%
+ true
+ true
+ %env(bool:APP_BOOL)%
+
+ en
+ es
+ fr
+
+
+ 123
+ 456
+ 789
+
+
+ %env(int:APP_INT)%
+ %env(int:APP_INT)%
+ %env(int:APP_INT)%
+
+
+
+ the name
+ the value
+
+
+ another name
+ another value
+
+
+
+
+ the name
+ the value
+
+
+ 12
+ 32
+
+
+
+
+ the name
+ the value
+
+
+ another name
+ another value
+
+
+
+ %env(string:APP_STRING)%
+ %env(string:APP_STRING)%
+ %env(string:APP_STRING)%
+
+
+ %env(string:APP_STRING)%
+ %env(string:APP_STRING)%
+ %env(string:APP_STRING)%
+
+
+
+ %env(string:APP_STRING)%
+
+ %env(string:APP_STRING)%
+ %env(string:APP_STRING)%
+ %env(string:APP_STRING)%
+
+
+
+
+ value of a
+ value of b
+ value of c
+
+
+ v1
+ v2
+ v3
+ v4
+ v5
+ v6
+ v7
+ v8
+ v9
+ v10
+ v11
+ v12
+ v13
+ v14
+ v15
+ v16
+ v17
+ v18
+ v19
+ v20
+ v21
+ v22
+ v23
+ v24
+ v25
+ v26
+ v27
+ v28
+ v29
+ v30
+ v31
+ v32
+ v33
+ v34
+ v35
+ v36
+ v37
+ v38
+ v39
+ v40
+ v41
+ v42
+ v43
+ v44
+ v45
+ v46
+ v47
+ v48
+ v49
+ v50
+ v51
+ v52
+ v53
+ v54
+ v55
+ v56
+ v57
+ v58
+ v59
+ v60
+ v61
+ v62
+ v63
+ v64
+ v65
+ v66
+ v67
+ v68
+ v69
+ v70
+ v71
+ v72
+ v73
+ v74
+ v75
+ v76
+ v77
+ v78
+ v79
+ v80
+ v81
+ v82
+ v83
+ v84
+ v85
+ v86
+ v87
+ v88
+ v89
+ v90
+ v91
+ v92
+ v93
+ v94
+ v95
+ v96
+ v97
+ v98
+ v99
+ v100
+ v101
+ v102
+ v103
+ v104
+ v105
+ v106
+ v107
+ v108
+ v109
+ v110
+ v111
+ v112
+ v113
+ v114
+ v115
+ v116
+ v117
+ v118
+ v119
+ v120
+ v121
+ v122
+ v123
+ v124
+ v125
+ v126
+ v127
+ v128
+ v129
+ v130
+ v131
+ v132
+ v133
+ v134
+ v135
+ v136
+ v137
+ v138
+ v139
+ v140
+ v141
+ v142
+ v143
+ v144
+ v145
+ v146
+ v147
+ v148
+ v149
+ v150
+ v151
+ v152
+ v153
+ v154
+ v155
+ v156
+ v157
+ v158
+ v159
+ v160
+ v161
+ v162
+ v163
+ v164
+ v165
+ v166
+ v167
+ v168
+ v169
+ v170
+ v171
+ v172
+ v173
+ v174
+ v175
+ v176
+ v177
+ v178
+ v179
+ v180
+ v181
+ v182
+ v183
+ v184
+ v185
+ v186
+ v187
+ v188
+ v189
+ v190
+ v191
+ v192
+ v193
+ v194
+ v195
+ v196
+ v197
+ v198
+ v199
+ v200
+ v201
+ v202
+ v203
+ v204
+ v205
+ v206
+ v207
+ v208
+ v209
+ v210
+ v211
+ v212
+ v213
+ v214
+ v215
+ v216
+ v217
+ v218
+ v219
+ v220
+ v221
+ v222
+ v223
+ v224
+ v225
+ v226
+ v227
+ v228
+ v229
+ v230
+ v231
+ v232
+ v233
+ v234
+ v235
+ v236
+ v237
+ v238
+ v239
+ v240
+ v241
+ v242
+ v243
+ v244
+ v245
+ v246
+ v247
+ v248
+ v249
+ v250
+ v251
+ v252
+ v253
+ v254
+ v255
+ v256
+ v257
+
+ VGhpcyBpcyBhIEJlbGwgY2hhciAH
+ Y-m-d\TH:i:sP
+
+
+ value
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Type/Symfony/ExampleACommand.php b/tests/Type/Symfony/data/ExampleACommand.php
similarity index 100%
rename from tests/Type/Symfony/ExampleACommand.php
rename to tests/Type/Symfony/data/ExampleACommand.php
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 @@
+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", $container->getParameter('app.list'));
+ assertType("array", $parameterBag->get('app.list'));
+ assertType("array", $this->getParameter('app.list'));
+ assertType("array", $container->getParameter('app.list_of_int'));
+ assertType("array", $parameterBag->get('app.list_of_int'));
+ assertType("array", $this->getParameter('app.list_of_int'));
+ assertType("array", $container->getParameter('app.list_of_int_as_processor'));
+ assertType("array", $parameterBag->get('app.list_of_int_as_processor'));
+ assertType("array", $this->getParameter('app.list_of_int_as_processor'));
+ assertType("array", $container->getParameter('app.list_of_list'));
+ assertType("array", $parameterBag->get('app.list_of_list'));
+ assertType("array", $this->getParameter('app.list_of_list'));
+ assertType("array", $container->getParameter('app.list_of_different_list'));
+ assertType("array", $parameterBag->get('app.list_of_different_list'));
+ assertType("array", $this->getParameter('app.list_of_different_list'));
+ assertType("array", $container->getParameter('app.array_of_list'));
+ assertType("array", $parameterBag->get('app.array_of_list'));
+ assertType("array", $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", $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 @@
+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/ExampleBCommand.php b/tests/Type/Symfony/data/ExampleBCommand.php
similarity index 100%
rename from tests/Type/Symfony/ExampleBCommand.php
rename to tests/Type/Symfony/data/ExampleBCommand.php
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 @@
+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|string', $input->getArgument('diff'));
+ assertType('array', $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|string', $input->getArgument('diff'));
+ assertType('array', $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|string', $input->getArgument('diff'));
+ assertType('array', $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 @@
+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", $container->getParameter('app.list'));
+ assertType("array", $parameterBag->get('app.list'));
+ assertType("array", $this->getParameter('app.list'));
+ assertType("array", $container->getParameter('app.list_of_list'));
+ assertType("array", $parameterBag->get('app.list_of_list'));
+ assertType("array", $this->getParameter('app.list_of_list'));
+ assertType("array", $container->getParameter('app.list_of_different_list'));
+ assertType("array", $parameterBag->get('app.list_of_different_list'));
+ assertType("array", $this->getParameter('app.list_of_different_list'));
+ assertType("array", $container->getParameter('app.array_of_list'));
+ assertType("array", $parameterBag->get('app.array_of_list'));
+ assertType("array", $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 @@
+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/ExampleOptionCommand.php b/tests/Type/Symfony/data/ExampleOptionCommand.php
similarity index 54%
rename from tests/Type/Symfony/ExampleOptionCommand.php
rename to tests/Type/Symfony/data/ExampleOptionCommand.php
index c18d55e2..b880173d 100644
--- a/tests/Type/Symfony/ExampleOptionCommand.php
+++ b/tests/Type/Symfony/data/ExampleOptionCommand.php
@@ -6,6 +6,7 @@
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
{
@@ -20,6 +21,7 @@ protected function configure(): void
$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);
@@ -29,18 +31,19 @@ protected function configure(): void
protected function execute(InputInterface $input, OutputInterface $output): int
{
- $a = $input->getOption('a');
- $b = $input->getOption('b');
- $c = $input->getOption('c');
- $d = $input->getOption('d');
- $e = $input->getOption('e');
-
- $bb = $input->getOption('bb');
- $cc = $input->getOption('cc');
- $dd = $input->getOption('dd');
- $ee = $input->getOption('ee');
-
- die;
+ assertType('bool', $input->getOption('a'));
+ assertType('string|null', $input->getOption('b'));
+ assertType('string|null', $input->getOption('c'));
+ assertType('array', $input->getOption('d'));
+ assertType('array', $input->getOption('e'));
+ assertType('bool|null', $input->getOption('f'));
+
+ assertType('1|string|null', $input->getOption('bb'));
+ assertType('1|string', $input->getOption('cc'));
+ assertType('array', $input->getOption('dd'));
+ assertType('array', $input->getOption('ee'));
+
+ assertType('array{a: bool, b: string|null, c: string|null, d: array, e: array, f: bool|null, bb: 1|string|null, cc: 1|string, dd: array, ee: array, 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 @@
+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', $input->getOption('d'));
+ assertType('array', $input->getOption('e'));
+ assertType('bool|null', $input->getOption('f'));
+
+ assertType('1|string|null', $input->getOption('bb'));
+ assertType('1|string', $input->getOption('cc'));
+ assertType('array', $input->getOption('dd'));
+ assertType('array', $input->getOption('ee'));
+
+ assertType('array{a: bool, b: string|null, c: string|null, d: array, e: array, f: bool|null, bb: 1|string|null, cc: 1|string, dd: array, ee: array, 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 @@
+', $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 @@
+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 @@
+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 @@
+denormalize('bar', 'Bar', 'format'));
+assertType('array', $serializer->denormalize('bar', 'Bar[]', 'format'));
+assertType('array>', $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 @@
+', $envelope->all(\Symfony\Component\Messenger\Stamp\ReceivedStamp::class));
+assertType('list', $envelope->all(random_bytes(1)));
+assertType('array, list>', $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 @@
+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 @@
+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 @@
+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 @@
+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 @@
+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 @@
+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 @@
+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 @@
+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 @@
+
+ */
+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 @@
+ ['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', $bag->get('foo', null, false));
+assertType('array', $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 @@
+ '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|bool|float|int|string>', $bag->all());
+assertType('array', $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 @@
+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/kernel_interface.php b/tests/Type/Symfony/data/kernel_interface.php
similarity index 55%
rename from tests/Type/Symfony/kernel_interface.php
rename to tests/Type/Symfony/data/kernel_interface.php
index aca451fb..e2239754 100644
--- a/tests/Type/Symfony/kernel_interface.php
+++ b/tests/Type/Symfony/data/kernel_interface.php
@@ -1,5 +1,7 @@
locateResource('');
-$bar = $kernel->locateResource('', null, true);
-$baz = $kernel->locateResource('', null, false);
-
-die;
+assertType('string', $kernel->locateResource(''));
+assertType('string', $kernel->locateResource('', null, true));
+assertType('array', $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 @@
+ ['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 @@
+ 'ea'];
+$propertyAccessor->setValue($array, 'foo', 'bar');
+assertType('array', $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 @@
+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 @@
+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 @@
+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 @@
+setCookie(Cookie::create('cookie_name'));
+
+assertType('array', $headerBag->getCookies());
+assertType('array', $headerBag->getCookies(ResponseHeaderBag::COOKIES_FLAT));
+assertType('array>>', $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 @@
+deserialize('bar', 'Bar', 'format'));
+assertType('array', $serializer->deserialize('bar', 'Bar[]', 'format'));
+assertType('array>', $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 @@
+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/denormalizer.php b/tests/Type/Symfony/denormalizer.php
deleted file mode 100644
index 208483b4..00000000
--- a/tests/Type/Symfony/denormalizer.php
+++ /dev/null
@@ -1,10 +0,0 @@
-denormalize('bar', 'Bar', 'format');
-$second = $serializer->denormalize('bar', 'Bar[]', 'format');
-$third = $serializer->denormalize('bar', 'Bar[][]', 'format');
-$fourth = $serializer->denormalize('bar');
-
-die;
diff --git a/tests/Type/Symfony/envelope_all.php b/tests/Type/Symfony/envelope_all.php
deleted file mode 100644
index f2bd3ec4..00000000
--- a/tests/Type/Symfony/envelope_all.php
+++ /dev/null
@@ -1,9 +0,0 @@
-all(\Symfony\Component\Messenger\Stamp\ReceivedStamp::class);
-$test2 = $envelope->all(random_bytes(1));
-$test3 = $envelope->all();
-
-die;
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 @@
- ['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/input_bag.php b/tests/Type/Symfony/input_bag.php
deleted file mode 100644
index 7a36d61f..00000000
--- a/tests/Type/Symfony/input_bag.php
+++ /dev/null
@@ -1,12 +0,0 @@
- 'bar', 'bar' => ['x']]);
-
-$test1 = $bag->get('foo');
-$test2 = $bag->get('foo', null);
-$test3 = $bag->get('foo', '');
-$test4 = $bag->get('foo', 'baz');
-$test5 = $bag->all();
-$test6 = $bag->all('bar');
-
-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 @@
-getContent();
-$content2 = $request->getContent(false);
-$content3 = $request->getContent(true);
-$content4 = $request->getContent(doBar());
-
-die;
diff --git a/tests/Type/Symfony/request_get_session.php b/tests/Type/Symfony/request_get_session.php
deleted file mode 100644
index b488455a..00000000
--- a/tests/Type/Symfony/request_get_session.php
+++ /dev/null
@@ -1,12 +0,0 @@
-getSession();
-$session1;
-
-if ($request->hasSession()) {
- $session2 = $request->getSession();
- $session2;
-}
diff --git a/tests/Type/Symfony/serializer.php b/tests/Type/Symfony/serializer.php
deleted file mode 100644
index fc16573e..00000000
--- a/tests/Type/Symfony/serializer.php
+++ /dev/null
@@ -1,10 +0,0 @@
-deserialize('bar', 'Bar', 'format');
-$second = $serializer->deserialize('bar', 'Bar[]', 'format');
-$third = $serializer->deserialize('bar', 'Bar[][]', 'format');
-$fourth = $serializer->deserialize('bar');
-
-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 @@
-
-
-
- ../src
-
-
-
-
-
-
-
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 @@
+*
+!.*