2021-01-04 11:11:04 +01:00
< ? php
2021-08-08 16:06:55 +02:00
/*
* This file is part of Composer .
*
* ( c ) Nils Adermann < naderman @ naderman . de >
* Jordi Boggiano < j . boggiano @ seld . be >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
2021-01-04 11:11:04 +01:00
namespace Composer ;
2021-03-01 10:21:26 +01:00
use Composer\Autoload\ClassLoader ;
2021-01-04 11:11:04 +01:00
use Composer\Semver\VersionParser ;
2021-08-08 16:06:55 +02:00
/**
* This class is copied in every Composer installed project and available to all
*
* See also https :// getcomposer . org / doc / 07 - runtime . md #installed-versions
*
2022-03-02 20:08:24 +01:00
* To require its presence , you can require `composer-runtime-api ^2.0`
2022-09-30 11:03:05 +02:00
*
* @ final
2021-08-08 16:06:55 +02:00
*/
2021-01-04 11:11:04 +01:00
class InstalledVersions
{
2022-03-02 20:08:24 +01:00
/**
* @ var mixed [] | null
2022-09-30 11:03:05 +02:00
* @ 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
2022-03-02 20:08:24 +01:00
*/
2021-08-08 16:06:55 +02:00
private static $installed ;
2022-03-02 20:08:24 +01:00
/**
* @ var bool | null
*/
2021-08-08 16:06:55 +02:00
private static $canGetVendors ;
2022-03-02 20:08:24 +01:00
/**
* @ var array []
2022-09-30 11:03:05 +02:00
* @ 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 []} > } >
2022-03-02 20:08:24 +01:00
*/
2021-08-08 16:06:55 +02:00
private static $installedByVendor = array ();
/**
* Returns a list of all package names which are present , either by being installed , replaced or provided
*
* @ return string []
* @ psalm - return list < string >
*/
public static function getInstalledPackages ()
{
$packages = array ();
foreach ( self :: getInstalled () as $installed ) {
$packages [] = array_keys ( $installed [ 'versions' ]);
}
if ( 1 === \count ( $packages )) {
return $packages [ 0 ];
}
return array_keys ( array_flip ( \call_user_func_array ( 'array_merge' , $packages )));
}
/**
* Returns a list of all package names with a specific type e . g . 'library'
*
* @ param string $type
* @ return string []
* @ psalm - return list < string >
*/
public static function getInstalledPackagesByType ( $type )
{
$packagesByType = array ();
foreach ( self :: getInstalled () as $installed ) {
foreach ( $installed [ 'versions' ] as $name => $package ) {
if ( isset ( $package [ 'type' ]) && $package [ 'type' ] === $type ) {
$packagesByType [] = $name ;
}
}
}
return $packagesByType ;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @ param string $packageName
* @ param bool $includeDevRequirements
* @ return bool
*/
public static function isInstalled ( $packageName , $includeDevRequirements = true )
{
foreach ( self :: getInstalled () as $installed ) {
if ( isset ( $installed [ 'versions' ][ $packageName ])) {
return $includeDevRequirements || empty ( $installed [ 'versions' ][ $packageName ][ 'dev_requirement' ]);
}
}
return false ;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e . g . If you want to know whether version 2.3 + of package foo / bar is installed , you would call :
*
* Composer\InstalledVersions :: satisfies ( new VersionParser , 'foo/bar' , '^2.3' )
*
* @ param VersionParser $parser Install composer / semver to have access to this class and functionality
* @ param string $packageName
* @ 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
* @ return bool
*/
public static function satisfies ( VersionParser $parser , $packageName , $constraint )
{
$constraint = $parser -> parseConstraints ( $constraint );
$provided = $parser -> parseConstraints ( self :: getVersionRanges ( $packageName ));
return $provided -> matches ( $constraint );
}
/**
* Returns a version constraint representing all the range ( s ) which are installed for a given package
*
* It is easier to use this via isInstalled () with the $constraint argument if you need to check
* whether a given version of a package is installed , and not just whether it exists
*
* @ param string $packageName
* @ return string Version constraint usable with composer / semver
*/
public static function getVersionRanges ( $packageName )
{
foreach ( self :: getInstalled () as $installed ) {
if ( ! isset ( $installed [ 'versions' ][ $packageName ])) {
continue ;
}
$ranges = array ();
if ( isset ( $installed [ 'versions' ][ $packageName ][ 'pretty_version' ])) {
$ranges [] = $installed [ 'versions' ][ $packageName ][ 'pretty_version' ];
}
if ( array_key_exists ( 'aliases' , $installed [ 'versions' ][ $packageName ])) {
$ranges = array_merge ( $ranges , $installed [ 'versions' ][ $packageName ][ 'aliases' ]);
}
if ( array_key_exists ( 'replaced' , $installed [ 'versions' ][ $packageName ])) {
$ranges = array_merge ( $ranges , $installed [ 'versions' ][ $packageName ][ 'replaced' ]);
}
if ( array_key_exists ( 'provided' , $installed [ 'versions' ][ $packageName ])) {
$ranges = array_merge ( $ranges , $installed [ 'versions' ][ $packageName ][ 'provided' ]);
}
return implode ( ' || ' , $ranges );
}
throw new \OutOfBoundsException ( 'Package "' . $packageName . '" is not installed' );
}
/**
* @ param string $packageName
* @ 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
*/
public static function getVersion ( $packageName )
{
foreach ( self :: getInstalled () as $installed ) {
if ( ! isset ( $installed [ 'versions' ][ $packageName ])) {
continue ;
}
if ( ! isset ( $installed [ 'versions' ][ $packageName ][ 'version' ])) {
return null ;
}
return $installed [ 'versions' ][ $packageName ][ 'version' ];
}
throw new \OutOfBoundsException ( 'Package "' . $packageName . '" is not installed' );
}
/**
* @ param string $packageName
* @ 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
*/
public static function getPrettyVersion ( $packageName )
{
foreach ( self :: getInstalled () as $installed ) {
if ( ! isset ( $installed [ 'versions' ][ $packageName ])) {
continue ;
}
if ( ! isset ( $installed [ 'versions' ][ $packageName ][ 'pretty_version' ])) {
return null ;
}
return $installed [ 'versions' ][ $packageName ][ 'pretty_version' ];
}
throw new \OutOfBoundsException ( 'Package "' . $packageName . '" is not installed' );
}
/**
* @ param string $packageName
* @ return string | null If the package is being replaced or provided but is not really installed , null will be returned as reference
*/
public static function getReference ( $packageName )
{
foreach ( self :: getInstalled () as $installed ) {
if ( ! isset ( $installed [ 'versions' ][ $packageName ])) {
continue ;
}
if ( ! isset ( $installed [ 'versions' ][ $packageName ][ 'reference' ])) {
return null ;
}
return $installed [ 'versions' ][ $packageName ][ 'reference' ];
}
throw new \OutOfBoundsException ( 'Package "' . $packageName . '" is not installed' );
}
/**
* @ param string $packageName
* @ 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 .
*/
public static function getInstallPath ( $packageName )
{
foreach ( self :: getInstalled () as $installed ) {
if ( ! isset ( $installed [ 'versions' ][ $packageName ])) {
continue ;
}
return isset ( $installed [ 'versions' ][ $packageName ][ 'install_path' ]) ? $installed [ 'versions' ][ $packageName ][ 'install_path' ] : null ;
}
throw new \OutOfBoundsException ( 'Package "' . $packageName . '" is not installed' );
}
/**
* @ return array
2022-09-30 11:03:05 +02:00
* @ psalm - return array { name : string , pretty_version : string , version : string , reference : string | null , type : string , install_path : string , aliases : string [], dev : bool }
2021-08-08 16:06:55 +02:00
*/
public static function getRootPackage ()
{
$installed = self :: getInstalled ();
return $installed [ 0 ][ 'root' ];
}
/**
* Returns the raw installed . php data for custom implementations
*
* @ 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 .
* @ return array []
2022-09-30 11:03:05 +02:00
* @ 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 []} > }
2021-08-08 16:06:55 +02:00
*/
public static function getRawData ()
{
@ 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 );
if ( null === self :: $installed ) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if ( substr ( __DIR__ , - 8 , 1 ) !== 'C' ) {
self :: $installed = include __DIR__ . '/installed.php' ;
} else {
self :: $installed = array ();
}
}
return self :: $installed ;
}
/**
* Returns the raw data of all installed . php which are currently loaded for custom implementations
*
* @ return array []
2022-09-30 11:03:05 +02:00
* @ 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 []} > } >
2021-08-08 16:06:55 +02:00
*/
public static function getAllRawData ()
{
return self :: getInstalled ();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project ' s autoloader in process ,
* and wants to ensure both projects have access to their version of installed . php .
*
* A typical case would be PHPUnit , where it would need to make sure it reads all
* the data it needs from this class , then call reload () with
* `require $CWD/vendor/composer/installed.php` ( or similar ) as input to make sure
* the project in which it runs can then also use this class safely , without
* interference between PHPUnit 's dependencies and the project' s dependencies .
*
* @ param array [] $data A vendor / composer / installed . php data set
* @ return void
*
2022-09-30 11:03:05 +02:00
* @ 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
2021-08-08 16:06:55 +02:00
*/
public static function reload ( $data )
{
self :: $installed = $data ;
self :: $installedByVendor = array ();
}
/**
* @ return array []
2022-09-30 11:03:05 +02:00
* @ 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 []} > } >
2021-08-08 16:06:55 +02:00
*/
private static function getInstalled ()
{
if ( null === self :: $canGetVendors ) {
self :: $canGetVendors = method_exists ( 'Composer\Autoload\ClassLoader' , 'getRegisteredLoaders' );
}
$installed = array ();
if ( self :: $canGetVendors ) {
foreach ( ClassLoader :: getRegisteredLoaders () as $vendorDir => $loader ) {
if ( isset ( self :: $installedByVendor [ $vendorDir ])) {
$installed [] = self :: $installedByVendor [ $vendorDir ];
} elseif ( is_file ( $vendorDir . '/composer/installed.php' )) {
$installed [] = self :: $installedByVendor [ $vendorDir ] = require $vendorDir . '/composer/installed.php' ;
if ( null === self :: $installed && strtr ( $vendorDir . '/composer' , '\\' , '/' ) === strtr ( __DIR__ , '\\' , '/' )) {
self :: $installed = $installed [ count ( $installed ) - 1 ];
}
}
}
}
if ( null === self :: $installed ) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if ( substr ( __DIR__ , - 8 , 1 ) !== 'C' ) {
self :: $installed = require __DIR__ . '/installed.php' ;
} else {
self :: $installed = array ();
}
}
$installed [] = self :: $installed ;
return $installed ;
}
2021-01-04 11:11:04 +01:00
}