Passed
Pull Request — master (#110)
by
unknown
11:26
created

Plugin   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 275
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 9
Bugs 0 Features 0
Metric Value
wmc 50
eloc 133
c 9
b 0
f 0
dl 0
loc 275
ccs 0
cts 125
cp 0
rs 8.4

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A buildAllConfigs() 0 10 1
A addFiles() 0 9 4
B reorderConfigs() 0 26 9
A orderFiles() 0 14 4
A readConfig() 0 9 2
B processPackage() 0 33 8
A scanPackages() 0 5 3
A build() 0 18 2
A getAllFiles() 0 17 5
A loadDotEnv() 0 5 3
A addFile() 0 15 4
A collectPackages() 0 8 1
A reorderFiles() 0 7 3

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
declare(strict_types=1);
4
5
namespace Yiisoft\Composer\Config;
6
7
use Composer\Composer;
8
use Composer\IO\IOInterface;
9
use Composer\Util\Filesystem;
10
use Yiisoft\Composer\Config\Config\ConfigFactory;
11
use Yiisoft\Composer\Config\Exception\BadConfigurationException;
12
use Yiisoft\Composer\Config\Exception\FailedReadException;
13
use Yiisoft\Composer\Config\Package\AliasesCollector;
14
use Yiisoft\Composer\Config\Package\PackageFinder;
15
use Yiisoft\Composer\Config\Reader\ReaderFactory;
16
use Dotenv\Dotenv;
0 ignored issues
show
Bug introduced by
The type Dotenv\Dotenv was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
18
final class Plugin
19
{
20
    /**
21
     * @var Package[] the array of active composer packages
22
     */
23
    private array $packages;
24
25
    private array $alternatives = [];
26
27
    private ?Package $rootPackage = null;
28
29
    /**
30
     * @var array config name => list of files
31
     * Important: defines config files processing order:
32
     * envs then constants then params then other configs
33
     */
34
    private array $files = [
35
        'envs' => [],
36
        'constants' => [],
37
        'params' => [],
38
    ];
39
40
    /**
41
     * @var array package name => configs as listed in `composer.json`
42
     */
43
    private array $originalFiles = [];
44
45
    private Builder $builder;
46
47
    /**
48
     * @var IOInterface
49
     */
50
    private IOInterface $io;
51
52
    private AliasesCollector $aliasesCollector;
53
54
    /**
55
     * Initializes the plugin object with the passed $composer and $io.
56
     *
57
     * @param Composer $composer
58
     * @param IOInterface $io
59
     */
60
    public function __construct(Composer $composer, IOInterface $io)
61
    {
62
        $baseDir = dirname($composer->getConfig()->get('vendor-dir')) . DIRECTORY_SEPARATOR;
63
        $this->builder = new Builder(new ConfigFactory(), realpath($baseDir));
64
        $this->aliasesCollector = new AliasesCollector(new Filesystem());
65
        $this->io = $io;
66
        $this->collectPackages($composer);
67
    }
68
69
    public static function buildAllConfigs(string $projectRootPath): void
70
    {
71
        $factory = new \Composer\Factory();
72
        $output = $factory::createOutput();
73
        $input = new \Symfony\Component\Console\Input\ArgvInput([]);
74
        $helperSet = new \Symfony\Component\Console\Helper\HelperSet();
75
        $io = new \Composer\IO\ConsoleIO($input, $output, $helperSet);
76
        $composer = $factory->createComposer($io, $projectRootPath . '/composer.json', true, $projectRootPath, false);
77
        $plugin = new self($composer, $io);
78
        $plugin->build();
79
    }
80
81
    public function build(): void
82
    {
83
        $this->io->overwriteError('<info>Assembling config files</info>');
84
85
        $this->scanPackages();
86
        $this->reorderConfigs();
87
        $this->reorderFiles();
88
89
        $this->builder->buildAllConfigs($this->files);
90
91
        $saveFiles = $this->files;
92
        $saveEnv = $_ENV;
93
        foreach ($this->alternatives as $name => $files) {
94
            $this->files = $saveFiles;
95
            $_ENV = $saveEnv;
96
            $builder = $this->builder->createAlternative($name);
97
            $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

97
            $this->addFiles(/** @scrutinizer ignore-type */ $this->rootPackage, $files);
Loading history...
98
            $builder->buildAllConfigs($this->files);
99
        }
100
    }
101
102
    private function scanPackages(): void
103
    {
104
        foreach ($this->packages as $package) {
105
            if ($package->isComplete()) {
106
                $this->processPackage($package);
107
            }
108
        }
109
    }
110
111
    private function reorderConfigs(): void
112
    {
113
        $order = [];
114
        foreach (array_reverse($this->packages) as $package) {
115
            if ($package->isComplete()) {
116
                $files = $package->getFiles();
117
                if (!empty($files)) {
118
                    foreach (array_keys($files) as $name) {
119
                        if (!in_array($name, $order)) {
120
                            $order[] = $name;
121
                        }
122
                    }
123
                }
124
            }
125
        }
126
127
        $files = [];
128
        foreach (array_keys($this->files) as $name) {
129
            if (!in_array($name, $order)) {
130
                $files[$name] = $this->files[$name];
131
            }
132
        }
133
        foreach ($order as $name) {
134
            $files[$name] = $this->files[$name];
135
        }
136
        $this->files = $files;
137
    }
138
139
    private function reorderFiles(): void
140
    {
141
        foreach (array_keys($this->files) as $name) {
142
            $this->files[$name] = $this->getAllFiles($name);
143
        }
144
        foreach ($this->files as $name => $files) {
145
            $this->files[$name] = $this->orderFiles($files);
146
        }
147
    }
148
149
    private function getAllFiles(string $name, array $stack = []): array
150
    {
151
        if (empty($this->files[$name])) {
152
            return [];
153
        }
154
        $res = [];
155
        foreach ($this->files[$name] as $file) {
156
            if (strncmp($file, '$', 1) === 0) {
157
                if (!in_array($name, $stack, true)) {
158
                    $res = array_merge($res, $this->getAllFiles(substr($file, 1), array_merge($stack, [$name])));
159
                }
160
            } else {
161
                $res[] = $file;
162
            }
163
        }
164
165
        return $res;
166
    }
167
168
    private function orderFiles(array $files): array
169
    {
170
        if ($files === []) {
171
            return [];
172
        }
173
        $keys = array_combine($files, $files);
174
        $res = [];
175
        foreach ($this->orderedFiles as $file) {
176
            if (array_key_exists($file, $keys)) {
0 ignored issues
show
Bug introduced by
It seems like $keys can also be of type false; however, parameter $search of array_key_exists() does only seem to accept array, 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

176
            if (array_key_exists($file, /** @scrutinizer ignore-type */ $keys)) {
Loading history...
177
                $res[$file] = $file;
178
            }
179
        }
180
181
        return array_values($res);
182
    }
183
184
    /**
185
     * Scans the given package and collects packages data.
186
     *
187
     * @param Package $package
188
     */
189
    private function processPackage(Package $package): void
190
    {
191
        $files = $package->getFiles();
192
        $this->originalFiles[$package->getPrettyName()] = $files;
193
194
        if (!empty($files)) {
195
            $this->addFiles($package, $files);
196
        }
197
        if ($package->isRoot()) {
198
            $this->rootPackage = $package;
199
            $this->loadDotEnv($package);
200
            $devFiles = $package->getDevFiles();
201
            if (!empty($devFiles)) {
202
                $this->addFiles($package, $devFiles);
203
            }
204
            $alternatives = $package->getAlternatives();
205
            if (is_string($alternatives)) {
206
                $this->alternatives = $this->readConfig($package, $alternatives);
207
            } elseif (is_array($alternatives)) {
208
                $this->alternatives = $alternatives;
209
            } elseif (!empty($alternatives)) {
210
                throw new BadConfigurationException('Alternatives must be array or path to configuration file.');
211
            }
212
        }
213
214
        $aliases = $this->aliasesCollector->collect($package);
215
216
        $this->builder->mergeAliases($aliases);
217
        $this->builder->setPackage($package->getPrettyName(), array_filter([
218
            'name' => $package->getPrettyName(),
219
            'version' => $package->getVersion(),
220
            'reference' => $package->getSourceReference() ?: $package->getDistReference(),
221
            'aliases' => $aliases,
222
        ]));
223
    }
224
225
    private function readConfig($package, $file): array
226
    {
227
        $path = $package->preparePath($file);
228
        if (!file_exists($path)) {
229
            throw new FailedReadException("failed read file: $file");
230
        }
231
        $reader = ReaderFactory::get($this->builder, $path);
232
233
        return $reader->read($path);
234
    }
235
236
    private function loadDotEnv(Package $package): void
237
    {
238
        $path = $package->preparePath('.env');
239
        if (file_exists($path) && class_exists(Dotenv::class)) {
240
            $this->addFile($package, 'envs', $path);
241
        }
242
    }
243
244
    /**
245
     * Adds given files to the list of files to be processed.
246
     * Prepares `constants` in reversed order (outer package first) because
247
     * constants cannot be redefined.
248
     *
249
     * @param Package $package
250
     * @param array $files
251
     */
252
    private function addFiles(Package $package, array $files): void
253
    {
254
        foreach ($files as $name => $paths) {
255
            $paths = (array) $paths;
256
            if ('constants' === $name) {
257
                $paths = array_reverse($paths);
258
            }
259
            foreach ($paths as $path) {
260
                $this->addFile($package, $name, $path);
261
            }
262
        }
263
    }
264
265
    private array $orderedFiles = [];
266
267
    private function addFile(Package $package, string $name, string $path): void
268
    {
269
        $path = $package->preparePath($path);
270
        if (!array_key_exists($name, $this->files)) {
271
            $this->files[$name] = [];
272
        }
273
        if (in_array($path, $this->files[$name], true)) {
274
            return;
275
        }
276
        if ('constants' === $name) {
277
            array_unshift($this->orderedFiles, $path);
278
            array_unshift($this->files[$name], $path);
279
        } else {
280
            $this->orderedFiles[] = $path;
281
            $this->files[$name][] = $path;
282
        }
283
    }
284
285
    private function collectPackages(Composer $composer): void
286
    {
287
        $vendorDir = $composer->getConfig()->get('vendor-dir');
288
        $rootPackage = $composer->getPackage();
289
        $packages = $composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();
290
        $packageFinder = new PackageFinder($vendorDir, $rootPackage, $packages);
291
292
        $this->packages = $packageFinder->findPackages();
293
    }
294
}
295