| @@ -26,8 +26,14 @@ | ||
| 26 | 26 | */ |
| 27 | 27 | class InstalledVersions |
| 28 | 28 | { |
| 29 | 29 | /** |
| 30 | + * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to | |
| 31 | + * @internal | |
| 32 | + */ | |
| 33 | + private static $selfDir = null; | |
| 34 | + | |
| 35 | + /** | |
| 30 | 36 | * @var mixed[]|null |
| 31 | 37 | * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null |
| 32 | 38 | */ |
| 33 | 39 | private static $installed; |
| @@ -32,8 +38,13 @@ | ||
| 32 | 38 | */ |
| 33 | 39 | private static $installed; |
| 34 | 40 | |
| 35 | 41 | /** |
| 42 | + * @var bool | |
| 43 | + */ | |
| 44 | + private static $installedIsLocalDir; | |
| 45 | + | |
| 46 | + /** | |
| 36 | 47 | * @var bool|null |
| 37 | 48 | */ |
| 38 | 49 | private static $canGetVendors; |
| 39 | 50 | |
| @@ -97,9 +108,9 @@ | ||
| 97 | 108 | public static function isInstalled($packageName, $includeDevRequirements = true) |
| 98 | 109 | { |
| 99 | 110 | foreach (self::getInstalled() as $installed) { |
| 100 | 111 | if (isset($installed['versions'][$packageName])) { |
| 101 | - return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); | |
| 112 | + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; | |
| 102 | 113 | } |
| 103 | 114 | } |
| 104 | 115 | |
| 105 | 116 | return false; |
| @@ -118,9 +129,9 @@ | ||
| 118 | 129 | * @return bool |
| 119 | 130 | */ |
| 120 | 131 | public static function satisfies(VersionParser $parser, $packageName, $constraint) |
| 121 | 132 | { |
| 122 | - $constraint = $parser->parseConstraints($constraint); | |
| 133 | + $constraint = $parser->parseConstraints((string) $constraint); | |
| 123 | 134 | $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); |
| 124 | 135 | |
| 125 | 136 | return $provided->matches($constraint); |
| 126 | 137 | } |
| @@ -308,11 +319,29 @@ | ||
| 308 | 319 | public static function reload($data) |
| 309 | 320 | { |
| 310 | 321 | self::$installed = $data; |
| 311 | 322 | self::$installedByVendor = array(); |
| 323 | + | |
| 324 | + // when using reload, we disable the duplicate protection to ensure that self::$installed data is | |
| 325 | + // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, | |
| 326 | + // so we have to assume it does not, and that may result in duplicate data being returned when listing | |
| 327 | + // all installed packages for example | |
| 328 | + self::$installedIsLocalDir = false; | |
| 312 | 329 | } |
| 313 | 330 | |
| 314 | 331 | /** |
| 332 | + * @return string | |
| 333 | + */ | |
| 334 | + private static function getSelfDir() | |
| 335 | + { | |
| 336 | + if (self::$selfDir === null) { | |
| 337 | + self::$selfDir = strtr(__DIR__, '\\', '/'); | |
| 338 | + } | |
| 339 | + | |
| 340 | + return self::$selfDir; | |
| 341 | + } | |
| 342 | + | |
| 343 | + /** | |
| 315 | 344 | * @return array[] |
| 316 | 345 | * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}> |
| 317 | 346 | */ |
| 318 | 347 | private static function getInstalled() |
| @@ -321,19 +350,29 @@ | ||
| 321 | 350 | self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); |
| 322 | 351 | } |
| 323 | 352 | |
| 324 | 353 | $installed = array(); |
| 354 | + $copiedLocalDir = false; | |
| 325 | 355 | |
| 326 | 356 | if (self::$canGetVendors) { |
| 357 | + $selfDir = self::getSelfDir(); | |
| 327 | 358 | foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { |
| 359 | + $vendorDir = strtr($vendorDir, '\\', '/'); | |
| 328 | 360 | if (isset(self::$installedByVendor[$vendorDir])) { |
| 329 | 361 | $installed[] = self::$installedByVendor[$vendorDir]; |
| 330 | 362 | } elseif (is_file($vendorDir.'/composer/installed.php')) { |
| 331 | - $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; | |
| 332 | - if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { | |
| 333 | - self::$installed = $installed[count($installed) - 1]; | |
| 363 | + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */ | |
| 364 | + $required = require $vendorDir.'/composer/installed.php'; | |
| 365 | + self::$installedByVendor[$vendorDir] = $required; | |
| 366 | + $installed[] = $required; | |
| 367 | + if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { | |
| 368 | + self::$installed = $required; | |
| 369 | + self::$installedIsLocalDir = true; | |
| 334 | 370 | } |
| 335 | 371 | } |
| 372 | + if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { | |
| 373 | + $copiedLocalDir = true; | |
| 374 | + } | |
| 336 | 375 | } |
| 337 | 376 | } |
| 338 | 377 | |
| 339 | 378 | if (null === self::$installed) { |
| @@ -339,14 +378,19 @@ | ||
| 339 | 378 | if (null === self::$installed) { |
| 340 | 379 | // only require the installed.php file if this file is loaded from its dumped location, |
| 341 | 380 | // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 |
| 342 | 381 | if (substr(__DIR__, -8, 1) !== 'C') { |
| 343 | - self::$installed = require __DIR__ . '/installed.php'; | |
| 382 | + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */ | |
| 383 | + $required = require __DIR__ . '/installed.php'; | |
| 384 | + self::$installed = $required; | |
| 344 | 385 | } else { |
| 345 | 386 | self::$installed = array(); |
| 346 | 387 | } |
| 347 | 388 | } |
| 348 | - $installed[] = self::$installed; | |
| 389 | + | |
| 390 | + if (self::$installed !== array() && !$copiedLocalDir) { | |
| 391 | + $installed[] = self::$installed; | |
| 392 | + } | |
| 349 | 393 | |
| 350 | 394 | return $installed; |
| 351 | 395 | } |
| 352 | 396 | } |