1*c609f1dcSAndreas Gohr<?php 2*c609f1dcSAndreas Gohr 3*c609f1dcSAndreas Gohr/* 4*c609f1dcSAndreas Gohr * This file is part of Composer. 5*c609f1dcSAndreas Gohr * 6*c609f1dcSAndreas Gohr * (c) Nils Adermann <naderman@naderman.de> 7*c609f1dcSAndreas Gohr * Jordi Boggiano <j.boggiano@seld.be> 8*c609f1dcSAndreas Gohr * 9*c609f1dcSAndreas Gohr * For the full copyright and license information, please view the LICENSE 10*c609f1dcSAndreas Gohr * file that was distributed with this source code. 11*c609f1dcSAndreas Gohr */ 12*c609f1dcSAndreas Gohr 13*c609f1dcSAndreas Gohrnamespace Composer; 14*c609f1dcSAndreas Gohr 15*c609f1dcSAndreas Gohruse Composer\Autoload\ClassLoader; 16*c609f1dcSAndreas Gohruse Composer\Semver\VersionParser; 17*c609f1dcSAndreas Gohr 18*c609f1dcSAndreas Gohr/** 19*c609f1dcSAndreas Gohr * This class is copied in every Composer installed project and available to all 20*c609f1dcSAndreas Gohr * 21*c609f1dcSAndreas Gohr * See also https://getcomposer.org/doc/07-runtime.md#installed-versions 22*c609f1dcSAndreas Gohr * 23*c609f1dcSAndreas Gohr * To require its presence, you can require `composer-runtime-api ^2.0` 24*c609f1dcSAndreas Gohr * 25*c609f1dcSAndreas Gohr * @final 26*c609f1dcSAndreas Gohr */ 27*c609f1dcSAndreas Gohrclass InstalledVersions 28*c609f1dcSAndreas Gohr{ 29*c609f1dcSAndreas Gohr /** 30*c609f1dcSAndreas Gohr * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to 31*c609f1dcSAndreas Gohr * @internal 32*c609f1dcSAndreas Gohr */ 33*c609f1dcSAndreas Gohr private static $selfDir = null; 34*c609f1dcSAndreas Gohr 35*c609f1dcSAndreas Gohr /** 36*c609f1dcSAndreas Gohr * @var mixed[]|null 37*c609f1dcSAndreas Gohr * @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 38*c609f1dcSAndreas Gohr */ 39*c609f1dcSAndreas Gohr private static $installed; 40*c609f1dcSAndreas Gohr 41*c609f1dcSAndreas Gohr /** 42*c609f1dcSAndreas Gohr * @var bool 43*c609f1dcSAndreas Gohr */ 44*c609f1dcSAndreas Gohr private static $installedIsLocalDir; 45*c609f1dcSAndreas Gohr 46*c609f1dcSAndreas Gohr /** 47*c609f1dcSAndreas Gohr * @var bool|null 48*c609f1dcSAndreas Gohr */ 49*c609f1dcSAndreas Gohr private static $canGetVendors; 50*c609f1dcSAndreas Gohr 51*c609f1dcSAndreas Gohr /** 52*c609f1dcSAndreas Gohr * @var array[] 53*c609f1dcSAndreas Gohr * @psalm-var array<string, 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[]}>}> 54*c609f1dcSAndreas Gohr */ 55*c609f1dcSAndreas Gohr private static $installedByVendor = array(); 56*c609f1dcSAndreas Gohr 57*c609f1dcSAndreas Gohr /** 58*c609f1dcSAndreas Gohr * Returns a list of all package names which are present, either by being installed, replaced or provided 59*c609f1dcSAndreas Gohr * 60*c609f1dcSAndreas Gohr * @return string[] 61*c609f1dcSAndreas Gohr * @psalm-return list<string> 62*c609f1dcSAndreas Gohr */ 63*c609f1dcSAndreas Gohr public static function getInstalledPackages() 64*c609f1dcSAndreas Gohr { 65*c609f1dcSAndreas Gohr $packages = array(); 66*c609f1dcSAndreas Gohr foreach (self::getInstalled() as $installed) { 67*c609f1dcSAndreas Gohr $packages[] = array_keys($installed['versions']); 68*c609f1dcSAndreas Gohr } 69*c609f1dcSAndreas Gohr 70*c609f1dcSAndreas Gohr if (1 === \count($packages)) { 71*c609f1dcSAndreas Gohr return $packages[0]; 72*c609f1dcSAndreas Gohr } 73*c609f1dcSAndreas Gohr 74*c609f1dcSAndreas Gohr return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); 75*c609f1dcSAndreas Gohr } 76*c609f1dcSAndreas Gohr 77*c609f1dcSAndreas Gohr /** 78*c609f1dcSAndreas Gohr * Returns a list of all package names with a specific type e.g. 'library' 79*c609f1dcSAndreas Gohr * 80*c609f1dcSAndreas Gohr * @param string $type 81*c609f1dcSAndreas Gohr * @return string[] 82*c609f1dcSAndreas Gohr * @psalm-return list<string> 83*c609f1dcSAndreas Gohr */ 84*c609f1dcSAndreas Gohr public static function getInstalledPackagesByType($type) 85*c609f1dcSAndreas Gohr { 86*c609f1dcSAndreas Gohr $packagesByType = array(); 87*c609f1dcSAndreas Gohr 88*c609f1dcSAndreas Gohr foreach (self::getInstalled() as $installed) { 89*c609f1dcSAndreas Gohr foreach ($installed['versions'] as $name => $package) { 90*c609f1dcSAndreas Gohr if (isset($package['type']) && $package['type'] === $type) { 91*c609f1dcSAndreas Gohr $packagesByType[] = $name; 92*c609f1dcSAndreas Gohr } 93*c609f1dcSAndreas Gohr } 94*c609f1dcSAndreas Gohr } 95*c609f1dcSAndreas Gohr 96*c609f1dcSAndreas Gohr return $packagesByType; 97*c609f1dcSAndreas Gohr } 98*c609f1dcSAndreas Gohr 99*c609f1dcSAndreas Gohr /** 100*c609f1dcSAndreas Gohr * Checks whether the given package is installed 101*c609f1dcSAndreas Gohr * 102*c609f1dcSAndreas Gohr * This also returns true if the package name is provided or replaced by another package 103*c609f1dcSAndreas Gohr * 104*c609f1dcSAndreas Gohr * @param string $packageName 105*c609f1dcSAndreas Gohr * @param bool $includeDevRequirements 106*c609f1dcSAndreas Gohr * @return bool 107*c609f1dcSAndreas Gohr */ 108*c609f1dcSAndreas Gohr public static function isInstalled($packageName, $includeDevRequirements = true) 109*c609f1dcSAndreas Gohr { 110*c609f1dcSAndreas Gohr foreach (self::getInstalled() as $installed) { 111*c609f1dcSAndreas Gohr if (isset($installed['versions'][$packageName])) { 112*c609f1dcSAndreas Gohr return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; 113*c609f1dcSAndreas Gohr } 114*c609f1dcSAndreas Gohr } 115*c609f1dcSAndreas Gohr 116*c609f1dcSAndreas Gohr return false; 117*c609f1dcSAndreas Gohr } 118*c609f1dcSAndreas Gohr 119*c609f1dcSAndreas Gohr /** 120*c609f1dcSAndreas Gohr * Checks whether the given package satisfies a version constraint 121*c609f1dcSAndreas Gohr * 122*c609f1dcSAndreas Gohr * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: 123*c609f1dcSAndreas Gohr * 124*c609f1dcSAndreas Gohr * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') 125*c609f1dcSAndreas Gohr * 126*c609f1dcSAndreas Gohr * @param VersionParser $parser Install composer/semver to have access to this class and functionality 127*c609f1dcSAndreas Gohr * @param string $packageName 128*c609f1dcSAndreas Gohr * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package 129*c609f1dcSAndreas Gohr * @return bool 130*c609f1dcSAndreas Gohr */ 131*c609f1dcSAndreas Gohr public static function satisfies(VersionParser $parser, $packageName, $constraint) 132*c609f1dcSAndreas Gohr { 133*c609f1dcSAndreas Gohr $constraint = $parser->parseConstraints((string) $constraint); 134*c609f1dcSAndreas Gohr $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); 135*c609f1dcSAndreas Gohr 136*c609f1dcSAndreas Gohr return $provided->matches($constraint); 137*c609f1dcSAndreas Gohr } 138*c609f1dcSAndreas Gohr 139*c609f1dcSAndreas Gohr /** 140*c609f1dcSAndreas Gohr * Returns a version constraint representing all the range(s) which are installed for a given package 141*c609f1dcSAndreas Gohr * 142*c609f1dcSAndreas Gohr * It is easier to use this via isInstalled() with the $constraint argument if you need to check 143*c609f1dcSAndreas Gohr * whether a given version of a package is installed, and not just whether it exists 144*c609f1dcSAndreas Gohr * 145*c609f1dcSAndreas Gohr * @param string $packageName 146*c609f1dcSAndreas Gohr * @return string Version constraint usable with composer/semver 147*c609f1dcSAndreas Gohr */ 148*c609f1dcSAndreas Gohr public static function getVersionRanges($packageName) 149*c609f1dcSAndreas Gohr { 150*c609f1dcSAndreas Gohr foreach (self::getInstalled() as $installed) { 151*c609f1dcSAndreas Gohr if (!isset($installed['versions'][$packageName])) { 152*c609f1dcSAndreas Gohr continue; 153*c609f1dcSAndreas Gohr } 154*c609f1dcSAndreas Gohr 155*c609f1dcSAndreas Gohr $ranges = array(); 156*c609f1dcSAndreas Gohr if (isset($installed['versions'][$packageName]['pretty_version'])) { 157*c609f1dcSAndreas Gohr $ranges[] = $installed['versions'][$packageName]['pretty_version']; 158*c609f1dcSAndreas Gohr } 159*c609f1dcSAndreas Gohr if (array_key_exists('aliases', $installed['versions'][$packageName])) { 160*c609f1dcSAndreas Gohr $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); 161*c609f1dcSAndreas Gohr } 162*c609f1dcSAndreas Gohr if (array_key_exists('replaced', $installed['versions'][$packageName])) { 163*c609f1dcSAndreas Gohr $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); 164*c609f1dcSAndreas Gohr } 165*c609f1dcSAndreas Gohr if (array_key_exists('provided', $installed['versions'][$packageName])) { 166*c609f1dcSAndreas Gohr $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); 167*c609f1dcSAndreas Gohr } 168*c609f1dcSAndreas Gohr 169*c609f1dcSAndreas Gohr return implode(' || ', $ranges); 170*c609f1dcSAndreas Gohr } 171*c609f1dcSAndreas Gohr 172*c609f1dcSAndreas Gohr throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 173*c609f1dcSAndreas Gohr } 174*c609f1dcSAndreas Gohr 175*c609f1dcSAndreas Gohr /** 176*c609f1dcSAndreas Gohr * @param string $packageName 177*c609f1dcSAndreas Gohr * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present 178*c609f1dcSAndreas Gohr */ 179*c609f1dcSAndreas Gohr public static function getVersion($packageName) 180*c609f1dcSAndreas Gohr { 181*c609f1dcSAndreas Gohr foreach (self::getInstalled() as $installed) { 182*c609f1dcSAndreas Gohr if (!isset($installed['versions'][$packageName])) { 183*c609f1dcSAndreas Gohr continue; 184*c609f1dcSAndreas Gohr } 185*c609f1dcSAndreas Gohr 186*c609f1dcSAndreas Gohr if (!isset($installed['versions'][$packageName]['version'])) { 187*c609f1dcSAndreas Gohr return null; 188*c609f1dcSAndreas Gohr } 189*c609f1dcSAndreas Gohr 190*c609f1dcSAndreas Gohr return $installed['versions'][$packageName]['version']; 191*c609f1dcSAndreas Gohr } 192*c609f1dcSAndreas Gohr 193*c609f1dcSAndreas Gohr throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 194*c609f1dcSAndreas Gohr } 195*c609f1dcSAndreas Gohr 196*c609f1dcSAndreas Gohr /** 197*c609f1dcSAndreas Gohr * @param string $packageName 198*c609f1dcSAndreas Gohr * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present 199*c609f1dcSAndreas Gohr */ 200*c609f1dcSAndreas Gohr public static function getPrettyVersion($packageName) 201*c609f1dcSAndreas Gohr { 202*c609f1dcSAndreas Gohr foreach (self::getInstalled() as $installed) { 203*c609f1dcSAndreas Gohr if (!isset($installed['versions'][$packageName])) { 204*c609f1dcSAndreas Gohr continue; 205*c609f1dcSAndreas Gohr } 206*c609f1dcSAndreas Gohr 207*c609f1dcSAndreas Gohr if (!isset($installed['versions'][$packageName]['pretty_version'])) { 208*c609f1dcSAndreas Gohr return null; 209*c609f1dcSAndreas Gohr } 210*c609f1dcSAndreas Gohr 211*c609f1dcSAndreas Gohr return $installed['versions'][$packageName]['pretty_version']; 212*c609f1dcSAndreas Gohr } 213*c609f1dcSAndreas Gohr 214*c609f1dcSAndreas Gohr throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 215*c609f1dcSAndreas Gohr } 216*c609f1dcSAndreas Gohr 217*c609f1dcSAndreas Gohr /** 218*c609f1dcSAndreas Gohr * @param string $packageName 219*c609f1dcSAndreas Gohr * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference 220*c609f1dcSAndreas Gohr */ 221*c609f1dcSAndreas Gohr public static function getReference($packageName) 222*c609f1dcSAndreas Gohr { 223*c609f1dcSAndreas Gohr foreach (self::getInstalled() as $installed) { 224*c609f1dcSAndreas Gohr if (!isset($installed['versions'][$packageName])) { 225*c609f1dcSAndreas Gohr continue; 226*c609f1dcSAndreas Gohr } 227*c609f1dcSAndreas Gohr 228*c609f1dcSAndreas Gohr if (!isset($installed['versions'][$packageName]['reference'])) { 229*c609f1dcSAndreas Gohr return null; 230*c609f1dcSAndreas Gohr } 231*c609f1dcSAndreas Gohr 232*c609f1dcSAndreas Gohr return $installed['versions'][$packageName]['reference']; 233*c609f1dcSAndreas Gohr } 234*c609f1dcSAndreas Gohr 235*c609f1dcSAndreas Gohr throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 236*c609f1dcSAndreas Gohr } 237*c609f1dcSAndreas Gohr 238*c609f1dcSAndreas Gohr /** 239*c609f1dcSAndreas Gohr * @param string $packageName 240*c609f1dcSAndreas Gohr * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. 241*c609f1dcSAndreas Gohr */ 242*c609f1dcSAndreas Gohr public static function getInstallPath($packageName) 243*c609f1dcSAndreas Gohr { 244*c609f1dcSAndreas Gohr foreach (self::getInstalled() as $installed) { 245*c609f1dcSAndreas Gohr if (!isset($installed['versions'][$packageName])) { 246*c609f1dcSAndreas Gohr continue; 247*c609f1dcSAndreas Gohr } 248*c609f1dcSAndreas Gohr 249*c609f1dcSAndreas Gohr return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; 250*c609f1dcSAndreas Gohr } 251*c609f1dcSAndreas Gohr 252*c609f1dcSAndreas Gohr throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 253*c609f1dcSAndreas Gohr } 254*c609f1dcSAndreas Gohr 255*c609f1dcSAndreas Gohr /** 256*c609f1dcSAndreas Gohr * @return array 257*c609f1dcSAndreas Gohr * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} 258*c609f1dcSAndreas Gohr */ 259*c609f1dcSAndreas Gohr public static function getRootPackage() 260*c609f1dcSAndreas Gohr { 261*c609f1dcSAndreas Gohr $installed = self::getInstalled(); 262*c609f1dcSAndreas Gohr 263*c609f1dcSAndreas Gohr return $installed[0]['root']; 264*c609f1dcSAndreas Gohr } 265*c609f1dcSAndreas Gohr 266*c609f1dcSAndreas Gohr /** 267*c609f1dcSAndreas Gohr * Returns the raw installed.php data for custom implementations 268*c609f1dcSAndreas Gohr * 269*c609f1dcSAndreas Gohr * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. 270*c609f1dcSAndreas Gohr * @return array[] 271*c609f1dcSAndreas Gohr * @psalm-return 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[]}>} 272*c609f1dcSAndreas Gohr */ 273*c609f1dcSAndreas Gohr public static function getRawData() 274*c609f1dcSAndreas Gohr { 275*c609f1dcSAndreas Gohr @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); 276*c609f1dcSAndreas Gohr 277*c609f1dcSAndreas Gohr if (null === self::$installed) { 278*c609f1dcSAndreas Gohr // only require the installed.php file if this file is loaded from its dumped location, 279*c609f1dcSAndreas Gohr // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 280*c609f1dcSAndreas Gohr if (substr(__DIR__, -8, 1) !== 'C') { 281*c609f1dcSAndreas Gohr self::$installed = include __DIR__ . '/installed.php'; 282*c609f1dcSAndreas Gohr } else { 283*c609f1dcSAndreas Gohr self::$installed = array(); 284*c609f1dcSAndreas Gohr } 285*c609f1dcSAndreas Gohr } 286*c609f1dcSAndreas Gohr 287*c609f1dcSAndreas Gohr return self::$installed; 288*c609f1dcSAndreas Gohr } 289*c609f1dcSAndreas Gohr 290*c609f1dcSAndreas Gohr /** 291*c609f1dcSAndreas Gohr * Returns the raw data of all installed.php which are currently loaded for custom implementations 292*c609f1dcSAndreas Gohr * 293*c609f1dcSAndreas Gohr * @return array[] 294*c609f1dcSAndreas Gohr * @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[]}>}> 295*c609f1dcSAndreas Gohr */ 296*c609f1dcSAndreas Gohr public static function getAllRawData() 297*c609f1dcSAndreas Gohr { 298*c609f1dcSAndreas Gohr return self::getInstalled(); 299*c609f1dcSAndreas Gohr } 300*c609f1dcSAndreas Gohr 301*c609f1dcSAndreas Gohr /** 302*c609f1dcSAndreas Gohr * Lets you reload the static array from another file 303*c609f1dcSAndreas Gohr * 304*c609f1dcSAndreas Gohr * This is only useful for complex integrations in which a project needs to use 305*c609f1dcSAndreas Gohr * this class but then also needs to execute another project's autoloader in process, 306*c609f1dcSAndreas Gohr * and wants to ensure both projects have access to their version of installed.php. 307*c609f1dcSAndreas Gohr * 308*c609f1dcSAndreas Gohr * A typical case would be PHPUnit, where it would need to make sure it reads all 309*c609f1dcSAndreas Gohr * the data it needs from this class, then call reload() with 310*c609f1dcSAndreas Gohr * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure 311*c609f1dcSAndreas Gohr * the project in which it runs can then also use this class safely, without 312*c609f1dcSAndreas Gohr * interference between PHPUnit's dependencies and the project's dependencies. 313*c609f1dcSAndreas Gohr * 314*c609f1dcSAndreas Gohr * @param array[] $data A vendor/composer/installed.php data set 315*c609f1dcSAndreas Gohr * @return void 316*c609f1dcSAndreas Gohr * 317*c609f1dcSAndreas Gohr * @psalm-param 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[]}>} $data 318*c609f1dcSAndreas Gohr */ 319*c609f1dcSAndreas Gohr public static function reload($data) 320*c609f1dcSAndreas Gohr { 321*c609f1dcSAndreas Gohr self::$installed = $data; 322*c609f1dcSAndreas Gohr self::$installedByVendor = array(); 323*c609f1dcSAndreas Gohr 324*c609f1dcSAndreas Gohr // when using reload, we disable the duplicate protection to ensure that self::$installed data is 325*c609f1dcSAndreas Gohr // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, 326*c609f1dcSAndreas Gohr // so we have to assume it does not, and that may result in duplicate data being returned when listing 327*c609f1dcSAndreas Gohr // all installed packages for example 328*c609f1dcSAndreas Gohr self::$installedIsLocalDir = false; 329*c609f1dcSAndreas Gohr } 330*c609f1dcSAndreas Gohr 331*c609f1dcSAndreas Gohr /** 332*c609f1dcSAndreas Gohr * @return string 333*c609f1dcSAndreas Gohr */ 334*c609f1dcSAndreas Gohr private static function getSelfDir() 335*c609f1dcSAndreas Gohr { 336*c609f1dcSAndreas Gohr if (self::$selfDir === null) { 337*c609f1dcSAndreas Gohr self::$selfDir = strtr(__DIR__, '\\', '/'); 338*c609f1dcSAndreas Gohr } 339*c609f1dcSAndreas Gohr 340*c609f1dcSAndreas Gohr return self::$selfDir; 341*c609f1dcSAndreas Gohr } 342*c609f1dcSAndreas Gohr 343*c609f1dcSAndreas Gohr /** 344*c609f1dcSAndreas Gohr * @return array[] 345*c609f1dcSAndreas Gohr * @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[]}>}> 346*c609f1dcSAndreas Gohr */ 347*c609f1dcSAndreas Gohr private static function getInstalled() 348*c609f1dcSAndreas Gohr { 349*c609f1dcSAndreas Gohr if (null === self::$canGetVendors) { 350*c609f1dcSAndreas Gohr self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); 351*c609f1dcSAndreas Gohr } 352*c609f1dcSAndreas Gohr 353*c609f1dcSAndreas Gohr $installed = array(); 354*c609f1dcSAndreas Gohr $copiedLocalDir = false; 355*c609f1dcSAndreas Gohr 356*c609f1dcSAndreas Gohr if (self::$canGetVendors) { 357*c609f1dcSAndreas Gohr $selfDir = self::getSelfDir(); 358*c609f1dcSAndreas Gohr foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { 359*c609f1dcSAndreas Gohr $vendorDir = strtr($vendorDir, '\\', '/'); 360*c609f1dcSAndreas Gohr if (isset(self::$installedByVendor[$vendorDir])) { 361*c609f1dcSAndreas Gohr $installed[] = self::$installedByVendor[$vendorDir]; 362*c609f1dcSAndreas Gohr } elseif (is_file($vendorDir.'/composer/installed.php')) { 363*c609f1dcSAndreas Gohr /** @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*c609f1dcSAndreas Gohr $required = require $vendorDir.'/composer/installed.php'; 365*c609f1dcSAndreas Gohr self::$installedByVendor[$vendorDir] = $required; 366*c609f1dcSAndreas Gohr $installed[] = $required; 367*c609f1dcSAndreas Gohr if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { 368*c609f1dcSAndreas Gohr self::$installed = $required; 369*c609f1dcSAndreas Gohr self::$installedIsLocalDir = true; 370*c609f1dcSAndreas Gohr } 371*c609f1dcSAndreas Gohr } 372*c609f1dcSAndreas Gohr if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { 373*c609f1dcSAndreas Gohr $copiedLocalDir = true; 374*c609f1dcSAndreas Gohr } 375*c609f1dcSAndreas Gohr } 376*c609f1dcSAndreas Gohr } 377*c609f1dcSAndreas Gohr 378*c609f1dcSAndreas Gohr if (null === self::$installed) { 379*c609f1dcSAndreas Gohr // only require the installed.php file if this file is loaded from its dumped location, 380*c609f1dcSAndreas Gohr // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 381*c609f1dcSAndreas Gohr if (substr(__DIR__, -8, 1) !== 'C') { 382*c609f1dcSAndreas Gohr /** @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*c609f1dcSAndreas Gohr $required = require __DIR__ . '/installed.php'; 384*c609f1dcSAndreas Gohr self::$installed = $required; 385*c609f1dcSAndreas Gohr } else { 386*c609f1dcSAndreas Gohr self::$installed = array(); 387*c609f1dcSAndreas Gohr } 388*c609f1dcSAndreas Gohr } 389*c609f1dcSAndreas Gohr 390*c609f1dcSAndreas Gohr if (self::$installed !== array() && !$copiedLocalDir) { 391*c609f1dcSAndreas Gohr $installed[] = self::$installed; 392*c609f1dcSAndreas Gohr } 393*c609f1dcSAndreas Gohr 394*c609f1dcSAndreas Gohr return $installed; 395*c609f1dcSAndreas Gohr } 396*c609f1dcSAndreas Gohr} 397