Plugin   A
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 245
Duplicated Lines 0 %

Test Coverage

Coverage 79.67%

Importance

Changes 11
Bugs 0 Features 0
Metric Value
wmc 42
eloc 116
c 11
b 0
f 0
dl 0
loc 245
ccs 98
cts 123
cp 0.7967
rs 9.0399

13 Methods

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

93
            $this->addFiles(/** @scrutinizer ignore-type */ $this->rootPackage, $files);
Loading history...
94
            $this->reorderFiles();
95
            $builder->buildAllConfigs($this->files);
96
        }
97 2
    }
98
99 2
    private function scanPackages(): void
100
    {
101 2
        foreach ($this->packages as $package) {
102 2
            if ($package->isComplete()) {
103 2
                $this->processPackage($package);
104
            }
105
        }
106 2
    }
107
108 2
    private function reorderFiles(): void
109
    {
110 2
        foreach (array_keys($this->files) as $name) {
111 2
            $this->files[$name] = $this->getAllFiles($name);
112
        }
113 2
        foreach ($this->files as $name => $files) {
114 2
            $this->files[$name] = $this->orderFiles($files);
115
        }
116 2
    }
117
118 2
    private function getAllFiles(string $name, array $stack = []): array
119
    {
120 2
        if (empty($this->files[$name])) {
121 2
            return [];
122
        }
123 2
        $res = [];
124 2
        foreach ($this->files[$name] as $file) {
125 2
            if (strncmp($file, '$', 1) === 0) {
126
                if (!in_array($name, $stack, true)) {
127
                    $res = array_merge($res, $this->getAllFiles(substr($file, 1), array_merge($stack, [$name])));
128
                }
129
            } else {
130 2
                $res[] = $file;
131
            }
132
        }
133
134 2
        return $res;
135
    }
136
137 2
    private function orderFiles(array $files): array
138
    {
139 2
        if ($files === []) {
140 2
            return [];
141
        }
142 2
        $keys = array_combine($files, $files);
143 2
        $res = [];
144 2
        foreach ($this->orderedFiles as $file) {
145 2
            if (array_key_exists($file, $keys)) {
146 2
                $res[$file] = $file;
147
            }
148
        }
149
150 2
        return array_values($res);
151
    }
152
153
    /**
154
     * Scans the given package and collects packages data.
155
     *
156
     * @param Package $package
157
     */
158 2
    private function processPackage(Package $package): void
159
    {
160 2
        $files = $package->getFiles();
161 2
        $this->originalFiles[$package->getPrettyName()] = $files;
162
163 2
        if (!empty($files)) {
164 2
            $this->addFiles($package, $files);
165
        }
166 2
        if ($package->isRoot()) {
167 2
            $this->rootPackage = $package;
168 2
            $this->loadDotEnv($package);
169 2
            $devFiles = $package->getDevFiles();
170 2
            if (!empty($devFiles)) {
171
                $this->addFiles($package, $devFiles);
172
            }
173 2
            $alternatives = $package->getAlternatives();
174 2
            if (is_string($alternatives)) {
175
                $this->alternatives = $this->readConfig($package, $alternatives);
176 2
            } elseif (is_array($alternatives)) {
177
                $this->alternatives = $alternatives;
178 2
            } elseif (!empty($alternatives)) {
179
                throw new BadConfigurationException('Alternatives must be array or path to configuration file.');
180
            }
181
        }
182
183 2
        $this->builder->setPackage($package->getPrettyName(), array_filter([
184 2
            'name' => $package->getPrettyName(),
185 2
            'version' => $package->getVersion(),
186 2
            'reference' => $package->getSourceReference() ?: $package->getDistReference(),
187
        ]));
188 2
    }
189
190
    private function readConfig(Package $package, string $file): array
191
    {
192
        $path = $package->prepareConfigFilePath($file);
193
        if (!file_exists($path)) {
194
            throw new FailedReadException("failed read file: $file");
195
        }
196
        $reader = ReaderFactory::get($this->builder, $path);
197
198
        try {
199
            return $reader->read($path);
200
        } catch (\Throwable $e) {
201
            throw new ConfigBuildException($e);
202
        }
203
    }
204
205 2
    private function loadDotEnv(Package $package): void
206
    {
207 2
        $path = $package->preparePath('.env');
208 2
        if (file_exists($path) && class_exists(Dotenv::class)) {
209
            $this->addFile($package, 'envs', $path);
210
        }
211 2
    }
212
213
    /**
214
     * Adds given files to the list of files to be processed.
215
     * Prepares `constants` in reversed order (outer package first) because
216
     * constants cannot be redefined.
217
     *
218
     * @param Package $package
219
     * @param array $files
220
     */
221 2
    private function addFiles(Package $package, array $files): void
222
    {
223 2
        foreach ($files as $name => $paths) {
224 2
            $paths = (array) $paths;
225 2
            if ('constants' === $name) {
226
                $paths = array_reverse($paths);
227
            }
228 2
            foreach ($paths as $path) {
229 2
                $this->addFile($package, $name, $path);
230
            }
231
        }
232 2
    }
233
234
    private array $orderedFiles = [];
235
236 2
    private function addFile(Package $package, string $name, string $path): void
237
    {
238 2
        $path = $package->prepareConfigFilePath($path);
239 2
        if (!array_key_exists($name, $this->files)) {
240 2
            $this->files[$name] = [];
241
        }
242 2
        if (in_array($path, $this->files[$name], true)) {
243
            return;
244
        }
245 2
        if ('constants' === $name) {
246
            array_unshift($this->orderedFiles, $path);
247
            array_unshift($this->files[$name], $path);
248
        } else {
249 2
            $this->orderedFiles[] = $path;
250 2
            $this->files[$name][] = $path;
251
        }
252 2
    }
253
254 2
    private function collectPackages(Composer $composer): void
255
    {
256 2
        $vendorDir = $composer->getConfig()->get('vendor-dir');
257 2
        $rootPackage = $composer->getPackage();
258 2
        $packages = $composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();
259 2
        $packageFinder = new PackageFinder($vendorDir, $rootPackage, $packages);
260
261 2
        $this->packages = $packageFinder->findPackages();
262 2
    }
263
}
264