Passed
Pull Request — master (#3)
by Dmitriy
13:39
created

Plugin::processPackage()   B

Complexity

Conditions 8
Paths 18

Size

Total Lines 34
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 25
nc 18
nop 1
dl 0
loc 34
rs 8.4444
c 0
b 0
f 0
1
<?php
2
3
namespace Yiisoft\Composer\Config;
4
5
use Composer\Composer;
6
use Composer\EventDispatcher\EventSubscriberInterface;
7
use Composer\IO\IOInterface;
8
use Composer\Plugin\PluginInterface;
9
use Composer\Script\Event;
10
use Composer\Script\ScriptEvents;
11
use Yiisoft\Composer\Config\Package\PackageFinder;
12
use Yiisoft\Composer\Config\Exceptions\BadConfigurationException;
13
use Yiisoft\Composer\Config\Exceptions\FailedReadException;
14
use Yiisoft\Composer\Config\Readers\ReaderFactory;
15
16
/**
17
 * Plugin class.
18
 */
19
class Plugin implements PluginInterface, EventSubscriberInterface
20
{
21
    /**
22
     * @var Package[] the array of active composer packages
23
     */
24
    private array $packages = [];
25
26
    private array $alternatives = [];
27
28
    private ?string $outputDir = null;
29
30
    private ?Package $rootPackage = null;
31
32
    /**
33
     * @var array config name => list of files
34
     */
35
    private array $files = [
36
        'dotenv' => [],
37
        'defines' => [],
38
        'params' => [],
39
    ];
40
41
    /**
42
     * @var array package name => configs as listed in `composer.json`
43
     */
44
    private array $originalFiles = [];
45
46
    /**
47
     * @var Builder
48
     */
49
    private Builder $builder;
50
51
    /**
52
     * @var Composer instance
53
     */
54
    private Composer $composer;
55
56
    /**
57
     * @var IOInterface
58
     */
59
    private IOInterface $io;
60
61
    private PackageFinder $packageFinder;
62
63
    /**
64
     * Initializes the plugin object with the passed $composer and $io.
65
     *
66
     * @param Composer $composer
67
     * @param IOInterface $io
68
     */
69
    public function activate(Composer $composer, IOInterface $io): void
70
    {
71
        $this->builder = new Builder();
72
        $this->packageFinder = new PackageFinder($composer);
73
        $this->composer = $composer;
74
        $this->io = $io;
75
    }
76
77
    /**
78
     * Returns list of events the plugin is subscribed to.
79
     * @return array list of events
80
     */
81
    public static function getSubscribedEvents(): array
82
    {
83
        return [
84
            ScriptEvents::POST_AUTOLOAD_DUMP => [
85
                ['onPostAutoloadDump', 0],
86
            ],
87
        ];
88
    }
89
90
    /**
91
     * This is the main function.
92
     */
93
    public function onPostAutoloadDump(Event $event): void
94
    {
95
        $this->io->overwriteError('<info>Assembling config files</info>');
96
97
        require_once $event->getComposer()->getConfig()->get('vendor-dir') . '/autoload.php';
98
        $this->scanPackages();
99
        $this->reorderFiles();
100
101
        $this->builder->setOutputDir($this->outputDir);
102
        $this->builder->buildAllConfigs($this->files);
103
104
        $saveFiles = $this->files;
105
        $saveEnv = $_ENV;
106
        foreach ($this->alternatives as $name => $files) {
107
            $this->files = $saveFiles;
108
            $_ENV = $saveEnv;
109
            $builder = $this->builder->createAlternative($name);
110
            $this->addFiles($this->rootPackage, $files);
0 ignored issues
show
Bug introduced by
It seems like $this->rootPackage can also be of type null; however, parameter $package of Yiisoft\Composer\Config\Plugin::addFiles() does only seem to accept Yiisoft\Composer\Config\Package, maybe add an additional type check? ( Ignorable by Annotation )

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

110
            $this->addFiles(/** @scrutinizer ignore-type */ $this->rootPackage, $files);
Loading history...
111
            $builder->buildAllConfigs($this->files);
112
        }
113
    }
114
115
    private function scanPackages(): void
116
    {
117
        foreach ($this->getPackages() as $package) {
118
            if ($package->isComplete()) {
119
                $this->processPackage($package);
120
            }
121
        }
122
    }
123
124
    private function reorderFiles(): void
125
    {
126
        foreach (array_keys($this->files) as $name) {
127
            $this->files[$name] = $this->getAllFiles($name);
128
        }
129
        foreach ($this->files as $name => $files) {
130
            $this->files[$name] = $this->orderFiles($files);
131
        }
132
    }
133
134
    private function getAllFiles(string $name, array $stack = []): array
135
    {
136
        if (empty($this->files[$name])) {
137
            return[];
138
        }
139
        $res = [];
140
        foreach ($this->files[$name] as $file) {
141
            if (strncmp($file, '$', 1) === 0) {
142
                if (!in_array($name, $stack, true)) {
143
                    $res = array_merge($res, $this->getAllFiles(substr($file, 1), array_merge($stack, [$name])));
144
                }
145
            } else {
146
                $res[] = $file;
147
            }
148
        }
149
150
        return $res;
151
    }
152
153
    private function orderFiles(array $files): array
154
    {
155
        if (empty($files)) {
156
            return [];
157
        }
158
        $keys = array_combine($files, $files);
159
        $res = [];
160
        foreach ($this->orderedFiles as $file) {
161
            if (isset($keys[$file])) {
162
                $res[$file] = $file;
163
            }
164
        }
165
166
        return array_values($res);
167
    }
168
169
    /**
170
     * Scans the given package and collects packages data.
171
     * @param Package $package
172
     */
173
    private function processPackage(Package $package)
174
    {
175
        $files = $package->getFiles();
176
        $this->originalFiles[$package->getPrettyName()] = $files;
177
178
        if (!empty($files)) {
179
            $this->addFiles($package, $files);
180
        }
181
        if ($package->isRoot()) {
182
            $this->rootPackage = $package;
183
            $this->loadDotEnv($package);
184
            $devFiles = $package->getDevFiles();
185
            if (!empty($devFiles)) {
186
                $this->addFiles($package, $devFiles);
187
            }
188
            $this->outputDir = $package->getOutputDir();
189
            $alternatives = $package->getAlternatives();
190
            if (is_string($alternatives)) {
0 ignored issues
show
introduced by
The condition is_string($alternatives) is always false.
Loading history...
191
                $this->alternatives = $this->readConfig($package, $alternatives);
192
            } elseif (is_array($alternatives)) {
0 ignored issues
show
introduced by
The condition is_array($alternatives) is always true.
Loading history...
193
                $this->alternatives = $alternatives;
194
            } elseif (!empty($alternatives)) {
195
                throw new BadConfigurationException('Alternatives must be array or path to configuration file.');
196
            }
197
        }
198
199
        $aliases = $package->collectAliases();
200
201
        $this->builder->mergeAliases($aliases);
202
        $this->builder->setPackage($package->getPrettyName(), array_filter([
203
            'name' => $package->getPrettyName(),
204
            'version' => $package->getVersion(),
205
            'reference' => $package->getSourceReference() ?: $package->getDistReference(),
206
            'aliases' => $aliases,
207
        ]));
208
    }
209
210
    private function readConfig($package, $file): array
211
    {
212
        $path = $package->preparePath($file);
213
        if (!file_exists($path)) {
214
            throw new FailedReadException("failed read file: $file");
215
        }
216
        $reader = ReaderFactory::get($this->builder, $path);
217
218
        return $reader->read($path);
219
    }
220
221
    private function loadDotEnv(Package $package): void
222
    {
223
        $path = $package->preparePath('.env');
224
        if (file_exists($path) && class_exists('Dotenv\Dotenv')) {
225
            $this->addFile($package, 'dotenv', $path);
226
        }
227
    }
228
229
    /**
230
     * Adds given files to the list of files to be processed.
231
     * Prepares `defines` in reversed order (outer package first) because
232
     * constants cannot be redefined.
233
     * @param Package $package
234
     * @param array $files
235
     */
236
    private function addFiles(Package $package, array $files): void
237
    {
238
        foreach ($files as $name => $paths) {
239
            $paths = (array) $paths;
240
            if ('defines' === $name) {
241
                $paths = array_reverse($paths);
242
            }
243
            foreach ($paths as $path) {
244
                $this->addFile($package, $name, $path);
245
            }
246
        }
247
    }
248
249
    private array $orderedFiles = [];
250
251
    private function addFile(Package $package, string $name, string $path): void
252
    {
253
        $path = $package->preparePath($path);
254
        if (!isset($this->files[$name])) {
255
            $this->files[$name] = [];
256
        }
257
        if (in_array($path, $this->files[$name], true)) {
258
            return;
259
        }
260
        if ('defines' === $name) {
261
            array_unshift($this->orderedFiles, $path);
262
            array_unshift($this->files[$name], $path);
263
        } else {
264
            $this->orderedFiles[] = $path;
265
            $this->files[$name][] = $path;
266
        }
267
    }
268
269
    /**
270
     * Gets [[packages]].
271
     * @return Package[]
272
     */
273
    private function getPackages(): array
274
    {
275
        if ([] === $this->packages) {
276
            $this->packages = $this->packageFinder->findPackages();
277
        }
278
279
        return $this->packages;
280
    }
281
282
}
283