Passed
Push — master ( 69412b...4be6ea )
by Michael
20:13 queued 10:57
created

InstalledVersions::satisfies()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 3
dl 0
loc 6
rs 10
1
<?php
2
3
/*
4
 * This file is part of Composer.
5
 *
6
 * (c) Nils Adermann <[email protected]>
7
 *     Jordi Boggiano <[email protected]>
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
37
     */
38
    private static $installedIsLocalDir;
39
40
    /**
41
     * @var bool|null
42
     */
43
    private static $canGetVendors;
44
45
    /**
46
     * @var array[]
47
     * @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[]}>}>
48
     */
49
    private static $installedByVendor = array();
50
51
    /**
52
     * Returns a list of all package names which are present, either by being installed, replaced or provided
53
     *
54
     * @return string[]
55
     * @psalm-return list<string>
56
     */
57
    public static function getInstalledPackages()
58
    {
59
        $packages = array();
60
        foreach (self::getInstalled() as $installed) {
61
            $packages[] = array_keys($installed['versions']);
62
        }
63
64
        if (1 === \count($packages)) {
65
            return $packages[0];
66
        }
67
68
        return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
69
    }
70
71
    /**
72
     * Returns a list of all package names with a specific type e.g. 'library'
73
     *
74
     * @param  string   $type
75
     * @return string[]
76
     * @psalm-return list<string>
77
     */
78
    public static function getInstalledPackagesByType($type)
79
    {
80
        $packagesByType = array();
81
82
        foreach (self::getInstalled() as $installed) {
83
            foreach ($installed['versions'] as $name => $package) {
84
                if (isset($package['type']) && $package['type'] === $type) {
85
                    $packagesByType[] = $name;
86
                }
87
            }
88
        }
89
90
        return $packagesByType;
91
    }
92
93
    /**
94
     * Checks whether the given package is installed
95
     *
96
     * This also returns true if the package name is provided or replaced by another package
97
     *
98
     * @param  string $packageName
99
     * @param  bool   $includeDevRequirements
100
     * @return bool
101
     */
102
    public static function isInstalled($packageName, $includeDevRequirements = true)
103
    {
104
        foreach (self::getInstalled() as $installed) {
105
            if (isset($installed['versions'][$packageName])) {
106
                return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
107
            }
108
        }
109
110
        return false;
111
    }
112
113
    /**
114
     * Checks whether the given package satisfies a version constraint
115
     *
116
     * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
117
     *
118
     *   Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
119
     *
120
     * @param  VersionParser $parser      Install composer/semver to have access to this class and functionality
0 ignored issues
show
Bug introduced by
The type Composer\Semver\VersionParser was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
121
     * @param  string        $packageName
122
     * @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
123
     * @return bool
124
     */
125
    public static function satisfies(VersionParser $parser, $packageName, $constraint)
126
    {
127
        $constraint = $parser->parseConstraints((string) $constraint);
128
        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
129
130
        return $provided->matches($constraint);
131
    }
132
133
    /**
134
     * Returns a version constraint representing all the range(s) which are installed for a given package
135
     *
136
     * It is easier to use this via isInstalled() with the $constraint argument if you need to check
137
     * whether a given version of a package is installed, and not just whether it exists
138
     *
139
     * @param  string $packageName
140
     * @return string Version constraint usable with composer/semver
141
     */
142
    public static function getVersionRanges($packageName)
143
    {
144
        foreach (self::getInstalled() as $installed) {
145
            if (!isset($installed['versions'][$packageName])) {
146
                continue;
147
            }
148
149
            $ranges = array();
150
            if (isset($installed['versions'][$packageName]['pretty_version'])) {
151
                $ranges[] = $installed['versions'][$packageName]['pretty_version'];
152
            }
153
            if (array_key_exists('aliases', $installed['versions'][$packageName])) {
154
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
155
            }
156
            if (array_key_exists('replaced', $installed['versions'][$packageName])) {
157
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
158
            }
159
            if (array_key_exists('provided', $installed['versions'][$packageName])) {
160
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
161
            }
162
163
            return implode(' || ', $ranges);
164
        }
165
166
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
167
    }
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 getVersion($packageName)
174
    {
175
        foreach (self::getInstalled() as $installed) {
176
            if (!isset($installed['versions'][$packageName])) {
177
                continue;
178
            }
179
180
            if (!isset($installed['versions'][$packageName]['version'])) {
181
                return null;
182
            }
183
184
            return $installed['versions'][$packageName]['version'];
185
        }
186
187
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
188
    }
189
190
    /**
191
     * @param  string      $packageName
192
     * @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
193
     */
194
    public static function getPrettyVersion($packageName)
195
    {
196
        foreach (self::getInstalled() as $installed) {
197
            if (!isset($installed['versions'][$packageName])) {
198
                continue;
199
            }
200
201
            if (!isset($installed['versions'][$packageName]['pretty_version'])) {
202
                return null;
203
            }
204
205
            return $installed['versions'][$packageName]['pretty_version'];
206
        }
207
208
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
209
    }
