Completed
Push — master ( ec8288...17c53e )
by Marco
18s queued 10s
created

Installer   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 180
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 96.43%

Importance

Changes 0
Metric Value
wmc 15
lcom 1
cbo 9
dl 0
loc 180
ccs 54
cts 56
cp 0.9643
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A activate() 0 4 1
A getSubscribedEvents() 0 8 1
A dumpVersionsClass() 0 16 2
A generateVersionsClass() 0 9 1
A writeVersionClassToFile() 0 20 2
A locateRootPackageInstallPath() 0 10 2
A getRootPackageAlias() 0 10 2
A getVersions() 0 23 4
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
    /** @var string */
35
    private static $generatedClassTemplate = <<<'PHP'
36
<?php
37
38
declare(strict_types=1);
39
40
namespace PackageVersions;
41
42
/**
43
 * This class is generated by ocramius/package-versions, specifically by
44
 * @see \PackageVersions\Installer
45
 *
46
 * This file is overwritten at every run of `composer install` or `composer update`.
47
 */
48
%s
49
{
50
    public const ROOT_PACKAGE_NAME = '%s';
51
    /**
52
     * Array of all available composer packages.
53
     * Dont read this array from your calling code, but use the \PackageVersions\Versions::getVersion() method instead.
54
     *
55
     * @var array<string, string>
56
     * @internal
57
     */
58
    public const VERSIONS          = %s;
59
60
    private function __construct()
61
    {
62
    }
63
64
    /**
65
     * @throws \OutOfBoundsException If a version cannot be located.
66
     *
67
     * @psalm-param key-of<self::VERSIONS> $packageName
68
     */
69
    public static function getVersion(string $packageName) : string
70
    {
71
        if (isset(self::VERSIONS[$packageName])) {
72
            return self::VERSIONS[$packageName];
73
        }
74
75
        throw new \OutOfBoundsException(
76
            'Required package "' . $packageName . '" is not installed: cannot detect its version'
77
        );
78
    }
79
}
80
81
PHP;
82
83
    /**
84
     * {@inheritDoc}
85
     */
86
    public function activate(Composer $composer, IOInterface $io) : void
87
    {
88
        // Nothing to do here, as all features are provided through event listeners
89
    }
90
91
    /**
92
     * {@inheritDoc}
93
     */
94 1
    public static function getSubscribedEvents() : array
95
    {
96
        return [
97 1
            ScriptEvents::POST_INSTALL_CMD => 'dumpVersionsClass',
98 1
            ScriptEvents::POST_UPDATE_CMD  => 'dumpVersionsClass',
99 1
            ScriptEvents::POST_AUTOLOAD_DUMP  => 'dumpVersionsClass',
100
        ];
101
    }
102
103
    /**
104
     * @throws RuntimeException
105
     */
106 14
    public static function dumpVersionsClass(Event $composerEvent) : void
107
    {
108 14
        $composer    = $composerEvent->getComposer();
109 14
        $rootPackage = $composer->getPackage();
110 14
        $versions    = iterator_to_array(self::getVersions($composer->getLocker(), $rootPackage));
111
112 14
        if (! array_key_exists('ocramius/package-versions', $versions)) {
113
            //plugin must be globally installed - we only want to generate versions for projects which specifically
114
            //require ocramius/package-versions
115 1
            return;
116
        }
117
118 13
        $versionClass = self::generateVersionsClass($rootPackage->getName(), $versions);
119
120 13
        self::writeVersionClassToFile($versionClass, $composer, $composerEvent->getIO());
121 13
    }
122
123
    /**
124
     * @param string[] $versions
125
     */
126 13
    private static function generateVersionsClass(string $rootPackageName, array $versions) : string
127
    {
128 13
        return sprintf(
129 13
            self::$generatedClassTemplate,
130 13
            'fin' . 'al ' . 'cla' . 'ss ' . 'Versions', // note: workaround for regex-based code parsers :-(
131 13
            $rootPackageName,
132 13
            var_export($versions, true)
133
        );
134
    }
135
136
    /**
137
     * @throws RuntimeException
138
     */
139 13
    private static function writeVersionClassToFile(string $versionClassSource, Composer $composer, IOInterface $io) : void
140
    {
141 13
        $installPath = self::locateRootPackageInstallPath($composer->getConfig(), $composer->getPackage())
142 13
            . '/src/PackageVersions/Versions.php';
143
144 13
        if (! file_exists(dirname($installPath))) {
145 1
            $io->write('<info>ocramius/package-versions:</info> Package not found (probably scheduled for removal); generation of version class skipped.');
146
147 1
            return;
148
        }
149
150 12
        $io->write('<info>ocramius/package-versions:</info> Generating version class...');
151
152 12
        $installPathTmp = $installPath . '_' . uniqid('tmp', true);
153 12
        file_put_contents($installPathTmp, $versionClassSource);
154 12
        chmod($installPathTmp, 0664);
155 12
        rename($installPathTmp, $installPath);
156
157 12
        $io->write('<info>ocramius/package-versions:</info> ...done generating version class');
158 12
    }
159
160
    /**
161
     * @throws RuntimeException
162
     */
163 13
    private static function locateRootPackageInstallPath(
164
        Config $composerConfig,
165
        RootPackageInterface $rootPackage
166
    ) : string {
167 13
        if (self::getRootPackageAlias($rootPackage)->getName() === 'ocramius/package-versions') {
168 3
            return dirname($composerConfig->get('vendor-dir'));
169
        }
170
171 10
        return $composerConfig->get('vendor-dir') . '/ocramius/package-versions';
172
    }
173
174 13
    private static function getRootPackageAlias(RootPackageInterface $rootPackage) : PackageInterface
175
    {
176 13
        $package = $rootPackage;
177
178 13
        while ($package instanceof AliasPackage) {
179 4
            $package = $package->getAliasOf();
180
        }
181
182 13
        return $package;
183
    }
184
185
    /**
186
     * @return Generator|string[]
187
     */
188 14
    private static function getVersions(Locker $locker, RootPackageInterface $rootPackage) : Generator
189
    {
190 14
        $lockData = $locker->getLockData();
191
192 14
        $lockData['packages-dev'] = $lockData['packages-dev'] ?? [];
193
194 14
        foreach (array_merge($lockData['packages'], $lockData['packages-dev']) as $package) {
195 14
            yield $package['name'] => $package['version'] . '@' . (
196 14
                $package['source']['reference']?? $package['dist']['reference'] ?? ''
197
            );
198
        }
199
200 14
        foreach ($rootPackage->getReplaces() as $replace) {
201 5
            $version = $replace->getPrettyConstraint();
202 5
            if ($version === 'self.version') {
203 5
                $version = $rootPackage->getPrettyVersion();
204
            }
205
206 5
            yield $replace->getTarget() => $version . '@' . $rootPackage->getSourceReference();
207
        }
208
209 14
        yield $rootPackage->getName() => $rootPackage->getPrettyVersion() . '@' . $rootPackage->getSourceReference();
210 14
    }
211
}
212