Passed
Push — master ( f5390f...ff2936 )
by Alexander
02:09
created

Plugin   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 240
Duplicated Lines 0 %

Test Coverage

Coverage 80.98%

Importance

Changes 10
Bugs 0 Features 0
Metric Value
wmc 41
eloc 113
c 10
b 0
f 0
dl 0
loc 240
ccs 98
cts 121
cp 0.8098
rs 9.1199

13 Methods

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

91
            $this->addFiles(/** @scrutinizer ignore-type */ $this->rootPackage, $files);
Loading history...
92
            $this->reorderFiles();
93
            $builder->buildAllConfigs($this->files);
94
        }
95 1
    }
96
97 1
    private function scanPackages(): void
98
    {
99 1
        foreach ($this->packages as $package) {
100 1
            if ($package->isComplete()) {
101 1
                $this->processPackage($package);
102
            }
103
        }
104 1
    }
105
106 1
    private function reorderFiles(): void
107
    {
108 1
        foreach (array_keys($this->files) as $name) {
109 1
            $this->files[$name] = $this->getAllFiles($name);
110
        }
111 1
        foreach ($this->files as $name => $files) {
112 1
            $this->files[$name] = $this->orderFiles($files);
113
        }
114 1
    }
115
116 1
    private function getAllFiles(string $name, array $stack = []): array
117
    {
118 1
        if (empty($this->files[$name])) {
119 1
            return [];
120
        }
121 1
        $res = [];
122 1
        foreach ($this->files[$name] as $file) {
123 1
            if (strncmp($file, '$', 1) === 0) {
124
                if (!in_array($name, $stack, true)) {
125
                    $res = array_merge($res, $this->getAllFiles(substr($file, 1), array_merge($stack, [$name])));
126
                }
127
            } else {
128 1
                $res[] = $file;
129
            }
130
        }
131
132 1
        return $res;
133
    }
134
135 1
    private function orderFiles(array $files): array
136
    {
137 1
        if ($files === []) {
138 1
            return [];
139
        }
140 1
        $keys = array_combine($files, $files);
141 1
        $res = [];
142 1
        foreach ($this->orderedFiles as $file) {
143 1
            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

143
            if (array_key_exists($file, /** @scrutinizer ignore-type */ $keys)) {
Loading history...
144 1
                $res[$file] = $file;
145
            }
146
        }
147
148 1
        return array_values($res);
149
    }
150
151
    /**
152
     * Scans the given package and collects packages data.
153
     *
154
     * @param Package $package
155
     */
156 1
    private function processPackage(Package $package): void
157
    {
158 1
        $files = $package->getFiles();
159 1
        $this->originalFiles[$package->getPrettyName()] = $files;
160
161 1
        if (!empty($files)) {
162 1
            $this->addFiles($package, $files);
163
        }
164 1
        if ($package->isRoot()) {
165 1
            $this->rootPackage = $package;
166 1
            $this->loadDotEnv($package);
167 1
            $devFiles = $package->getDevFiles();
168 1
            if (!empty($devFiles)) {
169
                $this->addFiles($package, $devFiles);
170
            }
171 1
            $alternatives = $package->getAlternatives();
172 1
            if (is_string($alternatives)) {
173
                $this->alternatives = $this->readConfig($package, $alternatives);
174 1
            } elseif (is_array($alternatives)) {
175
                $this->alternatives = $alternatives;
176 1
            } elseif (!empty($alternatives)) {
177
                throw new BadConfigurationException('Alternatives must be array or path to configuration file.');
178
            }
179
        }
180
181 1
        $this->builder->setPackage($package->getPrettyName(), array_filter([
182 1
            'name' => $package->getPrettyName(),
183 1
            'version' => $package->getVersion(),
184 1
            'reference' => $package->getSourceReference() ?: $package->getDistReference(),
185
        ]));
186 1
    }
187
188
    private function readConfig($package, $file): array
189
    {
190
        $path = $package->preparePath($file);
191
        if (!file_exists($path)) {
192
            throw new FailedReadException("failed read file: $file");
193
        }
194
        $reader = ReaderFactory::get($this->builder, $path);
195
196
        return $reader->read($path);
197
    }
198
199 1
    private function loadDotEnv(Package $package): void
200
    {
201 1
        $path = $package->preparePath('.env');
202 1
        if (file_exists($path) && class_exists(Dotenv::class)) {
203
            $this->addFile($package, 'envs', $path);
204
        }
205 1
    }
206
207
    /**
208
     * Adds given files to the list of files to be processed.
209
     * Prepares `constants` in reversed order (outer package first) because
210
     * constants cannot be redefined.
211
     *
212
     * @param Package $package
213
     * @param array $files
214
     */
215 1
    private function addFiles(Package $package, array $files): void
216
    {
217 1
        foreach ($files as $name => $paths) {
218 1
            $paths = (array) $paths;
219 1
            if ('constants' === $name) {
220
                $paths = array_reverse($paths);
221
            }
222 1
            foreach ($paths as $path) {
223 1
                $this->addFile($package, $name, $path);
224
            }
225
        }
226 1
    }
227
228
    private array $orderedFiles = [];
229
230 1
    private function addFile(Package $package, string $name, string $path): void
231
    {
232 1
        $path = $package->preparePath($path);
233 1
        if (!array_key_exists($name, $this->files)) {
234 1
            $this->files[$name] = [];
235
        }
236 1
        if (in_array($path, $this->files[$name], true)) {
237
            return;
238
        }
239 1
        if ('constants' === $name) {
240
            array_unshift($this->orderedFiles, $path);
241
            array_unshift($this->files[$name], $path);
242
        } else {
243 1
            $this->orderedFiles[] = $path;
244 1
            $this->files[$name][] = $path;
245
        }
246 1
    }
247
248 1
    private function collectPackages(Composer $composer): void
249
    {
250 1
        $vendorDir = $composer->getConfig()->get('vendor-dir');
251 1
        $rootPackage = $composer->getPackage();
252 1
        $packages = $composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();
253 1
        $packageFinder = new PackageFinder($vendorDir, $rootPackage, $packages);
254
255 1
        $this->packages = $packageFinder->findPackages();
256 1
    }
257
}
258