Builder   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 235
Duplicated Lines 0 %

Test Coverage

Coverage 68.28%

Importance

Changes 6
Bugs 1 Features 0
Metric Value
wmc 37
eloc 65
c 6
b 1
f 0
dl 0
loc 235
ccs 56
cts 82
cp 0.6828
rs 9.44

19 Methods

Rating   Name   Duplication   Size   Complexity  
A setOutputDir() 0 5 2
A __construct() 0 5 1
A rebuild() 0 12 6
A createAlternative() 0 7 1
A buildAbsPath() 0 3 2
A path() 0 3 1
A getOutputPath() 0 3 1
A getConfig() 0 7 2
A require() 0 3 1
A buildUserConfigs() 0 9 2
A setPackage() 0 3 1
A buildSystemConfigs() 0 3 1
A buildAllConfigs() 0 8 2
A getBaseDir() 0 3 1
A findOutputDir() 0 10 3
A defaultOutputDir() 0 9 2
A isAbsolutePath() 0 3 3
A findBaseDir() 0 18 3
A getVars() 0 8 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Composer\Config;
6
7
use function dirname;
8
use JsonException;
9
use Yiisoft\Composer\Config\Config\ConfigOutput;
10
use Yiisoft\Composer\Config\Config\ConfigOutputFactory;
11
use Yiisoft\Composer\Config\Util\Resolver;
12
13
use Yiisoft\Files\FileHelper;
14
15
/**
16
 * Builder assembles config files.
17
 */
