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

Installer::generateVersionsClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 4
cts 4
cp 1
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 2
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'
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

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