Completed
Push — master ( 2d6898...864f8b )
by Andrii
05:07
created

Builder::substitutePaths()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 0
cts 11
cp 0
rs 9.2
c 0
b 0
f 0
cc 4
eloc 7
nc 4
nop 3
crap 20
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
            $configs = [];
121
            foreach ($pathes as $path) {
122
                $configs[] = $this->readFile($path);
123
            }
124
            $this->buildConfig($name, $configs);
125
        }
126
        static::putFile($this->getOutputPath('__rebuild'), file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . '__rebuild.php'));
127
    }
128
129
    /**
130
     * Merges given configs and writes at given name.
131
     * @param mixed $name
132
     * @param array $configs
133
     */
134
    public function buildConfig($name, array $configs)
135
    {
136
        if (!$this->isSpecialConfig($name)) {
137
            array_push($configs, $this->addition, [
138
                'params' => $this->vars['params'],
139
            ]);
140
        }
141
        $this->vars[$name] = call_user_func_array([Helper::className(), 'mergeConfig'], $configs);
142
        $this->writeConfig($name, (array) $this->vars[$name]);
143
    }
144
145
    protected function isSpecialConfig($name)
146
    {
147
        return in_array($name, ['defines', 'params'], true);
148
    }
149
150
    /**
151
     * Writes config file by name.
152
     * @param string $name
153
     * @param array $data
154
     */
155
    public function writeConfig($name, array $data)
156
    {
157
        $data = $this->substitutePaths($data, dirname(dirname(dirname($this->outputDir))), static::BASE_DIR_MARKER);
158
        static::writeFile($this->getOutputPath($name), $data);
159
    }
160
161
    public function getOutputPath($name)
162
    {
163
        return $this->outputDir . DIRECTORY_SEPARATOR . $name . '.php';
164
    }
165
166
    /**
167
     * Writes config file by full path.
168
     * @param string $path
169
     * @param array $data
170
     */
171
    public static function writeFile($path, array $data)
172
    {
173
        if (!file_exists(dirname($path))) {
174
            mkdir(dirname($path), 0777, true);
175
        }
176
        $content = Helper::exportVar($data);
177
        $content = str_replace("'" . static::BASE_DIR_MARKER, "\$baseDir . '", $content);
178
        $content = str_replace("'?" . static::BASE_DIR_MARKER, "'?' . \$baseDir . '", $content);
179
        static::putFile($path, "<?php\n\n\$baseDir = dirname(dirname(dirname(__DIR__)));\n\nreturn $content;\n");
180
    }
181
182
    /**
183
     * Writes file if content changed.
184
     * @param string $path
185
     * @param string $content
186
     */
187
    protected static function putFile($path, $content)
188
    {
189
        if (file_exists($path) && $content === file_get_contents($path)) {
190
            return;
191
        }
192
        if (file_put_contents($path, $content) === FALSE) {
193
            throw new FailedWriteException("Failed write file $path");
194
        }
195
    }
196
197
    /**
198
     * Substitute all pathes in given array recursively with alias if applicable.
199
     * @param array $data
200
     * @param string $dir
201
     * @param string $alias
202
     * @return string
203
     */
204
    public static function substitutePaths($data, $dir, $alias)
205
    {
206
        foreach ($data as &$value) {
207
            if (is_string($value)) {
208
                $value = static::substitutePath($value, $dir, $alias);
209
            } elseif (is_array($value)) {
210
                $value = static::substitutePaths($value, $dir, $alias);
211
            }
212
        }
213
214
        return $data;
215
    }
216
217
    /**
218
     * Substitute path with alias if applicable.
219
     * @param string $path
220
     * @param string $dir
221
     * @param string $alias
222
     * @return string
223
     */
224
    protected static function substitutePath($path, $dir, $alias)
225
    {
226
        $skippable = strncmp($path, '?', 1) === 0 ? '?' : '';
227
        if ($skippable) {
228
            $path = substr($path, 1);
229
        }
230
        $result = (substr($path, 0, strlen($dir) + 1) === $dir . DIRECTORY_SEPARATOR) ? $alias . substr($path, strlen($dir)) : $path;
231
232
        return $skippable . $result;
233
    }
234
235
    public function readConfig($name)
236
    {
237
        return $this->readFile($this->getOutputPath($name));
238
    }
239
240
    /**
241
     * Reads config file.
242
     * @param string $__path
243
     * @return array configuration read from file
244
     */
245
    public function readFile($__path)
246
    {
247
        $__skippable = strncmp($__path, '?', 1) === 0 ? '?' : '';
248
        if ($__skippable) {
249
            $__path = substr($__path, 1);
250
        }
251
252
        if (file_exists($__path)) {
253
            /// Expose variables to be used in configs
254
            extract($this->vars);
255
256
            return (array) require $__path;
257
        }
258
259
        if (empty($__skippable)) {
260
            $this->writeError("Failed read file $__path");
261
        }
262
263
        return [];
264
    }
265
266
    public function setIo(IOInterface $io)
267
    {
268
        $this->io = $io;
269
    }
270
271
    protected function writeError($text)
272
    {
273
        if (isset($this->io)) {
274
            $this->io->writeError("<error>$text</error>");
275
        } else {
276
            echo $text . "\n";
277
        }
278
    }
279
}
280