210
211
    /**
212
     * @param  string      $packageName
213
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
214
     */
215
    public static function getReference($packageName)
216
    {
217
        foreach (self::getInstalled() as $installed) {
218
            if (!isset($installed['versions'][$packageName])) {
219
                continue;
220
            }
221
222
            if (!isset($installed['versions'][$packageName]['reference'])) {
223
                return null;
224
            }
225
226
            return $installed['versions'][$packageName]['reference'];
227
        }
228
229
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
230
    }
231
232
    /**
233
     * @param  string      $packageName
234
     * @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.
235
     */
236
    public static function getInstallPath($packageName)
237
    {
238
        foreach (self::getInstalled() as $installed) {
239
            if (!isset($installed['versions'][$packageName])) {
240
                continue;
241
            }
242
243
            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
244
        }
245
246
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
247
    }
248
249
    /**
250
     * @return array
251
     * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
252
     */
253
    public static function getRootPackage()
254
    {
255
        $installed = self::getInstalled();
256
257
        return $installed[0]['root'];
258
    }
259
260
    /**
261
     * Returns the raw installed.php data for custom implementations
262
     *
263
     * @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.
264
     * @return array[]
265
     * @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[]}>}
266
     */
267
    public static function getRawData()
268
    {
269
        @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);
270
271
        if (null === self::$installed) {
272
            // only require the installed.php file if this file is loaded from its dumped location,
273
            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
274
            if (substr(__DIR__, -8, 1) !== 'C') {
275
                self::$installed = include __DIR__ . '/installed.php';
276
            } else {
277
                self::$installed = array();
278
            }
279
        }
280
281
        return self::$installed;
282
    }
283
284
    /**
285
     * Returns the raw data of all installed.php which are currently loaded for custom implementations
286
     *
287
     * @return array[]
288
     * @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[]}>}>
289
     */
290
    public static function getAllRawData()
291
    {
292
        return self::getInstalled();
293
    }
294
295
    /**
296
     * Lets you reload the static array from another file
297
     *
298
     * This is only useful for complex integrations in which a project needs to use
299
     * this class but then also needs to execute another project's autoloader in process,
300
     * and wants to ensure both projects have access to their version of installed.php.
301
     *
302
     * A typical case would be PHPUnit, where it would need to make sure it reads all
303
     * the data it needs from this class, then call reload() with
304
     * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
305
     * the project in which it runs can then also use this class safely, without
306
     * interference between PHPUnit's dependencies and the project's dependencies.
307
     *
308
     * @param  array[] $data A vendor/composer/installed.php data set
309
     * @return void
310
     *
311
     * @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
312
     */
313
    public static function reload($data)
314
    {
315
        self::$installed = $data;
316
        self::$installedByVendor = array();
317
318
        // when using reload, we disable the duplicate protection to ensure that self::$installed data is
319
        // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
320
        // so we have to assume it does not, and that may result in duplicate data being returned when listing
321
        // all installed packages for example
322
        self::$installedIsLocalDir = false;
323
    }
324
325
    /**
326
     * @return array[]
327
     * @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[]}>}>
328
     */
329
    private static function getInstalled()
330
    {
331
        if (null === self::$canGetVendors) {
332
            self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
333
        }
334
335
        $installed = array();
336
        $copiedLocalDir = false;
337
338
        if (self::$canGetVendors) {
339
            $selfDir = strtr(__DIR__, '\\', '/');
340
            foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
341
                $vendorDir = strtr($vendorDir, '\\', '/');
342
                if (isset(self::$installedByVendor[$vendorDir])) {
343
                    $installed[] = self::$installedByVendor[$vendorDir];
344
                } elseif (is_file($vendorDir.'/composer/installed.php')) {
345
                    /** @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 */
346
                    $required = require $vendorDir.'/composer/installed.php';
347
                    self::$installedByVendor[$vendorDir] = $required;
348
                    $installed[] = $required;
349
                    if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
350
                        self::$installed = $required;
351
                        self::$installedIsLocalDir = true;
352
                    }
353
                }
354
                if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
355
                    $copiedLocalDir = true;
356
                }
357
            }
358
        }
359
360
        if (null === self::$installed) {
361
            // only require the installed.php file if this file is loaded from its dumped location,
362
            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
363
            if (substr(__DIR__, -8, 1) !== 'C') {
364
                /** @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 */
365
                $required = require __DIR__ . '/installed.php';
366
                self::$installed = $required;
367
            } else {
368
                self::$installed = array();
369
            }
370
        }
371
372
        if (self::$installed !== array() && !$copiedLocalDir) {
373
            $installed[] = self::$installed;
374
        }
375
376
        return $installed;
377
    }
378
}
379