Passed
Pull Request — master (#8)
by Dmitriy
28:38 queued 13:30
created

Plugin::getPackages()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 7
rs 10
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 Composer\Util\Filesystem;
8
use Yiisoft\Composer\Config\Configs\ConfigFactory;
9
use Yiisoft\Composer\Config\Exceptions\BadConfigurationException;
10
use Yiisoft\Composer\Config\Exceptions\FailedReadException;
11
use Yiisoft\Composer\Config\Package\AliasesCollector;
12
use Yiisoft\Composer\Config\Package\PackageFinder;
13
use Yiisoft\Composer\Config\Readers\ReaderFactory;
14
15
final class Plugin
16
{
17
    /**
18
     * @var Package[] the array of active composer packages
19
     */
20
    private array $packages;
21
22
    private array $alternatives = [];
23
24
    private ?string $outputDir = null;
25
26
    private ?Package $rootPackage = null;
27
28
    /**
29
     * @var array config name => list of files
30
     */
31
    private array $files = [
32
        'envs' => [],
33
        'dotenv' => [],
34
        'params' => [],
35
        'defines' => [],
36
        'constants' => [],
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
    private AliasesCollector $aliasesCollector;
52
53
    /**
54
     * Initializes the plugin object with the passed $composer and $io.
55
     *
56
     * @param Composer $composer
57
     * @param IOInterface $io
58
     */
59
    public function __construct(Composer $composer, IOInterface $io)
60
    {
61
        $baseDir = dirname($composer->getConfig()->get('vendor-dir')) . DIRECTORY_SEPARATOR;
62
        $this->builder = new Builder(new ConfigFactory(), realpath($baseDir));
63
        $this->aliasesCollector = new AliasesCollector(new Filesystem());
64
        $this->io = $io;
65
        $this->collectPackages($composer);
66
    }
67
68
    public function build(): void
69
    {
70
        $this->io->overwriteError('<info>Assembling config files</info>');
71
72
        $this->scanPackages();
73
        $this->reorderFiles();
74
75
        $this->builder->buildAllConfigs($this->files);
76
77
        $saveFiles = $this->files;
78
        $saveEnv = $_ENV;
79
        foreach ($this->alternatives as $name => $files) {
80
            $this->files = $saveFiles;
81
            $_ENV = $saveEnv;
82
            $builder = $this->builder->createAlternative($name);
83
            $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

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

134
            if (array_key_exists($file, /** @scrutinizer ignore-type */ $keys)) {
Loading history...
135
                $res[$file] = $file;
136
            }
137
        }
138
139
        return array_values($res);
140
    }
141
142
    /**
143
     * Scans the given package and collects packages data.
144
     *
145
     * @param Package $package
146
     */
147
    private function processPackage(Package $package): void
148
    {
149
        $files = $package->getFiles();
150
        $this->originalFiles[$package->getPrettyName()] = $files;
151
152
        if (!empty($files)) {
153
            $this->addFiles($package, $files);
154
        }
155
        if ($package->isRoot()) {
156
            $this->rootPackage = $package;
157
            $this->loadDotEnv($package);
158
            $devFiles = $package->getDevFiles();
159
            if (!empty($devFiles)) {
160
                $this->addFiles($package, $devFiles);
161
            }
162
            $this->outputDir = $package->getOutputDir();
163
            $alternatives = $package->getAlternatives();
164
            if (is_string($alternatives)) {
0 ignored issues
show
introduced by
The condition is_string($alternatives) is always false.
Loading history...
165
                $this->alternatives = $this->readConfig($package, $alternatives);
166
            } elseif (is_array($alternatives)) {
0 ignored issues
show
introduced by
The condition is_array($alternatives) is always true.
Loading history...
167
                $this->alternatives = $alternatives;
168
            } elseif (!empty($alternatives)) {
169
                throw new BadConfigurationException('Alternatives must be array or path to configuration file.');
170
            }
171
        }
172
173
        $aliases = $this->aliasesCollector->collect($package);
174
175
        $this->builder->mergeAliases($aliases);
176
        $this->builder->setPackage($package->getPrettyName(), array_filter([
177
            'name' => $package->getPrettyName(),
178
            'version' => $package->getVersion(),
179
            'reference' => $package->getSourceReference() ?: $package->getDistReference(),
180
            'aliases' => $aliases,
181
        ]));
182
    }
183
184
    private function readConfig($package, $file): array
185
    {
186
        $path = $package->preparePath($file);
187
        if (!file_exists($path)) {
188
            throw new FailedReadException("failed read file: $file");
189
        }
190
        $reader = ReaderFactory::get($this->builder, $path);
191
192
        return $reader->read($path);
193
    }
194
195
    private function loadDotEnv(Package $package): void
196
    {
197
        $path = $package->preparePath('.env');
198
        if (file_exists($path) && class_exists('Dotenv\Dotenv')) {
199
            $this->addFile($package, 'envs', $path);
200
            $this->addFile($package, 'dotenv', $path);
201
        }
202
    }
203
204
    /**
205
     * Adds given files to the list of files to be processed.
206
     * Prepares `constants` in reversed order (outer package first) because
207
     * constants cannot be redefined.
208
     *
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 ('constants' === $name || '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 ('constants' === $name || '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
    private function collectPackages(Composer $composer): void
246
    {
247
        $vendorDir = $composer->getConfig()->get('vendor-dir');
248
        $rootPackage = $composer->getPackage();
249
        $packages = $composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();
250
        $packageFinder = new PackageFinder($vendorDir, $rootPackage, $packages);
251
252
        $this->packages = $packageFinder->findPackages();
253
    }
254
}
255