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