Passed
Pull Request — master (#6)
by Dmitriy
10:40
created

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