Passed
Push — master ( 0e323d...76ab56 )
by Alexander
02:03
created

Builder   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 224
Duplicated Lines 0 %

Test Coverage

Coverage 20%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 36
eloc 63
c 5
b 0
f 0
dl 0
loc 224
ccs 16
cts 80
cp 0.2
rs 9.52

19 Methods

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