Skip to content

Commit 0306cbd

Browse files
authored
feat(core): load local and production configurations last (#1266)
1 parent 347513a commit 0306cbd

File tree

4 files changed

+171
-11
lines changed

4 files changed

+171
-11
lines changed

packages/core/src/Kernel/LoadConfig.php

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,25 @@
88
use RecursiveDirectoryIterator;
99
use RecursiveIteratorIterator;
1010
use SplFileInfo;
11+
use Tempest\Core\AppConfig;
1112
use Tempest\Core\ConfigCache;
1213
use Tempest\Core\Kernel;
14+
use Tempest\Support\Str;
15+
16+
use function Tempest\Support\arr;
1317

1418
/** @internal */
1519
final readonly class LoadConfig
1620
{
1721
public function __construct(
1822
private Kernel $kernel,
1923
private ConfigCache $cache,
24+
private AppConfig $appConfig,
2025
) {}
2126

2227
public function __invoke(): void
2328
{
24-
$configPaths = $this->cache->resolve(
25-
'config_cache',
26-
fn () => $this->find(),
27-
);
29+
$configPaths = $this->cache->resolve('config_cache', fn () => $this->find());
2830

2931
foreach ($configPaths as $path) {
3032
$configFile = require $path;
@@ -55,6 +57,41 @@ public function find(): array
5557
}
5658
}
5759

58-
return $configPaths;
60+
$suffixes = [
61+
'production' => ['.production.config.php', '.prod.config.php', '.prd.config.php'],
62+
'staging' => ['.staging.config.php', '.stg.config.php'],
63+
'testing' => ['.test.config.php'],
64+
'development' => ['.dev.config.php', '.local.config.php'],
65+
];
66+
67+
return arr($configPaths)
68+
->filter(fn (string $path) => match (true) {
69+
$this->appConfig->environment->isLocal() => ! Str\contains($path, [...$suffixes['production'], ...$suffixes['staging'], ...$suffixes['testing']]),
70+
$this->appConfig->environment->isProduction() => ! Str\contains($path, [...$suffixes['staging'], ...$suffixes['testing'], ...$suffixes['development']]),
71+
$this->appConfig->environment->isStaging() => ! Str\contains($path, [...$suffixes['testing'], ...$suffixes['development'], ...$suffixes['production']]),
72+
default => true,
73+
})
74+
->sortByCallback(function (string $path1, string $path2) use ($suffixes): int {
75+
$getPriority = fn (string $path): int => match (true) {
76+
Str\contains($path, '/vendor/') => 0,
77+
Str\contains($path, $suffixes['testing']) => 6,
78+
Str\contains($path, $suffixes['development']) => 5,
79+
Str\contains($path, $suffixes['production']) => 4,
80+
Str\contains($path, $suffixes['staging']) => 3,
81+
Str\contains($path, '.config.php') => 2,
82+
default => 1,
83+
};
84+
85+
$priorityA = $getPriority($path1);
86+
$priorityB = $getPriority($path2);
87+
88+
if ($priorityA !== $priorityB) {
89+
return $priorityA <=> $priorityB;
90+
}
91+
92+
return strcmp($path1, $path2);
93+
})
94+
->values()
95+
->toArray();
5996
}
6097
}

