diff --git a/bin/ecs.php b/bin/ecs.php index a661abad58..e51020001f 100755 --- a/bin/ecs.php +++ b/bin/ecs.php @@ -156,7 +156,7 @@ public function loadIfNotLoadedYet(string $file): void } /** @var EasyCodingStandardConsoleApplication $application */ -$application = $container->get(EasyCodingStandardConsoleApplication::class); +$application = $container->make(EasyCodingStandardConsoleApplication::class); $statusCode = $application->run(); exit($statusCode); diff --git a/composer.json b/composer.json index cfba02408c..548d1d823f 100644 --- a/composer.json +++ b/composer.json @@ -15,9 +15,8 @@ "php": ">=8.3", "composer/pcre": "^3.3.2", "composer/xdebug-handler": "^3.0.5", - "entropy/entropy": "dev-main", + "entropy/entropy": "dev-feature/ecs-console-and-container", "friendsofphp/php-cs-fixer": "^3.95.1|^4.0", - "illuminate/container": "12.39.*", "nette/utils": "4.0.8", "sebastian/diff": "^6.0|^7.0", "squizlabs/php_codesniffer": "^4.0.1", @@ -56,6 +55,12 @@ "tests/functions.php" ] }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/TomasVotruba/entropy.git" + } + ], "config": { "sort-packages": true, "platform-check": false, @@ -85,12 +90,5 @@ "symfony/event-dispatcher": "7.*", "symfony/process": "7.*", "symfony/stopwatch": "7.*" - }, - "extra": { - "patches": { - "illuminate/container": [ - "patches/illuminate-container-container-php.patch" - ] - } } } diff --git a/patches/illuminate-container-container-php.patch b/patches/illuminate-container-container-php.patch deleted file mode 100644 index 9b072c9660..0000000000 --- a/patches/illuminate-container-container-php.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- /dev/null -+++ ../Container.php -@@ -805,7 +805,7 @@ - // If the requested type is registered as a singleton we'll want to cache off - // the instances in "memory" so we can return it later without creating an - // entirely new instance of an object on each subsequent request for it. -- if ($this->isShared($abstract) && ! $needsContextualBuild) { -+ if (! $needsContextualBuild) { - $this->instances[$abstract] = $object; - } - diff --git a/src/Application/FileProcessorCollector.php b/src/Application/FileProcessorCollector.php index b0cb726774..5fa6676d20 100644 --- a/src/Application/FileProcessorCollector.php +++ b/src/Application/FileProcessorCollector.php @@ -18,8 +18,10 @@ final class FileProcessorCollector /** * orders matters, so Fixer can cleanup after Sniffer */ - public function __construct(SniffFileProcessor $sniffFileProcessor, FixerFileProcessor $fixerFileProcessor) - { + public function __construct( + SniffFileProcessor $sniffFileProcessor, + FixerFileProcessor $fixerFileProcessor + ) { $this->fileProcessors[] = $sniffFileProcessor; $this->fileProcessors[] = $fixerFileProcessor; } diff --git a/src/Caching/FileHashComputer.php b/src/Caching/FileHashComputer.php index e34a41230e..559037b3f9 100644 --- a/src/Caching/FileHashComputer.php +++ b/src/Caching/FileHashComputer.php @@ -24,8 +24,8 @@ public function computeConfig(string $filePath): string $ecsConfig = new ECSConfig(); $callable($ecsConfig); - // hash the container setup - $fileHash = sha1(Json::encode($ecsConfig->getBindings())); + // hash the registered checkers and their configuration + $fileHash = sha1(Json::encode($ecsConfig->getCheckerConfiguration())); return sha1($fileHash . SimpleParameterProvider::hash() . StaticVersionResolver::PACKAGE_VERSION); } diff --git a/src/Config/ECSConfig.php b/src/Config/ECSConfig.php index fbe8e7a5a6..c43fedd9ef 100644 --- a/src/Config/ECSConfig.php +++ b/src/Config/ECSConfig.php @@ -4,7 +4,7 @@ namespace Symplify\EasyCodingStandard\Config; -use Illuminate\Container\Container; +use Entropy\Container\Container; use Override; use PHP_CodeSniffer\Sniffs\Sniff; use PhpCsFixer\Fixer\ConfigurableFixerInterface; @@ -14,7 +14,6 @@ use PhpCsFixer\RuleSet\RuleSet; use PhpCsFixer\WhitespacesFixerConfig; use Symplify\EasyCodingStandard\Configuration\ECSConfigBuilder; -use Symplify\EasyCodingStandard\Contract\Console\Output\OutputFormatterInterface; use Symplify\EasyCodingStandard\DependencyInjection\CompilerPass\ConflictingCheckersCompilerPass; use Symplify\EasyCodingStandard\DependencyInjection\CompilerPass\RemoveExcludedCheckersCompilerPass; use Symplify\EasyCodingStandard\DependencyInjection\CompilerPass\RemoveMutualCheckersCompilerPass; @@ -29,9 +28,34 @@ final class ECSConfig extends Container { /** - * @var string[] + * Registered checkers, mapped to their configuration (empty array = no configuration). + * + * @var array, mixed[]> */ - private const array AUTOTAG_INTERFACES = [Sniff::class, FixerInterface::class, OutputFormatterInterface::class]; + private array $checkerConfiguration = []; + + /** + * Checkers removed by compiler passes (skip, mutual exclusion, …). + * + * @var array, true> + */ + private array $removedCheckers = []; + + /** + * Checkers already registered as a container service (registered exactly once, + * even when a class is declared in both a set and explicitly). + * + * @var array, true> + */ + private array $checkerServiceRegistered = []; + + /** + * Registration order, with duplicates preserved: a checker declared both in a + * set and explicitly is listed twice (both share the last-wins configuration). + * + * @var list> + */ + private array $checkerRegistrationOrder = []; public static function configure(): ECSConfigBuilder { @@ -76,18 +100,7 @@ public function rule(string $checkerClass): void { $this->assertCheckerClass($checkerClass); - $this->singleton($checkerClass); - $this->autowireWhitespaceAwareFixer($checkerClass); - - if (is_a($checkerClass, ConfigurableFixerInterface::class, true)) { - $this->extend( - $checkerClass, - static function (ConfigurableFixerInterface $configurableFixer): ConfigurableFixerInterface { - $configurableFixer->configure([]); - return $configurableFixer; - } - ); - } + $this->registerChecker($checkerClass, []); } /** @@ -110,30 +123,11 @@ public function ruleWithConfiguration(string $checkerClass, array $configuration { $this->assertCheckerClass($checkerClass); - $this->singleton($checkerClass); - - $this->autowireWhitespaceAwareFixer($checkerClass); - if (is_a($checkerClass, FixerInterface::class, true)) { Assert::isAOf($checkerClass, ConfigurableFixerInterface::class); - $this->extend($checkerClass, static function (ConfigurableFixerInterface $configurableFixer) use ( - $configuration - ): ConfigurableFixerInterface { - $configurableFixer->configure($configuration); - return $configurableFixer; - }); } - if (is_a($checkerClass, Sniff::class, true)) { - $this->extend($checkerClass, static function (Sniff $sniff) use ($configuration): Sniff { - foreach ($configuration as $propertyName => $value) { - Assert::propertyExists($sniff, $propertyName); - $sniff->{$propertyName} = $value; - } - - return $sniff; - }); - } + $this->registerChecker($checkerClass, $configuration); } /** @@ -253,20 +247,136 @@ public function boot(): void } /** - * @param string $abstract + * Checkers are returned in registration order with duplicates preserved (a class + * declared both in a set and explicitly appears twice, sharing one instance); + * every other service is resolved by the parent container. + * + * @template TType as object + * + * @param class-string $contractClass + * @return array */ #[Override] - public function singleton($abstract, mixed $concrete = null): void + public function findByContract(string $contractClass): array + { + // genuine (non-checker) services from the parent container + $instances = []; + foreach (parent::findByContract($contractClass) as $class => $instance) { + if (isset($this->checkerConfiguration[$class])) { + // checkers are handled below, in registration order with duplicates + continue; + } + + $instances[] = $instance; + } + + // checkers, in registration order, keeping duplicates and honouring removals + $checkerInstances = []; + foreach ($this->checkerRegistrationOrder as $checkerClass) { + if (isset($this->removedCheckers[$checkerClass])) { + continue; + } + + // avoid building checkers that cannot match the requested contract + if (! is_a($checkerClass, $contractClass, true)) { + continue; + } + + $checkerInstances[] = $this->make($checkerClass); + } + + $matchingCheckers = array_filter( + $checkerInstances, + static fn (object $instance): bool => $instance instanceof $contractClass + ); + + return [...$instances, ...array_values($matchingCheckers)]; + } + + /** + * Registered checker classes, minus those removed by compiler passes. + * + * @return array> + */ + public function getCheckerClasses(): array { - parent::singleton($abstract, $concrete); + return array_values(array_filter( + array_keys($this->checkerConfiguration), + fn (string $checkerClass): bool => ! isset($this->removedCheckers[$checkerClass]) + )); + } - foreach (self::AUTOTAG_INTERFACES as $autotagInterface) { - if (! is_a($abstract, $autotagInterface, true)) { + /** + * Registered checkers and their configuration, used for cache invalidation. + * + * @return array, mixed[]> + */ + public function getCheckerConfiguration(): array + { + $checkerConfiguration = []; + foreach ($this->checkerConfiguration as $checkerClass => $configuration) { + if (isset($this->removedCheckers[$checkerClass])) { continue; } - $this->tag($abstract, $autotagInterface); + $checkerConfiguration[$checkerClass] = $configuration; } + + return $checkerConfiguration; + } + + /** + * @param class-string $checkerClass + */ + public function removeChecker(string $checkerClass): void + { + $this->removedCheckers[$checkerClass] = true; + } + + /** + * @param class-string $checkerClass + * @param mixed[] $configuration + */ + private function registerChecker(string $checkerClass, array $configuration): void + { + // last registration wins for configuration, mirroring the previous container override behaviour + $this->checkerConfiguration[$checkerClass] = $configuration; + $this->checkerRegistrationOrder[] = $checkerClass; + unset($this->removedCheckers[$checkerClass]); + + // register the checker as a service exactly once; the factory reads the + // (possibly updated) configuration lazily at build time + if (isset($this->checkerServiceRegistered[$checkerClass])) { + return; + } + + $this->checkerServiceRegistered[$checkerClass] = true; + $this->service($checkerClass, fn (): object => $this->buildConfiguredChecker($checkerClass)); + } + + /** + * @param class-string $checkerClass + */ + private function buildConfiguredChecker(string $checkerClass): object + { + $checker = $this->build($checkerClass); + + if ($checker instanceof WhitespacesAwareFixerInterface) { + $checker->setWhitespacesConfig($this->make(WhitespacesFixerConfig::class)); + } + + $configuration = $this->checkerConfiguration[$checkerClass]; + + if ($checker instanceof ConfigurableFixerInterface) { + $checker->configure($configuration); + } elseif ($checker instanceof Sniff) { + foreach ($configuration as $propertyName => $value) { + Assert::propertyExists($checker, $propertyName); + $checker->{$propertyName} = $value; + } + } + + return $checker; } /** @@ -299,27 +409,4 @@ private function ensureCheckerClassesAreUnique(array $checkerClasses): void ); throw new InvalidArgumentException($errorMessage); } - - /** - * @param class-string $checkerClass - */ - private function autowireWhitespaceAwareFixer(string $checkerClass): void - { - if (! is_a($checkerClass, WhitespacesAwareFixerInterface::class, true)) { - return; - } - - $this->extend( - $checkerClass, - static function ( - WhitespacesAwareFixerInterface $whitespacesAwareFixer, - Container $container - ): WhitespacesAwareFixerInterface { - $whitespacesFixerConfig = $container->make(WhitespacesFixerConfig::class); - $whitespacesAwareFixer->setWhitespacesConfig($whitespacesFixerConfig); - - return $whitespacesAwareFixer; - } - ); - } } diff --git a/src/Console/Output/OutputFormatterCollector.php b/src/Console/Output/OutputFormatterCollector.php index 08a99c4268..07f4e443f0 100644 --- a/src/Console/Output/OutputFormatterCollector.php +++ b/src/Console/Output/OutputFormatterCollector.php @@ -17,8 +17,9 @@ final class OutputFormatterCollector /** * @param OutputFormatterInterface[] $outputFormatters */ - public function __construct(array $outputFormatters) - { + public function __construct( + array $outputFormatters + ) { foreach ($outputFormatters as $outputFormatter) { $this->outputFormatters[$outputFormatter->getName()] = $outputFormatter; } diff --git a/src/DependencyInjection/CompilerPass/CompilerPassHelper.php b/src/DependencyInjection/CompilerPass/CompilerPassHelper.php index 5acd3025a8..babe612d9e 100644 --- a/src/DependencyInjection/CompilerPass/CompilerPassHelper.php +++ b/src/DependencyInjection/CompilerPass/CompilerPassHelper.php @@ -4,55 +4,25 @@ namespace Symplify\EasyCodingStandard\DependencyInjection\CompilerPass; -use Illuminate\Container\Container; use PHP_CodeSniffer\Sniffs\Sniff; use PhpCsFixer\Fixer\FixerInterface; -use Symplify\EasyCodingStandard\Utils\PrivatesAccessorHelper; +use Symplify\EasyCodingStandard\Config\ECSConfig; final class CompilerPassHelper { /** - * @return string[] + * @return array> */ - public static function resolveCheckerClasses(Container $container): array + public static function resolveCheckerClasses(ECSConfig $ecsConfig): array { - $serviceTypes = array_keys($container->getBindings()); - - return array_filter($serviceTypes, static function (string $serviceType): bool { - if (is_a($serviceType, FixerInterface::class, true)) { - return true; - } - - return is_a($serviceType, Sniff::class, true); - }); + return $ecsConfig->getCheckerClasses(); } - public static function removeCheckerFromContainer(Container $container, string $checkerClass): void + /** + * @param class-string $checkerClass + */ + public static function removeCheckerFromContainer(ECSConfig $ecsConfig, string $checkerClass): void { - // remove instance - $container->offsetUnset($checkerClass); - - $tags = PrivatesAccessorHelper::getPropertyValue($container, 'tags'); - - // nothing to remove - if ($tags === []) { - return; - } - - // remove from tags - $checkerTagClasses = [FixerInterface::class, Sniff::class]; - - foreach ($checkerTagClasses as $checkerTagClass) { - foreach ($tags[$checkerTagClass] ?? [] as $key => $class) { - if ($class !== $checkerClass) { - continue; - } - - unset($tags[$checkerTagClass][$key]); - } - } - - // update value - PrivatesAccessorHelper::setPropertyValue($container, 'tags', $tags); + $ecsConfig->removeChecker($checkerClass); } } diff --git a/src/DependencyInjection/CompilerPass/ConflictingCheckersCompilerPass.php b/src/DependencyInjection/CompilerPass/ConflictingCheckersCompilerPass.php index 759dc6f1f0..ac0ce0a415 100644 --- a/src/DependencyInjection/CompilerPass/ConflictingCheckersCompilerPass.php +++ b/src/DependencyInjection/CompilerPass/ConflictingCheckersCompilerPass.php @@ -4,7 +4,6 @@ namespace Symplify\EasyCodingStandard\DependencyInjection\CompilerPass; -use Illuminate\Container\Container; use PHP_CodeSniffer\Standards\Generic\Sniffs\Files\EndFileNewlineSniff as GenericEndFileNewlineSniff; use PHP_CodeSniffer\Standards\Generic\Sniffs\Files\EndFileNoNewlineSniff; use PHP_CodeSniffer\Standards\Generic\Sniffs\PHP\LowerCaseConstantSniff; @@ -20,6 +19,7 @@ use PhpCsFixer\Fixer\PhpTag\BlankLineAfterOpeningTagFixer; use Symplify\CodingStandard\Fixer\Spacing\StandaloneLineConstructorParamFixer; use Symplify\CodingStandard\Fixer\Spacing\StandaloneLinePromotedPropertyFixer; +use Symplify\EasyCodingStandard\Config\ECSConfig; use Symplify\EasyCodingStandard\Exception\Configuration\ConflictingCheckersLoadedException; final class ConflictingCheckersCompilerPass @@ -42,9 +42,9 @@ final class ConflictingCheckersCompilerPass [DisallowTabIndentSniff::class, DisallowSpaceIndentSniff::class], ]; - public function process(Container $container): void + public function process(ECSConfig $ecsConfig): void { - $checkerTypes = CompilerPassHelper::resolveCheckerClasses($container); + $checkerTypes = CompilerPassHelper::resolveCheckerClasses($ecsConfig); if ($checkerTypes === []) { return; } diff --git a/src/DependencyInjection/CompilerPass/RemoveExcludedCheckersCompilerPass.php b/src/DependencyInjection/CompilerPass/RemoveExcludedCheckersCompilerPass.php index b2f6ff2ecb..0a67e98895 100644 --- a/src/DependencyInjection/CompilerPass/RemoveExcludedCheckersCompilerPass.php +++ b/src/DependencyInjection/CompilerPass/RemoveExcludedCheckersCompilerPass.php @@ -4,23 +4,23 @@ namespace Symplify\EasyCodingStandard\DependencyInjection\CompilerPass; -use Illuminate\Container\Container; +use Symplify\EasyCodingStandard\Config\ECSConfig; use Symplify\EasyCodingStandard\DependencyInjection\SimpleParameterProvider; use Symplify\EasyCodingStandard\ValueObject\Option; final class RemoveExcludedCheckersCompilerPass { - public function process(Container $container): void + public function process(ECSConfig $ecsConfig): void { $excludedCheckers = $this->getExcludedCheckersFromSkipParameter(); - foreach (array_keys($container->getBindings()) as $classType) { + foreach ($ecsConfig->getCheckerClasses() as $classType) { if (! in_array($classType, $excludedCheckers, true)) { continue; } - // remove service from container completely - CompilerPassHelper::removeCheckerFromContainer($container, $classType); + // remove checker from container completely + CompilerPassHelper::removeCheckerFromContainer($ecsConfig, $classType); } } diff --git a/src/DependencyInjection/CompilerPass/RemoveMutualCheckersCompilerPass.php b/src/DependencyInjection/CompilerPass/RemoveMutualCheckersCompilerPass.php index a74cb0f533..a2e143cefa 100644 --- a/src/DependencyInjection/CompilerPass/RemoveMutualCheckersCompilerPass.php +++ b/src/DependencyInjection/CompilerPass/RemoveMutualCheckersCompilerPass.php @@ -4,7 +4,6 @@ namespace Symplify\EasyCodingStandard\DependencyInjection\CompilerPass; -use Illuminate\Container\Container; use PHP_CodeSniffer\Standards\Generic\Sniffs\Arrays\DisallowLongArraySyntaxSniff; use PHP_CodeSniffer\Standards\Generic\Sniffs\Arrays\DisallowShortArraySyntaxSniff; use PHP_CodeSniffer\Standards\Generic\Sniffs\CodeAnalysis\AssignmentInConditionSniff; @@ -57,6 +56,7 @@ use PhpCsFixer\Fixer\Whitespace\NoTrailingWhitespaceFixer; use PhpCsFixer\Fixer\Whitespace\SingleBlankLineAtEofFixer; use Symplify\CodingStandard\Fixer\LineLength\LineLengthFixer; +use Symplify\EasyCodingStandard\Config\ECSConfig; final class RemoveMutualCheckersCompilerPass { @@ -120,9 +120,9 @@ final class RemoveMutualCheckersCompilerPass [LineLengthFixer::class, LineLengthSniff::class], ]; - public function process(Container $container): void + public function process(ECSConfig $ecsConfig): void { - $checkerTypes = CompilerPassHelper::resolveCheckerClasses($container); + $checkerTypes = CompilerPassHelper::resolveCheckerClasses($ecsConfig); if ($checkerTypes === []) { return; } @@ -132,12 +132,12 @@ public function process(Container $container): void return; } - foreach (array_keys($container->getBindings()) as $type) { + foreach ($ecsConfig->getCheckerClasses() as $type) { if (! in_array($type, $checkersToRemove, true)) { continue; } - CompilerPassHelper::removeCheckerFromContainer($container, $type); + CompilerPassHelper::removeCheckerFromContainer($ecsConfig, $type); } } diff --git a/src/DependencyInjection/EasyCodingStandardContainerFactory.php b/src/DependencyInjection/EasyCodingStandardContainerFactory.php index 00df680b2b..00183d5437 100644 --- a/src/DependencyInjection/EasyCodingStandardContainerFactory.php +++ b/src/DependencyInjection/EasyCodingStandardContainerFactory.php @@ -4,7 +4,6 @@ namespace Symplify\EasyCodingStandard\DependencyInjection; -use Illuminate\Container\Container; use Symfony\Component\Console\Input\ArgvInput; use Symplify\EasyCodingStandard\Caching\ChangedFilesDetector; use Symplify\EasyCodingStandard\Config\ECSConfig; diff --git a/src/DependencyInjection/LazyContainerFactory.php b/src/DependencyInjection/LazyContainerFactory.php index a2876ba980..feace165bd 100644 --- a/src/DependencyInjection/LazyContainerFactory.php +++ b/src/DependencyInjection/LazyContainerFactory.php @@ -4,42 +4,41 @@ namespace Symplify\EasyCodingStandard\DependencyInjection; -use Illuminate\Container\Container; -use PHP_CodeSniffer\Fixer; -use PHP_CodeSniffer\Sniffs\Sniff; +use Entropy\Container\Container; use PHP_CodeSniffer\Util\Tokens; use PhpCsFixer\Differ\DifferInterface; use PhpCsFixer\Differ\UnifiedDiffer; -use PhpCsFixer\Fixer\FixerInterface; use PhpCsFixer\WhitespacesFixerConfig; -use SebastianBergmann\Diff\Parser as DiffParser; use Symfony\Component\Console\Style\SymfonyStyle; -use Symplify\EasyCodingStandard\Application\SingleFileProcessor; use Symplify\EasyCodingStandard\Caching\Cache; use Symplify\EasyCodingStandard\Caching\CacheFactory; -use Symplify\EasyCodingStandard\Caching\ChangedFilesDetector; use Symplify\EasyCodingStandard\Config\ECSConfig; use Symplify\EasyCodingStandard\Console\Output\CheckstyleOutputFormatter; use Symplify\EasyCodingStandard\Console\Output\ConsoleOutputFormatter; use Symplify\EasyCodingStandard\Console\Output\GitlabOutputFormatter; use Symplify\EasyCodingStandard\Console\Output\JsonOutputFormatter; use Symplify\EasyCodingStandard\Console\Output\JUnitOutputFormatter; -use Symplify\EasyCodingStandard\Console\Output\OutputFormatterCollector; use Symplify\EasyCodingStandard\Console\Style\EasyCodingStandardStyle; use Symplify\EasyCodingStandard\Console\Style\EasyCodingStandardStyleFactory; use Symplify\EasyCodingStandard\Console\Style\SymfonyStyleFactory; -use Symplify\EasyCodingStandard\Contract\Console\Output\OutputFormatterInterface; -use Symplify\EasyCodingStandard\FixerRunner\Application\FixerFileProcessor; use Symplify\EasyCodingStandard\FixerRunner\WhitespacesFixerConfigFactory; -use Symplify\EasyCodingStandard\Parallel\Application\ParallelFileProcessor; -use Symplify\EasyCodingStandard\Skipper\Skipper\Skipper; -use Symplify\EasyCodingStandard\Skipper\Skipper\SkipSkipper; -use Symplify\EasyCodingStandard\SniffRunner\Application\SniffFileProcessor; -use Symplify\EasyCodingStandard\SniffRunner\DataCollector\SniffMetadataCollector; use Webmozart\Assert\Assert; final class LazyContainerFactory { + /** + * Output formatters are registered explicitly, so they can be collected by contract. + * + * @var array + */ + private const array OUTPUT_FORMATTER_CLASSES = [ + GitlabOutputFormatter::class, + CheckstyleOutputFormatter::class, + ConsoleOutputFormatter::class, + JsonOutputFormatter::class, + JUnitOutputFormatter::class, + ]; + /** * @param string[] $configFiles */ @@ -49,16 +48,8 @@ public function create(array $configFiles = []): ECSConfig $ecsConfig = new ECSConfig(); - // make sure these services have shared instance created just once, as use setters throughout the project - $ecsConfig->singleton(ChangedFilesDetector::class); - $ecsConfig->singleton(SniffMetadataCollector::class); - $ecsConfig->singleton(SingleFileProcessor::class); - $ecsConfig->singleton(ParallelFileProcessor::class); - $ecsConfig->singleton(Skipper::class); - $ecsConfig->singleton(SkipSkipper::class); - // console - $ecsConfig->singleton( + $ecsConfig->service( EasyCodingStandardStyle::class, static function (Container $container): EasyCodingStandardStyle { /** @var EasyCodingStandardStyleFactory $easyCodingStandardStyleFactory */ @@ -67,50 +58,31 @@ static function (Container $container): EasyCodingStandardStyle { } ); - $ecsConfig->singleton(SymfonyStyle::class, SymfonyStyleFactory::create(...)); - - $ecsConfig->singleton(Fixer::class); + $ecsConfig->service(SymfonyStyle::class, static fn (): SymfonyStyle => SymfonyStyleFactory::create()); // whitespace - $ecsConfig->singleton(WhitespacesFixerConfig::class, static function (): WhitespacesFixerConfig { + $ecsConfig->service(WhitespacesFixerConfig::class, static function (): WhitespacesFixerConfig { $whitespacesFixerConfigFactory = new WhitespacesFixerConfigFactory(); return $whitespacesFixerConfigFactory->create(); }); // caching - $ecsConfig->singleton(Cache::class, static function (Container $container): Cache { + $ecsConfig->service(Cache::class, static function (Container $container): Cache { /** @var CacheFactory $cacheFactory */ $cacheFactory = $container->make(CacheFactory::class); return $cacheFactory->create(); }); // diffing - $ecsConfig->singleton(DiffParser::class); - - // output - $ecsConfig->singleton(GitlabOutputFormatter::class); - $ecsConfig->singleton(CheckstyleOutputFormatter::class); - $ecsConfig->singleton(ConsoleOutputFormatter::class); - $ecsConfig->singleton(JsonOutputFormatter::class); - $ecsConfig->singleton(JUnitOutputFormatter::class); - $ecsConfig->singleton(OutputFormatterCollector::class); - - $ecsConfig->when(OutputFormatterCollector::class) - ->needs('$outputFormatters') - ->giveTagged(OutputFormatterInterface::class); - - $ecsConfig->singleton(DifferInterface::class, static fn (): DifferInterface => new UnifiedDiffer()); - - // @see https://gist.github.com/pionl/01c40225ceeed8b136306fdd96b5dabd - $ecsConfig->singleton(FixerFileProcessor::class); - $ecsConfig->when(FixerFileProcessor::class) - ->needs('$fixers') - ->giveTagged(FixerInterface::class); - - $ecsConfig->singleton(SniffFileProcessor::class); - $ecsConfig->when(SniffFileProcessor::class) - ->needs('$sniffs') - ->giveTagged(Sniff::class); + $ecsConfig->service(DifferInterface::class, static fn (): DifferInterface => new UnifiedDiffer()); + + // output formatters - autowired, registered so OutputFormatterCollector can find them by contract + foreach (self::OUTPUT_FORMATTER_CLASSES as $outputFormatterClass) { + $ecsConfig->service( + $outputFormatterClass, + static fn (Container $container): object => $container->build($outputFormatterClass) + ); + } // load default config first $configFiles = [__DIR__ . '/../../config/config.php', ...$configFiles]; diff --git a/src/Testing/PHPUnit/AbstractTestCase.php b/src/Testing/PHPUnit/AbstractTestCase.php index 80c97d763e..f442d7d08c 100644 --- a/src/Testing/PHPUnit/AbstractTestCase.php +++ b/src/Testing/PHPUnit/AbstractTestCase.php @@ -4,21 +4,21 @@ namespace Symplify\EasyCodingStandard\Testing\PHPUnit; -use Illuminate\Container\Container; use PHPUnit\Framework\TestCase; +use Symplify\EasyCodingStandard\Config\ECSConfig; use Symplify\EasyCodingStandard\DependencyInjection\LazyContainerFactory; use Webmozart\Assert\Assert; abstract class AbstractTestCase extends TestCase { - private ?Container $container = null; + private ?ECSConfig $ecsConfig = null; protected function setUp(): void { $lazyContainerFactory = new LazyContainerFactory(); - $this->container = $lazyContainerFactory->create(); - $this->container->boot(); + $this->ecsConfig = $lazyContainerFactory->create(); + $this->ecsConfig->boot(); } /** @@ -30,9 +30,9 @@ protected function createContainerWithConfigs(array $configs): void Assert::allFile($configs); $lazyContainerFactory = new LazyContainerFactory(); - $this->container = $lazyContainerFactory->create($configs); + $this->ecsConfig = $lazyContainerFactory->create($configs); - $this->container->boot(); + $this->ecsConfig->boot(); } /** @@ -43,8 +43,8 @@ protected function createContainerWithConfigs(array $configs): void */ protected function make(string $class): object { - Assert::notNull($this->container); + Assert::notNull($this->ecsConfig); - return $this->container->make($class); + return $this->ecsConfig->make($class); } } diff --git a/src/Utils/PrivatesAccessorHelper.php b/src/Utils/PrivatesAccessorHelper.php index ca79e623ba..78f6b0568e 100644 --- a/src/Utils/PrivatesAccessorHelper.php +++ b/src/Utils/PrivatesAccessorHelper.php @@ -8,6 +8,9 @@ final class PrivatesAccessorHelper { + /** + * @api used in tests to assert on private checker state + */ public static function getPropertyValue(object $object, string $propertyName): mixed { $reflectionProperty = new ReflectionProperty($object, $propertyName);