Passed
Push — master ( cd14ff...da2bd7 )
by Alexander
03:37 queued 25s
created

Config::substitutePathInString()   A

Complexity

Conditions 5
Paths 12

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5.5069

Importance

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