packages/support/src/Arr/functions.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,12 +1116,8 @@ function sort(iterable $array, bool $desc = false, ?bool $preserveKeys = null, i
11161116
* @template TValue
11171117
*
11181118
* @param iterable<TKey,TValue> $array
1119-
* @param callable $callback The function to use for comparing values. It should accept two parameters
1120-
* and return an integer less than, equal to, or greater than zero if the
1121-
* first argument is considered to be respectively less than, equal to, or
1122-
* greater than the second.
1123-
* @param bool|null $preserveKeys Preserves array keys if `true`; reindexes numerically if `false`.
1124-
* Defaults to `null`, which auto-detects preservation based on array type (associative or list).
1119+
* @param \Closure(TValue $a, TValue $b) $callback The function to use for comparing values. It should accept two parameters and return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second.
1120+
* @param bool|null $preserveKeys Preserves array keys if `true`; reindexes numerically if `false`. Defaults to `null`, which auto-detects preservation based on array type (associative or list).
11251121
* @return array<array-key, TValue> Key type depends on whether array keys are preserved or not.
11261122
*/
11271123
function sort_by_callback(iterable $array, callable $callback, ?bool $preserveKeys = null): array
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixtures
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php
2+
3+
namespace Tests\Tempest\Integration\Core\Config;
4+
5+
use Tempest\Core\AppConfig;
6+
use Tempest\Core\ConfigCache;
7+
use Tempest\Core\Environment;
8+
use Tempest\Core\Kernel\LoadConfig;
9+
use Tempest\Discovery\DiscoveryLocation;
10+
use Tempest\Support\Filesystem;
11+
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
12+
13+
final class LoadConfigTest extends FrameworkIntegrationTestCase
14+
{
15+
protected function tearDown(): void
16+
{
17+
Filesystem\delete_directory(__DIR__ . '/Fixtures');
18+
19+
parent::tearDown();
20+
}
21+
22+
protected function setUp(): void
23+
{
24+
parent::setUp();
25+
26+
Filesystem\ensure_directory_empty(__DIR__ . '/Fixtures');
27+
28+
$this->container->get(ConfigCache::class)->clear();
29+
$this->kernel->discoveryLocations = [
30+
new DiscoveryLocation('App', __DIR__ . '/Fixtures'),
31+
];
32+
}
33+
34+
public function test_config_loaded_in_order(): void
35+
{
36+
$this->setupFixtures([
37+
'db.local.config.php',
38+
'db.config.php',
39+
'db.stg.config.php',
40+
'db.prd.config.php',
41+
'db.test.config.php',
42+
'db.production.config.php',
43+
]);
44+
45+
$config = $this->container->get(LoadConfig::class)->find();
46+
47+
$this->assertStringContainsString('db.config.php', $config[0]);
48+
$this->assertStringContainsString('db.stg.config.php', $config[1]);
49+
$this->assertStringContainsString('db.prd.config.php', $config[2]);
50+
$this->assertStringContainsString('db.production.config.php', $config[3]);
51+
$this->assertStringContainsString('db.local.config.php', $config[4]);
52+
$this->assertStringContainsString('db.test.config.php', $config[5]);
53+
}
54+
55+
public function test_non_production_configs_are_discarded_in_production(): void
56+
{
57+
$this->setupFixtures([
58+
'db.local.config.php',
59+
'db.dev.config.php',
60+
'db.stg.config.php',
61+
'db.production.config.php',
62+
'db.config.php',
63+
]);
64+
65+
$this->container->get(AppConfig::class)->environment = Environment::PRODUCTION;
66+
$config = $this->container->get(LoadConfig::class)->find();
67+
68+
$this->assertCount(2, $config);
69+
$this->assertStringContainsString('db.config.php', $config[0]);
70+
$this->assertStringContainsString('db.production.config.php', $config[1]);
71+
}
72+
73+
public function test_non_staging_configs_are_discarded_in_staging(): void
74+
{
75+
$this->setupFixtures([
76+
'db.local.config.php',
77+
'db.dev.config.php',
78+
'db.stg.config.php',
79+
'db.production.config.php',
80+
'db.config.php',
81+
]);
82+
83+
$this->container->get(AppConfig::class)->environment = Environment::STAGING;
84+
$config = $this->container->get(LoadConfig::class)->find();
85+
86+
$this->assertCount(2, $config);
87+
$this->assertStringContainsString('db.config.php', $config[0]);
88+
$this->assertStringContainsString('db.stg.config.php', $config[1]);
89+
}
90+
91+
public function test_non_dev_configs_are_discarded_in_dev(): void
92+
{
93+
$this->setupFixtures([
94+
'db.local.config.php',
95+
'db.dev.config.php',
96+
'db.stg.config.php',
97+
'db.production.config.php',
98+
'db.config.php',
99+
]);
100+
101+
$this->container->get(AppConfig::class)->environment = Environment::LOCAL;
102+
$config = $this->container->get(LoadConfig::class)->find();
103+
104+
$this->assertCount(3, $config);
105+
$this->assertStringContainsString('db.config.php', $config[0]);
106+
$this->assertStringContainsString('db.dev.config.php', $config[1]);
107+
$this->assertStringContainsString('db.local.config.php', $config[2]);
108+
}
109+
110+
private function setupFixtures(array $configs): void
111+
{
112+
foreach ($configs as $config) {
113+
$db = str_replace('.php', '.sqlite', $config);
114+
115+
Filesystem\write_file(__DIR__ . '/Fixtures/' . $config, <<<PHP
116+
<?php
117+
118+
use Tempest\Database\Config\SQLiteConfig;
119+
120+
return new SQLiteConfig(
121+
path: '{$db}',
122+
);
123+
PHP);
124+
}
125+
}
126+
}

0 commit comments

Comments
 (0)