xref: /dokuwiki/vendor/composer/InstalledVersions.php (revision 28e9760a91ee147e8ce86c6d9f964e4d04b0acaf)
16cb05674SAndreas Gohr<?php
26cb05674SAndreas Gohr
3d3233986SAndreas Gohr/*
4d3233986SAndreas Gohr * This file is part of Composer.
5d3233986SAndreas Gohr *
6d3233986SAndreas Gohr * (c) Nils Adermann <naderman@naderman.de>
7d3233986SAndreas Gohr *     Jordi Boggiano <j.boggiano@seld.be>
8d3233986SAndreas Gohr *
9d3233986SAndreas Gohr * For the full copyright and license information, please view the LICENSE
10d3233986SAndreas Gohr * file that was distributed with this source code.
11d3233986SAndreas Gohr */
126cb05674SAndreas Gohr
136cb05674SAndreas Gohrnamespace Composer;
146cb05674SAndreas Gohr
156cb05674SAndreas Gohruse Composer\Autoload\ClassLoader;
166cb05674SAndreas Gohruse Composer\Semver\VersionParser;
176cb05674SAndreas Gohr
18d3233986SAndreas Gohr/**
19d3233986SAndreas Gohr * This class is copied in every Composer installed project and available to all
20d3233986SAndreas Gohr *
21d3233986SAndreas Gohr * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
22d3233986SAndreas Gohr *
23d3233986SAndreas Gohr * To require its presence, you can require `composer-runtime-api ^2.0`
24d3233986SAndreas Gohr *
25d3233986SAndreas Gohr * @final
26d3233986SAndreas Gohr */
276cb05674SAndreas Gohrclass InstalledVersions
286cb05674SAndreas Gohr{
29d3233986SAndreas Gohr    /**
30d3233986SAndreas Gohr     * @var mixed[]|null
31*28e9760aSAndreas 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
32d3233986SAndreas Gohr     */
33d3233986SAndreas Gohr    private static $installed;
34d3233986SAndreas Gohr
35d3233986SAndreas Gohr    /**
36d3233986SAndreas Gohr     * @var bool|null
37d3233986SAndreas Gohr     */
386cb05674SAndreas Gohr    private static $canGetVendors;
39d3233986SAndreas Gohr
40d3233986SAndreas Gohr    /**
41d3233986SAndreas Gohr     * @var array[]
42*28e9760aSAndreas 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[]}>}>
43d3233986SAndreas Gohr     */
446cb05674SAndreas Gohr    private static $installedByVendor = array();
456cb05674SAndreas Gohr
46d3233986SAndreas Gohr    /**
47d3233986SAndreas Gohr     * Returns a list of all package names which are present, either by being installed, replaced or provided
48d3233986SAndreas Gohr     *
49d3233986SAndreas Gohr     * @return string[]
50d3233986SAndreas Gohr     * @psalm-return list<string>
51d3233986SAndreas 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
66d3233986SAndreas Gohr    /**
67d3233986SAndreas Gohr     * Returns a list of all package names with a specific type e.g. 'library'
68d3233986SAndreas Gohr     *
69d3233986SAndreas Gohr     * @param  string   $type
70d3233986SAndreas Gohr     * @return string[]
71d3233986SAndreas Gohr     * @psalm-return list<string>
72d3233986SAndreas Gohr     */
73d3233986SAndreas Gohr    public static function getInstalledPackagesByType($type)
74d3233986SAndreas Gohr    {
75d3233986SAndreas Gohr        $packagesByType = array();
766cb05674SAndreas Gohr
77d3233986SAndreas Gohr        foreach (self::getInstalled() as $installed) {
78d3233986SAndreas Gohr            foreach ($installed['versions'] as $name => $package) {
79d3233986SAndreas Gohr                if (isset($package['type']) && $package['type'] === $type) {
80d3233986SAndreas Gohr                    $packagesByType[] = $name;
81d3233986SAndreas Gohr                }
82d3233986SAndreas Gohr            }
83d3233986SAndreas Gohr        }
846cb05674SAndreas Gohr
85d3233986SAndreas Gohr        return $packagesByType;
86d3233986SAndreas Gohr    }
876cb05674SAndreas Gohr
88d3233986SAndreas Gohr    /**
89d3233986SAndreas Gohr     * Checks whether the given package is installed
90d3233986SAndreas Gohr     *
91d3233986SAndreas Gohr     * This also returns true if the package name is provided or replaced by another package
92d3233986SAndreas Gohr     *
93d3233986SAndreas Gohr     * @param  string $packageName
94d3233986SAndreas Gohr     * @param  bool   $includeDevRequirements
95d3233986SAndreas Gohr     * @return bool
96d3233986SAndreas Gohr     */
97d3233986SAndreas Gohr    public static function isInstalled($packageName, $includeDevRequirements = true)
986cb05674SAndreas Gohr    {
996cb05674SAndreas Gohr        foreach (self::getInstalled() as $installed) {
1006cb05674SAndreas Gohr            if (isset($installed['versions'][$packageName])) {
101d3233986SAndreas Gohr                return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
1026cb05674SAndreas Gohr            }
1036cb05674SAndreas Gohr        }
1046cb05674SAndreas Gohr
1056cb05674SAndreas Gohr        return false;
1066cb05674SAndreas Gohr    }
1076cb05674SAndreas Gohr
108d3233986SAndreas Gohr    /**
109d3233986SAndreas Gohr     * Checks whether the given package satisfies a version constraint
110d3233986SAndreas Gohr     *
111d3233986SAndreas Gohr     * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
112d3233986SAndreas Gohr     *
113d3233986SAndreas Gohr     *   Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
114d3233986SAndreas Gohr     *
115d3233986SAndreas Gohr     * @param  VersionParser $parser      Install composer/semver to have access to this class and functionality
116d3233986SAndreas Gohr     * @param  string        $packageName
117d3233986SAndreas 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
118d3233986SAndreas Gohr     * @return bool
119d3233986SAndreas 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
128d3233986SAndreas Gohr    /**
129d3233986SAndreas Gohr     * Returns a version constraint representing all the range(s) which are installed for a given package
130d3233986SAndreas Gohr     *
131d3233986SAndreas Gohr     * It is easier to use this via isInstalled() with the $constraint argument if you need to check
132d3233986SAndreas Gohr     * whether a given version of a package is installed, and not just whether it exists
133d3233986SAndreas Gohr     *
134d3233986SAndreas Gohr     * @param  string $packageName
135d3233986SAndreas Gohr     * @return string Version constraint usable with composer/semver
136d3233986SAndreas 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
164d3233986SAndreas Gohr    /**
165d3233986SAndreas Gohr     * @param  string      $packageName
166d3233986SAndreas 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
167d3233986SAndreas 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
185d3233986SAndreas Gohr    /**
186d3233986SAndreas Gohr     * @param  string      $packageName
187d3233986SAndreas 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
188d3233986SAndreas 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
206d3233986SAndreas Gohr    /**
207d3233986SAndreas Gohr     * @param  string      $packageName
208d3233986SAndreas Gohr     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
209d3233986SAndreas 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
227d3233986SAndreas Gohr    /**
228d3233986SAndreas Gohr     * @param  string      $packageName
229d3233986SAndreas 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.
230d3233986SAndreas Gohr     */
231d3233986SAndreas Gohr    public static function getInstallPath($packageName)
232d3233986SAndreas Gohr    {
233d3233986SAndreas Gohr        foreach (self::getInstalled() as $installed) {
234d3233986SAndreas Gohr            if (!isset($installed['versions'][$packageName])) {
235d3233986SAndreas Gohr                continue;
236d3233986SAndreas Gohr            }
2376cb05674SAndreas Gohr
238d3233986SAndreas Gohr            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
239d3233986SAndreas Gohr        }
2406cb05674SAndreas Gohr
241d3233986SAndreas Gohr        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
242d3233986SAndreas Gohr    }
2436cb05674SAndreas Gohr
244d3233986SAndreas Gohr    /**
245d3233986SAndreas Gohr     * @return array
246*28e9760aSAndreas Gohr     * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
247d3233986SAndreas 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
255d3233986SAndreas Gohr    /**
256d3233986SAndreas Gohr     * Returns the raw installed.php data for custom implementations
257d3233986SAndreas Gohr     *
258d3233986SAndreas 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.
259d3233986SAndreas Gohr     * @return array[]
260*28e9760aSAndreas 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[]}>}
261d3233986SAndreas Gohr     */
2626cb05674SAndreas Gohr    public static function getRawData()
2636cb05674SAndreas Gohr    {
264d3233986SAndreas 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);
265d3233986SAndreas Gohr
266d3233986SAndreas Gohr        if (null === self::$installed) {
267d3233986SAndreas Gohr            // only require the installed.php file if this file is loaded from its dumped location,
268d3233986SAndreas Gohr            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
269d3233986SAndreas Gohr            if (substr(__DIR__, -8, 1) !== 'C') {
270d3233986SAndreas Gohr                self::$installed = include __DIR__ . '/installed.php';
271d3233986SAndreas Gohr            } else {
272d3233986SAndreas Gohr                self::$installed = array();
273d3233986SAndreas Gohr            }
274d3233986SAndreas Gohr        }
275d3233986SAndreas Gohr
2766cb05674SAndreas Gohr        return self::$installed;
2776cb05674SAndreas Gohr    }
2786cb05674SAndreas Gohr
279d3233986SAndreas Gohr    /**
280d3233986SAndreas Gohr     * Returns the raw data of all installed.php which are currently loaded for custom implementations
281d3233986SAndreas Gohr     *
282d3233986SAndreas Gohr     * @return array[]
283*28e9760aSAndreas 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[]}>}>
284d3233986SAndreas Gohr     */
285d3233986SAndreas Gohr    public static function getAllRawData()
286d3233986SAndreas Gohr    {
287d3233986SAndreas Gohr        return self::getInstalled();
288d3233986SAndreas Gohr    }
2896cb05674SAndreas Gohr
290d3233986SAndreas Gohr    /**
291d3233986SAndreas Gohr     * Lets you reload the static array from another file
292d3233986SAndreas Gohr     *
293d3233986SAndreas Gohr     * This is only useful for complex integrations in which a project needs to use
294d3233986SAndreas Gohr     * this class but then also needs to execute another project's autoloader in process,
295d3233986SAndreas Gohr     * and wants to ensure both projects have access to their version of installed.php.
296d3233986SAndreas Gohr     *
297d3233986SAndreas Gohr     * A typical case would be PHPUnit, where it would need to make sure it reads all
298d3233986SAndreas Gohr     * the data it needs from this class, then call reload() with
299d3233986SAndreas Gohr     * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
300d3233986SAndreas Gohr     * the project in which it runs can then also use this class safely, without
301d3233986SAndreas Gohr     * interference between PHPUnit's dependencies and the project's dependencies.
302d3233986SAndreas Gohr     *
303d3233986SAndreas Gohr     * @param  array[] $data A vendor/composer/installed.php data set
304d3233986SAndreas Gohr     * @return void
305d3233986SAndreas Gohr     *
306*28e9760aSAndreas 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
307d3233986SAndreas Gohr     */
3086cb05674SAndreas Gohr    public static function reload($data)
3096cb05674SAndreas Gohr    {
3106cb05674SAndreas Gohr        self::$installed = $data;
3116cb05674SAndreas Gohr        self::$installedByVendor = array();
3126cb05674SAndreas Gohr    }
3136cb05674SAndreas Gohr
314d3233986SAndreas Gohr    /**
315d3233986SAndreas Gohr     * @return array[]
316*28e9760aSAndreas 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[]}>}>
317d3233986SAndreas 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';
332d3233986SAndreas Gohr                    if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
333d3233986SAndreas Gohr                        self::$installed = $installed[count($installed) - 1];
334d3233986SAndreas Gohr                    }
3356cb05674SAndreas Gohr                }
3366cb05674SAndreas Gohr            }
3376cb05674SAndreas Gohr        }
3386cb05674SAndreas Gohr
339d3233986SAndreas Gohr        if (null === self::$installed) {
340d3233986SAndreas Gohr            // only require the installed.php file if this file is loaded from its dumped location,
341d3233986SAndreas Gohr            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
342d3233986SAndreas Gohr            if (substr(__DIR__, -8, 1) !== 'C') {
343d3233986SAndreas Gohr                self::$installed = require __DIR__ . '/installed.php';
344d3233986SAndreas Gohr            } else {
345d3233986SAndreas Gohr                self::$installed = array();
346d3233986SAndreas Gohr            }
347d3233986SAndreas Gohr        }
3486cb05674SAndreas Gohr        $installed[] = self::$installed;
3496cb05674SAndreas Gohr
3506cb05674SAndreas Gohr        return $installed;
3516cb05674SAndreas Gohr    }
3526cb05674SAndreas Gohr}
353