Skip to content

Commit 0b3a51f

Browse files
authored
Fix #95: Add ability to set sublayer between vendor and application (#105)
1 parent 5a1a93c commit 0b3a51f

44 files changed

Lines changed: 608 additions & 234 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ A number of options is available both for Composer plugin and a config loader. C
198198
},
199199
```
200200

201+
### `source-directory`
202+
201203
The `source-directory` option specifies where to read the configs from for a package the option is specified for.
202204
It is available for all packages, including the root package, which is typically an application.
203205
The value is a path relative to where the `composer.json` file is located. The default value is an empty string.
@@ -216,6 +218,70 @@ $config = new Config(
216218
$web = $config->get('web');
217219
```
218220

221+
### `vendor-override-layer`
222+
223+
The `vendor-override-layer` option adds a sublayer to the vendor, which allocates packages that will override
224+
the vendor's default configurations. This sublayer is located between the vendor and application layers.
225+
226+
This can be useful if you need to redefine default configurations even before the application layer. To do this,
227+
you need to create your own package with configurations meant to override the default ones:
228+
229+
```json
230+
"name": "vendor-name/package-name",
231+
"extra": {
232+
"config-plugin": {
233+
// ...
234+
}
235+
}
236+
```
237+
238+
And in the root file `composer.json` of your application, specify this package in the `vendor-override-layer` option:
239+
240+
```json
241+
"require": {
242+
"vendor-name/package-name": "version",
243+
"yiisoft/config": "version"
244+
},
245+
"extra": {
246+
"config-plugin-options": {
247+
"vendor-override-layer": "vendor-name/package-name"
248+
},
249+
"config-plugin": {
250+
// ...
251+
}
252+
},
253+
```
254+
255+
In the same way, several packages can be added to this sublayer:
256+
257+
```json
258+
"extra": {
259+
"config-plugin-options": {
260+
"vendor-override-layer": [
261+
"vendor-name/package-1",
262+
"vendor-name/package-2"
263+
]
264+
}
265+
}
266+
```
267+
268+
You can use wildcard pattern if there are too many packages:
269+
270+
```json
271+
"extra": {
272+
"config-plugin-options": {
273+
"vendor-override-layer": [
274+
"vendor-1/*",
275+
"vendor-2/config-*"
276+
]
277+
}
278+
}
279+
```
280+
281+
For more information about the wildcard syntax, see the [yiisoft/strings](https://github.com/yiisoft/strings).
282+
283+
> Please note that in this sublayer keys with the same names are not allowed similar to other layers.
284+
219285
## Environments
220286

221287
The plugin supports creating additional environments added to the base configuration. This allows you to create

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"require": {
3131
"php": "^7.4|^8.0",
3232
"composer-plugin-api": "^2.0",
33+
"yiisoft/strings": "^2.0",
3334
"yiisoft/var-dumper": "^1.1"
3435
},
3536
"require-dev": {

src/Composer/MergePlanProcess.php

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Yiisoft\Config\Composer;
66

77
use Composer\Composer;
8+
use Composer\Package\PackageInterface;
89
use Yiisoft\Config\MergePlan;
910
use Yiisoft\Config\Options;
1011
use Yiisoft\VarDumper\VarDumper;
@@ -37,16 +38,22 @@ public function __construct(Composer $composer)
3738
return;
3839
}
3940

40-
$this->addPackagesConfigsToMergePlan();
41+
$this->addPackagesConfigsToMergePlan(false);
42+
$this->addPackagesConfigsToMergePlan(true);
43+
4144
$this->addRootPackageConfigToMergePlan();
4245
$this->addEnvironmentsConfigsToMergePlan();
46+
4347
$this->updateMergePlan();
4448
}
4549

