Passed
Pull Request — master (#24)
by Dmitriy
37:45 queued 22:43
created

Config::build()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 5
c 0
b 0
f 0
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Yiisoft\Composer\Config\Config;
4
5
use Yiisoft\Arrays\ArrayHelper;
6
use Yiisoft\Composer\Config\Builder;
7
use Yiisoft\Composer\Config\ContentWriter;
8
use Yiisoft\Composer\Config\Reader\ReaderFactory;
9
use Yiisoft\Composer\Config\Util\ConfigMergeHelper;
0 ignored issues
show
Bug introduced by
The type Yiisoft\Composer\Config\Util\ConfigMergeHelper 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\Util\Helper;
11
use Yiisoft\Composer\Config\Util\PathHelper;
12
13
/**
14
 * Config class represents output configuration file.
15
 */
16
class Config
17
{
18
    private const BASE_DIR_MARKER = '<<<base-dir>>>';
19
20
    /**
21
     * @var string config name
22
     */
23
    private string $name;
24
25
    /**
26
     * @var array sources - paths to config source files
27
     */
28
    private array $sources = [];
29
30
    /**
31
     * @var array config value
32
     */
33
    protected array $values = [];
34
35
    protected Builder $builder;
36
37
    protected ContentWriter $contentWriter;
38
39
    public function __construct(Builder $builder, string $name)
40
    {
41
        $this->builder = $builder;
42
        $this->name = $name;
43
        $this->contentWriter = new ContentWriter();
44
    }
45
46
    public function clone(Builder $builder): self
47
    {
48
        $config = new Config($builder, $this->name);
49
        $config->sources = $this->sources;
50
        $config->values = $this->values;
51
52
        return $config;
53
    }
54
55
    public function getValues(): array
56
    {
57
        return $this->values;
58
    }
59
60
    public function load(array $paths = []): self
61
    {
62
        $this->sources = $this->loadFiles($paths);
63
64
        return $this;
65
    }
66
67
    private function loadFiles(array $paths): array
68
    {
69
        switch (count($paths)) {
70
            case 0:
71
                return [];
72
            case 1:
73
                return [$this->loadFile(reset($paths))];
74
        }
75
76
        $configs = [];
77
        foreach ($paths as $path) {
78
            $cs = $this->loadFiles($this->glob($path));
79
            foreach ($cs as $config) {
80
                if (!empty($config)) {
81
                    $configs[] = $config;
82
                }
83
            }
84
        }
85
86
        return $configs;
87
    }
88
89
    private function glob(string $path): array
90
    {
91
        if (strpos($path, '*') === false) {
92
            return [$path];
93
        }
94
95
        return glob($path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return glob($path) could return the type false which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
96
    }
97
98
    /**
99
     * Reads config file.
100
     *
101
     * @param string $path
102
     * @return array configuration read from file
103
     */
104
    protected function loadFile($path): array
105
    {
106
        $reader = ReaderFactory::get($this->builder, $path);
107
108
        return $reader->read($path);
109
    }
110
111
    /**
112
     * Merges given configs and writes at given name.
113
     *
114
     * @return Config
115
     */
116
    public function build(): self
117
    {
118
        $this->values = $this->calcValues($this->sources);
119
120
        return $this;
121
    }
122
123
    public function write(): self
124
    {
125
        $this->writeFile($this->getOutputPath(), $this->values);
126
127
        return $this;
128
    }
129
130
    protected function calcValues(array $sources): array
131
    {
132
        $values = ArrayHelper::merge(...$sources);
133
134
        return $this->substituteOutputDirs($values);
135
    }
136
137
    protected function writeFile(string $path, array $data): void
138
    {
139
        $depth = $this->findDepth();
140
        $baseDir = $depth > 0 ? "dirname(__DIR__, $depth)" : '__DIR__';
141
142
        $params = $this->paramsRequired() ? "(array) require __DIR__ . '/params.php'" : '[]';
143
        $constants = $this->constantsRequired() ? $this->builder->getConfig('constants')->buildRequires() : '';
144
        $envs = $this->envsRequired() ? "\$_ENV = array_merge((array) require __DIR__ . '/envs.php', \$_ENV);" : '';
145
        $variables = Helper::exportVar($data);
146
147
        $content = <<<PHP
148
<?php
149
\$baseDir = {$baseDir};
150
\$params = {$params};
151
{$constants}
152
{$envs}
153
return {$variables};
154
PHP;
155
156
        $this->contentWriter->write($path, $this->replaceMarkers($content) . "\n");
157
    }
158
159
    public function envsRequired(): bool
160
    {
161
        return true;
162
    }
163
164
    public function constantsRequired(): bool
165
    {
166
        return true;
167
    }
168
169
    public function paramsRequired(): bool
170
    {
171
        return true;
172
    }
173
174
    private function findDepth(): int
175
    {
176
        $outDir = PathHelper::realpath(dirname($this->getOutputPath()));
177
        $diff = substr($outDir, strlen(PathHelper::realpath($this->getBaseDir())));
178
179
        return substr_count($diff, '/');
180
    }
181
182
    private function replaceMarkers(string $content): string
183
    {
184
        return str_replace(
185
            ["'" . self::BASE_DIR_MARKER, "'?" . self::BASE_DIR_MARKER],
186
            ["\$baseDir . '", "'?' . \$baseDir . '"],
187
            $content
188
        );
189
    }
190
191
    /**
192
     * Substitute output paths in given data array recursively with marker.
193
     *
194
     * @param array $data
195
     * @return array
196
     */
197
    protected function substituteOutputDirs(array $data): array
198
    {
199
        $dir = PathHelper::normalize($this->getBaseDir());
200
201
        return $this->substitutePaths($data, $dir, self::BASE_DIR_MARKER);
202
    }
203
204
    /**
205
     * Substitute all paths in given array recursively with alias if applicable.
206
     *
207
     * @param array $data
208
     * @param string $dir
209
     * @param string $alias
210
     * @return array
211
     */
212
    private function substitutePaths($data, $dir, $alias): array
213
    {
214
        foreach ($data as &$value) {
215
            if (is_string($value)) {
216
                $value = $this->substitutePath($value, $dir, $alias);
217
            } elseif (is_array($value)) {
218
                $value = $this->substitutePaths($value, $dir, $alias);
219
            }
220
        }
221
222
        return $data;
223
    }
224
225
    /**
226
     * Substitute path with alias if applicable.
227
     *
228
     * @param string $path
229
     * @param string $dir
230
     * @param string $alias
231
     * @return string
232
     */
233
    private function substitutePath($path, $dir, $alias): string
234
    {
235
        $end = $dir . '/';
236
        $skippable = 0 === strncmp($path, '?', 1);
237
        if ($skippable) {
238
            $path = substr($path, 1);
239
        }
240
        if ($path === $dir) {
241
            $result = $alias;
242
        } elseif (strpos($path, $end) === 0) {
243
            $result = $alias . substr($path, strlen($end) - 1);
244
        } else {
245
            $result = $path;
246
        }
247
248
        return ($skippable ? '?' : '') . $result;
249
    }
250
251
    private function getBaseDir(): string
252
    {
253
        return $this->builder->getBaseDir();
254
    }
255
256
    protected function getOutputPath(string $name = null): string
257
    {
258
        return $this->builder->getOutputPath($name ?: $this->name);
259
    }
260
}
261