Test Failed
Pull Request — master (#124)
by Andrii
11:47
created

Config::buildVariables()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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