Test Failed
Pull Request — master (#124)
by Mirocow
16:46 queued 01:48
created

Config::constantsRequired()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 1
cts 1
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Composer\Config\Config;
6
7
use Yiisoft\Arrays\ArrayHelper;
8
use Yiisoft\Composer\Config\Builder;
9
use Yiisoft\Composer\Config\ContentWriter;
10
use Yiisoft\Composer\Config\Reader\ReaderFactory;
11
use Yiisoft\Composer\Config\Util\Helper;
0 ignored issues
show
Bug introduced by
The type Yiisoft\Composer\Config\Util\Helper was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
12
use Yiisoft\Composer\Config\Util\PathHelper;
13
14
/**
15
 * Config class represents output configuration file.
16
 */
17
class Config
18
{
19
    private const BASE_DIR_MARKER = '<<<base-dir>>>';
20
21
    /**
22
     * @var string config name
23
     */
24
    private string $name;
25
26
    /**
27
     * @var array sources - paths to config source files
28
     */
29
    private array $sources = [];
30
31
    /**
32
     * @var array config value
33
     */
34
    protected array $values = [];
35
36
    protected Builder $builder;
37
38
    protected ContentWriter $contentWriter;
39
40 2
    public function __construct(Builder $builder, string $name)
41
    {
42 2
        $this->builder = $builder;
43 2
        $this->name = $name;
44 2
        $this->contentWriter = new ContentWriter();
45 2
    }
46
47
    public function clone(Builder $builder): self
48
    {
49
        $config = new Config($builder, $this->name);
50
        $config->sources = $this->sources;
51
        $config->values = $this->values;
52
53
        return $config;
54
    }
55
56 1
    public function getValues(): array
57
    {
58 1
        return $this->values;
59
    }
60
61 1
    public function load(array $paths = []): self
62
    {
63 1
        $this->sources = $this->loadFiles($paths);
64
65 1
        return $this;
66
    }
67
68 1
    private function loadFiles(array $paths): array
69
    {
70 1
        switch (count($paths)) {
71 1
            case 0:
72 1
                return [];
73 1
            case 1:
74 1
                return [$this->loadFile(reset($paths))];
75
        }
76
77
        $configs = [];
78
        foreach ($paths as $path) {
79
            $cs = $this->loadFiles($this->glob($path));
80
            foreach ($cs as $config) {
81
                if (!empty($config)) {
82
                    $configs[] = $config;
83
                }
84
            }
85
        }
86
87
        return $configs;
88
    }
89
90
    private function glob(string $path): array
91
    {
92
        if (strpos($path, '*') === false) {
93
            return [$path];
94
        }
95
96
        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...
97
    }
98
99
    /**
100
     * Reads config file.
101
     *
102
     * @param string $path
103
     * @return array configuration read from file
104
     */
105 1
    protected function loadFile(string $path): array
106
    {
107 1
        $reader = ReaderFactory::get($this->builder, $path);
108
109 1
        return $reader->read($path);
110
    }
111
112
    /**
113
     * Merges given configs and writes at given name.
114
     *
115
     * @return Config
116
     */
117 1
    public function build(): self
118
    {
119 1
        $this->values = $this->calcValues($this->sources);
120
121 1
        return $this;
122
    }
123
124 1
    public function write(): self
125
    {
126 1
        $this->writeFile($this->getOutputPath(), $this->values);
127
128 1
        return $this;
129
    }
130
131 1
    protected function calcValues(array $sources): array
132
    {
133 1
        $values = ArrayHelper::merge(...$sources);
134
135 1
        return $this->substituteOutputDirs($values);
136
    }
137
138 1
    protected function writeFile(string $path, array $data): void
139
    {
140 1
        $depth = $this->findDepth();
141 1
        $baseDir = $depth > 0 ? "dirname(__DIR__, $depth)" : '__DIR__';
142
        $envs = $this->envsRequired() ? "\$_ENV = array_merge((array) require __DIR__ . '/envs.php', \$_ENV);" : '';
143 1
        $constants = $this->constantsRequired() ? $this->builder->getConfig('constants')->buildRequires() : '';
144 1
        $params = $this->paramsRequired() ? "\$params = (array) require __DIR__ . '/params.php';" : '';
145 1
        $uses = '';
146 1
        if($data) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $data of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
147
            $variables = $this->buildPhpPartials($data);
148
            $uses = implode("\n", $this->builder->uses);
149 1
        } else {
150
            $variables = '[]';
151 1
        }
152
153 1
        $content = <<<PHP
154
<?php
155 1
156
{$uses}
157 1
158
\$baseDir = {$baseDir};
159 1
160
{$envs}
161
162 1
{$constants}
163 1
164
{$params}
165 1
166
return {$variables};
167 1
PHP;
168
169
        $this->contentWriter->write($path, $this->replaceMarkers($content) . "\n");
170 1
    }
171
172 1
    /**
173
     * Build PHP partials
174
     * @param $data
175 1
     *
176
     * @return string
177 1
     */
178
    protected function buildPhpPartials($data)
179
    {
180 1
        $output = '';
181
        foreach ($data as $key => $value) {
182 1
            if (is_array($value)) {
183 1
                $output .= "'" . $key . "' => " . $this->buildPhpPartials($value) . ",\n";
184
            } elseif(is_string($value)) {
185 1
                if(isset($this->builder->closures["'{$value}'"])) {
186
                    $value = $this->builder->closures["'{$value}'"];
187
                } else {
188 1
                    $value = "'{$value}'";
189
                }
190 1
                $output .= "'" . $key . "' => {$value},\n";
191 1
            } else {
192 1
                if(is_bool($value)) {
193
                    $value = $value? 'true': 'false';
194
                    $output .= "'" . $key . "' => {$value},\n";
195
                } elseif(is_null($value)){
196
                    $output .= "'" . $key . "' => null,\n";
197
                } else {
198
                    $output .= "'" . $key . "' => {$value},\n";
199
                }
200
            }
201
        }
202
203 1
        if($output) {
204
            while (preg_match('~\'__(\w+)__\'~', $output, $m)) {
205 1
                foreach ($this->builder->closures as $closureKey => $closure) {
206
                    if (strpos($output, $closureKey) !== false) {
207 1
                        $output = str_replace($closureKey, $closure, $output);
208
                    }
209
                }
210
            }
211
        }
212
213
        return "[$output]";
214
    }
215
216
    protected function envsRequired(): bool
217 1
    {
218
        return true;
219 1
    }
220 1
221 1
    protected function constantsRequired(): bool
222
    {
223
        return true;
224 1
    }
225
226
    protected function paramsRequired(): bool
227
    {
228
        return true;
229
    }
230
231
    private function findDepth(): int
232
    {
233
        $outDir = PathHelper::realpath(dirname($this->getOutputPath()));
234 1
        $diff = substr($outDir, strlen(PathHelper::realpath($this->getBaseDir())));
235
236 1
        return substr_count($diff, '/');
237 1
    }
238
239 1
    private function replaceMarkers(string $content): string
240 1
    {
241
        return str_replace(
242
            ["'" . self::BASE_DIR_MARKER, "'?" . self::BASE_DIR_MARKER],
243 1
            ["\$baseDir . '", "'?' . \$baseDir . '"],
244
            $content
245
        );
246
    }
247
248
    /**
249
     * Substitute output paths in given data array recursively with marker.
250
     *
251
     * @param array $data
252
     * @return array
253 1
     */
254
    protected function substituteOutputDirs(array $data): array
255 1
    {
256 1
        $dir = PathHelper::normalize($this->getBaseDir());
257 1
258
        return $this->substitutePaths($data, $dir);
259
    }
260 1
261
    /**
262 1
     * Substitute all paths in given array recursively with marker if applicable.
263
     *
264
     * @param array $data
265 1
     * @param string $dir
266
     * @return array
267
     */
268 1
    private function substitutePaths($data, $dir): array
269
    {
270
        $res = [];
271 1
        foreach ($data as $key => $value) {
272
            $res[$this->substitutePath($key, $dir)] = $this->substitutePath($value, $dir);
273 1
        }
274
275
        return $res;
276 1
    }
277
278 1
    /**
279
     * Substitute all paths in given value if applicable.
280
     *
281
     * @param mixed $value
282
     * @param string $dir
283
     * @return mixed
284
     */
285
    private function substitutePath($value, $dir)
286
    {
287
        if (is_string($value)) {
288
            return $this->substitutePathInString($value, $dir);
289
        }
290
        if (is_array($value)) {
291
            return $this->substitutePaths($value, $dir);
292
        }
293
294
        return $value;
295
    }
296
297
    /**
298
     * Substitute path with marker in string if applicable.
299
     *
300
     * @param string $path
301
     * @param string $dir
302
     * @return string
303
     */
304
    private function substitutePathInString($path, $dir): string
305
    {
306
        $end = $dir . '/';
307
        $skippable = 0 === strncmp($path, '?', 1);
308
        if ($skippable) {
309
            $path = substr($path, 1);
310
        }
311
        if ($path === $dir) {
312
            $result = self::BASE_DIR_MARKER;
313
        } elseif (strpos($path, $end) === 0) {
314
            $result = self::BASE_DIR_MARKER . substr($path, strlen($end) - 1);
315
        } else {
316
            $result = $path;
317
        }
318
319
        return ($skippable ? '?' : '') . $result;
320
    }
321
322
    private function getBaseDir(): string
323
    {
324
        return $this->builder->getBaseDir();
325
    }
326
327
    protected function getOutputPath(string $name = null): string
328
    {
329
        return $this->builder->getOutputPath($name ?: $this->name);
330
    }
331
}
332