Passed
Push — master ( 8ba86d...7e6e4f )
by Andrii
14:06
created

Config::replaceMarkers()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 5
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
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-2018, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\composer\config\configs;
12
13
use hiqdev\composer\config\Builder;
14
use hiqdev\composer\config\exceptions\FailedWriteException;
15
use hiqdev\composer\config\Helper;
16
use hiqdev\composer\config\readers\ReaderFactory;
17
18
/**
19
 * Config class represents output configuration file.
20
 *
21
 * @author Andrii Vasyliev <[email protected]>
22
 */
23
class Config
24
{
25
    const UNIX_DS = '/';
26
    const BASE_DIR_MARKER = '<<<base-dir>>>';
27
28
    /**
29
     * @var string config name
30
     */
31
    protected $name;
32
33
    /**
34
     * @var array sources - paths to config source files
35
     */
36 1
    protected $sources = [];
37
38 1
    /**
39 1
     * @var array config value
40 1
     */
41
    protected $values = [];
42 1
43
    /**
44 1
     * @var Builder
45
     */
46
    protected $builder;
47 1
48
    public function __construct(Builder $builder, string $name)
49 1
    {
50
        $this->builder = $builder;
51
        $this->name = $name;
52
    }
53
54
    public function getBuilder(): Builder
55
    {
56
        return $this->builder;
57
    }
58
59
    public function getName(): string
60
    {
61
        return $this->name;
62
    }
63
64
    public function getValues(): array
65
    {
66
        return $this->values;
67
    }
68
69
    public function load(array $paths = [])
70
    {
71
        $configs = [];
72
        foreach ($paths as $path) {
73
            $config = $this->loadFile($path);
74
            if (!empty($config)) {
75
                $configs[] = $config;
76
            }
77
        }
78
79
        $this->sources = $configs;
80
81
        return $this;
82
    }
83
84
    /**
85
     * Reads config file.
86
     * @param string $path
87
     * @return array configuration read from file
88
     */
89
    protected function loadFile($path)
90
    {
91
        $reader = ReaderFactory::get($this->builder, $path);
92
93
        return $reader->read($path);
94
    }
95
96
    /**
97
     * Merges given configs and writes at given name.
98
     * @param mixed $name
99
     * @param array $configs
100
     */
101
    public function build()
102
    {
103
        $this->values = $this->calcValues($this->sources);
104
105
        return $this;
106
    }
107
108
    public function write()
109
    {
110
        $this->writeFile($this->getOutputPath(), $this->values);
111
112
        return $this;
113
    }
114
115
    protected function calcValues(array $sources)
116
    {
117
        $values = call_user_func_array([Helper::class, 'mergeConfig'], $sources);
118
119
        return $this->substituteOutputDirs($values);
120
    }
121
122
    protected function writeFile(string $path, array $data)
123
    {
124
        $this->writePhpFile($path, $data, true);
125
    }
126
127
    /**
128
     * Writes complete PHP config file by full path.
129
     * @param string $path
130
     * @param string|array $data
131
     * @param bool $withEnvAndDefines
132
     */
133
    protected function writePhpFile(string $path, $data, bool $withEnvAndDefines)
134
    {
135
        static::putFile($path, $this->replaceMarkers(implode("\n\n", array_filter([
136
            'header'  => '<?php',
137
            'baseDir' => '$baseDir = dirname(dirname(dirname(__DIR__)));',
138
            'dotenv'  => $withEnvAndDefines ? "\$_ENV = array_merge((array) require __DIR__ . '/dotenv.php', (array) \$_ENV);" : '',
139
            'defines' => $withEnvAndDefines ? $this->builder->getConfig('defines')->buildRequires() : '',
140
            'content' => is_array($data) ? $this->renderVars($data) : $data,
141
        ]))) . "\n");
142
    }
143
144
    protected function renderVars(array $vars)
145
    {
146
        return 'return ' . Helper::exportVar($vars) . ';';
147
    }
148
149
    protected function replaceMarkers(string $content): string
150
    {
151
        $content = str_replace("'" . static::BASE_DIR_MARKER, "\$baseDir . '", $content);
152
153
        return str_replace("'?" . static::BASE_DIR_MARKER, "'?' . \$baseDir . '", $content);
154
    }
155
156
    /**
157
     * Writes file if content changed.
158
     * @param string $path
159
     * @param string $content
160
     */
161
    protected static function putFile($path, $content)
162
    {
163
        if (file_exists($path) && $content === file_get_contents($path)) {
164
            return;
165
        }
166
        if (!file_exists(dirname($path))) {
167
            mkdir(dirname($path), 0777, true);
168
        }
169
        if (false === file_put_contents($path, $content)) {
170
            throw new FailedWriteException("Failed write file $path");
171
        }
172
    }
173
174
    /**
175
     * Substitute output paths in given data array recursively with marker.
176
     * @param array $data
177
     * @return array
178
     */
179
    public function substituteOutputDirs(array $data)
180
    {
181
        $dir = static::normalizePath(dirname(dirname(dirname($this->getOutputDir()))));
182
183
        return static::substitutePaths($data, $dir, static::BASE_DIR_MARKER);
184
    }
185
186
    /**
187
     * Normalizes given path with given directory separator.
188
     * Default forced to Unix directory separator for substitutePaths to work properly in Windows.
189
     * @param string $path path to be normalized
190
     * @param string $ds directory separator
191
     * @return string
192
     */
193
    public static function normalizePath($path, $ds = self::UNIX_DS)
194
    {
195
        return rtrim(strtr($path, '/\\', $ds . $ds), $ds);
196
    }
197
198
    /**
199
     * Substitute all paths in given array recursively with alias if applicable.
200
     * @param array $data
201
     * @param string $dir
202
     * @param string $alias
203
     * @return array
204
     */
205
    public static function substitutePaths($data, $dir, $alias)
206
    {
207
        foreach ($data as &$value) {
208
            if (is_string($value)) {
209
                $value = static::substitutePath($value, $dir, $alias);
210
            } elseif (is_array($value)) {
211
                $value = static::substitutePaths($value, $dir, $alias);
212
            }
213
        }
214
215
        return $data;
216
    }
217
218
    /**
219
     * Substitute path with alias if applicable.
220
     * @param string $path
221
     * @param string $dir
222
     * @param string $alias
223
     * @return string
224
     */
225
    protected static function substitutePath($path, $dir, $alias)
226
    {
227
        $end = $dir . self::UNIX_DS;
228
        $skippable = 0 === strncmp($path, '?', 1) ? '?' : '';
229
        if ($skippable) {
230
            $path = substr($path, 1);
231
        }
232
        $result = (substr($path, 0, strlen($dir)) === $dir) ? $alias . substr($path, strlen($dir) - 1) : $path;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
233
        if ($path === $dir) {
234
            $result = $alias;
235
        } elseif (substr($path, 0, strlen($end)) === $end) {
236
            $result = $alias . substr($path, strlen($end) - 1);
237
        } else {
238
            $result = $path;
239
        }
240
241
        return $skippable . $result;
242
    }
243
244
    public function getOutputDir(): string
245
    {
246
        return $this->builder->getOutputDir();
247
    }
248
249
    public function getOutputPath(string $name = null): string
250
    {
251
        return $this->builder->getOutputPath($name ?: $this->name);
252
    }
253
}
254