Passed
Pull Request — master (#6)
by Dmitriy
58:09 queued 43:13
created

Config::putFile()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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