Completed
Push — master ( 167ae3...6621a3 )
by Andrii
13s
created

Builder::normalizeDir()   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 OUTPUT_DIR_SUFFIX = '-output';
50
    const BASE_DIR_MARKER = '<<<base-dir>>>';
51
52
    public function __construct(array $files = [], $outputDir = null)
53
    {
54
        $this->setFiles($files);
55
        $this->setOutputDir($outputDir);
56
    }
57
58
    public function setFiles(array $files)
59
    {
60
        $this->files = $files;
61
    }
62
63
    public function setOutputDir($outputDir)
64
    {
65
        $this->outputDir = isset($outputDir) ? $outputDir : static::findOutputDir();
66
    }
67
68
    public function setAddition(array $addition)
69
    {
70
        $this->addition = $addition;
71
    }
72
73
    public function loadFiles()
74
    {
75
        $this->files    = $this->loadConfig('__files');
76
        $this->addition = $this->loadConfig('__addition');
77
    }
78
79
    public function saveFiles()
80
    {
81
        $this->writeConfig('__files',    $this->files);
82
        $this->writeConfig('__addition', $this->addition);
83
    }
84
85
    public static function rebuild($outputDir = null)
86
    {
87
        $builder = new self([], $outputDir);
88
        $builder->loadFiles();
89
        $builder->buildConfigs();
90
    }
91
92
    /**
93
     * Returns default output dir.
94
     * @param string $vendor path to vendor dir
95
     * @return string
96
     */
97
    public static function findOutputDir($vendor = null)
98
    {
99
        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...
100
            $dir = $vendor . '/hiqdev/' . basename(dirname(__DIR__));
101
        } else {
102
            $dir = dirname(__DIR__);
103
        }
104
105
        return $dir . static::OUTPUT_DIR_SUFFIX;
106
    }
107
108
    /**
109
     * Returns full path to assembled config file.
110
     * @param string $filename name of config
111
     * @param string $vendor path to vendor dir
112
     * @return string absolute path
113
     */
114
    public static function path($filename, $vendor = null)
115
    {
116
        return static::findOutputDir($vendor) . DIRECTORY_SEPARATOR . $filename . '.php';
117
    }
118
119
    /**
120
     * Builds configs by given files list.
121
     * @param null|array $files files to process: config name => list of files
122
     */
123
    public function buildConfigs($files = null)
124
    {
125
        if (is_null($files)) {
126
            $files = $this->files;
127
        }
128
        $resolver = new Resolver($files);
129
        $files = $resolver->get();
130
        foreach ($files as $name => $paths) {
131
            $olddefs = get_defined_constants();
132
            $configs = $this->loadConfigs($paths);
133
            $newdefs = get_defined_constants();
134
            $defines = array_diff_assoc($newdefs, $olddefs);
135
            $this->buildConfig($name, $configs, $defines);
136
        }
137
        static::putFile($this->getOutputPath('__rebuild'), file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . '__rebuild.php'));
138
    }
139
140
    protected function loadConfigs(array $paths)
141
    {
142
        $configs = [];
143
        foreach ($paths as $path) {
144
            $config = $this->loadFile($path);
145
            if (!empty($config)) {
146
                $configs[] = $config;
147
            }
148
        }
149
150
        return $configs;
151
    }
152
153
    /**
154
     * Merges given configs and writes at given name.
155
     * @param mixed $name
156
     * @param array $configs
157
     */
158
    public function buildConfig($name, array $configs, $defines = [])
159
    {
160
        if (!$this->isSpecialConfig($name)) {
161
            array_push($configs, $this->addition, [
162
                'params' => $this->vars['params'],
163
            ]);
164
        }
165
        $this->vars[$name] = call_user_func_array([Helper::className(), 'mergeConfig'], $configs);
166
        if ($name === 'params') {
167
            $this->vars[$name] = $this->pushEnvVars($this->vars[$name]);
168
        }
169
        $this->writeConfig($name, (array) $this->vars[$name], $defines);
170
    }
171
172
    protected function pushEnvVars($vars)
173
    {
174
        $env = $this->vars['dotenv'];
175
        if (!empty($vars)) {
176
            foreach (array_keys($vars) as $key) {
177
                $envKey = strtoupper(strtr($key, '.', '_'));
178
                if (isset($env[$envKey])) {
179
                    $vars[$key] = $env[$envKey];
180
                }
181
            }
182
        }
183
184
        return $vars;
185
    }
186
187
    protected function isSpecialConfig($name)
