Completed
Push — master ( 6621a3...679660 )
by Andrii
01:51
created

Builder::normalizePath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 2
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-2017, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\composer\config;
12
13
use Composer\IO\IOInterface;
14
use hiqdev\composer\config\exceptions\FailedWriteException;
15
16
/**
17
 * Builder assembles config files.
18
 *
19
 * @author Andrii Vasyliev <[email protected]>
20
 */
21
class Builder
22
{
23
    /**
24
     * @var string path to output assembled configs
25
     */
26
    protected $outputDir;
27
28
    /**
29
     * @var array files to build configs
30
     * @see buildConfigs()
31
     */
32
    protected $files = [];
33
34
    /**
35
     * @var array additional data to be merged into every config (e.g. aliases)
36
     */
37
    protected $addition = [];
38
39
    /**
40
     * @var IOInterface
41
     */
42
    protected $io;
43
44
    /**
45
     * @var array collected variables
46
     */
47
    protected $vars = [];
48
49
    const UNIX_DS = '/';
50
    const OUTPUT_DIR_SUFFIX = '-output';
51
    const BASE_DIR_MARKER = '<<<base-dir>>>';
52
53
    public function __construct(array $files = [], $outputDir = null)
54
    {
55
        $this->setFiles($files);
56
        $this->setOutputDir($outputDir);
57
    }
58
59
    public function setFiles(array $files)
60
    {
61
        $this->files = $files;
62
    }
63
64
    public function setOutputDir($outputDir)
65
    {
66
        $this->outputDir = isset($outputDir) ? $outputDir : static::findOutputDir();
67
    }
68
69
    public function setAddition(array $addition)
70
    {
71
        $this->addition = $addition;
72
    }
73
74
    public function loadFiles()
75
    {
76
        $this->files    = $this->loadConfig('__files');
77
        $this->addition = $this->loadConfig('__addition');
78
    }
79
80
    public function saveFiles()
81
    {
82
        $this->writeConfig('__files',    $this->files);
83
        $this->writeConfig('__addition', $this->addition);
84
    }
85
86
    public static function rebuild($outputDir = null)
87
    {
88
        $builder = new self([], $outputDir);
89
        $builder->loadFiles();
90
        $builder->buildConfigs();
91
    }
92
93
    /**
94
     * Returns default output dir.
95
     * @param string $vendor path to vendor dir
96
     * @return string
97
     */
98
    public static function findOutputDir($vendor = null)
99
    {
100
        if ($vendor) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $vendor of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
101
            $dir = $vendor . '/hiqdev/' . basename(dirname(__DIR__));
102
        } else {
103
            $dir = dirname(__DIR__);
104
        }
105
106
        return $dir . static::OUTPUT_DIR_SUFFIX;
107
    }
108
109
    /**
110
     * Returns full path to assembled config file.
111
     * @param string $filename name of config
112
     * @param string $vendor path to vendor dir
113
     * @return string absolute path
114
     */
115
    public static function path($filename, $vendor = null)
116
    {
117
        return static::findOutputDir($vendor) . DIRECTORY_SEPARATOR . $filename . '.php';
118
    }
119
120
    /**
121
     * Builds configs by given files list.
122
     * @param null|array $files files to process: config name => list of files
123
     */
124
    public function buildConfigs($files = null)
125
    {
126
        if (is_null($files)) {
127
            $files = $this->files;
128
        }
129
        $resolver = new Resolver($files);
130
        $files = $resolver->get();
131
        foreach ($files as $name => $paths) {
132
            $olddefs = get_defined_constants();
133
            $configs = $this->loadConfigs($paths);
134
            $newdefs = get_defined_constants();
135
            $defines = array_diff_assoc($newdefs, $olddefs);
136
            $this->buildConfig($name, $configs, $defines);
137
        }
138
        static::putFile($this->getOutputPath('__rebuild'), file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . '__rebuild.php'));
139
    }
140
141
    protected function loadConfigs(array $paths)
142
    {
143
        $configs = [];
144
        foreach ($paths as $path) {
145
            $config = $this->loadFile($path);
146
            if (!empty($config)) {
147
                $configs[] = $config;
148
            }
149
        }
150
151
        return $configs;
152
    }
153
154
    /**
155
     * Merges given configs and writes at given name.
156
     * @param mixed $name
157
     * @param array $configs
158
     */
159
    public function buildConfig($name, array $configs, $defines = [])
160
    {
161
        if (!$this->isSpecialConfig($name)) {
162
            array_push($configs, $this->addition, [
163
                'params' => $this->vars['params'],
164
            ]);
165
        }
166
        $this->vars[$name] = call_user_func_array([Helper::className(), 'mergeConfig'], $configs);
167
        if ($name === 'params') {
168
            $this->vars[$name] = $this->pushEnvVars($this->vars[$name]);
169
        }
170
        $this->writeConfig($name, (array) $this->vars[$name], $defines);
171
    }
172
173
    protected function pushEnvVars($vars)
