Completed
Push — master ( d038a3...c10658 )
by Andrii
10:32
created

Config   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 301
Duplicated Lines 0 %

Test Coverage

Coverage 10%

Importance

Changes 10
Bugs 0 Features 0
Metric Value
wmc 53
eloc 93
c 10
b 0
f 0
dl 0
loc 301
ccs 8
cts 80
cp 0.1
rs 6.96

28 Methods

Rating   Name   Duplication   Size   Complexity  
A getValues() 0 3 1
A getBuilder() 0 3 1
A getName() 0 3 1
A __construct() 0 4 1
A clone() 0 7 1
A getOutputPath() 0 3 2
A substitutePaths() 0 11 4
A build() 0 5 1
A withParams() 0 3 1
A getBaseDir() 0 3 1
A writeFile() 0 3 1
A calcValues() 0 5 1
A getOutputDir() 0 3 1
A replaceMarkers() 0 5 1
B putFile() 0 11 7
A withDefines() 0 3 1
A loadFile() 0 5 1
A write() 0 5 1
A load() 0 5 1
A loadFiles() 0 20 6
A substituteOutputDirs() 0 5 1
A writePhpFile() 0 13 6
A normalizePath() 0 3 1
A substitutePath() 0 16 5
A renderVars() 0 3 1
A glob() 0 7 2
A findDepth() 0 6 1
A withEnv() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Config often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Config, and based on these observations, apply Extract Interface, too.

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
use ReflectionException;
18
19
/**
20
 * Config class represents output configuration file.
21
 *
22
 * @author Andrii Vasyliev <[email protected]>
23
 */
