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