46-
private function addPackagesConfigsToMergePlan(): void
50+
private function addPackagesConfigsToMergePlan(bool $isVendorOverrideLayer): void
4751
{
48-
foreach ($this->helper->buildPackages() as $package) {
52+
$packages = $isVendorOverrideLayer ? $this->helper->getVendorOverridePackages() : $this->helper->getVendorPackages();
53+
54+
foreach ($packages as $name => $package) {
4955
$options = new Options($package->getExtra());
56+
$packageName = $isVendorOverrideLayer ? Options::VENDOR_OVERRIDE_PACKAGE_NAME : $name;
5057

5158
foreach ($this->helper->getPackageConfig($package) as $group => $files) {
5259
$files = (array) $files;
@@ -60,7 +67,7 @@ private function addPackagesConfigsToMergePlan(): void
6067
}
6168

6269
if (Options::isVariable($file)) {
63-
$this->mergePlan->add($file, $package->getPrettyName(), $group);
70+
$this->mergePlan->add($file, $packageName, $group);
6471
continue;
6572
}
6673

@@ -75,8 +82,8 @@ private function addPackagesConfigsToMergePlan(): void
7582

7683
foreach ($matches as $match) {
7784
$this->mergePlan->add(
78-
$this->helper->getRelativePackageFilePath($package, $match),
79-
$package->getPrettyName(),
85+
$this->normalizePackageFilePath($package, $match, $isVendorOverrideLayer),
86+
$packageName,
8087
$group,
8188
);
8289
}
@@ -89,8 +96,8 @@ private function addPackagesConfigsToMergePlan(): void
8996
}
9097

9198
$this->mergePlan->add(
92-
$this->helper->getRelativePackageFilePath($package, $absoluteFilePath),
93-
$package->getPrettyName(),
99+
$this->normalizePackageFilePath($package, $absoluteFilePath, $isVendorOverrideLayer),
100+
$packageName,
94101
$group,
95102
);
96103
}
@@ -158,4 +165,16 @@ private function normalizeLineEndings(string $value): string
158165
"\r" => "\n",
159166
]);
160167
}
168+
169+
private function normalizePackageFilePath(
170+
PackageInterface $package,
171+
string $absoluteFilePath,
172+
bool $isVendorOverrideLayer
173+
): string {
174+
if ($isVendorOverrideLayer) {
175+
return $this->helper->getRelativePackageFilePathWithPackageName($package, $absoluteFilePath);
176+
}
177+
178+
return $this->helper->getRelativePackageFilePath($package, $absoluteFilePath);
179+
}
161180
}