174
    {
175
        $env = $this->vars['dotenv'];
176
        if (!empty($vars)) {
177
            foreach (array_keys($vars) as $key) {
178
                $envKey = strtoupper(strtr($key, '.', '_'));
179
                if (isset($env[$envKey])) {
180
                    $vars[$key] = $env[$envKey];
181
                }
182
            }
183
        }
184
185
        return $vars;
186
    }
187
188
    protected function isSpecialConfig($name)
189
    {
190
        return in_array($name, ['dotenv', 'defines', 'params'], true);
191
    }
192
193
    /**
194
     * Writes config file by name.
195
     * @param string $name
196
     * @param array $data
197
     */
198
    public function writeConfig($name, array $data, array $defines = [])
199
    {
200
        $data = $this->substituteOutputDirs($data);
201
        $defines = $this->substituteOutputDirs($defines);
202
        if ('defines' === $name) {
203
            $data = $defines;
204
        }
205
        static::writeFile($this->getOutputPath($name), $data, $defines);
206
    }
207
208
    public function getOutputPath($name)
209
    {
210
        return $this->outputDir . DIRECTORY_SEPARATOR . $name . '.php';
211
    }
212
213
    /**
214
     * Writes config file by full path.
215
     * @param string $path
216
     * @param array $data
217
     */
218
    public static function writeFile($path, array $data, array $defines = [])
219
    {
220
        if (!file_exists(dirname($path))) {
221
            mkdir(dirname($path), 0777, true);
222
        }
223
        $content = Helper::exportDefines($defines) . "\nreturn " . Helper::exportVar($data);
224
        $content = str_replace("'" . static::BASE_DIR_MARKER, "\$baseDir . '", $content);
225
        $content = str_replace("'?" . static::BASE_DIR_MARKER, "'?' . \$baseDir . '", $content);
226
        static::putFile($path, "<?php\n\n\$baseDir = dirname(dirname(dirname(__DIR__)));\n\n$content;\n");
227
    }
228
229
    /**
230
     * Writes file if content changed.
231
     * @param string $path
232
     * @param string $content
233
     */
234
    protected static function putFile($path, $content)
235
    {
236
        if (file_exists($path) && $content === file_get_contents($path)) {
237
            return;
238
        }
239
        if (false === file_put_contents($path, $content)) {
240
            throw new FailedWriteException("Failed write file $path");
241
        }
242
    }
243
244
    /**
245
     * Substitute output paths in given data array recursively with marker.
246
     * @param array $data
247
     * @return array
248
     */
249
    public function substituteOutputDirs($data)
250
    {
251
        $dir = static::normalizePath(dirname(dirname(dirname($this->outputDir))));
252
253
        return static::substitutePaths($data, $dir, static::BASE_DIR_MARKER);
254
    }
255
256
    /**
257
     * Normalizes given path with given directory separator.
258
     * Default forced to Unix directory separator for substitutePaths to work properly in Windows.
259
     * @param string $path path to be normalized
260
     * @param string $ds directory separator.
261
     * @return string
262
     */
263
    public static function normalizePath($path, $ds = self::UNIX_DS)
264
    {
265
        return rtrim(strtr($path, '/\\', $ds . $ds), $ds);
266
    }
267
268
    /**
269
     * Substitute all paths in given array recursively with alias if applicable.
270
     * @param array $data
271
     * @param string $dir
272
     * @param string $alias
273
     * @return array
274
     */
275
    public static function substitutePaths($data, $dir, $alias)
276
    {
277
        foreach ($data as &$value) {
278
            if (is_string($value)) {
279
                $value = static::substitutePath($value, $dir, $alias);
280
            } elseif (is_array($value)) {
281
                $value = static::substitutePaths($value, $dir, $alias);
282
            }
283
        }
284
285
        return $data;
286
    }
287
288
    /**
289
     * Substitute path with alias if applicable.
290
     * @param string $path
291
     * @param string $dir
292
     * @param string $alias
293
     * @return string
294
     */
295
    protected static function substitutePath($path, $dir, $alias)
296
    {
297
        $dir .= self::UNIX_DS;
298
        $skippable = 0 === strncmp($path, '?', 1) ? '?' : '';
299
        if ($skippable) {
300
            $path = substr($path, 1);
301
        }
302
        $result = (substr($path, 0, strlen($dir)) === $dir) ? $alias . substr($path, strlen($dir) - 1) : $path;
303
304
        return $skippable . $result;
305
    }
306
307
    public function loadConfig($name)
308
    {
309
        return $this->loadFile($this->getOutputPath($name));
310
    }
311
312
    /**
313
     * Reads config file.
314
     * @param string $path
315
     * @return array configuration read from file
316
     */
317
    public function loadFile($path)
318
    {
319
        $reader = ReaderFactory::get($path);
320
321
        return $reader->read($path, $this);
322
    }
323
324
    public function setIo(IOInterface $io)
325
    {
326
        $this->io = $io;
327
    }
328
329
    protected function writeError($text)
330
    {
331
        if (isset($this->io)) {
332
            $this->io->writeError("<error>$text</error>");
333
        } else {
334
            echo $text . "\n";
335
        }
336
    }
337
338
    public function getVars()
339
    {
340
        return $this->vars;
341
    }
342
}
343