Passed
Pull Request — master (#13)
by Dmitriy
12:10
created

Plugin::getAllFiles()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 10
nc 5
nop 2
dl 0
loc 17
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
namespace Yiisoft\Composer\Config;
4
5
use Composer\Composer;
6
use Composer\IO\IOInterface;
7
use Yiisoft\Composer\Config\Exceptions\BadConfigurationException;
8
use Yiisoft\Composer\Config\Exceptions\FailedReadException;
9
use Yiisoft\Composer\Config\Package\PackageFinder;
10
use Yiisoft\Composer\Config\Readers\ReaderFactory;
11
12
final class Plugin
13
{
14
    /**
15
     * @var Package[] the array of active composer packages
16
     */
17
    private array $packages = [];
18
19
    private array $alternatives = [];
20
21
    private ?string $outputDir = null;
22
23
    private ?Package $rootPackage = null;
24
25
    /**
26
     * @var array config name => list of files
27
     */
28
    private array $files = [
29
        'dotenv' => [],
30
        'defines' => [],
31
        'params' => [],
32
    ];
33
34
    /**
35
     * @var array package name => configs as listed in `composer.json`
36
     */
37
    private array $originalFiles = [];
38
39
    /**
40
     * @var Builder
41
     */
42
    private Builder $builder;
43
44
    /**
45
     * @var Composer instance
46
     */
47
    private Composer $composer;
48
49
    /**
50
     * @var IOInterface
51
     */
52
    private IOInterface $io;
53
54
    private PackageFinder $packageFinder;
55
56
    /**
57
     * Initializes the plugin object with the passed $composer and $io.
58
     *
59
     * @param Composer $composer
60
     * @param IOInterface $io
61
     */
62
    public function __construct(Composer $composer, IOInterface $io)
63
    {
64
        $this->builder = new Builder();
65
        $this->packageFinder = new PackageFinder($composer);
66
        $this->composer = $composer;
67
        $this->io = $io;
68
    }
69
70
    public function build(): void
71
    {
72
        $this->io->overwriteError('<info>Assembling config files</info>');
73
74
        $this->scanPackages();
75
        $this->reorderFiles();
76
77
        $this->builder->setOutputDir($this->outputDir);
78
        $this->builder->buildAllConfigs($this->files);
79
80
        $saveFiles = $this->files;
81
        $saveEnv = $_ENV;
82
        foreach ($this->alternatives as $name => $files) {
83
            $this->files = $saveFiles;
84
            $_ENV = $saveEnv;
85
            $builder = $this->builder->createAlternative($name);
86
            $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

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