From 5e96785c69c5d6ec72dfdeaf9ae79b7b391b3545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=A4u=C3=9Fler?= Date: Thu, 20 Nov 2025 10:44:17 +0100 Subject: [PATCH] [FEATURE] Allow passing set configurator to `Config::createSet()` --- README.md | 26 +++++++------ src/Config/Config.php | 36 ++++++++++++++++-- src/Exception/SetConfiguratorIsNotValid.php | 41 +++++++++++++++++++++ tests/unit/Config/ConfigTest.php | 36 +++++++++++++++--- 4 files changed, 119 insertions(+), 20 deletions(-) create mode 100644 src/Exception/SetConfiguratorIsNotValid.php diff --git a/README.md b/README.md index 59f2616..83813be 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ The package provides a PHP configuration API for PHPStan. Add this to your `phpstan.php` file: ```php -# phpstan.php +// phpstan.php use EliasHaeussler\PHPStanConfig; @@ -110,18 +110,22 @@ $config->treatPhpDocTypesAsCertain(); $config->useCustomRule('ignoreAnnotationWithoutErrorIdentifier', false); // Include Doctrine set -$config->createSet(PHPStanConfig\Set\DoctrineSet::class) - ->withObjectManagerLoader('tests/object-manager.php') - ->withOrmRepositoryClass(\MyApp\Doctrine\BetterEntityRepository::class) - ->withOdmRepositoryClass(\MyApp\Doctrine\BetterDocumentRepository::class) -; +$config->createSet( + static function (PHPStanConfig\Set\DoctrineSet $set): void { + $set->withObjectManagerLoader('tests/object-manager.php'); + $set->withOrmRepositoryClass(\MyApp\Doctrine\BetterEntityRepository::class); + $set->withOdmRepositoryClass(\MyApp\Doctrine\BetterDocumentRepository::class); + }, +); // Include Symfony set -$config->createSet(PHPStanConfig\Set\SymfonySet::class) - ->withConsoleApplicationLoader('tests/build/console-application.php') - ->withContainerXmlPath('var/cache/test-container.xml') - ->disableConstantHassers() -; +$config->createSet( + static function (PHPStanConfig\Set\SymfonySet $set): void { + $set->withConsoleApplicationLoader('tests/build/console-application.php'); + $set->withContainerXmlPath('var/cache/test-container.xml'); + $set->disableConstantHassers(); + }, +); // Include TYPO3 set $typo3Set = PHPStanConfig\Set\TYPO3Set::create() diff --git a/src/Config/Config.php b/src/Config/Config.php index 4921c17..d0a563e 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -23,12 +23,16 @@ namespace EliasHaeussler\PHPStanConfig\Config; +use Closure; use EliasHaeussler\PHPStanConfig\Enums; use EliasHaeussler\PHPStanConfig\Exception; use EliasHaeussler\PHPStanConfig\Resource; use EliasHaeussler\PHPStanConfig\Set; +use ReflectionFunction; +use ReflectionNamedType; use function array_map; +use function is_a; use function preg_quote; use function sprintf; use function str_starts_with; @@ -70,21 +74,45 @@ public static function create(string $projectDirectory): self /** * @template T of Set\Set * - * @param class-string $className + * @param Closure(T, self): void|class-string $configuratorOrClassName * - * @return T + * @throws Exception\SetConfiguratorIsNotValid */ - public function createSet(string $className): Set\Set + public function createSet(Closure|string $configuratorOrClassName): self { + if ($configuratorOrClassName instanceof Closure) { + $reflection = new ReflectionFunction($configuratorOrClassName); + $type = $reflection->getParameters()[0]->getType(); + + if (!($type instanceof ReflectionNamedType)) { + throw new Exception\SetConfiguratorIsNotValid(); + } + + $className = $type->getName(); + $configurator = $configuratorOrClassName; + } else { + $className = $configuratorOrClassName; + $configurator = null; + } + + if (!is_a($className, Set\Set::class, true)) { + throw new Exception\SetConfiguratorIsNotValid(); + } + + /** @var T $set */ $set = $className::create(); if ($set instanceof Set\PathAwareSet) { $set->setProjectPath($this->projectPath); } + if (null !== $configurator) { + $configurator($set, $this); + } + $this->withSets($set); - return $set; + return $this; } public function withSets(Set\Set ...$sets): self diff --git a/src/Exception/SetConfiguratorIsNotValid.php b/src/Exception/SetConfiguratorIsNotValid.php new file mode 100644 index 0000000..3bed716 --- /dev/null +++ b/src/Exception/SetConfiguratorIsNotValid.php @@ -0,0 +1,41 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace EliasHaeussler\PHPStanConfig\Exception; + +/** + * SetConfiguratorIsNotValid. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class SetConfiguratorIsNotValid extends Exception +{ + public function __construct() + { + parent::__construct( + 'The given set configurator does not properly define the required set as first parameter.', + 1763631052, + ); + } +} diff --git a/tests/unit/Config/ConfigTest.php b/tests/unit/Config/ConfigTest.php index 9dacfe7..3c0c683 100644 --- a/tests/unit/Config/ConfigTest.php +++ b/tests/unit/Config/ConfigTest.php @@ -43,20 +43,46 @@ protected function setUp(): void $this->subject = Src\Config\Config::create('/my-project'); } + #[Framework\Attributes\Test] + public function createSetCreatesAndReturnsGivenSetByClassName(): void + { + $this->subject->createSet(Tests\Fixtures\DummySet::class); + + self::assertSame( + [ + 'includes' => [], + 'parameters' => [ + 'foo' => 'baz', + ], + ], + $this->subject->toArray(), + ); + } + #[Framework\Attributes\Test] public function createSetInitializesAndReturnsGivenSet(): void { - $expected = Tests\Fixtures\DummySet::create(); - $expected->setProjectPath(new Src\Resource\Path('/my-project')); + $validated = false; + + $this->subject->createSet( + static function (Tests\Fixtures\DummySet $set) use (&$validated) { + $validated = true; - $actual = $this->subject->createSet(Tests\Fixtures\DummySet::class); + $expected = Tests\Fixtures\DummySet::create(); + $expected->setProjectPath(new Src\Resource\Path('/my-project')); - self::assertEquals($expected, $actual); + self::assertEquals($expected, $set); + + $set->parameters = ['baz' => 'foo']; + }, + ); + + self::assertTrue($validated); self::assertSame( [ 'includes' => [], 'parameters' => [ - 'foo' => 'baz', + 'baz' => 'foo', ], ], $this->subject->toArray(),