137748cd8SNickeau<?php 237748cd8SNickeau 3*83c68632SNico/* 4*83c68632SNico * This file is part of Composer. 5*83c68632SNico * 6*83c68632SNico * (c) Nils Adermann <naderman@naderman.de> 7*83c68632SNico * Jordi Boggiano <j.boggiano@seld.be> 8*83c68632SNico * 9*83c68632SNico * For the full copyright and license information, please view the LICENSE 10*83c68632SNico * file that was distributed with this source code. 11*83c68632SNico */ 1237748cd8SNickeau 1337748cd8SNickeaunamespace Composer; 1437748cd8SNickeau 1537748cd8SNickeauuse Composer\Autoload\ClassLoader; 1637748cd8SNickeauuse Composer\Semver\VersionParser; 1737748cd8SNickeau 18*83c68632SNico/** 19*83c68632SNico * This class is copied in every Composer installed project and available to all 20*83c68632SNico * 21*83c68632SNico * See also https://getcomposer.org/doc/07-runtime.md#installed-versions 22*83c68632SNico * 23*83c68632SNico * To require its presence, you can require `composer-runtime-api ^2.0` 24*83c68632SNico * 25*83c68632SNico * @final 26*83c68632SNico */ 2737748cd8SNickeauclass InstalledVersions 2837748cd8SNickeau{ 29*83c68632SNico /** 30*83c68632SNico * @var mixed[]|null 31*83c68632SNico * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null 32*83c68632SNico */ 33*83c68632SNico private static $installed; 34*83c68632SNico 35*83c68632SNico /** 36*83c68632SNico * @var bool|null 37*83c68632SNico */ 3837748cd8SNickeau private static $canGetVendors; 39*83c68632SNico 40*83c68632SNico /** 41*83c68632SNico * @var array[] 42*83c68632SNico * @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[]}>}> 43*83c68632SNico */ 4437748cd8SNickeau private static $installedByVendor = array(); 4537748cd8SNickeau 46*83c68632SNico /** 47*83c68632SNico * Returns a list of all package names which are present, either by being installed, replaced or provided 48*83c68632SNico * 49*83c68632SNico * @return string[] 50*83c68632SNico * @psalm-return list<string> 51*83c68632SNico */ 5237748cd8SNickeau public static function getInstalledPackages() 5337748cd8SNickeau { 5437748cd8SNickeau $packages = array(); 5537748cd8SNickeau foreach (self::getInstalled() as $installed) { 5637748cd8SNickeau $packages[] = array_keys($installed['versions']); 5737748cd8SNickeau } 5837748cd8SNickeau 5937748cd8SNickeau if (1 === \count($packages)) { 6037748cd8SNickeau return $packages[0]; 6137748cd8SNickeau } 6237748cd8SNickeau 6337748cd8SNickeau return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); 6437748cd8SNickeau } 6537748cd8SNickeau 66*83c68632SNico /** 67*83c68632SNico * Returns a list of all package names with a specific type e.g. 'library' 68*83c68632SNico * 69*83c68632SNico * @param string $type 70*83c68632SNico * @return string[] 71*83c68632SNico * @psalm-return list<string> 72*83c68632SNico */ 73*83c68632SNico public static function getInstalledPackagesByType($type) 74*83c68632SNico { 75*83c68632SNico $packagesByType = array(); 7637748cd8SNickeau 77*83c68632SNico foreach (self::getInstalled() as $installed) { 78*83c68632SNico foreach ($installed['versions'] as $name => $package) { 79*83c68632SNico if (isset($package['type']) && $package['type'] === $type) { 80*83c68632SNico $packagesByType[] = $name; 81*83c68632SNico } 82*83c68632SNico } 83*83c68632SNico } 8437748cd8SNickeau 85*83c68632SNico return $packagesByType; 86*83c68632SNico } 8737748cd8SNickeau 88*83c68632SNico /** 89*83c68632SNico * Checks whether the given package is installed 90*83c68632SNico * 91*83c68632SNico * This also returns true if the package name is provided or replaced by another package 92*83c68632SNico * 93*83c68632SNico * @param string $packageName 94*83c68632SNico * @param bool $includeDevRequirements 95*83c68632SNico * @return bool 96*83c68632SNico */ 97*83c68632SNico public static function isInstalled($packageName, $includeDevRequirements = true) 9837748cd8SNickeau { 9937748cd8SNickeau foreach (self::getInstalled() as $installed) { 10037748cd8SNickeau if (isset($installed['versions'][$packageName])) { 101*83c68632SNico return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; 10237748cd8SNickeau } 10337748cd8SNickeau } 10437748cd8SNickeau 10537748cd8SNickeau return false; 10637748cd8SNickeau } 10737748cd8SNickeau 108*83c68632SNico /** 109*83c68632SNico * Checks whether the given package satisfies a version constraint 110*83c68632SNico * 111*83c68632SNico * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: 112*83c68632SNico * 113*83c68632SNico * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') 114*83c68632SNico * 115*83c68632SNico * @param VersionParser $parser Install composer/semver to have access to this class and functionality 116*83c68632SNico * @param string $packageName 117*83c68632SNico * @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 118*83c68632SNico * @return bool 119*83c68632SNico */ 12037748cd8SNickeau public static function satisfies(VersionParser $parser, $packageName, $constraint) 12137748cd8SNickeau { 122*83c68632SNico $constraint = $parser->parseConstraints((string) $constraint); 12337748cd8SNickeau $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); 12437748cd8SNickeau 12537748cd8SNickeau return $provided->matches($constraint); 12637748cd8SNickeau } 12737748cd8SNickeau 128*83c68632SNico /** 129*83c68632SNico * Returns a version constraint representing all the range(s) which are installed for a given package 130*83c68632SNico * 131*83c68632SNico * It is easier to use this via isInstalled() with the $constraint argument if you need to check 132*83c68632SNico * whether a given version of a package is installed, and not just whether it exists 133*83c68632SNico * 134*83c68632SNico * @param string $packageName 135*83c68632SNico * @return string Version constraint usable with composer/semver 136*83c68632SNico */ 13737748cd8SNickeau public static function getVersionRanges($packageName) 13837748cd8SNickeau { 13937748cd8SNickeau foreach (self::getInstalled() as $installed) { 14037748cd8SNickeau if (!isset($installed['versions'][$packageName])) { 14137748cd8SNickeau continue; 14237748cd8SNickeau } 14337748cd8SNickeau 14437748cd8SNickeau $ranges = array(); 14537748cd8SNickeau if (isset($installed['versions'][$packageName]['pretty_version'])) { 14637748cd8SNickeau $ranges[] = $installed['versions'][$packageName]['pretty_version']; 14737748cd8SNickeau } 14837748cd8SNickeau if (array_key_exists('aliases', $installed['versions'][$packageName])) { 14937748cd8SNickeau $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); 15037748cd8SNickeau } 15137748cd8SNickeau if (array_key_exists('replaced', $installed['versions'][$packageName])) { 15237748cd8SNickeau $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); 15337748cd8SNickeau } 15437748cd8SNickeau if (array_key_exists('provided', $installed['versions'][$packageName])) { 15537748cd8SNickeau $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); 15637748cd8SNickeau } 15737748cd8SNickeau 15837748cd8SNickeau return implode(' || ', $ranges); 15937748cd8SNickeau } 16037748cd8SNickeau 16137748cd8SNickeau throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 16237748cd8SNickeau } 16337748cd8SNickeau 164*83c68632SNico /** 165*83c68632SNico * @param string $packageName 166*83c68632SNico * @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 167*83c68632SNico */ 16837748cd8SNickeau public static function getVersion($packageName) 16937748cd8SNickeau { 17037748cd8SNickeau foreach (self::getInstalled() as $installed) { 17137748cd8SNickeau if (!isset($installed['versions'][$packageName])) { 17237748cd8SNickeau continue; 17337748cd8SNickeau } 17437748cd8SNickeau 17537748cd8SNickeau if (!isset($installed['versions'][$packageName]['version'])) { 17637748cd8SNickeau return null; 17737748cd8SNickeau } 17837748cd8SNickeau 17937748cd8SNickeau return $installed['versions'][$packageName]['version']; 18037748cd8SNickeau } 18137748cd8SNickeau 18237748cd8SNickeau throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 18337748cd8SNickeau } 18437748cd8SNickeau 185*83c68632SNico /** 186*83c68632SNico * @param string $packageName 187*83c68632SNico * @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 188*83c68632SNico */ 18937748cd8SNickeau public static function getPrettyVersion($packageName) 19037748cd8SNickeau { 19137748cd8SNickeau foreach (self::getInstalled() as $installed) { 19237748cd8SNickeau if (!isset($installed['versions'][$packageName])) { 19337748cd8SNickeau continue; 19437748cd8SNickeau } 19537748cd8SNickeau 19637748cd8SNickeau if (!isset($installed['versions'][$packageName]['pretty_version'])) { 19737748cd8SNickeau return null; 19837748cd8SNickeau } 19937748cd8SNickeau 20037748cd8SNickeau return $installed['versions'][$packageName]['pretty_version']; 20137748cd8SNickeau } 20237748cd8SNickeau 20337748cd8SNickeau throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 20437748cd8SNickeau } 20537748cd8SNickeau 206*83c68632SNico /** 207*83c68632SNico * @param string $packageName 208*83c68632SNico * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference 209*83c68632SNico */ 21037748cd8SNickeau public static function getReference($packageName) 21137748cd8SNickeau { 21237748cd8SNickeau foreach (self::getInstalled() as $installed) { 21337748cd8SNickeau if (!isset($installed['versions'][$packageName])) { 21437748cd8SNickeau continue; 21537748cd8SNickeau } 21637748cd8SNickeau 21737748cd8SNickeau if (!isset($installed['versions'][$packageName]['reference'])) { 21837748cd8SNickeau return null; 21937748cd8SNickeau } 22037748cd8SNickeau 22137748cd8SNickeau return $installed['versions'][$packageName]['reference']; 22237748cd8SNickeau } 22337748cd8SNickeau 22437748cd8SNickeau throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 22537748cd8SNickeau } 22637748cd8SNickeau 227*83c68632SNico /** 228*83c68632SNico * @param string $packageName 229*83c68632SNico * @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. 230*83c68632SNico */ 231*83c68632SNico public static function getInstallPath($packageName) 232*83c68632SNico { 233*83c68632SNico foreach (self::getInstalled() as $installed) { 234*83c68632SNico if (!isset($installed['versions'][$packageName])) { 235*83c68632SNico continue; 236*83c68632SNico } 23737748cd8SNickeau 238*83c68632SNico return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; 239*83c68632SNico } 24037748cd8SNickeau 241*83c68632SNico throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 242*83c68632SNico } 24337748cd8SNickeau 244*83c68632SNico /** 245*83c68632SNico * @return array 246*83c68632SNico * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} 247*83c68632SNico */ 24837748cd8SNickeau public static function getRootPackage() 24937748cd8SNickeau { 25037748cd8SNickeau $installed = self::getInstalled(); 25137748cd8SNickeau 25237748cd8SNickeau return $installed[0]['root']; 25337748cd8SNickeau } 25437748cd8SNickeau 255*83c68632SNico /** 256*83c68632SNico * Returns the raw installed.php data for custom implementations 257*83c68632SNico * 258*83c68632SNico * @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. 259*83c68632SNico * @return array[] 260*83c68632SNico * @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[]}>} 261*83c68632SNico */ 26237748cd8SNickeau public static function getRawData() 26337748cd8SNickeau { 26437748cd8SNickeau @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); 26537748cd8SNickeau 266*83c68632SNico if (null === self::$installed) { 267*83c68632SNico // only require the installed.php file if this file is loaded from its dumped location, 268*83c68632SNico // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 269*83c68632SNico if (substr(__DIR__, -8, 1) !== 'C') { 270*83c68632SNico self::$installed = include __DIR__ . '/installed.php'; 271*83c68632SNico } else { 272*83c68632SNico self::$installed = array(); 273*83c68632SNico } 274*83c68632SNico } 275*83c68632SNico 27637748cd8SNickeau return self::$installed; 27737748cd8SNickeau } 27837748cd8SNickeau 279*83c68632SNico /** 280*83c68632SNico * Returns the raw data of all installed.php which are currently loaded for custom implementations 281*83c68632SNico * 282*83c68632SNico * @return array[] 283*83c68632SNico * @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[]}>}> 284*83c68632SNico */ 28537748cd8SNickeau public static function getAllRawData() 28637748cd8SNickeau { 28737748cd8SNickeau return self::getInstalled(); 28837748cd8SNickeau } 28937748cd8SNickeau 290*83c68632SNico /** 291*83c68632SNico * Lets you reload the static array from another file 292*83c68632SNico * 293*83c68632SNico * This is only useful for complex integrations in which a project needs to use 294*83c68632SNico * this class but then also needs to execute another project's autoloader in process, 295*83c68632SNico * and wants to ensure both projects have access to their version of installed.php. 296*83c68632SNico * 297*83c68632SNico * A typical case would be PHPUnit, where it would need to make sure it reads all 298*83c68632SNico * the data it needs from this class, then call reload() with 299*83c68632SNico * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure 300*83c68632SNico * the project in which it runs can then also use this class safely, without 301*83c68632SNico * interference between PHPUnit's dependencies and the project's dependencies. 302*83c68632SNico * 303*83c68632SNico * @param array[] $data A vendor/composer/installed.php data set 304*83c68632SNico * @return void 305*83c68632SNico * 306*83c68632SNico * @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 307*83c68632SNico */ 30837748cd8SNickeau public static function reload($data) 30937748cd8SNickeau { 31037748cd8SNickeau self::$installed = $data; 31137748cd8SNickeau self::$installedByVendor = array(); 31237748cd8SNickeau } 31337748cd8SNickeau 314*83c68632SNico /** 315*83c68632SNico * @return array[] 316*83c68632SNico * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}> 317*83c68632SNico */ 31837748cd8SNickeau private static function getInstalled() 31937748cd8SNickeau { 32037748cd8SNickeau if (null === self::$canGetVendors) { 32137748cd8SNickeau self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); 32237748cd8SNickeau } 32337748cd8SNickeau 32437748cd8SNickeau $installed = array(); 32537748cd8SNickeau 32637748cd8SNickeau if (self::$canGetVendors) { 32737748cd8SNickeau foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { 32837748cd8SNickeau if (isset(self::$installedByVendor[$vendorDir])) { 32937748cd8SNickeau $installed[] = self::$installedByVendor[$vendorDir]; 33037748cd8SNickeau } elseif (is_file($vendorDir.'/composer/installed.php')) { 331*83c68632SNico /** @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 */ 332*83c68632SNico $required = require $vendorDir.'/composer/installed.php'; 333*83c68632SNico $installed[] = self::$installedByVendor[$vendorDir] = $required; 334*83c68632SNico if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { 335*83c68632SNico self::$installed = $installed[count($installed) - 1]; 336*83c68632SNico } 33737748cd8SNickeau } 33837748cd8SNickeau } 33937748cd8SNickeau } 34037748cd8SNickeau 341*83c68632SNico if (null === self::$installed) { 342*83c68632SNico // only require the installed.php file if this file is loaded from its dumped location, 343*83c68632SNico // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 344*83c68632SNico if (substr(__DIR__, -8, 1) !== 'C') { 345*83c68632SNico /** @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 */ 346*83c68632SNico $required = require __DIR__ . '/installed.php'; 347*83c68632SNico self::$installed = $required; 348*83c68632SNico } else { 349*83c68632SNico self::$installed = array(); 350*83c68632SNico } 351*83c68632SNico } 352*83c68632SNico 353*83c68632SNico if (self::$installed !== array()) { 35437748cd8SNickeau $installed[] = self::$installed; 355*83c68632SNico } 35637748cd8SNickeau 35737748cd8SNickeau return $installed; 35837748cd8SNickeau } 35937748cd8SNickeau} 360