vendor/composer/InstalledVersions.php line 249

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