Completed
Push — master ( 864f8b...c13991 )
by Andrii
06:22
created

Builder::substituteOutputDirs()   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 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
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, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\composer\config;
12
13
use Composer\IO\IOInterface;
14
15
/**
16
 * Builder assembles config files.
17
 *
18
 * @author Andrii Vasyliev <[email protected]>
19
 */
20
class Builder
21
{
22
    /**
23
     * @var string path to output assembled configs
24
     */
25
    protected $outputDir;
26
27
    /**
28
     * @var array files to build configs
29
     * @see buildConfigs()
30
     */
31
    protected $files = [];
32
33
    /**
34
     * @var array additional data to be merged into every config (e.g. aliases)
35
     */
36
    protected $addition = [];
37
38
    /**
39
     * @var IOInterface
40
     */
41
    protected $io;
42
43
    /**
44
     * @var array collected variables
45
     */
46
    protected $vars = [];
47
48
    const OUTPUT_DIR_SUFFIX = '-output';
49
    const BASE_DIR_MARKER = '<<<base-dir>>>';
50
51
    public function __construct(array $files = [], $outputDir = null)
52
    {
53
        $this->setFiles($files);
54
        $this->setOutputDir($outputDir);
55
    }
56
57
    public function setFiles(array $files)
58
    {
59
        $this->files = $files;
60
    }
61
62
    public function setOutputDir($outputDir)
63
    {
64
        $this->outputDir = isset($outputDir) ? $outputDir : static::defaultOutputDir();
65
    }
66
67
    public function setAddition(array $addition)
68
    {
69
        $this->addition = $addition;
70
    }
71
72
    public function loadFiles()
73
    {
74
        $this->files    = $this->readConfig('__files');
75
        $this->addition = $this->readConfig('__addition');
76
    }
77
78
    public function saveFiles()
79
    {
80
        $this->writeConfig('__files',    $this->files);
81
        $this->writeConfig('__addition', $this->addition);
82
    }
83
84
    public static function rebuild($outputDir = null)
85
    {
86
        $builder = new self([], $outputDir);
87
        $builder->loadFiles();
88
        $builder->buildConfigs();
89
    }
90
91
    /**
92
     * Returns default output dir.
93
     * @return string
94
     */
95
    public static function defaultOutputDir()
96
    {
97
        return dirname(__DIR__) . static::OUTPUT_DIR_SUFFIX;
98
    }
99
100
    /**
101
     * Returns full path to assembled config file.
102
     * @param string $filename name of config
103
     * @return string absolute path
104
     */
105
    public static function path($filename)
106
    {
107
        return static::defaultOutputDir() . DIRECTORY_SEPARATOR . $filename . '.php';
108
    }
109
110
    /**
111
     * Builds configs by given files list.
112
     * @param null|array $files files to process: config name => list of files
113
     */
114
    public function buildConfigs($files = null)
115
    {
116
        if (is_null($files)) {
117
            $files = $this->files;
118
        }
119
        foreach ($files as $name => $pathes) {
120
            $olddefs = get_defined_constants();
121
            $configs = $this->readConfigs($pathes);
122
            $newdefs = get_defined_constants();
123
            $defines = array_diff_assoc($newdefs, $olddefs);
124
            $this->buildConfig($name, $configs, $defines);
125
        }
126
        static::putFile($this->getOutputPath('__rebuild'), file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . '__rebuild.php'));
127
    }
128
129
    protected function readConfigs(array $pathes)
130
    {
131
        $configs = [];
132
        foreach ($pathes as $path) {
133
            $config = $this->readFile($path);
134
            if (!empty($config)) {
135
                $configs[] = $config;
136
            }
137
        }
138
139
        return $configs;
140
    }
141
142
    /**
143
     * Merges given configs and writes at given name.
144
     * @param mixed $name
145
     * @param array $configs
146
     */
147
    public function buildConfig($name, array $configs, $defines = [])
148
    {
149
        if (!$this->isSpecialConfig($name)) {
150
            array_push($configs, $this->addition, [
151
                'params' => $this->vars['params'],
152
            ]);
153
        }
154
        $this->vars[$name] = call_user_func_array([Helper::className(), 'mergeConfig'], $configs);
155
        $this->writeConfig($name, (array) $this->vars[$name], $defines);
156
    }
157
158
    protected function isSpecialConfig($name)
159
    {
160
        return in_array($name, ['defines', 'params'], true);
161
    }