188
    {
189
        return in_array($name, ['dotenv', 'defines', 'params'], true);
190
    }
191
192
    /**
193
     * Writes config file by name.
194
     * @param string $name
195
     * @param array $data
196
     */
197
    public function writeConfig($name, array $data, array $defines = [])
198
    {
199
        $data = $this->substituteOutputDirs($data);
200
        $defines = $this->substituteOutputDirs($defines);
201
        if ('defines' === $name) {
202
            $data = $defines;
203
        }
204
        static::writeFile($this->getOutputPath($name), $data, $defines);
205
    }
206
207
    public function getOutputPath($name)
208
    {
209
        return $this->outputDir . DIRECTORY_SEPARATOR . $name . '.php';
210
    }
211
212
    /**
213
     * Writes config file by full path.
214
     * @param string $path
215
     * @param array $data
216
     */
217
    public static function writeFile($path, array $data, array $defines = [])
218
    {
219
        if (!file_exists(dirname($path))) {
220
            mkdir(dirname($path), 0777, true);
221
        }
222
        $content = Helper::exportDefines($defines) . "\nreturn " . Helper::exportVar($data);
223
        $content = str_replace("'" . static::BASE_DIR_MARKER, "\$baseDir . '", $content);
224
        $content = str_replace("'?" . static::BASE_DIR_MARKER, "'?' . \$baseDir . '", $content);
225
        static::putFile($path, "<?php\n\n\$baseDir = dirname(dirname(dirname(__DIR__)));\n\n$content;\n");
226
    }
227
228
    /**
229
     * Writes file if content changed.
230
     * @param string $path
231
     * @param string $content
232
     */
233
    protected static function putFile($path, $content)
234
    {
235
        if (file_exists($path) && $content === file_get_contents($path)) {
236
            return;
237
        }
238
        if (false === file_put_contents($path, $content)) {
239
            throw new FailedWriteException("Failed write file $path");
240
        }
241
    }
242
243
    /**
244
     * Substitute output paths in given data array recursively with marker.
245
     * @param array $data
246
     * @return array
247
     */
248
    public function substituteOutputDirs($data)
249
    {
250
        $dir = static::normalizeDir(dirname(dirname(dirname($this->outputDir))));
251
252
        return static::substitutePaths($data, $dir, static::BASE_DIR_MARKER);
253
    }
254
255
    public static function normalizeDir($path, $ds = '/')
256
    {
257
        return rtrim(strtr($path, '\/', $ds), $ds);
258
    }
259
260
    /**
261
     * Substitute all paths in given array recursively with alias if applicable.
262
     * @param array $data
263
     * @param string $dir
264
     * @param string $alias
265
     * @return array
266
     */
267
    public static function substitutePaths($data, $dir, $alias)
268
    {
269
        foreach ($data as &$value) {
270
            if (is_string($value)) {
271
                $value = static::substitutePath($value, $dir, $alias);
272
            } elseif (is_array($value)) {
273
                $value = static::substitutePaths($value, $dir, $alias);
274
            }
275
        }
276
277
        return $data;
278
    }
279
280
    /**
281
     * Substitute path with alias if applicable.
282
     * @param string $path
283
     * @param string $dir
284
     * @param string $alias
285
     * @return string
286
     */
287
    protected static function substitutePath($path, $dir, $alias)
288
    {
289
        $skippable = 0 === strncmp($path, '?', 1) ? '?' : '';
290
        if ($skippable) {
291
            $path = substr($path, 1);
292
        }
293
        $result = (substr($path, 0, strlen($dir) + 1) === $dir . '/') ? $alias . substr($path, strlen($dir)) : $path;
294
295
        return $skippable . $result;
296
    }
297
298
    public function loadConfig($name)
299
    {
300
        return $this->loadFile($this->getOutputPath($name));
301
    }
302
303
    /**
304
     * Reads config file.
305
     * @param string $path
306
     * @return array configuration read from file
307
     */
308
    public function loadFile($path)
309
    {
310
        $reader = ReaderFactory::get($path);
311
312
        return $reader->read($path, $this);
313
    }
314
315
    public function setIo(IOInterface $io)
316
    {
317
        $this->io = $io;
318
    }
319
320
    protected function writeError($text)
321
    {
322
        if (isset($this->io)) {
323
            $this->io->writeError("<error>$text</error>");
324
        } else {
325
            echo $text . "\n";
326
        }
327
    }
328
329
    public function getVars()
330
    {
331
        return $this->vars;
332
    }
333
}
334