16cb05674SAndreas Gohr<?php 26cb05674SAndreas Gohr 3*d3233986SAndreas Gohr/* 4*d3233986SAndreas Gohr * This file is part of Composer. 5*d3233986SAndreas Gohr * 6*d3233986SAndreas Gohr * (c) Nils Adermann <naderman@naderman.de> 7*d3233986SAndreas Gohr * Jordi Boggiano <j.boggiano@seld.be> 8*d3233986SAndreas Gohr * 9*d3233986SAndreas Gohr * For the full copyright and license information, please view the LICENSE 10*d3233986SAndreas Gohr * file that was distributed with this source code. 11*d3233986SAndreas Gohr */ 126cb05674SAndreas Gohr 136cb05674SAndreas Gohrnamespace Composer; 146cb05674SAndreas Gohr 156cb05674SAndreas Gohruse Composer\Autoload\ClassLoader; 166cb05674SAndreas Gohruse Composer\Semver\VersionParser; 176cb05674SAndreas Gohr 18*d3233986SAndreas Gohr/** 19*d3233986SAndreas Gohr * This class is copied in every Composer installed project and available to all 20*d3233986SAndreas Gohr * 21*d3233986SAndreas Gohr * See also https://getcomposer.org/doc/07-runtime.md#installed-versions 22*d3233986SAndreas Gohr * 23*d3233986SAndreas Gohr * To require its presence, you can require `composer-runtime-api ^2.0` 24*d3233986SAndreas Gohr * 25*d3233986SAndreas Gohr * @final 26*d3233986SAndreas Gohr */ 276cb05674SAndreas Gohrclass InstalledVersions 286cb05674SAndreas Gohr{ 29*d3233986SAndreas Gohr /** 30*d3233986SAndreas Gohr * @var mixed[]|null 31*d3233986SAndreas Gohr * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null 32*d3233986SAndreas Gohr */ 33*d3233986SAndreas Gohr private static $installed; 34*d3233986SAndreas Gohr 35*d3233986SAndreas Gohr /** 36*d3233986SAndreas Gohr * @var bool|null 37*d3233986SAndreas Gohr */ 386cb05674SAndreas Gohr private static $canGetVendors; 39*d3233986SAndreas Gohr 40*d3233986SAndreas Gohr /** 41*d3233986SAndreas Gohr * @var array[] 42*d3233986SAndreas Gohr * @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> 43*d3233986SAndreas Gohr */ 446cb05674SAndreas Gohr private static $installedByVendor = array(); 456cb05674SAndreas Gohr 46*d3233986SAndreas Gohr /** 47*d3233986SAndreas Gohr * Returns a list of all package names which are present, either by being installed, replaced or provided 48*d3233986SAndreas Gohr * 49*d3233986SAndreas Gohr * @return string[] 50*d3233986SAndreas Gohr * @psalm-return list<string> 51*d3233986SAndreas Gohr */ 526cb05674SAndreas Gohr public static function getInstalledPackages() 536cb05674SAndreas Gohr { 546cb05674SAndreas Gohr $packages = array(); 556cb05674SAndreas Gohr foreach (self::getInstalled() as $installed) { 566cb05674SAndreas Gohr $packages[] = array_keys($installed['versions']); 576cb05674SAndreas Gohr } 586cb05674SAndreas Gohr 596cb05674SAndreas Gohr if (1 === \count($packages)) { 606cb05674SAndreas Gohr return $packages[0]; 616cb05674SAndreas Gohr } 626cb05674SAndreas Gohr 636cb05674SAndreas Gohr return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); 646cb05674SAndreas Gohr } 656cb05674SAndreas Gohr 66*d3233986SAndreas Gohr /** 67*d3233986SAndreas Gohr * Returns a list of all package names with a specific type e.g. 'library' 68*d3233986SAndreas Gohr * 69*d3233986SAndreas Gohr * @param string $type 70*d3233986SAndreas Gohr * @return string[] 71*d3233986SAndreas Gohr * @psalm-return list<string> 72*d3233986SAndreas Gohr */ 73*d3233986SAndreas Gohr public static function getInstalledPackagesByType($type) 74*d3233986SAndreas Gohr { 75*d3233986SAndreas Gohr $packagesByType = array(); 766cb05674SAndreas Gohr 77*d3233986SAndreas Gohr foreach (self::getInstalled() as $installed) { 78*d3233986SAndreas Gohr foreach ($installed['versions'] as $name => $package) { 79*d3233986SAndreas Gohr if (isset($package['type']) && $package['type'] === $type) { 80*d3233986SAndreas Gohr $packagesByType[] = $name; 81*d3233986SAndreas Gohr } 82*d3233986SAndreas Gohr } 83*d3233986SAndreas Gohr } 846cb05674SAndreas Gohr 85*d3233986SAndreas Gohr return $packagesByType; 86*d3233986SAndreas Gohr } 876cb05674SAndreas Gohr 88*d3233986SAndreas Gohr /** 89*d3233986SAndreas Gohr * Checks whether the given package is installed 90*d3233986SAndreas Gohr * 91*d3233986SAndreas Gohr * This also returns true if the package name is provided or replaced by another package 92*d3233986SAndreas Gohr * 93*d3233986SAndreas Gohr * @param string $packageName 94*d3233986SAndreas Gohr * @param bool $includeDevRequirements 95*d3233986SAndreas Gohr * @return bool 96*d3233986SAndreas Gohr */ 97*d3233986SAndreas Gohr public static function isInstalled($packageName, $includeDevRequirements = true) 986cb05674SAndreas Gohr { 996cb05674SAndreas Gohr foreach (self::getInstalled() as $installed) { 1006cb05674SAndreas Gohr if (isset($installed['versions'][$packageName])) { 101*d3233986SAndreas Gohr return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); 1026cb05674SAndreas Gohr } 1036cb05674SAndreas Gohr } 1046cb05674SAndreas Gohr 1056cb05674SAndreas Gohr return false; 1066cb05674SAndreas Gohr } 1076cb05674SAndreas Gohr 108*d3233986SAndreas Gohr /** 109*d3233986SAndreas Gohr * Checks whether the given package satisfies a version constraint 110*d3233986SAndreas Gohr * 111*d3233986SAndreas Gohr * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: 112*d3233986SAndreas Gohr * 113*d3233986SAndreas Gohr * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') 114*d3233986SAndreas Gohr * 115*d3233986SAndreas Gohr * @param VersionParser $parser Install composer/semver to have access to this class and functionality 116*d3233986SAndreas Gohr * @param string $packageName 117*d3233986SAndreas 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 118*d3233986SAndreas Gohr * @return bool 119*d3233986SAndreas Gohr */ 1206cb05674SAndreas Gohr public static function satisfies(VersionParser $parser, $packageName, $constraint) 1216cb05674SAndreas Gohr { 1226cb05674SAndreas Gohr $constraint = $parser->parseConstraints($constraint); 1236cb05674SAndreas Gohr $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); 1246cb05674SAndreas Gohr 1256cb05674SAndreas Gohr return $provided->matches($constraint); 1266cb05674SAndreas Gohr } 1276cb05674SAndreas Gohr 128*d3233986SAndreas Gohr /** 129*d3233986SAndreas Gohr * Returns a version constraint representing all the range(s) which are installed for a given package 130*d3233986SAndreas Gohr * 131*d3233986SAndreas Gohr * It is easier to use this via isInstalled() with the $constraint argument if you need to check 132*d3233986SAndreas Gohr * whether a given version of a package is installed, and not just whether it exists 133*d3233986SAndreas Gohr * 134*d3233986SAndreas Gohr * @param string $packageName 135*d3233986SAndreas Gohr * @return string Version constraint usable with composer/semver 136*d3233986SAndreas Gohr */ 1376cb05674SAndreas Gohr public static function getVersionRanges($packageName) 1386cb05674SAndreas Gohr { 1396cb05674SAndreas Gohr foreach (self::getInstalled() as $installed) { 1406cb05674SAndreas Gohr if (!isset($installed['versions'][$packageName])) { 1416cb05674SAndreas Gohr continue; 1426cb05674SAndreas Gohr } 1436cb05674SAndreas Gohr 1446cb05674SAndreas Gohr $ranges = array(); 1456cb05674SAndreas Gohr if (isset($installed['versions'][$packageName]['pretty_version'])) { 1466cb05674SAndreas Gohr $ranges[] = $installed['versions'][$packageName]['pretty_version']; 1476cb05674SAndreas Gohr } 1486cb05674SAndreas Gohr if (array_key_exists('aliases', $installed['versions'][$packageName])) { 1496cb05674SAndreas Gohr $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); 1506cb05674SAndreas Gohr } 1516cb05674SAndreas Gohr if (array_key_exists('replaced', $installed['versions'][$packageName])) { 1526cb05674SAndreas Gohr $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); 1536cb05674SAndreas Gohr } 1546cb05674SAndreas Gohr if (array_key_exists('provided', $installed['versions'][$packageName])) { 1556cb05674SAndreas Gohr $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); 1566cb05674SAndreas Gohr } 1576cb05674SAndreas Gohr 1586cb05674SAndreas Gohr return implode(' || ', $ranges); 1596cb05674SAndreas Gohr } 1606cb05674SAndreas Gohr 1616cb05674SAndreas Gohr throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 1626cb05674SAndreas Gohr } 1636cb05674SAndreas Gohr 164*d3233986SAndreas Gohr /** 165*d3233986SAndreas Gohr * @param string $packageName 166*d3233986SAndreas 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 167*d3233986SAndreas Gohr */ 1686cb05674SAndreas Gohr public static function getVersion($packageName) 1696cb05674SAndreas Gohr { 1706cb05674SAndreas Gohr foreach (self::getInstalled() as $installed) { 1716cb05674SAndreas Gohr if (!isset($installed['versions'][$packageName])) { 1726cb05674SAndreas Gohr continue; 1736cb05674SAndreas Gohr } 1746cb05674SAndreas Gohr 1756cb05674SAndreas Gohr if (!isset($installed['versions'][$packageName]['version'])) { 1766cb05674SAndreas Gohr return null; 1776cb05674SAndreas Gohr } 1786cb05674SAndreas Gohr 1796cb05674SAndreas Gohr return $installed['versions'][$packageName]['version']; 1806cb05674SAndreas Gohr } 1816cb05674SAndreas Gohr 1826cb05674SAndreas Gohr throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 1836cb05674SAndreas Gohr } 1846cb05674SAndreas Gohr 185*d3233986SAndreas Gohr /** 186*d3233986SAndreas Gohr * @param string $packageName 187*d3233986SAndreas 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 188*d3233986SAndreas Gohr */ 1896cb05674SAndreas Gohr public static function getPrettyVersion($packageName) 1906cb05674SAndreas Gohr { 1916cb05674SAndreas Gohr foreach (self::getInstalled() as $installed) { 1926cb05674SAndreas Gohr if (!isset($installed['versions'][$packageName])) { 1936cb05674SAndreas Gohr continue; 1946cb05674SAndreas Gohr } 1956cb05674SAndreas Gohr 1966cb05674SAndreas Gohr if (!isset($installed['versions'][$packageName]['pretty_version'])) { 1976cb05674SAndreas Gohr return null; 1986cb05674SAndreas Gohr } 1996cb05674SAndreas Gohr 2006cb05674SAndreas Gohr return $installed['versions'][$packageName]['pretty_version']; 2016cb05674SAndreas Gohr } 2026cb05674SAndreas Gohr 2036cb05674SAndreas Gohr throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 2046cb05674SAndreas Gohr } 2056cb05674SAndreas Gohr 206*d3233986SAndreas Gohr /** 207*d3233986SAndreas Gohr * @param string $packageName 208*d3233986SAndreas Gohr * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference 209*d3233986SAndreas Gohr */ 2106cb05674SAndreas Gohr public static function getReference($packageName) 2116cb05674SAndreas Gohr { 2126cb05674SAndreas Gohr foreach (self::getInstalled() as $installed) { 2136cb05674SAndreas Gohr if (!isset($installed['versions'][$packageName])) { 2146cb05674SAndreas Gohr continue; 2156cb05674SAndreas Gohr } 2166cb05674SAndreas Gohr 2176cb05674SAndreas Gohr if (!isset($installed['versions'][$packageName]['reference'])) { 2186cb05674SAndreas Gohr return null; 2196cb05674SAndreas Gohr } 2206cb05674SAndreas Gohr 2216cb05674SAndreas Gohr return $installed['versions'][$packageName]['reference']; 2226cb05674SAndreas Gohr } 2236cb05674SAndreas Gohr 2246cb05674SAndreas Gohr throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 2256cb05674SAndreas Gohr } 2266cb05674SAndreas Gohr 227*d3233986SAndreas Gohr /** 228*d3233986SAndreas Gohr * @param string $packageName 229*d3233986SAndreas 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. 230*d3233986SAndreas Gohr */ 231*d3233986SAndreas Gohr public static function getInstallPath($packageName) 232*d3233986SAndreas Gohr { 233*d3233986SAndreas Gohr foreach (self::getInstalled() as $installed) { 234*d3233986SAndreas Gohr if (!isset($installed['versions'][$packageName])) { 235*d3233986SAndreas Gohr continue; 236*d3233986SAndreas Gohr } 2376cb05674SAndreas Gohr 238*d3233986SAndreas Gohr return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; 239*d3233986SAndreas Gohr } 2406cb05674SAndreas Gohr 241*d3233986SAndreas Gohr throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 242*d3233986SAndreas Gohr } 2436cb05674SAndreas Gohr 244*d3233986SAndreas Gohr /** 245*d3233986SAndreas Gohr * @return array 246*d3233986SAndreas Gohr * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string} 247*d3233986SAndreas Gohr */ 2486cb05674SAndreas Gohr public static function getRootPackage() 2496cb05674SAndreas Gohr { 2506cb05674SAndreas Gohr $installed = self::getInstalled(); 2516cb05674SAndreas Gohr 2526cb05674SAndreas Gohr return $installed[0]['root']; 2536cb05674SAndreas Gohr } 2546cb05674SAndreas Gohr 255*d3233986SAndreas Gohr /** 256*d3233986SAndreas Gohr * Returns the raw installed.php data for custom implementations 257*d3233986SAndreas Gohr * 258*d3233986SAndreas 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. 259*d3233986SAndreas Gohr * @return array[] 260*d3233986SAndreas Gohr * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} 261*d3233986SAndreas Gohr */ 2626cb05674SAndreas Gohr public static function getRawData() 2636cb05674SAndreas Gohr { 264*d3233986SAndreas 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); 265*d3233986SAndreas Gohr 266*d3233986SAndreas Gohr if (null === self::$installed) { 267*d3233986SAndreas Gohr // only require the installed.php file if this file is loaded from its dumped location, 268*d3233986SAndreas Gohr // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 269*d3233986SAndreas Gohr if (substr(__DIR__, -8, 1) !== 'C') { 270*d3233986SAndreas Gohr self::$installed = include __DIR__ . '/installed.php'; 271*d3233986SAndreas Gohr } else { 272*d3233986SAndreas Gohr self::$installed = array(); 273*d3233986SAndreas Gohr } 274*d3233986SAndreas Gohr } 275*d3233986SAndreas Gohr 2766cb05674SAndreas Gohr return self::$installed; 2776cb05674SAndreas Gohr } 2786cb05674SAndreas Gohr 279*d3233986SAndreas Gohr /** 280*d3233986SAndreas Gohr * Returns the raw data of all installed.php which are currently loaded for custom implementations 281*d3233986SAndreas Gohr * 282*d3233986SAndreas Gohr * @return array[] 283*d3233986SAndreas Gohr * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> 284*d3233986SAndreas Gohr */ 285*d3233986SAndreas Gohr public static function getAllRawData() 286*d3233986SAndreas Gohr { 287*d3233986SAndreas Gohr return self::getInstalled(); 288*d3233986SAndreas Gohr } 2896cb05674SAndreas Gohr 290*d3233986SAndreas Gohr /** 291*d3233986SAndreas Gohr * Lets you reload the static array from another file 292*d3233986SAndreas Gohr * 293*d3233986SAndreas Gohr * This is only useful for complex integrations in which a project needs to use 294*d3233986SAndreas Gohr * this class but then also needs to execute another project's autoloader in process, 295*d3233986SAndreas Gohr * and wants to ensure both projects have access to their version of installed.php. 296*d3233986SAndreas Gohr * 297*d3233986SAndreas Gohr * A typical case would be PHPUnit, where it would need to make sure it reads all 298*d3233986SAndreas Gohr * the data it needs from this class, then call reload() with 299*d3233986SAndreas Gohr * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure 300*d3233986SAndreas Gohr * the project in which it runs can then also use this class safely, without 301*d3233986SAndreas Gohr * interference between PHPUnit's dependencies and the project's dependencies. 302*d3233986SAndreas Gohr * 303*d3233986SAndreas Gohr * @param array[] $data A vendor/composer/installed.php data set 304*d3233986SAndreas Gohr * @return void 305*d3233986SAndreas Gohr * 306*d3233986SAndreas Gohr * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data 307*d3233986SAndreas Gohr */ 3086cb05674SAndreas Gohr public static function reload($data) 3096cb05674SAndreas Gohr { 3106cb05674SAndreas Gohr self::$installed = $data; 3116cb05674SAndreas Gohr self::$installedByVendor = array(); 3126cb05674SAndreas Gohr } 3136cb05674SAndreas Gohr 314*d3233986SAndreas Gohr /** 315*d3233986SAndreas Gohr * @return array[] 316*d3233986SAndreas Gohr * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> 317*d3233986SAndreas Gohr */ 3186cb05674SAndreas Gohr private static function getInstalled() 3196cb05674SAndreas Gohr { 3206cb05674SAndreas Gohr if (null === self::$canGetVendors) { 3216cb05674SAndreas Gohr self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); 3226cb05674SAndreas Gohr } 3236cb05674SAndreas Gohr 3246cb05674SAndreas Gohr $installed = array(); 3256cb05674SAndreas Gohr 3266cb05674SAndreas Gohr if (self::$canGetVendors) { 3276cb05674SAndreas Gohr foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { 3286cb05674SAndreas Gohr if (isset(self::$installedByVendor[$vendorDir])) { 3296cb05674SAndreas Gohr $installed[] = self::$installedByVendor[$vendorDir]; 3306cb05674SAndreas Gohr } elseif (is_file($vendorDir.'/composer/installed.php')) { 3316cb05674SAndreas Gohr $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; 332*d3233986SAndreas Gohr if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { 333*d3233986SAndreas Gohr self::$installed = $installed[count($installed) - 1]; 334*d3233986SAndreas Gohr } 3356cb05674SAndreas Gohr } 3366cb05674SAndreas Gohr } 3376cb05674SAndreas Gohr } 3386cb05674SAndreas Gohr 339*d3233986SAndreas Gohr if (null === self::$installed) { 340*d3233986SAndreas Gohr // only require the installed.php file if this file is loaded from its dumped location, 341*d3233986SAndreas Gohr // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 342*d3233986SAndreas Gohr if (substr(__DIR__, -8, 1) !== 'C') { 343*d3233986SAndreas Gohr self::$installed = require __DIR__ . '/installed.php'; 344*d3233986SAndreas Gohr } else { 345*d3233986SAndreas Gohr self::$installed = array(); 346*d3233986SAndreas Gohr } 347*d3233986SAndreas Gohr } 3486cb05674SAndreas Gohr $installed[] = self::$installed; 3496cb05674SAndreas Gohr 3506cb05674SAndreas Gohr return $installed; 3516cb05674SAndreas Gohr } 3526cb05674SAndreas Gohr} 353