Completed
Pull Request — master (#118)
by Marco
01:39
created

FallbackVersions   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 93
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 0

Test Coverage

Coverage 91.43%

Importance

Changes 0
Metric Value
wmc 11
lcom 0
cbo 0
dl 0
loc 93
ccs 32
cts 35
cp 0.9143
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A getVersion() 0 12 2
A __construct() 0 3 1
B getPackageData() 0 44 6
A getVersions() 0 10 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PackageVersions;
6
7
use Generator;
8
use OutOfBoundsException;
9
use UnexpectedValueException;
10
use function array_key_exists;
11
use function array_merge;
12
use function basename;
13
use function file_exists;
14
use function file_get_contents;
15
use function getcwd;
16
use function iterator_to_array;
17
use function json_decode;
18
use function json_encode;
19
use function sprintf;
20
21
/**
22
 * @internal
23
 *
24
 * This is a fallback for {@see \PackageVersions\Versions::getVersion()}
25
 * Do not use this class directly: it is intended to be only used when
26
 * {@see \PackageVersions\Versions} fails to be generated, which typically
27
 * happens when running composer with `--no-scripts` flag)
28
 */
29
final class FallbackVersions
30
{
31
    public const ROOT_PACKAGE_NAME = 'unknown/root-package@UNKNOWN';
32
33
    private function __construct()
34
    {
35
    }
36
37
    /**
38
     * @throws OutOfBoundsException If a version cannot be located.
39
     * @throws UnexpectedValueException If the composer.lock file could not be located.
40
     */
41 5
    public static function getVersion(string $packageName) : string
42
    {
43 5
        $versions = iterator_to_array(self::getVersions(self::getPackageData()));
44
45 4
        if (! array_key_exists($packageName, $versions)) {
46 1
            throw new OutOfBoundsException(
47 1
                'Required package "' . $packageName . '" is not installed: check your ./vendor/composer/installed.json and/or ./composer.lock files'
48
            );
49
        }
50
51 3
        return $versions[$packageName];
52
    }
53
54
    /**
55
     * @return mixed[]
56
     *
57
     * @throws UnexpectedValueException
58
     */
59 5
    private static function getPackageData() : array
60
    {
61
        $checkedPaths = [
62
            // The top-level project's ./vendor/composer/installed.json
63 5
            getcwd() . '/vendor/composer/installed.json',
64
            __DIR__ . '/../../../../composer/installed.json',
65
            // The top-level project's ./composer.lock
66 5
            getcwd() . '/composer.lock',
67
            __DIR__ . '/../../../../../composer.lock',
68
            // This package's composer.lock
69
            __DIR__ . '/../../composer.lock',
70
        ];
71
72 5
        $packageData = [];
73 5
        foreach ($checkedPaths as $path) {
74 5
            if (! file_exists($path)) {
75 5
                continue;
76
            }
77
78 4
            $data = json_decode(file_get_contents($path), true);
79 4
            switch (basename($path)) {
80 4
                case 'installed.json':
81 3
                    $packageData[] = $data;
82 3
                    break;
83 3
                case 'composer.lock':
84 3
                    $packageData[] = $data['packages'] + ($data['packages-dev'] ?? []);
85 3
                    break;
86
                default:
87
                    // intentionally left blank
88
            }
89
        }
90
91 5
        if ($packageData !== []) {
92 4
            return array_merge(...$packageData);
93
        }
94
95 1
        throw new UnexpectedValueException(sprintf(
96
            'PackageVersions could not locate the `vendor/composer/installed.json` or your `composer.lock` '
97
            . 'location. This is assumed to be in %s. If you customized your composer vendor directory and ran composer '
98
            . 'installation with --no-scripts or if you deployed without the required composer files, then you are on '
99 1
            . 'your own, and we can\'t really help you. Fix your shit and cut the tooling some slack.',
100 1
            json_encode($checkedPaths)
101
        ));
102
    }
103
104
    /**
105
     * @param mixed[] $packageData
106
     *
107
     * @return Generator&string[]
0 ignored issues
show
Documentation introduced by
The doc-type Generator&string[] could not be parsed: Unknown type name "Generator&string" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
108
     *
109
     * @psalm-return Generator<string, string>
110
     */
111 4
    private static function getVersions(array $packageData) : Generator
112
    {
113 4
        foreach ($packageData as $package) {
114 4
            yield $package['name'] => $package['version'] . '@' . (
115 4
                $package['source']['reference'] ?? $package['dist']['reference'] ?? ''
116
            );
117
        }
118
119 4
        yield self::ROOT_PACKAGE_NAME => self::ROOT_PACKAGE_NAME;
120 4
    }
121
}
122