Passed
Pull Request — master (#5)
by Dmitriy
11:29
created

Plugin   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 264
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 116
c 2
b 0
f 0
dl 0
loc 264
rs 9.0399
wmc 42

13 Methods

Rating   Name   Duplication   Size   Complexity  
A addFiles() 0 9 4
A loadDotEnv() 0 5 3
A addFile() 0 15 4
A orderFiles() 0 14 4
A readConfig() 0 9 2
B processPackage() 0 34 8
A getAllFiles() 0 17 5
A reorderFiles() 0 7 3
A scanPackages() 0 5 3
A getSubscribedEvents() 0 5 1
A activate() 0 7 1
A getPackages() 0 7 2
A onPostAutoloadDump() 0 19 2

How to fix   Complexity   

Complex Class

Complex classes like Plugin often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Plugin, and based on these observations, apply Extract Interface, too.

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 Composer\Util\Filesystem;
12
use Yiisoft\Composer\Config\Configs\ConfigFactory;
13
use Yiisoft\Composer\Config\exceptions\BadConfigurationException;
14
use Yiisoft\Composer\Config\exceptions\FailedReadException;
15
use Yiisoft\Composer\Config\Package\AliasesCollector;
16
use Yiisoft\Composer\Config\Package\PackageFinder;
17
use Yiisoft\Composer\Config\readers\ReaderFactory;
18
19
/**
20
 * Plugin class.
21
 */
22
class Plugin implements PluginInterface, EventSubscriberInterface
23
{
24
    /**
25
     * @var Package[] the array of active composer packages
26
     */
27
    private array $packages = [];
28
29
    private array $alternatives = [];
30
31
    private ?string $outputDir = null;
32
33
    private ?Package $rootPackage = null;
34
35
    /**
36
     * @var array config name => list of files
37
     */
38
    private array $files = [
39
        'dotenv' => [],
40
        'defines' => [],
41
        'params' => [],
42
    ];
43
44
    /**
45
     * @var array package name => configs as listed in `composer.json`
46
     */
47
    private array $originalFiles = [];
48
49
    /**
50
     * @var Builder
51
     */
52
    private Builder $builder;
53
54
    /**
55
     * @var Composer instance
56
     */
57
    private Composer $composer;
58
59
    /**
60
     * @var IOInterface
61
     */
62
    private IOInterface $io;
63
64
    private PackageFinder $packageFinder;
65
66
    private AliasesCollector $aliasesCollector;
67
68
    /**
69
     * Initializes the plugin object with the passed $composer and $io.
70
     *
71
     * @param Composer $composer
72
     * @param IOInterface $io
73
     */
74
    public function activate(Composer $composer, IOInterface $io): void
75
    {
76
        $this->builder = new Builder(new ConfigFactory());
77
        $this->packageFinder = new PackageFinder($composer);
78
        $this->aliasesCollector = new AliasesCollector(new Filesystem());
79
        $this->composer = $composer;
80
        $this->io = $io;
81
    }
82
83
    /**
84
     * Returns list of events the plugin is subscribed to.
85
     * @return array list of events
86
     */
87
    public static function getSubscribedEvents(): array
88
    {
89
        return [
90
            ScriptEvents::POST_AUTOLOAD_DUMP => [
91
                ['onPostAutoloadDump', 0],
92
            ],
93
        ];
94
    }
95
96
    /**
97
     * This is the main function.
98
     */
99
    public function onPostAutoloadDump(Event $event): void
100
    {
101
        $this->io->overwriteError('<info>Assembling config files</info>');
102
103
        require_once $event->getComposer()->getConfig()->get('vendor-dir') . '/autoload.php';
104
        $this->scanPackages();
105
        $this->reorderFiles();
106
107
        $this->builder->setOutputDir($this->outputDir);
108
        $this->builder->buildAllConfigs($this->files);
109
110
        $saveFiles = $this->files;
111
        $saveEnv = $_ENV;
112
        foreach ($this->alternatives as $name => $files) {
113
            $this->files = $saveFiles;
114
            $_ENV = $saveEnv;
115
            $builder = $this->builder->createAlternative($name);
116
            $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

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