Passed
Push — master ( b6f0e0...b7a10a )
by Alexander
16:24 queued 11:42
created

ConfigOutput::getOutputPath()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

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