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