Passed
Pull Request — master (#54)
by Alexander
12:39
created

Plugin::build()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 12
dl 0
loc 17
c 1
b 0
f 0
rs 9.8666
ccs 0
cts 0
cp 0
cc 2
nc 2
nop 0
crap 6
1
<?php
2
3
namespace Yiisoft\Composer\Config;
4
5
use Composer\Composer;
6
use Composer\IO\IOInterface;
7
use Composer\Util\Filesystem;
8
use Yiisoft\Composer\Config\Config\ConfigFactory;
9
use Yiisoft\Composer\Config\Exception\BadConfigurationException;
10
use Yiisoft\Composer\Config\Exception\FailedReadException;
11
use Yiisoft\Composer\Config\Package\AliasesCollector;
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
    private AliasesCollector $aliasesCollector;
51
52
    /**
53
     * Initializes the plugin object with the passed $composer and $io.
54
     *
55
     * @param Composer $composer
56
     * @param IOInterface $io
57
     */
58
    public function __construct(Composer $composer, IOInterface $io)
59
    {
60
        $baseDir = dirname($composer->getConfig()->get('vendor-dir')) . DIRECTORY_SEPARATOR;
61
        $this->builder = new Builder(new ConfigFactory(), realpath($baseDir));
62
        $this->aliasesCollector = new AliasesCollector(new Filesystem());
63
        $this->io = $io;
64
        $this->collectPackages($composer);
65
    }
66
67
    public function build(): void
68
    {
69
        $this->io->overwriteError('<info>Assembling config files</info>');
70
71
        $this->scanPackages();
72
        $this->reorderFiles();
73
74
        $this->builder->buildAllConfigs($this->files);
75
76
        $saveFiles = $this->files;
77
        $saveEnv = $_ENV;
78
        foreach ($this->alternatives as $name => $files) {
79
            $this->files = $saveFiles;
80
            $_ENV = $saveEnv;
81
            $builder = $this->builder->createAlternative($name);
82
            $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

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

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