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