24
class Config
25
{
26
    const UNIX_DS = '/';
27
    const BASE_DIR_MARKER = '<<<base-dir>>>';
28
29
    /**
30
     * @var string config name
31
     */
32
    protected $name;
33
34
    /**
35
     * @var array sources - paths to config source files
36 1
     */
37
    protected $sources = [];
38 1
39 1
    /**
40 1
     * @var array config value
41
     */
42 1
    protected $values = [];
43
44 1
    /**
45
     * @var Builder
46
     */
47 1
    protected $builder;
48
49 1
    public function __construct(Builder $builder, string $name)
50
    {
51
        $this->builder = $builder;
52
        $this->name = $name;
53
    }
54
55
    public function clone(Builder $builder)
56
    {
57
        $config = new Config($builder, $this->name);
58
        $config->sources = $this->sources;
59
        $config->values = $this->values;
60
61
        return $config;
62
    }
63
64
    public function getBuilder(): Builder
65
    {
66
        return $this->builder;
67
    }
68
69
    public function getName(): string
70
    {
71
        return $this->name;
72
    }
73
74
    public function getValues(): array
75
    {
76
        return $this->values;
77
    }
78
79
    public function load(array $paths = []): self
80
    {
81
        $this->sources = $this->loadFiles($paths);
82
83
        return $this;
84
    }
85
86
    protected function loadFiles(array $paths): array
87
    {
88
        switch (count($paths)) {
89
        case 0:
90
            return [];
91
        case 1:
92
            return [$this->loadFile(reset($paths))];
93
        }
94
95
        $configs = [];
96
        foreach ($paths as $path) {
97
            $cs = $this->loadFiles($this->glob($path));
98
            foreach ($cs as $config) {
99
                if (!empty($config)) {
100
                    $configs[] = $config;
101
                }
102
            }
103
        }
104
105
        return $configs;
106
    }
107
108
    protected function glob(string $path): array
109
    {
110
        if (strpos($path, '*') === FALSE) {
111
            return [$path];
112
        }
113
114
        return glob($path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return glob($path) could return the type false which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
115
    }
116
117
    /**
118
     * Reads config file.
119
     * @param string $path
120
     * @return array configuration read from file
121
     */
122
    protected function loadFile($path): array
123
    {
124
        $reader = ReaderFactory::get($this->builder, $path);
125
126
        return $reader->read($path);
127
    }
128
129
    /**
130
     * Merges given configs and writes at given name.
131
     * @return Config
132
     */
133
    public function build(): self
134
    {
135
        $this->values = $this->calcValues($this->sources);
136
137
        return $this;
138
    }
139
140
    public function write(): self
141
    {
142
        $this->writeFile($this->getOutputPath(), $this->values);
143
144
        return $this;
145
    }
146
147
    protected function calcValues(array $sources): array
148
    {
149
        $values = call_user_func_array([Helper::class, 'mergeConfig'], $sources);
150
151
        return $this->substituteOutputDirs($values);
152
    }
153
154
    protected function writeFile(string $path, array $data): void
155
    {
156
        $this->writePhpFile($path, $data);
157
    }
158
159
    /**
160
     * Writes complete PHP config file by full path.
161
     * @param string $path
162
     * @param string|array $data
163
     * @param bool $withEnv
164
     * @param bool $withDefines
165
     * @throws FailedWriteException
166
     * @throws ReflectionException
167
     */
168
    protected function writePhpFile(string $path, $data): void
169
    {
170
        $depth = $this->findDepth();
171
        $baseDir = $depth>0 ? "dirname(__DIR__, $depth)" : '__DIR__';
172
        static::putFile($path, $this->replaceMarkers(implode("\n\n", array_filter([
173
            'header'  => '<?php',
174
            'baseDir' => "\$baseDir = $baseDir;",
175
            'BASEDIR' => "defined('COMPOSER_CONFIG_PLUGIN_BASEDIR') or define('COMPOSER_CONFIG_PLUGIN_BASEDIR', \$baseDir);",
176
            'dotenv'  => $this->withEnv() ? "\$_ENV = array_merge((array) require __DIR__ . '/dotenv.php', (array) \$_ENV);" : '',
177
            'defines' => $this->withDefines() ? $this->builder->getConfig('defines')->buildRequires() : '',
178
            'params'  => $this->withParams() ? "\$params = require __DIR__ . '/params.php';" : '',
179
            'content' => is_array($data) ? $this->renderVars($data) : $data,
180
        ]))) . "\n");
181
    }
182
183
    private function withEnv(): bool
184
    {
185
        return !in_array(static::class, [System::class, DotEnv::class], true);
186
    }
187
188
    private function withDefines(): bool
189
    {
190
        return !in_array(static::class, [System::class, DotEnv::class, Defines::class], true);
191
    }
192
193
    private function withParams(): bool
194
    {
195
        return !in_array(static::class, [System::class, DotEnv::class, Defines::class, Params::class], true);
196
    }
197
198
    private function findDepth()
199
    {
200
        $outDir = dirname($this->getOutputPath());
201
        $diff = substr($outDir, strlen($this->getBaseDir()));
202
203
        return substr_count($diff, DIRECTORY_SEPARATOR);
204
    }
205
206
    /**
207
     * @param array $vars array to be exported
208
     * @return string
209
     * @throws ReflectionException
210
     */
211
    protected function renderVars(array $vars): string
212
    {
213
        return 'return ' . Helper::exportVar($vars) . ';';
214
    }
215
216
    protected function replaceMarkers(string $content): string
217
    {
218
        $content = str_replace("'" . static::BASE_DIR_MARKER, "\$baseDir . '", $content);
219
220
        return str_replace("'?" . static::BASE_DIR_MARKER, "'?' . \$baseDir . '", $content);
221
    }
222
223
    /**
224
     * Writes file if content changed.
225
     * @param string $path
226
     * @param string $content
227
     * @throws FailedWriteException
228
     */
229
    protected static function putFile($path, $content): void
230
    {
231
        if (file_exists($path) && $content === file_get_contents($path)) {
232
            return;
233
        }
234
        $dirname = dirname($path);
235
        if (!file_exists($dirname) && !mkdir($dirname, 0777, true) && !is_dir($dirname)) {
236
            throw new FailedWriteException(sprintf('Directory "%s" was not created', $dirname));
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(array $data): array
249
    {
250
        $dir = static::normalizePath($this->getBaseDir());
251
252
        return static::substitutePaths($data, $dir, static::BASE_DIR_MARKER);
253
    }
254
255
    /**
256
     * Normalizes given path with given directory separator.
257
     * Default forced to Unix directory separator for substitutePaths to work properly in Windows.
258
     * @param string $path path to be normalized
259
     * @param string $ds directory separator
260
     * @return string
261
     */
262
    public static function normalizePath($path, $ds = self::UNIX_DS): string
263
    {
264
        return rtrim(strtr($path, '/\\', $ds . $ds), $ds);
265
    }
266
267
    /**
268
     * Substitute all paths in given array recursively with alias if applicable.
269
     * @param array $data
270
     * @param string $dir
271
     * @param string $alias
272
     * @return array
273
     */
274
    public static function substitutePaths($data, $dir, $alias): array
275
    {
276
        foreach ($data as &$value) {
277
            if (is_string($value)) {
278
                $value = static::substitutePath($value, $dir, $alias);
279
            } elseif (is_array($value)) {
280
                $value = static::substitutePaths($value, $dir, $alias);
281
            }
282
        }
283
284
        return $data;
285
    }
286
287
    /**
288
     * Substitute path with alias if applicable.
289
     * @param string $path
290
     * @param string $dir
291
     * @param string $alias
292
     * @return string
293
     */
294
    protected static function substitutePath($path, $dir, $alias): string
295
    {
296
        $end = $dir . self::UNIX_DS;
297
        $skippable = 0 === strncmp($path, '?', 1) ? '?' : '';
298
        if ($skippable) {
299
            $path = substr($path, 1);
300
        }
301
        if ($path === $dir) {
302
            $result = $alias;
303
        } elseif (strpos($path, $end) === 0) {
304
            $result = $alias . substr($path, strlen($end) - 1);
305
        } else {
306
            $result = $path;
307
        }
308
309
        return $skippable . $result;
310
    }
311
312
    public function getBaseDir(): string
313
    {
314
        return dirname(__DIR__, 5);
315
    }
316
317
    public function getOutputDir(): string
318
    {
319
        return $this->builder->getOutputDir();
320
    }
321
322
    public function getOutputPath(string $name = null): string
323
    {
324
        return $this->builder->getOutputPath($name ?: $this->name);
325
    }
326
}
327