From 4df3118a72914a255abbcf0755b22508ecde1c77 Mon Sep 17 00:00:00 2001 From: Yakov Lipkovich Date: Sun, 4 Feb 2024 22:56:56 -0700 Subject: [PATCH 01/14] [docs] Fix outdated Twig Analyzer configuration documentation (#332) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 753e59c..7644c0f 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ To leverage the real Twig file analyzer, you have to configure a checker for the ```xml - + ``` From dd06f3064269a79136f7c522380d2e76adefb826 Mon Sep 17 00:00:00 2001 From: Farhad Safarov Date: Fri, 31 May 2024 18:41:01 +0300 Subject: [PATCH 02/14] [doc] update versions table --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7644c0f..b793748 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ vendor/bin/psalm-plugin enable psalm/plugin-symfony | Symfony Psalm Plugin | PHP | Symfony | Psalm | |----------------------|------------|---------|-------| -| 5.x | ^7.4, ^8.0 | 5, 6 | 5 | +| 5.x | ^7.4, ^8.0 | 5, 6, 7 | 5 | | 4.x | ^7.4, ^8.0 | 4, 5, 6 | 4 | | 3.x | ^7.1, ^8.0 | 4, 5, 6 | 4 | | 2.x | ^7.1, ^8.0 | 4, 5 | 4 | From 257eb04160677c6ef2cdb7dc9366f8d318898837 Mon Sep 17 00:00:00 2001 From: Farhad Safarov Date: Wed, 5 Jun 2024 09:31:06 +0300 Subject: [PATCH 03/14] Fix static analysis errors & CS (#338) --- src/Handler/ConsoleHandler.php | 8 ++++---- src/Handler/ContainerHandler.php | 4 ++-- src/Handler/HeaderBagHandler.php | 3 +-- src/Plugin.php | 2 +- src/Symfony/ContainerMeta.php | 11 +++++------ src/Twig/TemplateFileAnalyzer.php | 11 ++++------- 6 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/Handler/ConsoleHandler.php b/src/Handler/ConsoleHandler.php index c18f511..def0894 100644 --- a/src/Handler/ConsoleHandler.php +++ b/src/Handler/ConsoleHandler.php @@ -57,7 +57,7 @@ public static function afterMethodCallAnalysis(AfterMethodCallAnalysisEvent $eve break; case 'Symfony\Component\Console\Input\InputInterface::getargument': $identifier = self::getNodeIdentifier($args[0]->value); - if (!$identifier) { + if (null === $identifier) { break; } @@ -70,7 +70,7 @@ public static function afterMethodCallAnalysis(AfterMethodCallAnalysisEvent $eve break; case 'Symfony\Component\Console\Input\InputInterface::getoption': $identifier = self::getNodeIdentifier($args[0]->value); - if (!$identifier) { + if (null === $identifier) { break; } @@ -126,7 +126,7 @@ private static function analyseArgument(array $args, StatementsSource $statement $normalizedParams = self::normalizeArgumentParams($args); $identifier = self::getNodeIdentifier($normalizedParams['name']->value); - if (!$identifier) { + if (null === $identifier) { return; } @@ -170,7 +170,7 @@ private static function analyseOption(array $args, StatementsSource $statements_ $normalizedParams = self::normalizeOptionParams($args); $identifier = self::getNodeIdentifier($normalizedParams['name']->value); - if (!$identifier) { + if (null === $identifier) { return; } diff --git a/src/Handler/ContainerHandler.php b/src/Handler/ContainerHandler.php index c22ae57..849e300 100644 --- a/src/Handler/ContainerHandler.php +++ b/src/Handler/ContainerHandler.php @@ -105,7 +105,7 @@ public static function afterMethodCallAnalysis(AfterMethodCallAnalysisEvent $eve if ('self' === $className) { $className = $event->getStatementsSource()->getSource()->getFQCLN(); } - if (!$idArgument->name instanceof Identifier || !$className) { + if (!$idArgument->name instanceof Identifier || null === $className) { return; } @@ -141,7 +141,7 @@ public static function afterMethodCallAnalysis(AfterMethodCallAnalysisEvent $eve if (!$service->isPublic()) { /** @var class-string $kernelTestCaseClass */ $kernelTestCaseClass = 'Symfony\Bundle\FrameworkBundle\Test\KernelTestCase'; - $isTestContainer = $context->parent + $isTestContainer = null !== $context->parent && ($kernelTestCaseClass === $context->parent || is_subclass_of($context->parent, $kernelTestCaseClass) ); diff --git a/src/Handler/HeaderBagHandler.php b/src/Handler/HeaderBagHandler.php index e582708..401a17a 100644 --- a/src/Handler/HeaderBagHandler.php +++ b/src/Handler/HeaderBagHandler.php @@ -6,7 +6,6 @@ use PhpParser\Node\Scalar\String_; use Psalm\Plugin\EventHandler\Event\MethodReturnTypeProviderEvent; use Psalm\Plugin\EventHandler\MethodReturnTypeProviderInterface; -use Psalm\Type; use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TInt; use Psalm\Type\Atomic\TNull; @@ -24,7 +23,7 @@ public static function getClassLikeNames(): array ]; } - public static function getMethodReturnType(MethodReturnTypeProviderEvent $event): ?Type\Union + public static function getMethodReturnType(MethodReturnTypeProviderEvent $event): ?Union { $fq_classlike_name = $event->getFqClasslikeName(); $method_name_lowercase = $event->getMethodNameLowercase(); diff --git a/src/Plugin.php b/src/Plugin.php index ab61d40..d329c74 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -29,7 +29,7 @@ */ class Plugin implements PluginEntryPointInterface { - public function __invoke(RegistrationInterface $api, \SimpleXMLElement $config = null): void + public function __invoke(RegistrationInterface $api, ?\SimpleXMLElement $config = null): void { require_once __DIR__.'/Handler/HeaderBagHandler.php'; require_once __DIR__.'/Handler/ContainerHandler.php'; diff --git a/src/Symfony/ContainerMeta.php b/src/Symfony/ContainerMeta.php index 7dfee1d..e055ac8 100644 --- a/src/Symfony/ContainerMeta.php +++ b/src/Symfony/ContainerMeta.php @@ -44,9 +44,9 @@ public function __construct(array $containerXmlPaths) /** * @throws ServiceNotFoundException */ - public function get(string $id, string $contextClass = null): Definition + public function get(string $id, ?string $contextClass = null): Definition { - if ($contextClass && isset($this->classLocators[$contextClass]) && isset($this->serviceLocators[$this->classLocators[$contextClass]]) && isset($this->serviceLocators[$this->classLocators[$contextClass]][$id])) { + if (null !== $contextClass && isset($this->classLocators[$contextClass]) && isset($this->serviceLocators[$this->classLocators[$contextClass]]) && isset($this->serviceLocators[$this->classLocators[$contextClass]][$id])) { $id = $this->serviceLocators[$this->classLocators[$contextClass]][$id]; try { @@ -91,12 +91,12 @@ private function init(array $containerXmlPaths): void $containerXmlPath = null; foreach ($containerXmlPaths as $filePath) { $containerXmlPath = realpath((string) $filePath); - if ($containerXmlPath) { + if (false !== $containerXmlPath) { break; } } - if (!$containerXmlPath) { + if (!is_string($containerXmlPath)) { throw new ConfigException('Container xml file(s) not found!'); } @@ -117,8 +117,7 @@ private function init(array $containerXmlPaths): void } foreach ($this->container->findTaggedServiceIds('container.service_locator') as $key => $_) { - $definition = $this->container->getDefinition($key); - foreach ($definition->getArgument(0) as $id => $argument) { + foreach ($this->container->getDefinition($key)->getArgument(0) as $id => $argument) { if ($argument instanceof Reference) { $this->addServiceLocator($key, $id, $argument); } elseif ($argument instanceof ServiceClosureArgument) { diff --git a/src/Twig/TemplateFileAnalyzer.php b/src/Twig/TemplateFileAnalyzer.php index cbb3251..76b968c 100644 --- a/src/Twig/TemplateFileAnalyzer.php +++ b/src/Twig/TemplateFileAnalyzer.php @@ -18,15 +18,12 @@ */ class TemplateFileAnalyzer extends FileAnalyzer { - /** - * @var string - */ - private static $rootPath = 'templates'; + private static string $rootPath = 'templates'; /** * @var list */ - private static $extensionClasses = []; + private static array $extensionClasses = []; /** * @param list $extensionClasses @@ -37,8 +34,8 @@ public static function initExtensions(array $extensionClasses): void } public function analyze( - PsalmContext $file_context = null, - PsalmContext $global_context = null + ?PsalmContext $file_context = null, + ?PsalmContext $global_context = null ): void { $codebase = $this->project_analyzer->getCodebase(); $taint = $codebase->taint_flow_graph; From 7afa8ae48e6bba00cd211977f4f4e0b09883094e Mon Sep 17 00:00:00 2001 From: Farhad Safarov Date: Wed, 5 Jun 2024 10:04:22 +0300 Subject: [PATCH 04/14] Change workflow schedule for every week on Saturday (#339) --- .github/workflows/integrate.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index 620a5f9..6292cc8 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -6,7 +6,7 @@ on: branches: - master schedule: - - cron: "0 10 * * *" + - cron: "0 10 * * 6" jobs: static-code-analysis: From b60ac39231d59c2fc4e549deed2a92d4404b4a0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 11:00:54 +0300 Subject: [PATCH 05/14] Bump ramsey/composer-install from 1 to 3 (#334) Bumps [ramsey/composer-install](https://github.com/ramsey/composer-install) from 1 to 3. - [Release notes](https://github.com/ramsey/composer-install/releases) - [Commits](https://github.com/ramsey/composer-install/compare/v1...v3) --- updated-dependencies: - dependency-name: ramsey/composer-install dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/integrate.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index 6292cc8..60203af 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -36,7 +36,7 @@ jobs: php-version: ${{ matrix.php-version }} - name: Install Composer dependencies - uses: ramsey/composer-install@v1 + uses: ramsey/composer-install@v3 with: dependency-versions: ${{ matrix.dependencies }} @@ -96,7 +96,7 @@ jobs: run: composer config extra.symfony.require ${{ matrix.symfony-version }}.* - name: Install Composer dependencies - uses: ramsey/composer-install@v1 + uses: ramsey/composer-install@v3 - name: "Run unit tests with phpunit" run: vendor/bin/phpunit --configuration=phpunit.xml From bd550b334373027e836f4825807a45f6f8cf4ba0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 11:14:22 +0300 Subject: [PATCH 06/14] Bump actions/checkout from 3 to 4 (#321) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/integrate.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index 60203af..1396060 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -27,7 +27,7 @@ jobs: steps: - name: "Checkout" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Install PHP with extensions" uses: shivammathur/setup-php@v2 @@ -83,7 +83,7 @@ jobs: steps: - name: "Checkout" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Install PHP with extensions" uses: shivammathur/setup-php@v2 From 5f0c5a41be8e04aaf7fae25f6fa35d38528ab544 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 11:52:21 +0300 Subject: [PATCH 07/14] Bump actions/cache from 3 to 4 (#331) Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/integrate.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index 1396060..44fbb68 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -41,7 +41,7 @@ jobs: dependency-versions: ${{ matrix.dependencies }} - name: "Cache cache directory for vimeo/psalm" - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: .build/psalm key: php-${{ matrix.php-version }}-psalm-${{ github.sha }} From af23a49e535ec32fcab1b7bcb7639d8710dcaf71 Mon Sep 17 00:00:00 2001 From: Farhad Safarov Date: Wed, 5 Jun 2024 15:00:04 +0300 Subject: [PATCH 08/14] Update dependencies (#341) --- .github/workflows/integrate.yaml | 8 +------- README.md | 2 +- composer.json | 4 ++-- src/Handler/ContainerHandler.php | 15 ++++++--------- src/Symfony/ContainerMeta.php | 24 +++++++++--------------- 5 files changed, 19 insertions(+), 34 deletions(-) diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index 44fbb68..ebd2abc 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -18,8 +18,8 @@ jobs: fail-fast: false matrix: php-version: - - 8.0 - 8.1 + - 8.2 dependencies: - highest @@ -64,20 +64,14 @@ jobs: fail-fast: false matrix: php-version: - - 8.0 - 8.1 - 8.2 symfony-version: - - 5 - 6 - 7 exclude: - - php-version: 8.0 - symfony-version: 6 - - php-version: 8.0 - symfony-version: 7 - php-version: 8.1 symfony-version: 7 diff --git a/README.md b/README.md index b793748..ccac626 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ vendor/bin/psalm-plugin enable psalm/plugin-symfony | Symfony Psalm Plugin | PHP | Symfony | Psalm | |----------------------|------------|---------|-------| -| 5.x | ^7.4, ^8.0 | 5, 6, 7 | 5 | +| 5.x | ^8.0 | 5, 6, 7 | 5 | | 4.x | ^7.4, ^8.0 | 4, 5, 6 | 4 | | 3.x | ^7.1, ^8.0 | 4, 5, 6 | 4 | | 2.x | ^7.1, ^8.0 | 4, 5 | 4 | diff --git a/composer.json b/composer.json index 6d3bc61..d30e15d 100644 --- a/composer.json +++ b/composer.json @@ -10,10 +10,10 @@ } ], "require": { - "php": "^7.4 || ^8.0", + "php": "^8.1", "ext-simplexml": "*", "symfony/framework-bundle": "^5.0 || ^6.0 || ^7.0", - "vimeo/psalm": "^5.1" + "vimeo/psalm": "^5.24" }, "require-dev": { "symfony/form": "^5.0 || ^6.0 || ^7.0", diff --git a/src/Handler/ContainerHandler.php b/src/Handler/ContainerHandler.php index 849e300..e750deb 100644 --- a/src/Handler/ContainerHandler.php +++ b/src/Handler/ContainerHandler.php @@ -35,10 +35,7 @@ class ContainerHandler implements AfterMethodCallAnalysisInterface, AfterClassLi 'Symfony\Bundle\FrameworkBundle\Test\TestContainer', ]; - /** - * @var ContainerMeta|null - */ - private static $containerMeta; + private static ?ContainerMeta $containerMeta = null; /** * @var array collection of cower-cased class names that are present in the container @@ -114,7 +111,7 @@ public static function afterMethodCallAnalysis(AfterMethodCallAnalysisEvent $eve } else { try { $serviceId = \constant($className.'::'.$idArgument->name->name); - } catch (\Exception $e) { + } catch (\Exception) { return; } } @@ -133,7 +130,7 @@ public static function afterMethodCallAnalysis(AfterMethodCallAnalysisEvent $eve } $class = $service->getClass(); - if ($class) { + if (null !== $class) { $codebase->classlikes->addFullyQualifiedClassName($class); $event->setReturnTypeCandidate(new Union([new TNamedObject($class)])); } @@ -152,7 +149,7 @@ public static function afterMethodCallAnalysis(AfterMethodCallAnalysisEvent $eve ); } } - } catch (ServiceNotFoundException $e) { + } catch (ServiceNotFoundException) { IssueBuffer::accepts( new ServiceNotFound($serviceId, new CodeLocation($statements_source, $firstArg->value)), $statements_source->getSuppressedIssues() @@ -160,7 +157,7 @@ public static function afterMethodCallAnalysis(AfterMethodCallAnalysisEvent $eve } } - public static function afterClassLikeVisit(AfterClassLikeVisitEvent $event) + public static function afterClassLikeVisit(AfterClassLikeVisitEvent $event): void { $codebase = $event->getCodebase(); $statements_source = $event->getStatementsSource(); @@ -221,7 +218,7 @@ function ($c) use ($methodName) { private static function followsParameterNamingConvention(string $name): bool { - if (0 === strpos($name, 'env(')) { + if (str_starts_with($name, 'env(')) { return true; } diff --git a/src/Symfony/ContainerMeta.php b/src/Symfony/ContainerMeta.php index e055ac8..e7d86bb 100644 --- a/src/Symfony/ContainerMeta.php +++ b/src/Symfony/ContainerMeta.php @@ -19,22 +19,19 @@ class ContainerMeta /** * @var array */ - private $classNames = []; + private array $classNames = []; /** * @var array */ - private $classLocators = []; + private array $classLocators = []; /** * @var array> */ - private $serviceLocators = []; + private array $serviceLocators = []; - /** - * @var ContainerBuilder - */ - private $container; + private ContainerBuilder $container; public function __construct(array $containerXmlPaths) { @@ -67,10 +64,7 @@ public function get(string $id, ?string $contextClass = null): Definition return $definition; } - /** - * @return mixed|null - */ - public function getParameter(string $key) + public function getParameter(string $key): mixed { return $this->container->getParameter($key); } @@ -111,7 +105,7 @@ private function init(array $containerXmlPaths): void $this->classLocators[$this->container->getDefinition($id)->getClass() ?? $id] = (string) $reference; } elseif ($definition->hasTag('container.service_locator')) { continue; - } elseif ($className = $definition->getClass()) { + } elseif (null !== $className = $definition->getClass()) { $this->classNames[] = $className; } } @@ -138,10 +132,10 @@ private function addServiceLocator(string $key, string $id, Reference $reference try { $definition = $this->getDefinition((string) $reference); $className = $definition->getClass(); - if ($className) { + if (null !== $className) { $this->classNames[] = $className; } - } catch (ServiceNotFoundException $e) { + } catch (ServiceNotFoundException) { } } @@ -155,7 +149,7 @@ private function getDefinition(string $id): Definition } catch (ServiceNotFoundException $serviceNotFoundException) { try { $alias = $this->container->getAlias($id); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { throw $serviceNotFoundException; } From 2ee2ae1f5efc42df14fc961d70e4127dd6f814ba Mon Sep 17 00:00:00 2001 From: Farhad Safarov Date: Wed, 5 Jun 2024 17:43:24 +0300 Subject: [PATCH 09/14] Fix & improve tests (#342) --- composer.json | 4 +- src/Plugin.php | 40 +++++------ .../PropertyAccessorInterface.stubphp | 8 +-- .../Core/Authorization/Voter/Voter.stubphp | 42 ----------- .../acceptance/ContainerService.feature | 47 ++++++------- .../acceptance/ContainerXml.feature | 51 ++++++-------- tests/acceptance/acceptance/HeaderBag.feature | 25 +++---- .../acceptance/NamingConventions.feature | 69 +++++++------------ .../PropertyAccessorInterface.feature | 30 ++++---- tests/acceptance/acceptance/Voter.feature | 40 ----------- .../acceptance/forms/AbstractType.feature | 2 +- .../forms/AbstractTypeExtension.feature | 2 +- .../forms/FormBuilterInterface.feature | 2 +- .../forms/FormConfigBuilterInterface.feature | 2 +- .../forms/FormConfigInterface.feature | 2 +- .../acceptance/forms/FormView.feature | 2 +- 16 files changed, 126 insertions(+), 242 deletions(-) delete mode 100644 src/Stubs/common/Component/Security/Core/Authorization/Voter/Voter.stubphp delete mode 100644 tests/acceptance/acceptance/Voter.feature diff --git a/composer.json b/composer.json index d30e15d..9b77764 100644 --- a/composer.json +++ b/composer.json @@ -16,14 +16,14 @@ "vimeo/psalm": "^5.24" }, "require-dev": { - "symfony/form": "^5.0 || ^6.0 || ^7.0", "doctrine/annotations": "^1.8|^2", "doctrine/orm": "^2.9", "phpunit/phpunit": "~7.5 || ~9.5", "symfony/cache-contracts": "^1.0 || ^2.0", "symfony/console": "*", + "symfony/form": "^5.0 || ^6.0 || ^7.0", "symfony/messenger": "^5.0 || ^6.0 || ^7.0", - "symfony/security-guard": "*", + "symfony/security-core": "*", "symfony/serializer": "^5.0 || ^6.0 || ^7.0", "symfony/validator": "*", "twig/twig": "^2.10 || ^3.0", diff --git a/src/Plugin.php b/src/Plugin.php index d329c74..ce9c543 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -29,7 +29,7 @@ */ class Plugin implements PluginEntryPointInterface { - public function __invoke(RegistrationInterface $api, ?\SimpleXMLElement $config = null): void + public function __invoke(RegistrationInterface $registration, ?\SimpleXMLElement $config = null): void { require_once __DIR__.'/Handler/HeaderBagHandler.php'; require_once __DIR__.'/Handler/ContainerHandler.php'; @@ -39,13 +39,13 @@ public function __invoke(RegistrationInterface $api, ?\SimpleXMLElement $config require_once __DIR__.'/Handler/DoctrineQueryBuilderHandler.php'; require_once __DIR__.'/Provider/FormGetErrorsReturnTypeProvider.php'; - $api->registerHooksFromClass(HeaderBagHandler::class); - $api->registerHooksFromClass(ConsoleHandler::class); - $api->registerHooksFromClass(ContainerDependencyHandler::class); - $api->registerHooksFromClass(RequiredSetterHandler::class); + $registration->registerHooksFromClass(HeaderBagHandler::class); + $registration->registerHooksFromClass(ConsoleHandler::class); + $registration->registerHooksFromClass(ContainerDependencyHandler::class); + $registration->registerHooksFromClass(RequiredSetterHandler::class); if (class_exists(\Doctrine\ORM\QueryBuilder::class)) { - $api->registerHooksFromClass(DoctrineQueryBuilderHandler::class); + $registration->registerHooksFromClass(DoctrineQueryBuilderHandler::class); } if (class_exists(AnnotationRegistry::class)) { @@ -54,10 +54,10 @@ public function __invoke(RegistrationInterface $api, ?\SimpleXMLElement $config /** @psalm-suppress DeprecatedMethod */ AnnotationRegistry::registerLoader('class_exists'); } - $api->registerHooksFromClass(DoctrineRepositoryHandler::class); + $registration->registerHooksFromClass(DoctrineRepositoryHandler::class); require_once __DIR__.'/Handler/AnnotationHandler.php'; - $api->registerHooksFromClass(AnnotationHandler::class); + $registration->registerHooksFromClass(AnnotationHandler::class); } if (isset($config->containerXml)) { @@ -83,14 +83,14 @@ public function __invoke(RegistrationInterface $api, ?\SimpleXMLElement $config require_once __DIR__.'/Handler/ParameterBagHandler.php'; ParameterBagHandler::init($containerMeta); - $api->registerHooksFromClass(ParameterBagHandler::class); + $registration->registerHooksFromClass(ParameterBagHandler::class); } - $api->registerHooksFromClass(ContainerHandler::class); + $registration->registerHooksFromClass(ContainerHandler::class); - $this->addStubs($api, __DIR__.'/Stubs/common'); - $this->addStubs($api, __DIR__.'/Stubs/'.Kernel::MAJOR_VERSION); - $this->addStubs($api, __DIR__.'/Stubs/php'); + $this->addStubs($registration, __DIR__.'/Stubs/common'); + $this->addStubs($registration, __DIR__.'/Stubs/'.Kernel::MAJOR_VERSION); + $this->addStubs($registration, __DIR__.'/Stubs/php'); if (isset($config->twigCachePath)) { $twig_cache_path = getcwd().DIRECTORY_SEPARATOR.ltrim((string) $config->twigCachePath, DIRECTORY_SEPARATOR); @@ -99,15 +99,15 @@ public function __invoke(RegistrationInterface $api, ?\SimpleXMLElement $config } require_once __DIR__.'/Twig/CachedTemplatesTainter.php'; - $api->registerHooksFromClass(CachedTemplatesTainter::class); + $registration->registerHooksFromClass(CachedTemplatesTainter::class); require_once __DIR__.'/Twig/CachedTemplatesMapping.php'; - $api->registerHooksFromClass(CachedTemplatesMapping::class); + $registration->registerHooksFromClass(CachedTemplatesMapping::class); CachedTemplatesMapping::setCachePath($twig_cache_path); } require_once __DIR__.'/Twig/AnalyzedTemplatesTainter.php'; - $api->registerHooksFromClass(AnalyzedTemplatesTainter::class); + $registration->registerHooksFromClass(AnalyzedTemplatesTainter::class); if (isset($config->twigRootPath)) { $twig_root_path = trim((string) $config->twigRootPath, DIRECTORY_SEPARATOR); @@ -119,20 +119,20 @@ public function __invoke(RegistrationInterface $api, ?\SimpleXMLElement $config TemplateFileAnalyzer::setTemplateRootPath($twig_root_path); } - $api->registerHooksFromClass(FormGetErrorsReturnTypeProvider::class); + $registration->registerHooksFromClass(FormGetErrorsReturnTypeProvider::class); } - private function addStubs(RegistrationInterface $api, string $path): void + private function addStubs(RegistrationInterface $registration, string $path): void { if (!is_dir($path)) { - // e.g. looking for stubs for version 3, but there aren't any at time of writing, so don't try and load them. + // e.g., looking for stubs for version 3, but there aren't any at time of writing, so don't try and load them. return; } $a = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path)); foreach ($a as $file) { if (!$file->isDir()) { - $api->addStubFile($file->getPathname()); + $registration->addStubFile($file->getPathname()); } } } diff --git a/src/Stubs/common/Component/PropertyAccess/PropertyAccessorInterface.stubphp b/src/Stubs/common/Component/PropertyAccess/PropertyAccessorInterface.stubphp index db8bd35..607809e 100644 --- a/src/Stubs/common/Component/PropertyAccess/PropertyAccessorInterface.stubphp +++ b/src/Stubs/common/Component/PropertyAccess/PropertyAccessorInterface.stubphp @@ -11,7 +11,7 @@ interface PropertyAccessorInterface * @psalm-param mixed $value * @psalm-param-out T $objectOrArray */ - public function setValue(&$objectOrArray, $propertyPath, $value); + public function setValue(object|array &$objectOrArray, string|PropertyPathInterface $propertyPath, mixed $value); /** * @param object|array $objectOrArray @@ -19,7 +19,7 @@ interface PropertyAccessorInterface * * @return mixed */ - public function getValue($objectOrArray, $propertyPath); + public function getValue(object|array $objectOrArray, string|PropertyPathInterface $propertyPath): mixed; /** * @param object|array $objectOrArray @@ -27,7 +27,7 @@ interface PropertyAccessorInterface * * @return bool */ - public function isWritable($objectOrArray, $propertyPath); + public function isWritable(object|array $objectOrArray, string|PropertyPathInterface $propertyPath): bool; /** * @param object|array $objectOrArray @@ -35,5 +35,5 @@ interface PropertyAccessorInterface * * @return bool */ - public function isReadable($objectOrArray, $propertyPath); + public function isReadable(object|array $objectOrArray, string|PropertyPathInterface $propertyPath): bool; } diff --git a/src/Stubs/common/Component/Security/Core/Authorization/Voter/Voter.stubphp b/src/Stubs/common/Component/Security/Core/Authorization/Voter/Voter.stubphp deleted file mode 100644 index 57d8841..0000000 --- a/src/Stubs/common/Component/Security/Core/Authorization/Voter/Voter.stubphp +++ /dev/null @@ -1,42 +0,0 @@ -container->get(SomeService::class)->do(); + $container->get(SomeService::class)->do(); } } """ @@ -30,19 +33,11 @@ Feature: Container service Scenario: Asserting psalm recognizes return type of service got via 'ContainerInterface::get()'. Given I have the following code """ - container->get(SomeService::class)->nosuchmethod(); + $container->get(SomeService::class)->nosuchmethod(); } } """ @@ -55,18 +50,16 @@ Feature: Container service Scenario: Container get(self::class) should not crash Given I have the following code """ - container->get(self::class)->index(); + $container->get(self::class)->index(); } } """ When I run Psalm Then I see these errors - | Type | Message | - | MissingReturnType | Method SomeController::index does not have a return type, expecting void | + | Type | Message | + | MixedMethodCall | Cannot determine the type of the object on the left hand side of this expression | + And I see no other errors diff --git a/tests/acceptance/acceptance/ContainerXml.feature b/tests/acceptance/acceptance/ContainerXml.feature index 8c4f8ab..8021a8e 100644 --- a/tests/acceptance/acceptance/ContainerXml.feature +++ b/tests/acceptance/acceptance/ContainerXml.feature @@ -7,19 +7,22 @@ Feature: Container XML config """ ../../tests/acceptance/container.xml """ + And I have the following code preamble + """ + container->get('service_container')->has('lorem'); + return $container->get('service_container')->has('lorem'); } } """ @@ -29,15 +32,11 @@ Feature: Container XML config Scenario: Psalm emits when service ID not found in container' Given I have the following code """ - container->get('not_a_service')->has('lorem'); + $container->get('not_a_service')->has('lorem'); } } """ @@ -49,18 +48,12 @@ Feature: Container XML config Scenario: Using service both via alias and class const Given I have the following code """ - container->get('http_kernel'); - $this->container->get(HttpKernelInterface::class); + $container->get('http_kernel'); + $container->get(HttpKernelInterface::class); } } """ @@ -70,15 +63,11 @@ Feature: Container XML config Scenario: Using private service Given I have the following code """ - container->get('private_service'); + $container->get('private_service'); } } """ diff --git a/tests/acceptance/acceptance/HeaderBag.feature b/tests/acceptance/acceptance/HeaderBag.feature index 157278c..ae1a753 100644 --- a/tests/acceptance/acceptance/HeaderBag.feature +++ b/tests/acceptance/acceptance/HeaderBag.feature @@ -2,7 +2,7 @@ Feature: Header get Background: - Given I have issue handler "UnusedFunctionCall" suppressed + Given I have issue handler "UnusedVariable" suppressed And I have Symfony plugin enabled And I have the following code preamble """ @@ -11,24 +11,23 @@ Feature: Header get use Symfony\Component\HttpFoundation\Request; """ - Scenario: HeaderBag get method return type should return `?string` (unless third argument is provided for < Sf4.4) + Scenario: HeaderBag get method return type should return `?string` Given I have the following code """ class App { public function index(Request $request): void { - $string = $request->headers->get('nullable_string'); - if (!$string) { - return; - } - - trim($string); + /** @psalm-trace $nullableString */ + $nullableString = $request->headers->get('nullable_string'); } } """ When I run Psalm - Then I see no errors + Then I see these errors + | Type | Message | + | Trace | $nullableString: null\|string | + And I see no other errors Scenario: HeaderBag get method return type should return `string` if default value is provided with string Given I have the following code @@ -37,11 +36,13 @@ Feature: Header get { public function index(Request $request): void { + /** @psalm-trace $string */ $string = $request->headers->get('string', 'string'); - - trim($string); } } """ When I run Psalm - Then I see no errors + Then I see these errors + | Type | Message | + | Trace | $string: string | + And I see no other errors diff --git a/tests/acceptance/acceptance/NamingConventions.feature b/tests/acceptance/acceptance/NamingConventions.feature index a376170..928149f 100644 --- a/tests/acceptance/acceptance/NamingConventions.feature +++ b/tests/acceptance/acceptance/NamingConventions.feature @@ -7,19 +7,22 @@ Feature: Naming conventions """ ../../tests/acceptance/container.xml """ + And I have the following code preamble + """ + container->get('service_container')->has('lorem'); + return $container->get('service_container')->has('lorem'); } } """ @@ -29,15 +32,11 @@ Feature: Naming conventions Scenario: Detects service naming convention violation Given I have the following code """ - container->get('wronglyNamedService'); + $container->get('wronglyNamedService'); } } """ @@ -50,17 +49,11 @@ Feature: Naming conventions Scenario: No service naming convention violation when using FQCNs Given I have the following code """ - container->get(HttpKernelInterface::class); + $container->get(HttpKernelInterface::class); } } """ @@ -70,15 +63,11 @@ Feature: Naming conventions Scenario: No naming convention violation for parameter Given I have the following code """ - container->getParameter('kernel.cache_dir'); + $container->getParameter('kernel.cache_dir'); } } """ @@ -88,15 +77,11 @@ Feature: Naming conventions Scenario: Detects parameter naming convention violation Given I have the following code """ - container->getParameter('wronglyNamedParameter'); + $container->getParameter('wronglyNamedParameter'); } } """ @@ -109,15 +94,11 @@ Feature: Naming conventions Scenario: No parameter naming convention violation when using environment variables Given I have the following code """ - container->getParameter('env(SOME_ENV_VAR)'); + $container->getParameter('env(SOME_ENV_VAR)'); } } """ diff --git a/tests/acceptance/acceptance/PropertyAccessorInterface.feature b/tests/acceptance/acceptance/PropertyAccessorInterface.feature index 79499b9..8503fe6 100644 --- a/tests/acceptance/acceptance/PropertyAccessorInterface.feature +++ b/tests/acceptance/acceptance/PropertyAccessorInterface.feature @@ -9,6 +9,7 @@ Feature: PropertyAccessorInterface setValue($company, 'name', 'Acme v2'); - array_key_exists('name', $company); + /** @psalm-trace $company */ """ When I run Psalm - Then I see no errors + Then I see these errors + | Type | Message | + | Trace | $company: array | + And I see no other errors Scenario: Set value keeps object instance if an object is passed Given I have the following code @@ -36,28 +40,26 @@ Feature: PropertyAccessorInterface $propertyAccessor->setValue($company, 'name', 'Acme v2'); - echo $company->name; + /** @psalm-trace $company */ """ When I run Psalm - Then I see no errors + Then I see these errors + | Type | Message | + | Trace | $company: Company | + And I see no other errors Scenario: Set value does not modify the propertyAccessor variable Given I have the following code """ - use Symfony\Component\PropertyAccess\PropertyAccessorInterface; - class Company { - /** - * @var PropertyAccessorInterface - */ - private $propertyAccessor; - - public function __construct(PropertyAccessorInterface $propertyAccessor) { - $this->propertyAccessor = $propertyAccessor; + public function __construct( + private PropertyAccessorInterface $propertyAccessor, + ) { } - public function doThings(Company $thing): void { + public function doThings(Company $thing): void + { $this->propertyAccessor->setValue($thing, 'foo', 'bar'); $this->propertyAccessor->setValue($thing, 'foo', 'bar'); } diff --git a/tests/acceptance/acceptance/Voter.feature b/tests/acceptance/acceptance/Voter.feature deleted file mode 100644 index 2dca842..0000000 --- a/tests/acceptance/acceptance/Voter.feature +++ /dev/null @@ -1,40 +0,0 @@ -@symfony-common -Feature: Voter abstract class - - Background: - Given I have Symfony plugin enabled - And I have the following code preamble - """ - Date: Wed, 5 Jun 2024 19:00:38 +0200 Subject: [PATCH 10/14] Fix using service locators with int keys (#337) Co-authored-by: Farhad Safarov --- src/Symfony/ContainerMeta.php | 5 ++++- tests/acceptance/container.xml | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Symfony/ContainerMeta.php b/src/Symfony/ContainerMeta.php index e7d86bb..433c021 100644 --- a/src/Symfony/ContainerMeta.php +++ b/src/Symfony/ContainerMeta.php @@ -125,7 +125,10 @@ private function init(array $containerXmlPaths): void } } - private function addServiceLocator(string $key, string $id, Reference $reference): void + /** + * @param array-key $id + */ + private function addServiceLocator(string $key, mixed $id, Reference $reference): void { $this->serviceLocators[$key][$id] = (string) $reference; diff --git a/tests/acceptance/container.xml b/tests/acceptance/container.xml index 409c7d9..7b34076 100644 --- a/tests/acceptance/container.xml +++ b/tests/acceptance/container.xml @@ -84,6 +84,7 @@ + From 179db305f49f2b2737c65d42c1f9f9c61a722fa2 Mon Sep 17 00:00:00 2001 From: michnovka Date: Wed, 5 Jun 2024 19:01:18 +0200 Subject: [PATCH 11/14] Update CacheInterface.stubphp (#333) * Update CacheInterface.stubphp * cs fix --------- Co-authored-by: Farhad Safarov --- src/Stubs/common/Contracts/Cache/CacheInterface.stubphp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Stubs/common/Contracts/Cache/CacheInterface.stubphp b/src/Stubs/common/Contracts/Cache/CacheInterface.stubphp index ab725a8..2dddc30 100644 --- a/src/Stubs/common/Contracts/Cache/CacheInterface.stubphp +++ b/src/Stubs/common/Contracts/Cache/CacheInterface.stubphp @@ -9,7 +9,7 @@ interface CacheInterface /** * @template T * - * @psalm-param CallbackInterface|callable(CacheItemInterface, bool): T $callback + * @psalm-param (callable(CacheItemInterface, bool): T)|(callable(ItemInterface, bool): T)|CallbackInterface $callback * @psalm-return T */ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null); From 57163219b52113111c4929b68f33a87ab10533ec Mon Sep 17 00:00:00 2001 From: Farhad Safarov Date: Wed, 5 Jun 2024 21:59:28 +0300 Subject: [PATCH 12/14] Test Symfony 7 versions (#343) --- .github/workflows/integrate.yaml | 7 +++++-- .../acceptance/PropertyAccessorInterface.feature | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index ebd2abc..b0f637a 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -69,11 +69,14 @@ jobs: symfony-version: - 6 - - 7 + - 7.0 + - 7.1 exclude: - php-version: 8.1 - symfony-version: 7 + symfony-version: 7.0 + - php-version: 8.1 + symfony-version: 7.1 steps: - name: "Checkout" diff --git a/tests/acceptance/acceptance/PropertyAccessorInterface.feature b/tests/acceptance/acceptance/PropertyAccessorInterface.feature index 8503fe6..f0efe4b 100644 --- a/tests/acceptance/acceptance/PropertyAccessorInterface.feature +++ b/tests/acceptance/acceptance/PropertyAccessorInterface.feature @@ -1,4 +1,4 @@ -@symfony-common +@symfony-7 Feature: PropertyAccessorInterface Background: From 0fe09bf25cb64deef73e8eab7be89f9401d2f5b8 Mon Sep 17 00:00:00 2001 From: Farhad Safarov Date: Thu, 6 Jun 2024 00:20:57 +0300 Subject: [PATCH 13/14] Handle Symfony 7.1 containerxml (#344) fixes https://github.com/psalm/psalm-plugin-symfony/issues/336 --- src/Symfony/ContainerMeta.php | 12 +++++++++--- tests/acceptance/container.xml | 11 +++++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Symfony/ContainerMeta.php b/src/Symfony/ContainerMeta.php index 433c021..f64347b 100644 --- a/src/Symfony/ContainerMeta.php +++ b/src/Symfony/ContainerMeta.php @@ -97,14 +97,20 @@ private function init(array $containerXmlPaths): void $xml->load($containerXmlPath); foreach ($this->container->getDefinitions() as $definition) { + if ($definition->hasTag('container.service_locator')) { + continue; + } + $definitionFactory = $definition->getFactory(); if ($definition->hasTag('container.service_locator_context') && is_array($definitionFactory)) { /** @var Reference $reference */ $reference = $definitionFactory[0]; $id = $definition->getTag('container.service_locator_context')[0]['id']; - $this->classLocators[$this->container->getDefinition($id)->getClass() ?? $id] = (string) $reference; - } elseif ($definition->hasTag('container.service_locator')) { - continue; + try { + $this->classLocators[$this->container->getDefinition($id)->getClass() ?? $id] = (string) $reference; + } catch (ServiceNotFoundException) { + continue; + } } elseif (null !== $className = $definition->getClass()) { $this->classNames[] = $className; } diff --git a/tests/acceptance/container.xml b/tests/acceptance/container.xml index 7b34076..3d20c51 100644 --- a/tests/acceptance/container.xml +++ b/tests/acceptance/container.xml @@ -46,7 +46,7 @@ - + @@ -66,7 +66,7 @@ - + @@ -87,5 +87,12 @@ + + + + kernel::registerContainerConfiguration() + + + From 58e109257764e8e7eab10a43a4212bbd70435f67 Mon Sep 17 00:00:00 2001 From: Farhad Safarov Date: Thu, 6 Jun 2024 18:34:33 +0300 Subject: [PATCH 14/14] Deduct return type from environment variable processors (#346) Fixes https://github.com/psalm/psalm-plugin-symfony/issues/345 --- src/Handler/ParameterBagHandler.php | 26 +++++----------- src/Symfony/ContainerMeta.php | 38 ++++++++++++++++++++++++ tests/acceptance/container.xml | 6 ++++ tests/unit/Symfony/ContainerMetaTest.php | 27 ++++++++++++++++- 4 files changed, 77 insertions(+), 20 deletions(-) diff --git a/src/Handler/ParameterBagHandler.php b/src/Handler/ParameterBagHandler.php index f8a84ad..46c01d1 100644 --- a/src/Handler/ParameterBagHandler.php +++ b/src/Handler/ParameterBagHandler.php @@ -42,30 +42,18 @@ public static function afterMethodCallAnalysis(AfterMethodCallAnalysisEvent $eve } $argument = $expr->args[0]->value->value; + try { - $parameter = self::$containerMeta->getParameter($argument); - } catch (ParameterNotFoundException $e) { + $parameterTypes = self::$containerMeta->guessParameterType($argument); + } catch (ParameterNotFoundException) { // maybe emit ParameterNotFound issue return; } - // @todo find a better way to calculate return type - switch (gettype($parameter)) { - case 'string': - $event->setReturnTypeCandidate(new Union([Atomic::create('string')])); - break; - case 'boolean': - $event->setReturnTypeCandidate(new Union([Atomic::create('bool')])); - break; - case 'integer': - $event->setReturnTypeCandidate(new Union([Atomic::create('int')])); - break; - case 'double': - $event->setReturnTypeCandidate(new Union([Atomic::create('float')])); - break; - case 'array': - $event->setReturnTypeCandidate(new Union([Atomic::create('array')])); - break; + if (null === $parameterTypes || [] === $parameterTypes) { + return; } + + $event->setReturnTypeCandidate(new Union(array_map(fn (string $parameterType): Atomic => Atomic::create($parameterType), $parameterTypes))); } } diff --git a/src/Symfony/ContainerMeta.php b/src/Symfony/ContainerMeta.php index f64347b..c2a6c16 100644 --- a/src/Symfony/ContainerMeta.php +++ b/src/Symfony/ContainerMeta.php @@ -9,7 +9,9 @@ use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\EnvVarProcessor; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Reference; @@ -69,6 +71,29 @@ public function getParameter(string $key): mixed return $this->container->getParameter($key); } + /** + * @throw ParameterNotFoundException + * + * @return ?array + */ + public function guessParameterType(string $key): ?array + { + $parameter = $this->getParameter($key); + + if (is_string($parameter) && str_starts_with($parameter, '%env(')) { + return $this->envParameterType($parameter); + } + + return match (gettype($parameter)) { + 'string' => ['string'], + 'boolean' => ['bool'], + 'integer' => ['int'], + 'double' => ['float'], + 'array' => ['array'], + default => null, + }; + } + /** * @return array */ @@ -168,4 +193,17 @@ private function getDefinition(string $id): Definition return $definition; } + + private function envParameterType(string $envParameter): ?array + { + // extract bool from %env(bool:ENV_PARAM)%, string from %env(string:ENV_PARAM)% + $type = preg_match('/^%env\((\w+):/', $envParameter, $matches) ? $matches[1] : null; + + $envVarTypes = EnvVarProcessor::getProvidedTypes(); + if (!isset($envVarTypes[$type])) { + return null; + } + + return explode('|', $envVarTypes[$type]); + } } diff --git a/tests/acceptance/container.xml b/tests/acceptance/container.xml index 3d20c51..4e12de5 100644 --- a/tests/acceptance/container.xml +++ b/tests/acceptance/container.xml @@ -21,6 +21,12 @@ + %env(bool:ENV_PARAM)% + %env(string:ENV_PARAM)% + %env(custom_type:ENV_PARAM)% + %env(json:ENV_PARAM)% + %env(enum:ENV_PARAM)% + %env(url:ENV_PARAM)% diff --git a/tests/unit/Symfony/ContainerMetaTest.php b/tests/unit/Symfony/ContainerMetaTest.php index 9f380d9..7120aab 100644 --- a/tests/unit/Symfony/ContainerMetaTest.php +++ b/tests/unit/Symfony/ContainerMetaTest.php @@ -117,7 +117,32 @@ public function testGetParameter(): void ], $this->containerMeta->getParameter('nested_collection')); } - public function testGetParameterP(): void + /** + * @dataProvider guessParameterTypeProvider + */ + public function testGuessParameterType(?array $expectedTypes, string $parameterName): void + { + $this->assertSame($expectedTypes, $this->containerMeta->guessParameterType($parameterName)); + } + + public function guessParameterTypeProvider(): iterable + { + yield [['string'], 'kernel.environment']; + yield [['bool'], 'debug_enabled']; + yield [['string'], 'version']; + yield [['int'], 'integer_one']; + yield [['float'], 'pi']; + yield [['array'], 'collection1']; + yield [['array'], 'nested_collection']; + yield [['bool'], 'env_param_bool']; + yield [['string'], 'env_param_string']; + yield [null, 'env_param_custom_type']; + yield [['array'], 'env_param_json']; + yield [['BackedEnum'], 'env_param_enum']; + yield [['array'], 'env_param_url']; + } + + public function testGetParameterNonExistent(): void { $this->expectException(ParameterNotFoundException::class); $this->containerMeta->getParameter('non_existent');