Subversion Repositories oidplus

Rev

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

Rev Author Line No. Line
868 daniel-mar 1
<?php
2
 
958 daniel-mar 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
 */
868 daniel-mar 12
 
13
namespace Composer;
14
 
15
use Composer\Autoload\ClassLoader;
16
use Composer\Semver\VersionParser;
17
 
958 daniel-mar 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
 */
868 daniel-mar 27
class InstalledVersions
28
{
958 daniel-mar 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;
868 daniel-mar 34
 
958 daniel-mar 35
    /**
36
     * @var bool|null
37
     */
38
    private static $canGetVendors;
868 daniel-mar 39
 
958 daniel-mar 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();
868 daniel-mar 45
 
958 daniel-mar 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
        }
868 daniel-mar 58
 
958 daniel-mar 59
        if (1 === \count($packages)) {
60
            return $packages[0];
61
        }
868 daniel-mar 62
 
958 daniel-mar 63
        return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
64
    }
868 daniel-mar 65
 
958 daniel-mar 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();
868 daniel-mar 76
 
958 daniel-mar 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
        }
868 daniel-mar 84
 
958 daniel-mar 85
        return $packagesByType;
86
    }
868 daniel-mar 87
 
958 daniel-mar 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])) {
1417 daniel-mar 101
                return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
958 daniel-mar 102
            }
103
        }
868 daniel-mar 104
 
958 daniel-mar 105
        return false;
106
    }
868 daniel-mar 107
 
958 daniel-mar 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
    {
1417 daniel-mar 122
        $constraint = $parser->parseConstraints($constraint);
958 daniel-mar 123
        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
868 daniel-mar 124
 
958 daniel-mar 125
        return $provided->matches($constraint);
126
    }
868 daniel-mar 127
 
958 daniel-mar 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
            }
868 daniel-mar 143
 
958 daniel-mar 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
            }
868 daniel-mar 157
 
958 daniel-mar 158
            return implode(' || ', $ranges);
159
        }
868 daniel-mar 160
 
958 daniel-mar 161
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
162
    }
868 daniel-mar 163
 
958 daniel-mar 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
            }
868 daniel-mar 174
 
958 daniel-mar 175
            if (!isset($installed['versions'][$packageName]['version'])) {
176
                return null;
177
            }
868 daniel-mar 178
 
958 daniel-mar 179
            return $installed['versions'][$packageName]['version'];
180
        }
868 daniel-mar 181
 
958 daniel-mar 182
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
183
    }
868 daniel-mar 184
 
958 daniel-mar 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
            }
868 daniel-mar 195
 
958 daniel-mar 196
            if (!isset($installed['versions'][$packageName]['pretty_version'])) {
197
                return null;
198
            }
868 daniel-mar 199
 
958 daniel-mar 200
            return $installed['versions'][$packageName]['pretty_version'];
201
        }
868 daniel-mar 202
 
958 daniel-mar 203
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
204
    }
868 daniel-mar 205
 
958 daniel-mar 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
            }
868 daniel-mar 216
 
958 daniel-mar 217
            if (!isset($installed['versions'][$packageName]['reference'])) {
218
                return null;
219
            }
868 daniel-mar 220
 
958 daniel-mar 221
            return $installed['versions'][$packageName]['reference'];
222
        }
868 daniel-mar 223
 
958 daniel-mar 224
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
225
    }
868 daniel-mar 226
 
958 daniel-mar 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
            }
868 daniel-mar 237
 
958 daniel-mar 238
            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
239
        }
868 daniel-mar 240
 
958 daniel-mar 241
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
242
    }
868 daniel-mar 243
 
958 daniel-mar 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();
868 daniel-mar 251
 
958 daniel-mar 252
        return $installed[0]['root'];
253
    }
868 daniel-mar 254
 
958 daniel-mar 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);
868 daniel-mar 265
 
958 daniel-mar 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
        }
868 daniel-mar 275
 
958 daniel-mar 276
        return self::$installed;
277
    }
868 daniel-mar 278
 
958 daniel-mar 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
    }
868 daniel-mar 289
 
958 daniel-mar 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
    }
868 daniel-mar 313
 
958 daniel-mar 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
        }
868 daniel-mar 323
 
958 daniel-mar 324
        $installed = array();
868 daniel-mar 325
 
958 daniel-mar 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')) {
1417 daniel-mar 331
                    $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
958 daniel-mar 332
                    if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
333
                        self::$installed = $installed[count($installed) - 1];
334
                    }
335
                }
336
            }
337
        }
868 daniel-mar 338
 
958 daniel-mar 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') {
1417 daniel-mar 343
                self::$installed = require __DIR__ . '/installed.php';
958 daniel-mar 344
            } else {
345
                self::$installed = array();
346
            }
347
        }
1417 daniel-mar 348
        $installed[] = self::$installed;
868 daniel-mar 349
 
958 daniel-mar 350
        return $installed;
351
    }
868 daniel-mar 352
}