Skip to content

Commit 2c5d712

Browse files
committed
Switch DI container from illuminate/container to entropy/entropy
Replace the Illuminate container with entropy/entropy. RectorConfig extends Entropy\Container\Container and reuses the container's own primitives: - register() makes a factory-less service discoverable by contract, so findByContract() returns it without any tag layer (tag/tagged/autotag gone) - findByContract() returns a plain 0-indexed list, so variadic spreads such as new NodeTraverser(...$visitors) stay positional - afterResolving() runs setter injection once after a service is built, drained at the outermost make() so aggregate cycles (type mappers <-> the collection they belong to) resolve without a loop - forgetByContract() drops a service from the container; RectorConfig extends it to clear its own bookkeeping, so skip()/test reset need no reflection Removed along the way: the giveTagged() contextual-binding API, service aliases (the one alias was already backed by a factory), the RegisteredService value object, and the ContainerMemento reflection helper. No WeakMap, to keep the source downgradable; service build failures propagate as entropy throws them.
1 parent bc7a659 commit 2c5d712

20 files changed

Lines changed: 185 additions & 382 deletions

File tree

‎composer.json‎

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"composer/semver": "^3.4",
1919
"composer/xdebug-handler": "^3.0.5",
2020
"doctrine/inflector": "^2.1",
21-
"illuminate/container": "12.39.*",
21+
"entropy/entropy": "^0.4.9",
2222
"nette/utils": "^4.1.4",
2323
"nikic/php-parser": "^5.8",
2424
"ondram/ci-detector": "^4.2",
@@ -55,7 +55,6 @@
5555
"symplify/easy-coding-standard": "^13.2.13",
5656
"symplify/phpstan-extensions": "^12.0.2",
5757
"symplify/phpstan-rules": "^14.12",
58-
"symplify/vendor-patches": "^11.5",
5958
"tomasvotruba/class-leak": "^2.1",
6059
"tomasvotruba/fast-unit": "^0.1",
6160
"tomasvotruba/type-coverage": "^2.3",
@@ -115,22 +114,12 @@
115114
"preload": "php build/build-preload.php .",
116115
"release": "vendor/bin/rng --from-commit X --to-commit Y --remote-repository rectorphp/rector-symfony --remote-repository rectorphp/rector-doctrine --remote-repository rectorphp/rector-phpunit"
117116
},
118-
"extra": {
119-
"patches": {
120-
"illuminate/container": [
121-
"https://raw.githubusercontent.com/rectorphp/vendor-patches/main/patches/illuminate-container-container-php.patch"
122-
]
123-
},
124-
"composer-exit-on-patch-failure": true,
125-
"enable-patching": true
126-
},
127117
"config": {
128118
"sort-packages": true,
129119
"platform-check": false,
130120
"allow-plugins": {
131121
"phpstan/extension-installer": true,
132-
"rector/extension-installer": true,
133-
"cweagans/composer-patches": true
122+
"rector/extension-installer": true
134123
}
135124
},
136125
"minimum-stability": "dev",

‎rules/CodingStyle/ClassNameImport/ClassNameImportSkipper.php‎

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,14 @@
2222
* @param ClassNameImportSkipVoterInterface[] $classNameImportSkipVoters
2323
*/
2424
public function __construct(
25-
private iterable $classNameImportSkipVoters,
25+
private array $classNameImportSkipVoters,
2626
private UseImportsResolver $useImportsResolver,
2727
) {
2828
}
2929