18
class Builder
19
{
20
    private const OUTPUT_DIR_SUFFIX = '-output';
21
22
    /**
23
     * @var string path to the Composer project root
24
     */
25
    private string $baseDir;
26
27
    /**
28
     * @var string path to output assembled configs
29
     */
30
    private string $outputDir;
31
32
    /**
33
     * @var ConfigOutput[] configurations
34
     */
35
    private array $configs = [];
36
37
    private ConfigOutputFactory $configFactory;
38
39
    /**
40
     * Builder constructor.
41
     *
42
     * @param ConfigOutputFactory $configFactory
43
     * @param string $baseDir path to the Composer project root
44
     */
45 2
    public function __construct(ConfigOutputFactory $configFactory, string $baseDir)
46
    {
47 2
        $this->configFactory = $configFactory;
48 2
        $this->baseDir = $baseDir;
49 2
        $this->outputDir = self::findOutputDir($baseDir);
50 2
    }
51
52
    public function createAlternative($name): self
53
    {
54
        $alt = new static($this->configFactory, $this->baseDir);
55
        $alt->setOutputDir($this->outputDir . DIRECTORY_SEPARATOR . $name);
56
        $alt->configs['packages'] = $this->getConfig('packages')->clone($alt);
57
58
        return $alt;
59
    }
60
61
    public function setOutputDir(?string $outputDir): void
62
    {
63
        $this->outputDir = $outputDir
64
            ? static::buildAbsPath($this->getBaseDir(), $outputDir)
65
            : static::findOutputDir($this->getBaseDir());
66
    }
67
68 2
    public static function rebuild(?string $baseDir = null): void
69
    {
70
        // Ensure COMPOSER_HOME is set in case web server does not give PHP OS environment variables
71 2
        if (!(getenv('APPDATA') || getenv('HOME') || getenv('COMPOSER_HOME'))) {
72
            $path = sys_get_temp_dir() . '/.composer';
73
            if (!is_dir($path) && !mkdir($path)) {
74
                throw new \RuntimeException(sprintf('Directory "%s" was not created', $path));
75
            }
76
            putenv('COMPOSER_HOME=' . $path);
77
        }
78
79 2
        Plugin::buildAllConfigs($baseDir ?? self::findBaseDir());
80 2
    }
81
82
    /**
83
     * Returns default output dir.
84
     *
85
     * @param string|null $baseDir path to the root Composer package. When `null`,
86
     *
87
     * @throws JsonException
88
     *
89
     * @return string
90
     */
91 16
    private static function findOutputDir(string $baseDir = null): string
92
    {
93 16
        if ($baseDir === null) {
94
            $baseDir = static::findBaseDir();
95
        }
96 16
        $path = $baseDir . DIRECTORY_SEPARATOR . 'composer.json';
97 16
        $data = @json_decode(file_get_contents($path), true);
98 16
        $dir = $data['extra'][Package::EXTRA_OUTPUT_DIR_OPTION_NAME] ?? null;
99
100 16
        return $dir ? static::buildAbsPath($baseDir, $dir) : static::defaultOutputDir($baseDir);
101
    }
102
103
    private static function findBaseDir(): string
104
    {
105
        $candidates = [
106
            // normal relative path
107
            dirname(__DIR__, 4),
108
            // console
109
            getcwd(),
110
            // symlinked web
111
            dirname(getcwd()),
112
        ];
113
114
        foreach ($candidates as $baseDir) {
115
            if (file_exists($baseDir . DIRECTORY_SEPARATOR . 'composer.json')) {
116
                return $baseDir;
117
            }
118
        }
119
120
        throw new \RuntimeException('Cannot find directory that contains composer.json');
121
    }
122
123
    /**
124
     * Returns default output dir.
125
     *
126
     * @param string|null $baseDir path to base directory
127
     *
128
     * @return string
129
     */
130 14
    private static function defaultOutputDir(string $baseDir = null): string
131
    {
132 14
        if ($baseDir) {
133 14
            $dir = $baseDir . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'yiisoft' . DIRECTORY_SEPARATOR . basename(dirname(__DIR__));
134
        } else {
135
            $dir = dirname(__DIR__);
136
        }
137
138 14
        return $dir . static::OUTPUT_DIR_SUFFIX;
139
    }
140
141
    /**
142
     * Returns full path to assembled config file.
143
     *
144
     * @param string $filename name of config
145
     * @param string|null $baseDir path to base dir
146
     *
147
     * @throws JsonException
148
     *
149
     * @return string absolute path
150
     */
151 14
    public static function path(string $filename, string $baseDir = null): string
152
    {
153 14
        return static::buildAbsPath(static::findOutputDir($baseDir), $filename . '.php');
154
    }
155
156 16
    private static function buildAbsPath(string $dir, string $file): string
157
    {
158 16
        return self::isAbsolutePath($file) ? $file : $dir . DIRECTORY_SEPARATOR . $file;
159
    }
160
161 16
    private static function isAbsolutePath(string $path): bool
162
    {
163 16
        return strpos($path, '/') === 0 || strpos($path, ':') === 1 || strpos($path, '\\\\') === 0;
164
    }
165
166
    /**
167
     * Builds all (user and system) configs by given files list.
168
     *
169
     * @param array $files files to process: config name => list of files
170
     */
171 2
    public function buildAllConfigs(array $files): void
172
    {
173 2
        if (is_dir($this->outputDir)) {
174 2
            FileHelper::clearDirectory($this->outputDir);
175
        }
176
177 2
        $this->buildUserConfigs($files);
178 2
        $this->buildSystemConfigs();
179 2
    }
180
181
    /**
182
     * Builds configs by given files list.
183
     *
184
     * @param array $files files to process: config name => list of files
185
     *
186
     * @return array
187
     */
188 2
    private function buildUserConfigs(array $files): array
189
    {
190 2
        $resolver = new Resolver($files);
191 2
        $files = $resolver->get();
192 2
        foreach ($files as $name => $paths) {
193 2
            $this->getConfig($name)->load($paths)->build()->write();
194
        }
195
196 2
        return $files;
197
    }
198
199 2
    private function buildSystemConfigs(): void
200
    {
201 2
        $this->getConfig('packages')->build()->write();
202 2
    }
203
204 2
    public function getOutputPath(string $name): string
205
    {
206 2
        return $this->outputDir . DIRECTORY_SEPARATOR . $name . '.php';
207
    }
208
209 2
    public function getConfig(string $name)
210
    {
211 2
        if (!array_key_exists($name, $this->configs)) {
212 2
            $this->configs[$name] = $this->configFactory->create($this, $name);
213
        }
214
215 2
        return $this->configs[$name];
216
    }
217
218 2
    public function getVars(): array
219
    {
220 2
        $vars = [];
221 2
        foreach ($this->configs as $name => $config) {
222 2
            $vars[$name] = $config->getValues();
223
        }
224
225 2
        return $vars;
226
    }
227
228 2
    public function setPackage(string $name, array $data): void
229
    {
230 2
        $this->getConfig('packages')->setValue($name, $data);
231 2
    }
232
233
    /**
234
     * @return string a full path to the project root
235
     */
236 2
    public function getBaseDir(): string
237
    {
238 2
        return $this->baseDir;
239
    }
240
241
    /**
242
     * Require another configuration by name.
243
     *
244
     * It will result in "require 'my-config' in the assembled configuration file.
245
     *
246
     * @param string $config config name
247
     *
248
     * @return callable
249
     */
250
    public static function require(string $config): callable
251
    {
252
        return static fn () => require $config;
253
    }
254
}
255