Passed
Push — master ( e2e8c7...d238de )
by Andrii
14:06
created

Config::putFile()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 7
c 2
b 0
f 0
dl 0
loc 11
ccs 0
cts 7
cp 0
rs 8.8333
cc 7
nc 4
nop 2
crap 56
1
<?php
2
/**
3
 * Composer plugin for config assembling
4
 *
5
 * @link      https://github.com/hiqdev/composer-config-plugin
6
 * @package   composer-config-plugin
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2016-2018, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\composer\config\configs;
12
13
use hiqdev\composer\config\Builder;
14
use hiqdev\composer\config\exceptions\FailedWriteException;
15
use hiqdev\composer\config\Helper;
16
use hiqdev\composer\config\readers\ReaderFactory;
17
use ReflectionException;
18
19
/**
20
 * Config class represents output configuration file.
21
 *
22
 * @author Andrii Vasyliev <[email protected]>
23
 */
24
class Config
25
{
26
    const UNIX_DS = '/';
27
    const BASE_DIR_MARKER = '<<<base-dir>>>';
28
29
    /**
30
     * @var string config name
31
     */
32
    protected $name;
33
34
    /**
35
     * @var array sources - paths to config source files
36 1
     */
37
    protected $sources = [];
38 1
39 1
    /**
40 1
     * @var array config value
41
     */
42 1
    protected $values = [];
43
44 1
    /**
45
     * @var Builder
46
     */
47 1
    protected $builder;
48
49 1
    public function __construct(Builder $builder, string $name)
50
    {
51
        $this->builder = $builder;
52
        $this->name = $name;
53
    }
54
55
    public function clone(Builder $builder)
56
    {
57
        $config = new Config($builder, $this->name);
58
        $config->sources = $this->sources;
59
        $config->values = $this->values;
60
61
        return $config;
62
    }
63
64
    public function getBuilder(): Builder
65
    {
66
        return $this->builder;
67
    }
68
69
    public function getName(): string
70
    {
71
        return $this->name;
72
    }
73
74
    public function getValues(): array
75
    {
76
        return $this->values;
77
    }
78
79
    public function load(array $paths = []): self
80
    {
81
        $configs = [];
82
        foreach ($paths as $path) {
83
            $config = $this->loadFile($path);
84
            if (!empty($config)) {
85
                $configs[] = $config;
86
            }
87
        }
88
89
        $this->sources = $configs;
90
91
        return $this;
92
    }
93
94
    /**
95
     * Reads config file.
96
     * @param string $path
97
     * @return array configuration read from file
98
     */
99
    protected function loadFile($path): array
100
    {
101
        $reader = ReaderFactory::get($this->builder, $path);
102
103
        return $reader->read($path);
104
    }
105
106
    /**
107
     * Merges given configs and writes at given name.
108
     * @return Config
109
     */
110
    public function build(): self
111
    {
112
        $this->values = $this->calcValues($this->sources);
113
114
        return $this;
115
    }
116
117
    public function write(): self
118
    {
119
        $this->writeFile($this->getOutputPath(), $this->values);
120
121
        return $this;
122
    }
123
124
    protected function calcValues(array $sources): array
125
    {
126
        $values = call_user_func_array([Helper::class, 'mergeConfig'], $sources);
127
128
        return $this->substituteOutputDirs($values);
129
    }
130
131
    protected function writeFile(string $path, array $data): void
132
    {
133
        $this->writePhpFile($path, $data);
134
    }
135
136
    /**
137
     * Writes complete PHP config file by full path.
138
     * @param string $path
139
     * @param string|array $data
140
     * @param bool $withEnv
141
     * @param bool $withDefines
142
     * @throws FailedWriteException
143
     * @throws ReflectionException
144
     */
145
    protected function writePhpFile(string $path, $data): void
146
    {
147
        $depth = $this->findDepth();
148
        $baseDir = $depth>0 ? "dirname(__DIR__, $depth)" : '__DIR__';
149
        static::putFile($path, $this->replaceMarkers(implode("\n\n", array_filter([
150
            'header'  => '<?php',
151
            'baseDir' => "\$baseDir = $baseDir;",
152
            'BASEDIR' => "defined('COMPOSER_CONFIG_PLUGIN_BASEDIR') or define('COMPOSER_CONFIG_PLUGIN_BASEDIR', \$baseDir);",
153
            'dotenv'  => $this->withEnv() ? "\$_ENV = array_merge((array) require __DIR__ . '/dotenv.php', (array) \$_ENV);" : '',
154
            'defines' => $this->withDefines() ? $this->builder->getConfig('defines')->buildRequires() : '',
155
            'params'  => $this->withParams() ? "\$params = require __DIR__ . '/params.php';" : '',
156
            'content' => is_array($data) ? $this->renderVars($data) : $data,
157
        ]))) . "\n");
158
    }
159
160
    private function withEnv(): bool
161
    {
162
        return !in_array(static::class, [System::class, DotEnv::class], true);
163
    }
164
165
    private function withDefines(): bool
166
    {
167
        return !in_array(static::class, [System::class, DotEnv::class, Defines::class], true);
168
    }
169
170
    private function withParams(): bool
171
    {
172
        return !in_array(static::class, [System::class, DotEnv::class, Defines::class, Params::class], true);
173
    }
174
175
    private function findDepth()
176
    {
177
        $outDir = dirname($this->getOutputPath());
178
        $diff = substr($outDir, strlen($this->getBaseDir()));
179
180
        return substr_count($diff, DIRECTORY_SEPARATOR);
181
    }
182
183
    /**
184
     * @param array $vars array to be exported
185
     * @return string
186
     * @throws ReflectionException
187
     */
188
    protected function renderVars(array $vars): string
189
    {
190
        return 'return ' . Helper::exportVar($vars) . ';';
191
    }
192
193
    protected function replaceMarkers(string $content): string
194
    {
195
        $content = str_replace("'" . static::BASE_DIR_MARKER, "\$baseDir . '", $content);
196
197
        return str_replace("'?" . static::BASE_DIR_MARKER, "'?' . \$baseDir . '", $content);
198
    }
199
200
    /**
201
     * Writes file if content changed.
202
     * @param string $path
203
     * @param string $content
204
     * @throws FailedWriteException
205
     */
206
    protected static function putFile($path, $content): void
207
    {
208
        if (file_exists($path) && $content === file_get_contents($path)) {
209
            return;
210
        }
211
        $dirname = dirname($path);
212
        if (!file_exists($dirname) && !mkdir($dirname, 0777, true) && !is_dir($dirname)) {
213
            throw new FailedWriteException(sprintf('Directory "%s" was not created', $dirname));
214
        }
215
        if (false === file_put_contents($path, $content)) {
216
            throw new FailedWriteException("Failed write file $path");
217
        }
218
    }
219
220
    /**
221
     * Substitute output paths in given data array recursively with marker.
222
     * @param array $data
223
     * @return array
224
     */
225
    public function substituteOutputDirs(array $data): array
226
    {
227
        $dir = static::normalizePath($this->getBaseDir());
228
229
        return static::substitutePaths($data, $dir, static::BASE_DIR_MARKER);
230
    }
231
232
    /**
233
     * Normalizes given path with given directory separator.
234
     * Default forced to Unix directory separator for substitutePaths to work properly in Windows.
235
     * @param string $path path to be normalized
236
     * @param string $ds directory separator
237
     * @return string
238
     */
239
    public static function normalizePath($path, $ds = self::UNIX_DS): string
240
    {
241
        return rtrim(strtr($path, '/\\', $ds . $ds), $ds);
242
    }
243
244
    /**
245
     * Substitute all paths in given array recursively with alias if applicable.
246
     * @param array $data
247
     * @param string $dir
248
     * @param string $alias
249
     * @return array
250
     */
251
    public static function substitutePaths($data, $dir, $alias): array
252
    {
253
        foreach ($data as &$value) {
254
            if (is_string($value)) {
255
                $value = static::substitutePath($value, $dir, $alias);
256
            } elseif (is_array($value)) {
257
                $value = static::substitutePaths($value, $dir, $alias);
258
            }
259
        }
260
261
        return $data;
262
    }
263
264
    /**
265
     * Substitute path with alias if applicable.
266
     * @param string $path
267
     * @param string $dir
268
     * @param string $alias
269
     * @return string
270
     */
271
    protected static function substitutePath($path, $dir, $alias): string
272
    {
273
        $end = $dir . self::UNIX_DS;
274
        $skippable = 0 === strncmp($path, '?', 1) ? '?' : '';
275
        if ($skippable) {
276
            $path = substr($path, 1);
277
        }
278
        if ($path === $dir) {
279
            $result = $alias;
280
        } elseif (strpos($path, $end) === 0) {
281
            $result = $alias . substr($path, strlen($end) - 1);
282
        } else {
283
            $result = $path;
284
        }
285
286
        return $skippable . $result;
287
    }
288
289
    public function getBaseDir(): string
290
    {
291
        return dirname(__DIR__, 5);
292
    }
293
294
    public function getOutputDir(): string
295
    {
296
        return $this->builder->getOutputDir();
297
    }
298
299
    public function getOutputPath(string $name = null): string
300
    {
301
        return $this->builder->getOutputPath($name ?: $this->name);
302
    }
303
}
304