30-
public function shouldSkipNameForFullyQualifiedObjectType(
31-
File $file,
32-
Node $node,
33-
FullyQualifiedObjectType $fullyQualifiedObjectType
34-
): bool {
35-
foreach ($this->classNameImportSkipVoters as $classNameImportSkipVoter) {
36-
if ($classNameImportSkipVoter->shouldSkip($file, $fullyQualifiedObjectType, $node)) {
37-
return true;
38-
}
39-
}
40-
41-
return false;
30+
public function shouldSkipNameForFullyQualifiedObjectType(File $file, Node $node, FullyQualifiedObjectType $fullyQualifiedObjectType): bool
31+
{
32+
return array_any($this->classNameImportSkipVoters, fn (ClassNameImportSkipVoterInterface $classNameImportSkipVoter): bool => $classNameImportSkipVoter->shouldSkip($file, $fullyQualifiedObjectType, $node));
4233
}
4334

4435
/**

‎src/Config/RectorConfig.php‎

Lines changed: 94 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,18 @@
66

77
use Composer\Semver\Semver;
88
use Deprecated;
9-
use Illuminate\Container\Container;
9+
use Entropy\Container\Container;
1010
use Override;
1111
use Rector\Caching\Contract\ValueObject\Storage\CacheStorageInterface;
1212
use Rector\Composer\InstalledPackageResolver;
1313
use Rector\Configuration\Option;
1414
use Rector\Configuration\Parameter\SimpleParameterProvider;
1515
use Rector\Configuration\RectorConfigBuilder;
1616
use Rector\Contract\DependencyInjection\RelatedConfigInterface;
17-
use Rector\Contract\DependencyInjection\ResettableInterface;
1817
use Rector\Contract\Rector\ConfigurableRectorInterface;
1918
use Rector\Contract\Rector\RectorInterface;
20-
use Rector\DependencyInjection\Laravel\ContainerMemento;
2119
use Rector\Enum\Config\Defaults;
20+
use Rector\Exception\DependencyInjection\ServiceCreationFailedException;
2221
use Rector\Exception\ShouldNotHappenException;
2322
use Rector\Skipper\SkipCriteriaResolver\SkippedClassResolver;
2423
use Rector\Validation\RectorConfigValidator;
@@ -27,6 +26,7 @@
2726
use Rector\ValueObject\PolyfillPackage;
2827
use Rector\VersionBonding\ValueObject\ComposerBoundRuleConfiguration;
2928
use Symfony\Component\Console\Command\Command;
29+
use Throwable;
3030
use Webmozart\Assert\Assert;
3131

3232
/**
@@ -51,14 +51,23 @@ final class RectorConfig extends Container
5151
private array $registeredComposerBoundRuleConfigurations = [];
5252

5353
/**
54-
* @var string[]
54+
* Optional override, e.g. injected by a test to read the versions from a standalone "composer.json"
5555
*/
56-
private array $autotagInterfaces = [Command::class, ResettableInterface::class];
56+
private ?InstalledPackageResolver $installedPackageResolver = null;
5757

5858
/**
59-
* Optional override, e.g. injected by a test to read the versions from a standalone "composer.json"
59+
* Explicitly registered service ids, used for bound() and to drive forgetting on skip()/reset.
60+
*
61+
* @var array<class-string, true>
6062
*/
61-
private ?InstalledPackageResolver $installedPackageResolver = null;
63+
private array $boundAbstracts = [];
64+
65+
/**
66+
* Service ids that got a factory closure registered on the entropy container.
67+
*
68+
* @var array<class-string, true>
69+
*/
70+
private array $factoryBound = [];
6271

6372
private static ?bool $recreated = null;
6473

@@ -274,13 +283,11 @@ public function rule(string $rectorClass): void
274283

275284
$this->singleton($rectorClass);
276285

277-
// the same rule can be registered by multiple sets, tag it only once,
286+
// the same rule can be registered by multiple sets, record it only once,
278287
// otherwise it is run twice on every node and listed twice in the reports
279288
if (! isset($this->registeredRectorClasses[$rectorClass])) {
280289
$this->registeredRectorClasses[$rectorClass] = true;
281290

282-
$this->tag($rectorClass, RectorInterface::class);
283-
284291
// for cache invalidation in case of change
285292
SimpleParameterProvider::addParameter(Option::REGISTERED_RECTOR_RULES, $rectorClass);
286293
}
@@ -304,7 +311,6 @@ public function rule(string $rectorClass): void
304311
public function command(string $commandClass): void
305312
{
306313
$this->singleton($commandClass);
307-
$this->tag($commandClass, Command::class);
308314
}
309315

310316
public function import(string $filePath): void
@@ -500,32 +506,98 @@ public function boot(): void
500506
}
501507

