Passed
Push — master ( 77a8ea...8a6a58 )
by Andrii
10:48
created

Config::clone()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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