Passed
Push — master ( 79129d...8ab249 )
by Kiran
12:29 queued 06:33
created

InstalledVersions   D

Complexity

Total Complexity 59

Size/Duplication

Total Lines 368
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 109
c 0
b 0
f 0
dl 0
loc 368
rs 4.08
wmc 59

15 Methods

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

358
            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...
359
                $vendorDir = strtr($vendorDir, '\\', '/');
360
                if (isset(self::$installedByVendor[$vendorDir])) {
361
                    $installed[] = self::$installedByVendor[$vendorDir];
362
                } elseif (is_file($vendorDir.'/composer/installed.php')) {
363
                    /** @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 */
364
                    $required = require $vendorDir.'/composer/installed.php';
365
                    self::$installedByVendor[$vendorDir] = $required;
366
                    $installed[] = $required;
367
                    if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
368
                        self::$installed = $required;
369
                        self::$installedIsLocalDir = true;
370
                    }
371
                }
372
                if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
373
                    $copiedLocalDir = true;
374
                }
375
            }
376
        }
377
378
        if (null === self::$installed) {
379
            // only require the installed.php file if this file is loaded from its dumped location,
380
            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
381
            if (substr(__DIR__, -8, 1) !== 'C') {
382
                /** @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 */
383
                $required = require __DIR__ . '/installed.php';
384
                self::$installed = $required;
385
            } else {
386
                self::$installed = array();
387
            }
388
        }
389
390
        if (self::$installed !== array() && !$copiedLocalDir) {
391
            $installed[] = self::$installed;
392
        }
393
394
        return $installed;
395
    }
396
}
397