502508
// completely forget the Rector rule only when no path specified
503-
ContainerMemento::forgetService($this, $skippedClass);
509+
$this->forgetByContract($skippedClass);
504510
}
505511
}
506512

507513
/**
508-
* @internal Use to add tag on service registrations
514+
* Register a shared service. Without a $concrete factory the entropy container autowires the
515+
* class on demand via reflection; register() makes it discoverable by the interfaces it
516+
* implements, so findByContract() can find it without any explicit tagging.
517+
*
518+
* @param class-string $abstract
519+
* @param (callable(self): object)|null $concrete
509520
*/
510-
public function autotagInterface(string $interface): void
521+
public function singleton(string $abstract, ?callable $concrete = null): void
511522
{
512-
$this->autotagInterfaces[] = $interface;
523+
$this->boundAbstracts[$abstract] = true;
524+
525+
if ($concrete === null) {
526+
// no factory: let the entropy container discover it by contract
527+
$this->register($abstract);
528+
return;
529+
}
530+
531+
if (! isset($this->factoryBound[$abstract])) {
532+
$this->factoryBound[$abstract] = true;
533+
// entropy calls the factory with the container instance, which is always this RectorConfig
534+
parent::service($abstract, fn (): object => $concrete($this));
535+
}
513536
}
514537

515538
/**
516-
* @param string $abstract
539+
* @template TObject of object
540+
* @param class-string<TObject> $class
541+
* @return TObject
517542
*/
518543
#[Override]
519-
public function singleton($abstract, mixed $concrete = null): void
544+
public function make(string $class): object
545+
{
546+
try {
547+
/** @var TObject $object */
548+
$object = parent::make($class);
549+
return $object;
550+
} catch (ServiceCreationFailedException $serviceCreationFailedException) {
551+
// a deeper make() already named the failing service, keep it as-is
552+
throw $serviceCreationFailedException;
553+
} catch (Throwable $throwable) {
554+
// name the service that failed to build, keep the root cause as previous
555+
throw new ServiceCreationFailedException($class, 0, $throwable);
556+
}
557+
}
558+
559+
/**
560+
* PSR-11 style accessor, kept for call sites that read services eagerly.
561+
*
562+
* @template TObject of object
563+
* @param class-string<TObject> $id
564+
* @return TObject
565+
*/
566+
public function get(string $id): object
520567
{
521-
parent::singleton($abstract, $concrete);
568+
return $this->make($id);
569+
}
522570

