Installer::deactivate()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 0
nc 1
nop 2
dl 0
loc 2
ccs 0
cts 1
cp 0
crap 2
rs 10
c 0
b 0
f 0
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\Script\Event;
16
use Composer\Script\ScriptEvents;
17
use Generator;
18
use RuntimeException;
19
use function array_key_exists;
20
use function array_merge;
21
use function chmod;
22
use function dirname;
23
use function file_exists;
24
use function file_put_contents;
25
use function is_writable;
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 ComposerV2Plugin, 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
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
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 15
    public static function dumpVersionsClass(Event $composerEvent) : void
112
    {
113 15
        $composer    = $composerEvent->getComposer();
114 15
        $rootPackage = $composer->getPackage();
115 15
        $versions    = iterator_to_array(self::getVersions($composer->getLocker(), $rootPackage));
116
117 15
        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 14
        $versionClass = self::generateVersionsClass($rootPackage->getName(), $versions);
124
125 14
        self::writeVersionClassToFile($versionClass, $composer, $composerEvent->getIO());
126 14
    }
127
128
    /**
129
     * @param string[] $versions
130
     */
131 14
    private static function generateVersionsClass(string $rootPackageName, array $versions) : string
132
    {
133 14
        return sprintf(
134 14
            self::$generatedClassTemplate,
135 14
            'fin' . 'al ' . 'cla' . 'ss ' . 'Versions', // note: workaround for regex-based code parsers :-(
136 14
            $rootPackageName,
137 14
            var_export($versions, true)
138
        );
139
    }
140
141
    /**
142
     * @throws RuntimeException
143
     */
144 14
    private static function writeVersionClassToFile(string $versionClassSource, Composer $composer, IOInterface $io) : void
145
    {
146 14
        $installPath = self::locateRootPackageInstallPath($composer->getConfig(), $composer->getPackage())
147 14
            . '/src/PackageVersions/Versions.php';
148
149 14
        $installDir = dirname($installPath);
150 14
        if (! file_exists($installDir)) {
151 1
            $io->write('<info>ocramius/package-versions:</info> Package not found (probably scheduled for removal); generation of version class skipped.');
152
153 1
            return;
154
        }
155
156 13
        if (! is_writable($installDir)) {
157 1
            $io->write(
158 1
                sprintf(
159 1
                    '<info>ocramius/package-versions:</info> %s is not writable; generation of version class skipped.',
160 1
                    $installDir
161
                )
162
            );
163
164 1
            return;
165
        }
166
167 12
        $io->write('<info>ocramius/package-versions:</info> Generating version class...');
168
169 12
        $installPathTmp = $installPath . '_' . uniqid('tmp', true);
170 12
        file_put_contents($installPathTmp, $versionClassSource);
171 12
        chmod($installPathTmp, 0664);
172 12
        rename($installPathTmp, $installPath);
173
174 12
        $io->write('<info>ocramius/package-versions:</info> ...done generating version class');
175 12
    }
176
177
    /**
178
     * @throws RuntimeException
179
     */
180 14
    private static function locateRootPackageInstallPath(
181
        Config $composerConfig,
182
        RootPackageInterface $rootPackage
183
    ) : string {
184 14
        if (self::getRootPackageAlias($rootPackage)->getName() === 'ocramius/package-versions') {
185 3
            return dirname($composerConfig->get('vendor-dir'));
186
        }
187
188 11
        return $composerConfig->get('vendor-dir') . '/ocramius/package-versions';
189
    }
190
191 14
    private static function getRootPackageAlias(RootPackageInterface $rootPackage) : PackageInterface
192
    {
193 14
        $package = $rootPackage;
194
195 14
        while ($package instanceof AliasPackage) {
196 4
            $package = $package->getAliasOf();
197
        }
198
199 14
        return $package;
200
    }
201
202
    /**
203
     * @return Generator&string[]
204
     *
205
     * @psalm-return Generator<string, string>
206
     */
207 15
    private static function getVersions(Locker $locker, RootPackageInterface $rootPackage) : Generator
208
    {
209 15
        $lockData = $locker->getLockData();
210
211 15
        $lockData['packages-dev'] = $lockData['packages-dev'] ?? [];
212
213 15
        foreach (array_merge($lockData['packages'], $lockData['packages-dev']) as $package) {
214 15
            yield $package['name'] => $package['version'] . '@' . (
215 15
                $package['source']['reference']?? $package['dist']['reference'] ?? ''
216
            );
217
        }
218
219 15
        foreach ($rootPackage->getReplaces() as $replace) {
220 6
            $version = $replace->getPrettyConstraint();
221 6
            if ($version === 'self.version') {
222 6
                $version = $rootPackage->getPrettyVersion();
223
            }
224
225 6
            yield $replace->getTarget() => $version . '@' . $rootPackage->getSourceReference();
226
        }
227
228 15
        yield $rootPackage->getName() => $rootPackage->getPrettyVersion() . '@' . $rootPackage->getSourceReference();
229 15
    }
230
}
231