Passed
Push — master ( f5390f...ff2936 )
by Alexander
02:09
created

Builder::buildAllConfigs()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 10
cc 2
nc 2
nop 1
crap 2
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
use Yiisoft\Files\FileHelper;
12
13
use function dirname;
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 Config[] configurations
34
     */
35
    private array $configs = [];
36
37
    private ConfigFactory $configFactory;
38
39
    /**
40
     * Builder constructor.
41
     *
42
     * @param ConfigFactory $configFactory
43
     * @param string $baseDir path to the Composer project root
44
     */
45 1
    public function __construct(ConfigFactory $configFactory, string $baseDir)
46
    {
47 1
        $this->configFactory = $configFactory;
48 1
        $this->baseDir = $baseDir;
49 1
        $this->outputDir = self::findOutputDir($baseDir);
50 1
    }
51
52
    public function createAlternative($name): Builder
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 1
    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 1
        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 1
        Plugin::buildAllConfigs($baseDir ?? self::findBaseDir());
80 1
    }
81
82
    /**
83
     * Returns default output dir.
84
     *
85
     * @param string|null $baseDir path to the root Composer package. When `null`,
86
     * @return string
87
     * @throws JsonException
88
     */
89 8
    private static function findOutputDir(string $baseDir = null): string
90
    {
91 8
        if ($baseDir === null) {
92
            $baseDir = static::findBaseDir();
93
        }
94 8
        $path = $baseDir . DIRECTORY_SEPARATOR . 'composer.json';
95 8
        $data = @json_decode(file_get_contents($path), true);
96 8
        $dir = $data['extra'][Package::EXTRA_OUTPUT_DIR_OPTION_NAME] ?? null;
97
98 8
        return $dir ? static::buildAbsPath($baseDir, $dir) : static::defaultOutputDir($baseDir);
99
    }
100
101
    private static function findBaseDir(): string
102
    {
103
        $candidates = [
104
            // normal relative path
105
            dirname(__DIR__, 4),
106
            // console
107
            getcwd(),
108
            // symlinked web
109
            dirname(getcwd())
110
        ];
111
112
        foreach ($candidates as $baseDir) {
113
            if (file_exists($baseDir . DIRECTORY_SEPARATOR . 'composer.json')) {
114
                return $baseDir;
115
            }
116
        }
117
118
        throw new \RuntimeException('Cannot find directory that contains composer.json');
119
    }
120
121
    /**
122
     * Returns default output dir.
123
     *
124
     * @param string $baseDir path to base directory
125
     * @return string
126
     */
127 7
    private static function defaultOutputDir(string $baseDir = null): string
128
    {
129 7
        if ($baseDir) {
130 7
            $dir = $baseDir . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'yiisoft' . DIRECTORY_SEPARATOR . basename(dirname(__DIR__));
131
        } else {
132
            $dir = dirname(__DIR__);
133
        }
134
135 7
        return $dir . static::OUTPUT_DIR_SUFFIX;
136
    }
137
138
    /**
139
     * Returns full path to assembled config file.
140
     *
141
     * @param string $filename name of config
142
     * @param string $baseDir path to base dir
143
     * @return string absolute path
144
     * @throws JsonException
145
     */
146 7
    public static function path(string $filename, string $baseDir = null): string
147
    {
148 7
        return static::buildAbsPath(static::findOutputDir($baseDir), $filename . '.php');
149
    }
150
151 8
    private static function buildAbsPath(string $dir, string $file): string
152
    {
153 8
        return self::isAbsolutePath($file) ? $file : $dir . DIRECTORY_SEPARATOR . $file;
154
    }
155
156 8
    private static function isAbsolutePath(string $path): bool
157
    {
158 8
        return strpos($path, '/') === 0 || strpos($path, ':') === 1 || strpos($path, '\\\\') === 0;
159
    }
160
161
    /**
162
     * Builds all (user and system) configs by given files list.
163
     *
164
     * @param null|array $files files to process: config name => list of files
165
     */
166 1
    public function buildAllConfigs(array $files): void
167
    {
168 1
        if (is_dir($this->outputDir)) {
169 1
            FileHelper::clearDirectory($this->outputDir);
170
        }
171
172 1
        $this->buildUserConfigs($files);
173 1
        $this->buildSystemConfigs();
174 1
    }
175
176
    /**
177
     * Builds configs by given files list.
178
     *
179
     * @param null|array $files files to process: config name => list of files
180
     * @return array
181
     */
182 1
    private function buildUserConfigs(array $files): array
183
    {
184 1
        $resolver = new Resolver($files);
185 1
        $files = $resolver->get();
186 1
        foreach ($files as $name => $paths) {
187 1
            $this->getConfig($name)->load($paths)->build()->write();
188
        }
189
190 1
        return $files;
191
    }
192
193 1
    private function buildSystemConfigs(): void
194
    {
195 1
        $this->getConfig('packages')->build()->write();
196 1
    }
197
198 1
    public function getOutputPath(string $name): string
199
    {
200 1
        return $this->outputDir . DIRECTORY_SEPARATOR . $name . '.php';
201
    }
202
203 1
    public function getConfig(string $name)
204
    {
205 1
        if (!array_key_exists($name, $this->configs)) {
206 1
            $this->configs[$name] = $this->configFactory->create($this, $name);
207
        }
208
209 1
        return $this->configs[$name];
210
    }
211
212 1
    public function getVars(): array
213
    {
214 1
        $vars = [];
215 1
        foreach ($this->configs as $name => $config) {
216 1
            $vars[$name] = $config->getValues();
217
        }
218
219 1
        return $vars;
220
    }
221
222 1
    public function setPackage(string $name, array $data): void
223
    {
224 1
        $this->getConfig('packages')->setValue($name, $data);
225 1
    }
226
227
    /**
228
     * @return string a full path to the project root
229
     */
230 1
    public function getBaseDir(): string
231
    {
232 1
        return $this->baseDir;
233
    }
234
235
    /**
236
     * Require another configuration by name.
237
     *
238
     * It will result in "require 'my-config' in the assembled configuration file.
239
     *
240
     * @param string $config config name
241
     * @return callable
242
     */
243
    public static function require(string $config): callable
244
    {
245
        return static fn () => require $config;
246
    }
247
}
248