523-
foreach ($this->autotagInterfaces as $autotagInterface) {
524-
if (! is_a($abstract, $autotagInterface, true)) {
571+
/**
572+
* @param class-string $abstract
573+
*/
574+
public function bound(string $abstract): bool
575+
{
576+
return isset($this->boundAbstracts[$abstract]);
577+
}
578+
579+
/**
580+
* Forget every service of the contract, both from the entropy container and from the local
581+
* bookkeeping, so a skipped or reset service is not seen as bound and cannot be resurrected
582+
* through discovery.
583+
*
584+
* @param class-string $contract
585+
*/
586+
#[Override]
587+
public function forgetByContract(string $contract): void
588+
{
589+
parent::forgetByContract($contract);
590+
591+
foreach (array_keys($this->boundAbstracts) as $abstract) {
592+
if (! is_a($abstract, $contract, true)) {
525593
continue;
526594
}
527595

528-
$this->tag($abstract, $autotagInterface);
596+
unset(
597+
$this->boundAbstracts[$abstract],
598+
$this->factoryBound[$abstract],
599+
$this->registeredRectorClasses[$abstract],
600+
);
529601
}
530602
}
531603

@@ -559,7 +631,7 @@ public function getRuleConfigurations(): array
559631
*/
560632
public function getMainRectorClasses(): array
561633
{
562-
return $this->tags[RectorInterface::class] ?? [];
634+
return array_keys($this->registeredRectorClasses);
563635
}
564636

565637
/**

‎src/Config/RegisteredService.php‎

Lines changed: 0 additions & 30 deletions
This file was deleted.

‎src/Configuration/RectorConfigBuilder.php‎

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,9 @@
1616
use Rector\Config\Level\TypeDeclarationDocblocksLevel;
1717
use Rector\Config\Level\TypeDeclarationLevel;
1818
use Rector\Config\RectorConfig;
19-
use Rector\Config\RegisteredService;
2019
use Rector\Configuration\Levels\LevelRulesResolver;
2120
use Rector\Configuration\Parameter\SimpleParameterProvider;
2221
use Rector\Console\Notifier;
23-
use Rector\Contract\PhpParser\DecoratingNodeVisitorInterface;
2422
use Rector\Contract\Rector\ConfigurableRectorInterface;
2523
use Rector\Contract\Rector\RectorInterface;
2624
use Rector\Doctrine\Set\DoctrineSetList;
@@ -173,7 +171,7 @@ final class RectorConfigBuilder
173171
private array $typeGuardedClasses = [];
174172

175173
/**
176-
* @var RegisteredService[]
174+
* @var array<class-string>
177175
*/
178176
private array $registerServices = [];
179177

@@ -280,15 +278,7 @@ public function __invoke(RectorConfig $rectorConfig): void
280278

281279
// must be in upper part, as these services might be used by rule registered bellow
282280
foreach ($this->registerServices as $registerService) {
283-
$rectorConfig->singleton($registerService->getClassName());
284-
285-
if ($registerService->getAlias()) {
286-
$rectorConfig->alias($registerService->getClassName(), $registerService->getAlias());
287-
}
288-
289-
if ($registerService->getTag()) {
290-
$rectorConfig->tag($registerService->getClassName(), $registerService->getTag());
291-
}
281+
$rectorConfig->singleton($registerService);
292282
}
293283

294284
if ($this->skip !== []) {
@@ -1114,9 +1104,12 @@ public function withTypeGuardedClasses(array $typeGuardedClasses): self
11141104
return $this;
11151105
}
11161106

1117-
public function registerService(string $className, ?string $alias = null, ?string $tag = null): self
1107+
/**
1108+
* @param class-string $className
1109+
*/
1110+
public function registerService(string $className): self
11181111
{
1119-
$this->registerServices[] = new RegisteredService($className, $alias, $tag);
1112+
$this->registerServices[] = $className;
11201113

11211114
return $this;
11221115
}
@@ -1130,11 +1123,7 @@ public function registerDecoratingNodeVisitor(string $decoratingNodeVisitorClass
11301123
{
11311124
Assert::isAOf($decoratingNodeVisitorClass, NodeVisitor::class);
11321125

1133-
$this->registerServices[] = new RegisteredService(
1134-
$decoratingNodeVisitorClass,
1135-
null,
1136-
DecoratingNodeVisitorInterface::class
1137-
);
1126+
$this->registerServices[] = $decoratingNodeVisitorClass;
11381127

11391128
return $this;
11401129
}

‎src/Console/Output/OutputFormatterCollector.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ final class OutputFormatterCollector
1717
/**
1818
* @param OutputFormatterInterface[] $outputFormatters
1919
*/
20-
public function __construct(iterable $outputFormatters)
20+
public function __construct(array $outputFormatters)
2121
{
2222
foreach ($outputFormatters as $outputFormatter) {
2323
$this->outputFormatters[$outputFormatter->getName()] = $outputFormatter;

0 commit comments

Comments
 (0)