From 8f9478869aa3fd5893a846b7cc62f919a460f4cd Mon Sep 17 00:00:00 2001 From: Caleb White Date: Tue, 3 Feb 2026 15:56:44 -0600 Subject: [PATCH 1/2] perf: use hash map for installed packages We can turn a package lookup into O(1) by using a hash map instead of an O(n) with a linear array. --- src/Composer/InstalledPackageResolver.php | 20 ++++++------------- src/Composer/ValueObject/InstalledPackage.php | 3 +++ src/Set/ValueObject/ComposerTriggeredSet.php | 12 +++++------ 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/Composer/InstalledPackageResolver.php b/src/Composer/InstalledPackageResolver.php index 50c4bc44ff3..cf7615c2e57 100644 --- a/src/Composer/InstalledPackageResolver.php +++ b/src/Composer/InstalledPackageResolver.php @@ -17,7 +17,7 @@ final class InstalledPackageResolver { /** - * @var null|InstalledPackage[] + * @var null|array */ private ?array $resolvedInstalledPackages = null; @@ -33,7 +33,7 @@ public function __construct( } /** - * @return InstalledPackage[] + * @return array */ public function resolve(): array { @@ -61,28 +61,20 @@ public function resolve(): array public function resolvePackageVersion(string $packageName): ?string { - $installedPackages = $this->resolve(); - foreach ($installedPackages as $installedPackage) { - if ($installedPackage->getName() !== $packageName) { - continue; - } - - return $installedPackage->getVersion(); - } - - return null; + return ($this->resolve()[$packageName] ?? null)?->getVersion(); } /** * @param mixed[] $packages - * @return InstalledPackage[] + * @return array */ private function createInstalledPackages(array $packages): array { $installedPackages = []; foreach ($packages as $package) { - $installedPackages[] = new InstalledPackage($package['name'], $package['version_normalized']); + $name = $package['name']; + $installedPackages[$name] = new InstalledPackage($name, $package['version_normalized']); } return $installedPackages; diff --git a/src/Composer/ValueObject/InstalledPackage.php b/src/Composer/ValueObject/InstalledPackage.php index 0e19e21c1d1..291c7c389ab 100644 --- a/src/Composer/ValueObject/InstalledPackage.php +++ b/src/Composer/ValueObject/InstalledPackage.php @@ -4,6 +4,9 @@ namespace Rector\Composer\ValueObject; +/** + * @api + */ final readonly class InstalledPackage { public function __construct( diff --git a/src/Set/ValueObject/ComposerTriggeredSet.php b/src/Set/ValueObject/ComposerTriggeredSet.php index 3964c7865be..c094d213d52 100644 --- a/src/Set/ValueObject/ComposerTriggeredSet.php +++ b/src/Set/ValueObject/ComposerTriggeredSet.php @@ -40,19 +40,17 @@ public function getSetFilePath(): string } /** - * @param InstalledPackage[] $installedPackages + * @param array $installedPackages */ public function matchInstalledPackages(array $installedPackages): bool { - foreach ($installedPackages as $installedPackage) { - if ($installedPackage->getName() !== $this->packageName) { - continue; - } + $installedVersion = ($installedPackages[$this->packageName] ?? null)?->getVersion(); - return Semver::satisfies($installedPackage->getVersion(), '^' . $this->version); + if ($installedVersion === null) { + return false; } - return false; + return Semver::satisfies($installedVersion, '^' . $this->version); } public function getName(): string From 594448ee221325b39880961f93eff53877935112 Mon Sep 17 00:00:00 2001 From: Caleb White Date: Fri, 27 Mar 2026 14:05:38 -0500 Subject: [PATCH 2/2] chore: review updates --- src/Composer/InstalledPackageResolver.php | 8 +++++++- src/Set/ValueObject/ComposerTriggeredSet.php | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Composer/InstalledPackageResolver.php b/src/Composer/InstalledPackageResolver.php index cf7615c2e57..c2046cfed6f 100644 --- a/src/Composer/InstalledPackageResolver.php +++ b/src/Composer/InstalledPackageResolver.php @@ -61,7 +61,13 @@ public function resolve(): array public function resolvePackageVersion(string $packageName): ?string { - return ($this->resolve()[$packageName] ?? null)?->getVersion(); + $package = $this->resolve()[$packageName] ?? null; + + if (! $package instanceof InstalledPackage) { + return null; + } + + return $package->getVersion(); } /** diff --git a/src/Set/ValueObject/ComposerTriggeredSet.php b/src/Set/ValueObject/ComposerTriggeredSet.php index c094d213d52..13725c12300 100644 --- a/src/Set/ValueObject/ComposerTriggeredSet.php +++ b/src/Set/ValueObject/ComposerTriggeredSet.php @@ -44,13 +44,13 @@ public function getSetFilePath(): string */ public function matchInstalledPackages(array $installedPackages): bool { - $installedVersion = ($installedPackages[$this->packageName] ?? null)?->getVersion(); + $package = $installedPackages[$this->packageName] ?? null; - if ($installedVersion === null) { + if (! $package instanceof InstalledPackage) { return false; } - return Semver::satisfies($installedVersion, '^' . $this->version); + return Semver::satisfies($package->getVersion(), '^' . $this->version); } public function getName(): string