vendor/composer/InstalledVersions.php line 264

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