162
163
    /**
164
     * Writes config file by name.
165
     * @param string $name
166
     * @param array $data
167
     */
168
    public function writeConfig($name, array $data, array $defines = [])
169
    {
170
        $data = $this->substituteOutputDirs($data);
171
        $defines = $this->substituteOutputDirs($defines);
172
        static::writeFile($this->getOutputPath($name), $data, $defines);
173
    }
174
175
    public function getOutputPath($name)
176
    {
177
        return $this->outputDir . DIRECTORY_SEPARATOR . $name . '.php';
178
    }
179
180
    /**
181
     * Writes config file by full path.
182
     * @param string $path
183
     * @param array $data
184
     */
185
    public static function writeFile($path, array $data, array $defines = [])
186
    {
187
        if (!file_exists(dirname($path))) {
188
            mkdir(dirname($path), 0777, true);
189
        }
190
        $content = Helper::exportDefines($defines) . "\nreturn " . Helper::exportVar($data);
191
        $content = str_replace("'" . static::BASE_DIR_MARKER, "\$baseDir . '", $content);
192
        $content = str_replace("'?" . static::BASE_DIR_MARKER, "'?' . \$baseDir . '", $content);
193
        static::putFile($path, "<?php\n\n\$baseDir = dirname(dirname(dirname(__DIR__)));\n\n$content;\n");
194
    }
195
196
    /**
197
     * Writes file if content changed.
198
     * @param string $path
199
     * @param string $content
200
     */
201
    protected static function putFile($path, $content)
202
    {
203
        if (file_exists($path) && $content === file_get_contents($path)) {
204
            return;
205
        }
206
        if (file_put_contents($path, $content) === FALSE) {
207
            throw new FailedWriteException("Failed write file $path");
208
        }
209
    }
210
211
    /**
212
     * Substitute output pathes in given data array recursively with marker.
213
     * @param array $data
214
     * @return array
215
     */
216
    public function substituteOutputDirs($data)
217
    {
218
        return static::substitutePaths($data, dirname(dirname(dirname($this->outputDir))), static::BASE_DIR_MARKER);
219
    }
220
221
    /**
222
     * Substitute all pathes in given array recursively with alias if applicable.
223
     * @param array $data
224
     * @param string $dir
225
     * @param string $alias
226
     * @return array
227
     */
228
    public static function substitutePaths($data, $dir, $alias)
229
    {
230
        foreach ($data as &$value) {
231
            if (is_string($value)) {
232
                $value = static::substitutePath($value, $dir, $alias);
233
            } elseif (is_array($value)) {
234
                $value = static::substitutePaths($value, $dir, $alias);
235
            }
236
        }
237
238
        return $data;
239
    }
240
241
    /**
242
     * Substitute path with alias if applicable.
243
     * @param string $path
244
     * @param string $dir
245
     * @param string $alias
246
     * @return string
247
     */
248
    protected static function substitutePath($path, $dir, $alias)
249
    {
250
        $skippable = strncmp($path, '?', 1) === 0 ? '?' : '';
251
        if ($skippable) {
252
            $path = substr($path, 1);
253
        }
254
        $result = (substr($path, 0, strlen($dir) + 1) === $dir . DIRECTORY_SEPARATOR) ? $alias . substr($path, strlen($dir)) : $path;
255
256
        return $skippable . $result;
257
    }
258
259
    public function readConfig($name)
260
    {
261
        return $this->readFile($this->getOutputPath($name));
262
    }
263
264
    /**
265
     * Reads config file.
266
     * @param string $__path
267
     * @return array configuration read from file
268
     */
269
    public function readFile($__path)
270
    {
271
        $__skippable = strncmp($__path, '?', 1) === 0 ? '?' : '';
272
        if ($__skippable) {
273
            $__path = substr($__path, 1);
274
        }
275
276
        if (file_exists($__path)) {
277
            /// Expose variables to be used in configs
278
            extract($this->vars);
279
            $__res = require $__path;
280
281
            return is_array($__res) ? $__res : [];
282
        }
283
284
        if (empty($__skippable)) {
285
            $this->writeError("Failed read file $__path");
286
        }
287
288
        return [];
289
    }
290
291
    public function setIo(IOInterface $io)
292
    {
293
        $this->io = $io;
294
    }
295
296
    protected function writeError($text)
297
    {
298
        if (isset($this->io)) {
299
            $this->io->writeError("<error>$text</error>");
300
        } else {
301
            echo $text . "\n";
302
        }
303
    }
304
}
305