src/Composer/PackageFilesProcess.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public function paths(): ConfigPaths
6262
*/
6363
private function process(array $packageNames): void
6464
{
65-
foreach ($this->helper->buildPackages() as $package) {
65+
foreach ($this->helper->getPackages() as $package) {
6666
$options = new Options($package->getExtra());
6767

6868
foreach ($this->helper->getPackageConfig($package) as $files) {

src/Composer/ProcessHelper.php

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
use Composer\Package\PackageInterface;
1111
use Yiisoft\Config\ConfigPaths;
1212
use Yiisoft\Config\Options;
13+
use Yiisoft\Strings\WildcardPattern;
1314

1415
use function dirname;
16+
use function is_string;
1517
use function realpath;
1618
use function str_replace;
1719

@@ -25,6 +27,11 @@ final class ProcessHelper
2527
private Options $rootPackageOptions;
2628
private array $rootPackageExtra;
2729

30+
/**
31+
* @psalm-var array<string, CompletePackage>
32+
*/
33+
private array $packages;
34+
2835
/**
2936
* @param Composer $composer The composer instance.
3037
*/
@@ -45,16 +52,53 @@ public function __construct(Composer $composer)
4552
$this->composer = $composer;
4653
$this->rootPackageOptions = new Options($this->rootPackageExtra);
4754
$this->paths = new ConfigPaths($rootPath, $this->rootPackageOptions->sourceDirectory());
55+
$this->packages = (new PackagesListBuilder($this->composer))->build();
56+
}
57+
58+
/**
59+
* Returns all vendor packages.
60+
*
61+
* @psalm-return array<string, CompletePackage>
62+
*/
63+
public function getPackages(): array
64+
{
65+
return $this->packages;
4866
}
4967

5068
/**
51-
* Builds and returns packages.
69+
* Returns vendor packages without packages from the vendor override sublayer.
5270
*
53-
* @return array<string, CompletePackage>
71+
* @psalm-return array<string, CompletePackage>
5472
*/
55-
public function buildPackages(): array
73+
public function getVendorPackages(): array
5674
{
57-
return (new PackagesListBuilder($this->composer))->build();
75+
$vendorPackages = [];
76+
77+
foreach ($this->packages as $name => $package) {
78+
if (!$this->isVendorOverridePackage($name)) {
79+
$vendorPackages[$name] = $package;
80+
}
81+
}
82+
83+
return $vendorPackages;
84+
}
85+
86+
/**
87+
* Returns vendor packages only from the vendor override sublayer.
88+
*
89+
* @psalm-return array<string, CompletePackage>
90+
*/
91+
public function getVendorOverridePackages(): array
92+
{
93+
$vendorOverridePackages = [];
94+
95+
foreach ($this->packages as $name => $package) {
96+
if ($this->isVendorOverridePackage($name)) {
97+
$vendorOverridePackages[$name] = $package;
98+
}
99+
}
100+
101+
return $vendorOverridePackages;
58102
}
59103

60104
/**
@@ -84,6 +128,19 @@ public function getRelativePackageFilePath(PackageInterface $package, string $fi
84128
return str_replace("{$this->getPackageRootDirectoryPath($package)}/", '', $filePath);
85129
}
86130

131+
/**
132+
* Returns the relative path to the package file including the package name.
133+
*
134+
* @param PackageInterface $package The package instance.
135+
* @param string $filePath The absolute path to the package file.
136+
*
137+
* @return string The relative path to the package file including the package name.
138+
*/
139+
public function getRelativePackageFilePathWithPackageName(PackageInterface $package, string $filePath): string
140+
{
141+
return "{$package->getPrettyName()}/{$this->getRelativePackageFilePath($package, $filePath)}";
142+
}
143+
87144
/**
88145
* Returns the package filename excluding the source directory {@see Options::sourceDirectory()}.
89146
*
@@ -184,4 +241,26 @@ private function getPackageRootDirectoryPath(PackageInterface $package): string
184241
{
185242
return $this->composer->getInstallationManager()->getInstallPath($package);
186243
}
244+
245+
/**
246+
* Checks whether the package is in the vendor override sublayer.
247+
*
248+
* @param string $package The package name.
249+
*
250+
* @return bool Whether the package is in the vendor override sublayer.
251+
*/
252+
private function isVendorOverridePackage(string $package): bool
253+
{
254+
foreach ($this->rootPackageOptions->vendorOverrideLayerPackages() as $pattern) {
255+
if (!is_string($pattern)) {
256+
continue;
257+
}
258+
259+
if ($package === $pattern || (new WildcardPattern($pattern))->match($package)) {
260+
return true;
261+
}
262+
}
263+
264+
return false;
265+
}
187266
}

src/Config.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ final class Config
3939
*/
4040
public function __construct(
4141
ConfigPaths $paths,
42-
?string $environment = null,
42+
string $environment = null,
4343
array $modifiers = [],
4444
string $paramsGroup = 'params'
4545
) {
@@ -53,8 +53,9 @@ public function __construct(
5353
$this->throwException(sprintf('The "%s" configuration environment does not exist.', $environment));
5454
}
5555

56-
$this->merger = new Merger($paths, $modifiers);
57-
$this->filesExtractor = new FilesExtractor($paths, $mergePlan, $environment, $modifiers);
56+
$dataModifiers = new DataModifiers($modifiers);
57+
$this->merger = new Merger($paths, $dataModifiers);
58+
$this->filesExtractor = new FilesExtractor($paths, $mergePlan, $dataModifiers, $environment);
5859
}
5960

6061
/**

src/ConfigPaths.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ public function absolute(string $file, string $package = Options::ROOT_PACKAGE_N
4848
return "$this->configPath/$file";
4949
}
5050

51+
if ($package === Options::VENDOR_OVERRIDE_PACKAGE_NAME) {
52+
return "$this->vendorPath/$file";
53+
}
54+
5155
return "$this->vendorPath/$package/$file";
5256
}
5357

src/Context.php

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,21 @@
1010
final class Context
1111
{
1212
public const VENDOR = 1;
13-
public const APPLICATION = 2;
14-
public const ENVIRONMENT = 3;
13+
public const VENDOR_OVERRIDE = 2;
14+
public const APPLICATION = 3;
15+
public const ENVIRONMENT = 4;
1516

1617
private string $group;
1718
private string $package;
18-
private int $level;
19+
private int $layer;
1920
private string $file;
2021
private bool $isVariable;
2122

22-
public function __construct(string $group, string $package, int $level, string $file, bool $isVariable)
23+
public function __construct(string $group, string $package, int $layer, string $file, bool $isVariable)
2324
{
2425
$this->group = $group;
2526
$this->package = $package;
26-
$this->level = $level;
27+
$this->layer = $layer;
2728
$this->file = $file;
2829
$this->isVariable = $isVariable;
2930
}
@@ -38,14 +39,9 @@ public function package(): string
3839
return $this->package;
3940
}
4041

41-
public function level(): int
42+
public function layer(): int
4243
{
43-
return $this->level;
44-
}
45-
46-
public function isVendor(): bool
47-
{
48-
return $this->level === self::VENDOR;
44+
return $this->layer;
4945
}
5046

5147
public function file(): string

0 commit comments

Comments
 (0)