vendor/composer/InstalledVersions.php line 273

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