Completed
Push — master ( ef8a42...b5fe4d )
by Andrii
15:02
created

Config::withParams()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 0
cp 0
rs 10
cc 1
nc 1
nop 0
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);
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): void
145
    {
146
        $depth = $this->findDepth();
147
        $baseDir = $depth>0 ? "dirname(__DIR__, $depth)" : '__DIR__';
148
        static::putFile($path, $this->replaceMarkers(implode("\n\n", array_filter([
149
            'header'  => '<?php',
150
            'baseDir' => "\$baseDir = $baseDir;",
151
            'BASEDIR' => "defined('COMPOSER_CONFIG_PLUGIN_BASEDIR') or define('COMPOSER_CONFIG_PLUGIN_BASEDIR', \$baseDir);",
152
            'dotenv'  => $this->withEnv() ? "\$_ENV = array_merge((array) require __DIR__ . '/dotenv.php', (array) \$_ENV);" : '',
153
            'defines' => $this->withDefines() ? $this->builder->getConfig('defines')->buildRequires() : '',
154
            'params'  => $this->withParams() ? "\$params = require __DIR__ . '/params.php';" : '',
155
            'content' => is_array($data) ? $this->renderVars($data) : $data,
156
        ]))) . "\n");
157
    }
158
159
    private function withEnv(): bool
160
    {
161
        return !in_array(static::class, [System::class, DotEnv::class], true);
162
    }
163
164
    private function withDefines(): bool
165
    {
166
        return !in_array(static::class, [System::class, DotEnv::class, Defines::class], true);
167
    }
168
169
    private function withParams(): bool
170
    {
171
        return !in_array(static::class, [System::class, DotEnv::class, Defines::class, Params::class], true);
172
    }
173
174
    private function findDepth()
175
    {
176
        $outDir = dirname($this->getOutputPath());
177
        $diff = substr($outDir, strlen($this->getBaseDir()));
178
179
        return substr_count($diff, DIRECTORY_SEPARATOR);
180
    }
181
182
    /**
183
     * @param array $vars array to be exported
184
     * @return string
185
     * @throws \ReflectionException
186
     */
187
    protected function renderVars(array $vars): string
188
    {
189
        return 'return ' . Helper::exportVar($vars) . ';';
190
    }
191
192
    protected function replaceMarkers(string $content): string
193
    {
194
        $content = str_replace("'" . static::BASE_DIR_MARKER, "\$baseDir . '", $content);
195
196
        return str_replace("'?" . static::BASE_DIR_MARKER, "'?' . \$baseDir . '", $content);
197
    }
198
199
    /**
200
     * Writes file if content changed.
201
     * @param string $path
202
     * @param string $content
203
     * @throws FailedWriteException
204
     */
205
    protected static function putFile($path, $content): void
206
    {
207
        if (file_exists($path) && $content === file_get_contents($path)) {
208
            return;
209
        }
210
        if (!file_exists(dirname($path))) {
211
            mkdir(dirname($path), 0777, true);
212
        }
213
        if (false === file_put_contents($path, $content)) {
214
            throw new FailedWriteException("Failed write file $path");
215
        }
216
    }
217
218
    /**
219
     * Substitute output paths in given data array recursively with marker.
220
     * @param array $data
221
     * @return array
222
     */
223
    public function substituteOutputDirs(array $data): array
224
    {
225
        $dir = static::normalizePath($this->getBaseDir());
226
227
        return static::substitutePaths($data, $dir, static::BASE_DIR_MARKER);
228
    }
229
230
    /**
231
     * Normalizes given path with given directory separator.
232
     * Default forced to Unix directory separator for substitutePaths to work properly in Windows.
233
     * @param string $path path to be normalized
234
     * @param string $ds directory separator
235
     * @return string
236
     */
237
    public static function normalizePath($path, $ds = self::UNIX_DS): string
238
    {
239
        return rtrim(strtr($path, '/\\', $ds . $ds), $ds);
240
    }
241
242
    /**
243
     * Substitute all paths in given array recursively with alias if applicable.
244
     * @param array $data
245
     * @param string $dir
246
     * @param string $alias
247
     * @return array
248
     */
249
    public static function substitutePaths($data, $dir, $alias): array
250
    {
251
        foreach ($data as &$value) {
252
            if (is_string($value)) {
253
                $value = static::substitutePath($value, $dir, $alias);
254
            } elseif (is_array($value)) {
255
                $value = static::substitutePaths($value, $dir, $alias);
256
            }
257
        }
258
259
        return $data;
260
    }
261
262
    /**
263
     * Substitute path with alias if applicable.
264
     * @param string $path
265
     * @param string $dir
266
     * @param string $alias
267
     * @return string
268
     */
269
    protected static function substitutePath($path, $dir, $alias): string
270
    {
271
        $end = $dir . self::UNIX_DS;
272
        $skippable = 0 === strncmp($path, '?', 1) ? '?' : '';
273
        if ($skippable) {
274
            $path = substr($path, 1);
275
        }
276
        $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...
277
        if ($path === $dir) {
278
            $result = $alias;
279
        } elseif (substr($path, 0, strlen($end)) === $end) {
280
            $result = $alias . substr($path, strlen($end) - 1);
281
        } else {
282
            $result = $path;
283
        }
284
285
        return $skippable . $result;
286
    }
287
288
    public function getBaseDir(): string
289
    {
290
        return dirname(__DIR__, 5);
291
    }
292
293
    public function getOutputDir(): string
294
    {
295
        return $this->builder->getOutputDir();
296
    }
297
298
    public function getOutputPath(string $name = null): string
299
    {
300
        return $this->builder->getOutputPath($name ?: $this->name);
301
    }
302
}
303