Passed
Pull Request — master (#133)
by Jordi
11:37
created

Installer::generateVersionsClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 2
b 0
f 0
nc 1
nop 2
dl 0
loc 7
rs 10
ccs 6
cts 6
cp 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PackageVersions;
6
7
use Composer\Composer;
8
use Composer\Config;
9
use Composer\EventDispatcher\EventSubscriberInterface;
10
use Composer\IO\IOInterface;
11
use Composer\Package\AliasPackage;
12
use Composer\Package\Locker;
13
use Composer\Package\PackageInterface;
14
use Composer\Package\RootPackageInterface;
15
use Composer\Plugin\PluginInterface;
16
use Composer\Script\Event;
17
use Composer\Script\ScriptEvents;
18
use Generator;
19
use RuntimeException;
20
use function array_key_exists;
21
use function array_merge;
22
use function chmod;
23
use function dirname;
24
use function file_exists;
25
use function file_put_contents;
26
use function iterator_to_array;
27
use function rename;
28
use function sprintf;
29
use function uniqid;
30
use function var_export;
31
32
final class Installer implements PluginInterface, EventSubscriberInterface
33
{
34
    private static string $generatedClassTemplate = <<<'PHP'
35
<?php
36
37
declare(strict_types=1);
38
39
namespace PackageVersions;
40
41
use OutOfBoundsException;
42
43
/**
44
 * This class is generated by ocramius/package-versions, specifically by
45
 * @see \PackageVersions\Installer
46
 *
47
 * This file is overwritten at every run of `composer install` or `composer update`.
48
 */
49
%s
50
{
51
    public const ROOT_PACKAGE_NAME = '%s';
52
    /**
53
     * Array of all available composer packages.
54
     * Dont read this array from your calling code, but use the \PackageVersions\Versions::getVersion() method instead.
55
     *
56
     * @var array<string, string>
57
     * @internal
58
     */
59
    public const VERSIONS          = %s;
60
61
    private function __construct()
62
    {
63
    }
64
65
    /**
66
     * @throws OutOfBoundsException If a version cannot be located.
67
     *
68
     * @psalm-param key-of<self::VERSIONS> $packageName
69
     * @psalm-pure
70
     */
71
    public static function getVersion(string $packageName) : string
72
    {
73
        if (isset(self::VERSIONS[$packageName])) {
74
            return self::VERSIONS[$packageName];
75
        }
76
77
        throw new OutOfBoundsException(
78
            'Required package "' . $packageName . '" is not installed: check your ./vendor/composer/installed.json and/or ./composer.lock files'
79
        );
80
    }
81
}
82
83
PHP;
84
85
    public function activate(Composer $composer, IOInterface $io) : void
86
    {
87
        // Nothing to do here, as all features are provided through event listeners
88
    }
89
90
    public function deactivate(Composer $composer, IOInterface $io) : void
0 ignored issues
show
Unused Code introduced by
The parameter $io is not used and could be removed. ( Ignorable by Annotation )

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

90
    public function deactivate(Composer $composer, /** @scrutinizer ignore-unused */ IOInterface $io) : void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $composer is not used and could be removed. ( Ignorable by Annotation )

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

90
    public function deactivate(/** @scrutinizer ignore-unused */ Composer $composer, IOInterface $io) : void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
91
    {
92
        // Nothing to do here, as all features are provided through event listeners
93
    }
94
95
    public function uninstall(Composer $composer, IOInterface $io) : void
0 ignored issues
show
Unused Code introduced by
The parameter $io is not used and could be removed. ( Ignorable by Annotation )

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

95
    public function uninstall(Composer $composer, /** @scrutinizer ignore-unused */ IOInterface $io) : void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $composer is not used and could be removed. ( Ignorable by Annotation )

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

95
    public function uninstall(/** @scrutinizer ignore-unused */ Composer $composer, IOInterface $io) : void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
96
    {
97
        // Nothing to do here, as all features are provided through event listeners
98
    }
99
100
    /**
101
     * {@inheritDoc}
102
     */
103 1
    public static function getSubscribedEvents() : array
104
    {
105 1
        return [ScriptEvents::POST_AUTOLOAD_DUMP => 'dumpVersionsClass'];
106
    }
107
108
    /**
109
     * @throws RuntimeException
110
     */
111 14
    public static function dumpVersionsClass(Event $composerEvent) : void
112
    {
113 14
        $composer    = $composerEvent->getComposer();
114 14
        $rootPackage = $composer->getPackage();
115 14
        $versions    = iterator_to_array(self::getVersions($composer->getLocker(), $rootPackage));
116
117 14
        if (! array_key_exists('ocramius/package-versions', $versions)) {
118
            //plugin must be globally installed - we only want to generate versions for projects which specifically
119
            //require ocramius/package-versions
120 1
            return;
121
        }
122
123 13
        $versionClass = self::generateVersionsClass($rootPackage->getName(), $versions);
124
125 13
        self::writeVersionClassToFile($versionClass, $composer, $composerEvent->getIO());
126 13
    }
127
128
    /**
129
     * @param string[] $versions
130
     */
131 13
    private static function generateVersionsClass(string $rootPackageName, array $versions) : string
132
    {
133 13
        return sprintf(
134 13
            self::$generatedClassTemplate,
135 13
            'fin' . 'al ' . 'cla' . 'ss ' . 'Versions', // note: workaround for regex-based code parsers :-(
136 13
            $rootPackageName,
137 13
            var_export($versions, true)
138
        );
139
    }
140
141
    /**
142
     * @throws RuntimeException
143
     */
144 13
    private static function writeVersionClassToFile(string $versionClassSource, Composer $composer, IOInterface $io) : void
145
    {
146 13
        $installPath = self::locateRootPackageInstallPath($composer->getConfig(), $composer->getPackage())
147 13
            . '/src/PackageVersions/Versions.php';
148
149 13
        if (! file_exists(dirname($installPath))) {
150 1
            $io->write('<info>ocramius/package-versions:</info> Package not found (probably scheduled for removal); generation of version class skipped.');
151
152 1
            return;
153
        }
154
155 12
        $io->write('<info>ocramius/package-versions:</info> Generating version class...');
156
157 12
        $installPathTmp = $installPath . '_' . uniqid('tmp', true);
158 12
        file_put_contents($installPathTmp, $versionClassSource);
159 12
        chmod($installPathTmp, 0664);
160 12
        rename($installPathTmp, $installPath);
161
162 12
        $io->write('<info>ocramius/package-versions:</info> ...done generating version class');
163 12
    }
164
165
    /**
166
     * @throws RuntimeException
167
     */
168 13
    private static function locateRootPackageInstallPath(
169
        Config $composerConfig,
170
        RootPackageInterface $rootPackage
171
    ) : string {
172 13
        if (self::getRootPackageAlias($rootPackage)->getName() === 'ocramius/package-versions') {
173 3
            return dirname($composerConfig->get('vendor-dir'));
174
        }
175
176 10
        return $composerConfig->get('vendor-dir') . '/ocramius/package-versions';
177
    }
178
179 13
    private static function getRootPackageAlias(RootPackageInterface $rootPackage) : PackageInterface
180
    {
181 13
        $package = $rootPackage;
182
183 13
        while ($package instanceof AliasPackage) {
184 4
            $package = $package->getAliasOf();
185
        }
186
187 13
        return $package;
188
    }
189
190
    /**
191
     * @return Generator&string[]
192
     *
193
     * @psalm-return Generator<string, string>
194
     */
195 14
    private static function getVersions(Locker $locker, RootPackageInterface $rootPackage) : Generator
196
    {
197 14
        $lockData = $locker->getLockData();
198
199 14
        $lockData['packages-dev'] = $lockData['packages-dev'] ?? [];
200
201 14
        foreach (array_merge($lockData['packages'], $lockData['packages-dev']) as $package) {
202 14
            yield $package['name'] => $package['version'] . '@' . (
203 14
                $package['source']['reference']?? $package['dist']['reference'] ?? ''
204
            );
205
        }
206
207 14
        foreach ($rootPackage->getReplaces() as $replace) {
208 5
            $version = $replace->getPrettyConstraint();
209 5
            if ($version === 'self.version') {
210 5
                $version = $rootPackage->getPrettyVersion();
211
            }
212
213 5
            yield $replace->getTarget() => $version . '@' . $rootPackage->getSourceReference();
214
        }
215
216 14
        yield $rootPackage->getName() => $rootPackage->getPrettyVersion() . '@' . $rootPackage->getSourceReference();
217 14
    }
218
}
219