Completed
Push — master ( 35823f...05fff1 )
by Andrii
11:55
created

Builder::pushEnvVars()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 0
cts 6
cp 0
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 3
nop 1
crap 12
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
        foreach (array_keys($vars) as $key) {
176
            $envKey = strtoupper(strtr($key, '.', '_'));
177
            if (isset($env[$envKey])) {
178
                $vars[$key] = $env[$envKey];
179
            }
180
        }
181
182
        return $vars;
183
    }
184
185
    protected function isSpecialConfig($name)
186
    {
187
        return in_array($name, ['dotenv', 'defines', 'params'], true);
188
    }
189
190
    /**
191
     * Writes config file by name.
192
     * @param string $name
193
     * @param array $data
194
     */
195
    public function writeConfig($name, array $data, array $defines = [])
196
    {
197
        $data = $this->substituteOutputDirs($data);
198
        $defines = $this->substituteOutputDirs($defines);
199
        if ('defines' === $name) {
200
            $data = $defines;
201
        }
202
        static::writeFile($this->getOutputPath($name), $data, $defines);
203
    }
204
205
    public function getOutputPath($name)
206
    {
207
        return $this->outputDir . DIRECTORY_SEPARATOR . $name . '.php';
208
    }
209
210
    /**
211
     * Writes config file by full path.
212
     * @param string $path
213
     * @param array $data
214
     */
215
    public static function writeFile($path, array $data, array $defines = [])
216
    {
217
        if (!file_exists(dirname($path))) {
218
            mkdir(dirname($path), 0777, true);
219
        }
220
        $content = Helper::exportDefines($defines) . "\nreturn " . Helper::exportVar($data);
221
        $content = str_replace("'" . static::BASE_DIR_MARKER, "\$baseDir . '", $content);
222
        $content = str_replace("'?" . static::BASE_DIR_MARKER, "'?' . \$baseDir . '", $content);
223
        static::putFile($path, "<?php\n\n\$baseDir = dirname(dirname(dirname(__DIR__)));\n\n$content;\n");
224
    }
225
226
    /**
227
     * Writes file if content changed.
228
     * @param string $path
229
     * @param string $content
230
     */
231
    protected static function putFile($path, $content)
232
    {
233
        if (file_exists($path) && $content === file_get_contents($path)) {
234
            return;
235
        }
236
        if (false === file_put_contents($path, $content)) {
237
            throw new FailedWriteException("Failed write file $path");
238
        }
239
    }
240
241
    /**
242
     * Substitute output paths in given data array recursively with marker.
243
     * @param array $data
244
     * @return array
245
     */
246
    public function substituteOutputDirs($data)
247
    {
248
        return static::substitutePaths($data, dirname(dirname(dirname($this->outputDir))), static::BASE_DIR_MARKER);
249
    }
250
251
    /**
252
     * Substitute all paths in given array recursively with alias if applicable.
253
     * @param array $data
254
     * @param string $dir
255
     * @param string $alias
256
     * @return array
257
     */
258
    public static function substitutePaths($data, $dir, $alias)
259
    {
260
        foreach ($data as &$value) {
261
            if (is_string($value)) {
262
                $value = static::substitutePath($value, $dir, $alias);
263
            } elseif (is_array($value)) {
264
                $value = static::substitutePaths($value, $dir, $alias);
265
            }
266
        }
267
268
        return $data;
269
    }
270
271
    /**
272
     * Substitute path with alias if applicable.
273
     * @param string $path
274
     * @param string $dir
275
     * @param string $alias
276
     * @return string
277
     */
278
    protected static function substitutePath($path, $dir, $alias)
279
    {
280
        $skippable = 0 === strncmp($path, '?', 1) ? '?' : '';
281
        if ($skippable) {
282
            $path = substr($path, 1);
283
        }
284
        $result = (substr($path, 0, strlen($dir) + 1) === $dir . DIRECTORY_SEPARATOR) ? $alias . substr($path, strlen($dir)) : $path;
285
286
        return $skippable . $result;
287
    }
288
289
    public function loadConfig($name)
290
    {
291
        return $this->loadFile($this->getOutputPath($name));
292
    }
293
294
    /**
295
     * Reads config file.
296
     * @param string $path
297
     * @return array configuration read from file
298
     */
299
    public function loadFile($path)
300
    {
301
        $reader = ReaderFactory::get($path);
302
303
        return $reader->read($path, $this);
304
    }
305
306
    public function setIo(IOInterface $io)
307
    {
308
        $this->io = $io;
309
    }
310
311
    protected function writeError($text)
312
    {
313
        if (isset($this->io)) {
314
            $this->io->writeError("<error>$text</error>");
315
        } else {
316
            echo $text . "\n";
317
        }
318
    }
319
320
    public function getVars()
321
    {
322
        return $this->vars;
323
    }
324
}
325