Skip to content
Open
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
1 change: 0 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

name: build

on:
Expand Down
87 changes: 53 additions & 34 deletions src/Boot/src/Bootloader/ConfigurationBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,63 +6,82 @@

use Psr\Container\ContainerInterface;
use Spiral\Boot\DirectoriesInterface;
use Spiral\Boot\EnvironmentInterface;
use Spiral\Config\ConfigManager;
use Spiral\Config\ConfiguratorInterface;
use Spiral\Config\Loader\DirectoryLoader;
use Spiral\Config\Loader\ConfigsMergerInterface;
use Spiral\Config\Loader\DirectoriesRepository;
use Spiral\Config\Loader\DirectoriesRepositoryInterface;
use Spiral\Config\Loader\FileLoaderInterface;
use Spiral\Config\Loader\FileLoaderRegistry;
use Spiral\Config\Loader\JsonLoader;
use Spiral\Config\Loader\MergeFileStrategyLoader;
use Spiral\Config\Loader\PhpLoader;
use Spiral\Core\BinderInterface;
use Spiral\Config\Loader\RecursiveConfigMerger;
use Spiral\Config\Loader\SingleFileStrategyLoader;
use Spiral\Config\LoaderInterface;
use Spiral\Core\ConfigsInterface;

/**
* Bootloads core services.
* Boot core service responsible for configuration loading and management.
*/
final class ConfigurationBootloader extends Bootloader
{
protected const SINGLETONS = [
// configuration
ConfigsInterface::class => ConfiguratorInterface::class,
ConfiguratorInterface::class => ConfigManager::class,
ConfigManager::class => [self::class, 'configManager'],
];
public function __construct(
private readonly ContainerInterface $container,
) {}

private readonly ConfiguratorInterface $configurator;
public function defineSingletons(): array
{
return [
ConfigsInterface::class => ConfiguratorInterface::class,
ConfiguratorInterface::class => ConfigManager::class,
FileLoaderRegistry::class => $this->createFileLoader(...),
ConfigManager::class => $this->createConfigManager(...),
ConfigsMergerInterface::class => RecursiveConfigMerger::class,
LoaderInterface::class => $this->createConfigLoader(...),
DirectoriesRepositoryInterface::class => $this->createDirectories(...),
];
}

/** @var FileLoaderInterface[] */
private array $loaders;
private function createConfigLoader(EnvironmentInterface $env): LoaderInterface
{
return match ($env->get('CONFIG_STRATEGY', 'single')) {
'merge' => $this->container->get(MergeFileStrategyLoader::class),
default => $this->container->get(SingleFileStrategyLoader::class),
};
}

public function __construct(
ContainerInterface $container,
private readonly DirectoriesInterface $directories,
private readonly BinderInterface $binder,
) {
$this->loaders = [
'php' => $container->get(PhpLoader::class),
'json' => $container->get(JsonLoader::class),
];
private function createDirectories(DirectoriesInterface $directories): DirectoriesRepositoryInterface
{
return new DirectoriesRepository([
$directories->get('config'),
]);
}

$this->configurator = $this->createConfigManager();
private function createFileLoader(PhpLoader $phpLoader, JsonLoader $jsonLoader): FileLoaderRegistry
{
return new FileLoaderRegistry([
'php' => $phpLoader,
'json' => $jsonLoader,
]);
}

public function addLoader(string $ext, FileLoaderInterface $loader): void
public function setDirectories(array $directories): void
{
if (!isset($this->loaders[$ext]) || $loader::class !== $this->loaders[$ext]::class) {
$this->loaders[$ext] = $loader;
$this->binder->bindSingleton(ConfigManager::class, $this->createConfigManager());
}
$this->container->get(DirectoriesRepositoryInterface::class)->setDirectories($directories);
}

private function createConfigManager(): ConfigManager
public function addLoader(string $ext, FileLoaderInterface $loader): void
{
return new ConfigManager(
new DirectoryLoader($this->directories->get('config'), $this->loaders),
true,
);
$this->container->get(FileLoaderRegistry::class)->register($ext, $loader);
}

private function configManager(): ConfiguratorInterface
private function createConfigManager(LoaderInterface $loader): ConfigManager
{
return $this->configurator;
return new ConfigManager(
loader: $loader,
strict: true,
);
}
}
52 changes: 47 additions & 5 deletions src/Boot/tests/ConfigsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,71 @@

namespace Spiral\Tests\Boot;

use Spiral\Boot\DirectoriesInterface;
use Spiral\Config\ConfiguratorInterface;
use Spiral\Config\Loader\DirectoriesRepositoryInterface;
use Spiral\Tests\Boot\Fixtures\FooConfig;
use Spiral\Tests\Boot\Fixtures\TestConfig;
use Spiral\Tests\Boot\Fixtures\TestCore;
use Traversable;

class ConfigsTest extends TestCase
{
public function testDirectories(): void
{
$core = TestCore::create([
'root' => __DIR__,
'root' => __DIR__,
'config' => __DIR__ . '/config',
])->run();

/** @var TestConfig $config */
$config = $core->getContainer()->get(TestConfig::class);
/** @var TestConfig $testConfig */
$testConfig = $core->getContainer()->get(TestConfig::class);

self::assertSame(['key' => 'value'], $config->toArray());
/** @var FooConfig $fooConfig */
$fooConfig = $core->getContainer()->get(FooConfig::class);

self::assertSame(['key' => 'value1'], $testConfig->toArray());
self::assertSame(['key' => 'value'], $fooConfig->toArray());
}

public function testCustomDirectoriesRepository(): void
{
$core = TestCore::create([
'root' => __DIR__,
'config' => __DIR__ . '/config',
])->run();

$core->getContainer()->bindSingleton(
DirectoriesRepositoryInterface::class,
static function (DirectoriesInterface $dirs): DirectoriesRepositoryInterface {
return new class($dirs->get('config')) implements DirectoriesRepositoryInterface {
public function __construct(
private string $rootDir,
) {}


public function getIterator(): Traversable
{
yield $this->rootDir;
}
};
},
);

/** @var TestConfig $testConfig */
$testConfig = $core->getContainer()->get(TestConfig::class);

/** @var FooConfig $fooConfig */
$fooConfig = $core->getContainer()->get(FooConfig::class);

self::assertSame(['key' => 'value'], $testConfig->toArray());
self::assertSame(['key' => 'value'], $fooConfig->toArray());
}

public function testCustomConfigLoader(): void
{
$core = TestCore::create([
'root' => __DIR__,
'root' => __DIR__,
'config' => __DIR__ . '/config',
])->run();

Expand Down
13 changes: 11 additions & 2 deletions src/Boot/tests/Fixtures/ConfigBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Boot\Bootloader\ConfigurationBootloader;
use Spiral\Boot\Bootloader\CoreBootloader;
use Spiral\Boot\DirectoriesInterface;
use Spiral\Core\Container;

class ConfigBootloader extends Bootloader
Expand Down Expand Up @@ -41,8 +42,12 @@ public function init(Container $container, AbstractKernel $kernel): void
$container->bind('efg', 'foo');
}

public function boot(ConfigurationBootloader $configuration, AbstractKernel $kernel, Container $container): void
{
public function boot(
ConfigurationBootloader $configuration,
DirectoriesInterface $directories,
AbstractKernel $kernel,
Container $container,
): void {
// won't be executed
$kernel->booting(static function (AbstractKernel $kernel) use ($container): void {
$container->bind('ghi', 'foo');
Expand All @@ -53,5 +58,9 @@ public function boot(ConfigurationBootloader $configuration, AbstractKernel $ker
});

$configuration->addLoader('yaml', $container->get(TestLoader::class));
$configuration->setDirectories([
$directories->get('config') . '/prod',
$directories->get('config'),
]);
}
}
12 changes: 12 additions & 0 deletions src/Boot/tests/Fixtures/FooConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Spiral\Tests\Boot\Fixtures;

use Spiral\Core\InjectableConfig;

class FooConfig extends InjectableConfig
{
public const CONFIG = 'foo';
}
2 changes: 1 addition & 1 deletion src/Boot/tests/FunctionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function testSpiral(): void
$c = $core->getContainer();

ContainerScope::runScope($c, static function (): void {
self::assertSame(['key' => 'value'], spiral(TestConfig::class)->toArray());
self::assertSame(['key' => 'value1'], spiral(TestConfig::class)->toArray());
});
}

Expand Down
5 changes: 5 additions & 0 deletions src/Boot/tests/config/foo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

declare(strict_types=1);

return ['key' => 'value'];
5 changes: 5 additions & 0 deletions src/Boot/tests/config/prod/test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

declare(strict_types=1);

return ['key' => 'value1'];
1 change: 1 addition & 0 deletions src/Config/src/ConfigManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* Load config files, provides container injection and modifies config data on
* bootloading.
*
* @internal
* @implements ConfiguratorInterface<object>
*/
#[Singleton]
Expand Down
14 changes: 14 additions & 0 deletions src/Config/src/Loader/ConfigsMergerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Spiral\Config\Loader;

interface ConfigsMergerInterface
{
/**
* @param array ...$config
* @return array
*/
public function merge(array ...$config): array;
}
42 changes: 42 additions & 0 deletions src/Config/src/Loader/DirectoriesRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Spiral\Config\Loader;

use Traversable;

/**
* @internal
*/
final class DirectoriesRepository implements DirectoriesRepositoryInterface
{
/** @var string[] */
private array $directories;

/**
* @param string[] $directories
*/
public function __construct(array $directories)
{
$this->setDirectories($directories);
}

public function setDirectories(array $directories): void
{
$this->directories = \array_map(
static fn(string $dir): string => \rtrim($dir, '/'),
$directories,
);
}

public function addDirectory(string $directory): void
{
$this->directories[] = \rtrim($directory, '/');
}

public function getIterator(): Traversable
{
return new \ArrayIterator($this->directories);
}
}
10 changes: 10 additions & 0 deletions src/Config/src/Loader/DirectoriesRepositoryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Spiral\Config\Loader;

/**
* @extends \IteratorAggregate<array-key, string>
*/
interface DirectoriesRepositoryInterface extends \IteratorAggregate {}
4 changes: 4 additions & 0 deletions src/Config/src/Loader/DirectoryLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
use Spiral\Config\Exception\LoaderException;
use Spiral\Config\LoaderInterface;

/**
* @internal
* @deprecated Use {@see SingleFileStrategyLoader} instead. Will be removed in 4.0.
*/
final class DirectoryLoader implements LoaderInterface
{
private readonly string $directory;
Expand Down
39 changes: 39 additions & 0 deletions src/Config/src/Loader/FileLoaderRegistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Spiral\Config\Loader;

use Spiral\Core\Attribute\Singleton;

/**
* @internal
*/
#[Singleton]
final class FileLoaderRegistry
{
public function __construct(
/**@var array<non-empty-string, FileLoaderInterface> */
private array $loaders = [],
) {}

/**
* @param non-empty-string $ext
*/
public function register(string $ext, FileLoaderInterface $loader): void
{
if (!isset($this->loaders[$ext]) || $this->loaders[$ext]::class !== $loader::class) {
$this->loaders[$ext] = $loader;
}
}

public function getExtensions(): array
{
return \array_keys($this->loaders);
}

public function getLoader(string $ext): FileLoaderInterface
{
return $this->loaders[$ext];
}
}
Loading
Loading