Subversion Repositories oidplus

Rev

Rev 1407 | Rev 1422 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

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