InstalledVersions   B
last analyzed

Complexity

Total Complexity 52

Size/Duplication

Total Lines 324
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 52
eloc 93
c 1
b 0
f 0
dl 0
loc 324
rs 7.44

14 Methods

Rating   Name   Duplication   Size   Complexity  
A getRawData() 0 15 3
A getInstalledPackagesByType() 0 13 5
A getInstalledPackages() 0 12 3
B getVersionRanges() 0 25 7
A getInstallPath() 0 11 4
A isInstalled() 0 9 4
A getReference() 0 15 4
A getAllRawData() 0 3 1
A satisfies() 0 6 1
A getPrettyVersion() 0 15 4
A getVersion() 0 15 4
A getRootPackage() 0 5 1
B getInstalled() 0 33 10
A reload() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like InstalledVersions often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use InstalledVersions, and based on these observations, apply Extract Interface, too.

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

325
            foreach (ClassLoader::/** @scrutinizer ignore-call */ getRegisteredLoaders() as $vendorDir => $loader) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
326
                if (isset(self::$installedByVendor[$vendorDir])) {
327
                    $installed[] = self::$installedByVendor[$vendorDir];
328
                } elseif (is_file($vendorDir.'/composer/installed.php')) {
329
                    $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
330
                    if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
331
                        self::$installed = $installed[count($installed) - 1];
332
                    }
333
                }
334
            }
335
        }
336
337
        if (null === self::$installed) {
338
            // only require the installed.php file if this file is loaded from its dumped location,
339
            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
340
            if (substr(__DIR__, -8, 1) !== 'C') {
341
                self::$installed = require __DIR__ . '/installed.php';
342
            } else {
343
                self::$installed = array();
344
            }
345
        }
346
        $installed[] = self::$installed;
347
348
        return $installed;
349
    }
350
}
351