Passed
Pull Request — master (#42)
by Dmitriy
26:25 queued 11:25
created

Builder::findDirContainsComposerJsonRecursively()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 12
rs 10
cc 3
nc 3
nop 1
1
<?php
2
3
namespace Yiisoft\Composer\Config;
4
5
use JsonException;
6
use Yiisoft\Composer\Config\Config\Config;
7
use Yiisoft\Composer\Config\Config\ConfigFactory;
8
use Yiisoft\Composer\Config\Util\Resolver;
9
10
use function dirname;
11
12
/**
13
 * Builder assembles config files.
14
 */
15
class Builder
16
{
17
    private const OUTPUT_DIR_SUFFIX = '-output';
18
19
    /**
20
     * @var string path to the Composer project root
21
     */
22
    private string $baseDir;
23
24
    /**
25
     * @var string path to output assembled configs
26
     */
27
    private string $outputDir;
28
29
    /**
30
     * @var Config[] configurations
31
     */
32
    private array $configs = [];
33
34
    private ConfigFactory $configFactory;
35
36
    /**
37
     * Builder constructor.
38
     *
39
     * @param ConfigFactory $configFactory
40
     * @param string $baseDir path to the Composer project root
41
     */
42
    public function __construct(ConfigFactory $configFactory, string $baseDir)
43
    {
44
        $this->configFactory = $configFactory;
45
        $this->baseDir = $baseDir;
46
        $this->outputDir = self::findOutputDir($baseDir);
47
    }
48
49
    public function createAlternative($name): Builder
50
    {
51
        $alt = new static($this->configFactory, $this->baseDir);
52
        $alt->setOutputDir($this->outputDir . DIRECTORY_SEPARATOR . $name);
53
        foreach (['aliases', 'packages'] as $key) {
54
            $alt->configs[$key] = $this->getConfig($key)->clone($alt);
55
        }
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
        if ($baseDir === null) {
70
            $baseDir = static::findDirContainsComposerJsonRecursively(getcwd());
71
        }
72
        $builder = new self(new ConfigFactory(), $baseDir);
73
        $files = $builder->getConfig('__files')->load();
74
75
        $builder->buildUserConfigs($files->getValues());
76
    }
77
78
    /**
79
     * Returns default output dir.
80
     *
81
     * @param string|null $baseDir path to the root Composer package. When `null`,
82
     * @return string
83
     * @throws JsonException
84
     */
85
    private static function findOutputDir(string $baseDir = null): string
86
    {
87
        if ($baseDir === null) {
88
            $baseDir = static::findDirContainsComposerJsonRecursively(getcwd());
89
        }
90
        $path = $baseDir . DIRECTORY_SEPARATOR . 'composer.json';
91
        $data = @json_decode(file_get_contents($path), true);
92
        $dir = $data['extra'][Package::EXTRA_OUTPUT_DIR_OPTION_NAME] ?? null;
93
94
        return $dir ? static::buildAbsPath($baseDir, $dir) : static::defaultOutputDir($baseDir);
95
    }
96
97
    private static function findDirContainsComposerJsonRecursively(string $cwd): string
98
    {
99
        if (file_exists($cwd . DIRECTORY_SEPARATOR . 'composer.json')) {
100
            return $cwd;
101
        }
102
103
        $candidateDirectory = dirname($cwd);
104
        if ($cwd === $candidateDirectory) {
105
            throw new \RuntimeException('Cannot find directory contains composer.json');
106
        }
107
108
        return static::findDirContainsComposerJsonRecursively($candidateDirectory);
109
    }
110
111
    /**
112
     * Returns default output dir.
113
     *
114
     * @param string $baseDir path to base directory
115
     * @return string
116
     */
117
    private static function defaultOutputDir(string $baseDir = null): string
118
    {
119
        if ($baseDir) {
120
            $dir = $baseDir . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'yiisoft' . DIRECTORY_SEPARATOR . basename(dirname(__DIR__));
121
        } else {
122
            $dir = dirname(__DIR__);
123
        }
124
125
        return $dir . static::OUTPUT_DIR_SUFFIX;
126
    }
127
128
    /**
129
     * Returns full path to assembled config file.
130
     *
131
     * @param string $filename name of config
132
     * @param string $baseDir path to base dir
133
     * @return string absolute path
134
     * @throws JsonException
135
     */
136
    public static function path(string $filename, string $baseDir = null): string
137
    {
138
        return static::buildAbsPath(static::findOutputDir($baseDir), $filename . '.php');
139
    }
140
141
    private static function buildAbsPath(string $dir, string $file): string
142
    {
143
        return self::isAbsolutePath($file) ? $file : $dir . DIRECTORY_SEPARATOR . $file;
144
    }
145
146
    private static function isAbsolutePath(string $path): string
147
    {
148
        return strpos($path, '/') === 0 || strpos($path, ':') === 1 || strpos($path, '\\\\') === 0;
0 ignored issues
show
Bug Best Practice introduced by
The expression return strpos($path, '/'...rpos($path, '\\') === 0 returns the type boolean which is incompatible with the type-hinted return string.
Loading history...
149
    }
150
151
    /**
152
     * Builds all (user and system) configs by given files list.
153
     *
154
     * @param null|array $files files to process: config name => list of files
155
     */
156
    public function buildAllConfigs(array $files): void
157
    {
158
        $this->buildUserConfigs($files);
159
        $this->buildSystemConfigs($files);
160
    }
161
162
    /**
163
     * Builds configs by given files list.
164
     *
165
     * @param null|array $files files to process: config name => list of files
166
     * @return array
167
     */
168
    private function buildUserConfigs(array $files): array
169
    {
170
        $resolver = new Resolver($files);
171
        $files = $resolver->get();
172
        foreach ($files as $name => $paths) {
173
            $this->getConfig($name)->load($paths)->build()->write();
174
        }
175
176
        return $files;
177
    }
178
179
    private function buildSystemConfigs(array $files): void
180
    {
181
        $this->getConfig('__files')->setValues($files);
182
        foreach (['__files', 'aliases', 'packages'] as $name) {
183
            $this->getConfig($name)->build()->write();
184
        }
185
    }
186
187
    public function getOutputPath(string $name): string
188
    {
189
        return $this->outputDir . DIRECTORY_SEPARATOR . $name . '.php';
190
    }
191
192
    public function getConfig(string $name)
193
    {
194
        if (!array_key_exists($name, $this->configs)) {
195
            $this->configs[$name] = $this->configFactory->create($this, $name);
196
        }
197
198
        return $this->configs[$name];
199
    }
200
201
    public function getVars(): array
202
    {
203
        $vars = [];
204
        foreach ($this->configs as $name => $config) {
205
            $vars[$name] = $config->getValues();
206
        }
207
208
        return $vars;
209
    }
210
211
    public function mergeAliases(array $aliases): void
212
    {
213
        $this->getConfig('aliases')->mergeValues($aliases);
214
    }
215
216
    public function setPackage(string $name, array $data): void
217
    {
218
        $this->getConfig('packages')->setValue($name, $data);
219
    }
220
221
    /**
222
     * @return string a full path to the project root
223
     */
224
    public function getBaseDir(): string
225
    {
226
        return $this->baseDir;
227
    }
228
}
229