Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 42 additions & 5 deletions packages/core/src/Kernel/LoadConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,25 @@
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;
use Tempest\Core\AppConfig;
use Tempest\Core\ConfigCache;
use Tempest\Core\Kernel;
use Tempest\Support\Str;

use function Tempest\Support\arr;

/** @internal */
final readonly class LoadConfig
{
public function __construct(
private Kernel $kernel,
private ConfigCache $cache,
private AppConfig $appConfig,
) {}

public function __invoke(): void
{
$configPaths = $this->cache->resolve(
'config_cache',
fn () => $this->find(),
);
$configPaths = $this->cache->resolve('config_cache', fn () => $this->find());

foreach ($configPaths as $path) {
$configFile = require $path;
Expand Down Expand Up @@ -55,6 +57,41 @@ public function find(): array
}
}

return $configPaths;
$suffixes = [
'production' => ['.production.config.php', '.prod.config.php', '.prd.config.php'],
'staging' => ['.staging.config.php', '.stg.config.php'],
'testing' => ['.test.config.php'],
'development' => ['.dev.config.php', '.local.config.php'],
];

return arr($configPaths)
->filter(fn (string $path) => match (true) {
$this->appConfig->environment->isLocal() => ! Str\contains($path, [...$suffixes['production'], ...$suffixes['staging'], ...$suffixes['testing']]),
$this->appConfig->environment->isProduction() => ! Str\contains($path, [...$suffixes['staging'], ...$suffixes['testing'], ...$suffixes['development']]),
$this->appConfig->environment->isStaging() => ! Str\contains($path, [...$suffixes['testing'], ...$suffixes['development'], ...$suffixes['production']]),
default => true,
})
->sortByCallback(function (string $path1, string $path2) use ($suffixes): int {
$getPriority = fn (string $path): int => match (true) {
Str\contains($path, '/vendor/') => 0,
Str\contains($path, $suffixes['testing']) => 6,
Str\contains($path, $suffixes['development']) => 5,
Str\contains($path, $suffixes['production']) => 4,
Str\contains($path, $suffixes['staging']) => 3,
Str\contains($path, '.config.php') => 2,
default => 1,
};

$priorityA = $getPriority($path1);
$priorityB = $getPriority($path2);

if ($priorityA !== $priorityB) {
return $priorityA <=> $priorityB;
}

return strcmp($path1, $path2);
})
->values()
->toArray();
}
}
8 changes: 2 additions & 6 deletions packages/support/src/Arr/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -1116,12 +1116,8 @@ function sort(iterable $array, bool $desc = false, ?bool $preserveKeys = null, i
* @template TValue
*
* @param iterable<TKey,TValue> $array
* @param callable $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.
* @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).
* @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.
* @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).
* @return array<array-key, TValue> Key type depends on whether array keys are preserved or not.
*/
function sort_by_callback(iterable $array, callable $callback, ?bool $preserveKeys = null): array
Expand Down
1 change: 1 addition & 0 deletions tests/Integration/Core/Config/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixtures
126 changes: 126 additions & 0 deletions tests/Integration/Core/Config/LoadConfigTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php

namespace Tests\Tempest\Integration\Core\Config;

use Tempest\Core\AppConfig;
use Tempest\Core\ConfigCache;
use Tempest\Core\Environment;
use Tempest\Core\Kernel\LoadConfig;
use Tempest\Discovery\DiscoveryLocation;
use Tempest\Support\Filesystem;
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;

final class LoadConfigTest extends FrameworkIntegrationTestCase
{
protected function tearDown(): void
{
Filesystem\delete_directory(__DIR__ . '/Fixtures');

parent::tearDown();
}

protected function setUp(): void
{
parent::setUp();

Filesystem\ensure_directory_empty(__DIR__ . '/Fixtures');

$this->container->get(ConfigCache::class)->clear();
$this->kernel->discoveryLocations = [
new DiscoveryLocation('App', __DIR__ . '/Fixtures'),
];
}

public function test_config_loaded_in_order(): void
{
$this->setupFixtures([
'db.local.config.php',
'db.config.php',
'db.stg.config.php',
'db.prd.config.php',
'db.test.config.php',
'db.production.config.php',
]);

$config = $this->container->get(LoadConfig::class)->find();

$this->assertStringContainsString('db.config.php', $config[0]);
$this->assertStringContainsString('db.stg.config.php', $config[1]);
$this->assertStringContainsString('db.prd.config.php', $config[2]);
$this->assertStringContainsString('db.production.config.php', $config[3]);
$this->assertStringContainsString('db.local.config.php', $config[4]);
$this->assertStringContainsString('db.test.config.php', $config[5]);
}

public function test_non_production_configs_are_discarded_in_production(): void
{
$this->setupFixtures([
'db.local.config.php',
'db.dev.config.php',
'db.stg.config.php',
'db.production.config.php',
'db.config.php',
]);

$this->container->get(AppConfig::class)->environment = Environment::PRODUCTION;
$config = $this->container->get(LoadConfig::class)->find();

$this->assertCount(2, $config);
$this->assertStringContainsString('db.config.php', $config[0]);
$this->assertStringContainsString('db.production.config.php', $config[1]);
}

public function test_non_staging_configs_are_discarded_in_staging(): void
{
$this->setupFixtures([
'db.local.config.php',
'db.dev.config.php',
'db.stg.config.php',
'db.production.config.php',
'db.config.php',
]);

$this->container->get(AppConfig::class)->environment = Environment::STAGING;
$config = $this->container->get(LoadConfig::class)->find();

$this->assertCount(2, $config);
$this->assertStringContainsString('db.config.php', $config[0]);
$this->assertStringContainsString('db.stg.config.php', $config[1]);
}

public function test_non_dev_configs_are_discarded_in_dev(): void
{
$this->setupFixtures([
'db.local.config.php',
'db.dev.config.php',
'db.stg.config.php',
'db.production.config.php',
'db.config.php',
]);

$this->container->get(AppConfig::class)->environment = Environment::LOCAL;
$config = $this->container->get(LoadConfig::class)->find();

$this->assertCount(3, $config);
$this->assertStringContainsString('db.config.php', $config[0]);
$this->assertStringContainsString('db.dev.config.php', $config[1]);
$this->assertStringContainsString('db.local.config.php', $config[2]);
}

private function setupFixtures(array $configs): void
{
foreach ($configs as $config) {
$db = str_replace('.php', '.sqlite', $config);

Filesystem\write_file(__DIR__ . '/Fixtures/' . $config, <<<PHP
<?php

use Tempest\Database\Config\SQLiteConfig;

return new SQLiteConfig(
path: '{$db}',
);
PHP);
}
}
}