Skip to content

Commit

Permalink
Merge pull request #651 from hydephp/improve-the-yaml-configuration-l…
Browse files Browse the repository at this point in the history
…oader

Improve the Yaml configuration loader
  • Loading branch information
caendesilva authored Jul 4, 2024
2 parents c42ca54 + 0bb7122 commit 1ac6ce6
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 39 deletions.
75 changes: 55 additions & 20 deletions src/Foundation/Internal/LoadYamlConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,39 @@
/**
* @internal Bootstrap service that loads the YAML configuration file.
*
* @implements \LaravelZero\Framework\Contracts\BoostrapperContract [sic]
*
* @see docs/digging-deeper/customization.md#yaml-configuration
*
* It also supports loading multiple configuration namespaces, where a configuration namespace is defined
* as the first level in the service container configuration repository array, and usually corresponds
* one-to-one with a file in the config directory. This feature, by design, requires a top-level
* configuration entry to be present as 'hyde' in the YAML file. Existing config files
* will be parsed as normal, but can be migrated by indenting all entries by one
* level, and adding a top-level 'hyde' key. Then additional namespaces can
* be added underneath as needed.
* as a firs level entry in the service container configuration repository array, and corresponds
* one-to-one with a file in the config directory, and a root-level key in the YAML file.
*
* This feature, by design, requires a top-level configuration entry to be present as 'hyde' in the YAML file.
* Existing config files will be parsed as normal, but can be migrated by indenting all entries by one level,
* and adding a top-level 'hyde' key. Then additional namespaces can be added underneath as needed.
*/
class LoadYamlConfiguration
{
protected array $config;
protected array $yaml;

/**
* Performs a core task that needs to be performed on
* early stages of the framework.
*/
public function bootstrap(): void
{
if ($this->hasYamlConfigFile()) {
$this->config = Config::all();
$this->yaml = $this->parseYamlFile();

$this->supportSettingSidebarHeaderFromSiteName();
$this->supportSettingRssFeedTitleFromSiteName();

$this->mergeParsedConfiguration();

Config::set($this->config);
}
}

Expand All @@ -46,8 +59,8 @@ protected function hasYamlConfigFile(): bool
|| file_exists(Hyde::path('hyde.yaml'));
}

/** @return array<string, scalar> */
protected function getYaml(): array
/** @return array<string, scalar|array> */
protected function parseYamlFile(): array
{
return Arr::undot((array) Yaml::parse(file_get_contents($this->getFile())));
}
Expand All @@ -61,33 +74,55 @@ protected function getFile(): string

protected function mergeParsedConfiguration(): void
{
$yaml = $this->getYaml();
$yaml = $this->yaml;

// If the Yaml file contains namespaces, we merge those using more granular logic
// that only applies the namespace data to each configuration namespace.
if ($this->configurationContainsNamespaces($yaml)) {
if ($this->configurationContainsNamespaces()) {
/** @var array<string, array<string, scalar>> $yaml */
foreach ($yaml as $namespace => $data) {
$this->mergeConfiguration($namespace, Arr::undot((array) $data));
}

return;
} else {
// Otherwise, we can merge using the default strategy, which is simply applying all the data to the hyde namespace.
$this->mergeConfiguration('hyde', $yaml);
}

// Otherwise, we can merge using the default strategy, which is simply applying all the data to the hyde namespace.
$this->mergeConfiguration('hyde', $yaml);
}

protected function mergeConfiguration(string $namespace, array $yamlData): void
{
Config::set($namespace, array_merge(
Config::getArray($namespace, []),
$this->config[$namespace] = array_merge(
$this->config[$namespace] ?? [],
$yamlData
));
);
}

protected function configurationContainsNamespaces(): bool
{
return array_key_first($this->yaml) === 'hyde';
}

private function supportSettingSidebarHeaderFromSiteName(): void
{
$sidebarHeaderIsNotSetInPhpConfig = ($this->config['docs']['sidebar']['header'] ?? null) === 'HydePHP Docs';
$siteNameFromYaml = $this->configurationContainsNamespaces() ? ($this->yaml['hyde']['name'] ?? null) : ($this->yaml['name'] ?? null);

if ($sidebarHeaderIsNotSetInPhpConfig) {
if ($siteNameFromYaml !== null) {
$this->config['docs']['sidebar']['header'] = $siteNameFromYaml.' Docs';
}
}
}

protected function configurationContainsNamespaces(array $yaml): bool
private function supportSettingRssFeedTitleFromSiteName(): void
{
return array_key_first($yaml) === 'hyde';
$rssFeedTitleIsNotSetInPhpConfig = ($this->config['hyde']['rss']['description'] ?? null) === 'HydePHP RSS Feed';
$siteNameFromYaml = $this->configurationContainsNamespaces() ? ($this->yaml['hyde']['name'] ?? null) : ($this->yaml['name'] ?? null);

if ($rssFeedTitleIsNotSetInPhpConfig) {
if ($siteNameFromYaml !== null) {
$this->config['hyde']['rss']['description'] = $siteNameFromYaml.' RSS Feed';
}
}
}
}
132 changes: 113 additions & 19 deletions tests/Feature/LoadYamlConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ class LoadYamlConfigurationTest extends TestCase
{
public function testCanDefineHydeConfigSettingsInHydeYmlFile()
{
config(['hyde' => []]);

$this->file('hyde.yml', <<<'YAML'
name: HydePHP
url: "http://localhost"
Expand Down Expand Up @@ -44,9 +42,6 @@ public function testCanDefineHydeConfigSettingsInHydeYmlFile()

public function testCanDefineMultipleConfigSettingsInHydeYmlFile()
{
config(['hyde' => []]);
config(['docs' => []]);

$this->file('hyde.yml', <<<'YAML'
hyde:
name: HydePHP
Expand Down Expand Up @@ -125,15 +120,14 @@ public function testConfigurationOptionsAreMerged()

public function testCanAddConfigurationOptionsInNamespacedArray()
{
config(['hyde' => []]);

$this->file('hyde.yml', <<<'YAML'
hyde:
name: HydePHP
foo: bar
bar:
baz: qux
YAML);

$this->runBootstrapper();

$this->assertSame('HydePHP', Config::get('hyde.name'));
Expand All @@ -143,56 +137,52 @@ public function testCanAddConfigurationOptionsInNamespacedArray()

public function testCanAddArbitraryNamespacedData()
{
config(['hyde' => []]);

$this->file('hyde.yml', <<<'YAML'
hyde:
some: thing
foo:
bar: baz
YAML);

$this->runBootstrapper();

$this->assertSame('baz', Config::get('foo.bar'));
}

public function testAdditionalNamespacesRequireTheHydeNamespaceToBePresent()
{
config(['hyde' => []]);

$this->file('hyde.yml', <<<'YAML'
foo:
bar: baz
YAML);

$this->runBootstrapper();

$this->assertNull(Config::get('foo.bar'));
}

public function testAdditionalNamespacesRequiresHydeNamespaceToBeTheFirstEntry()
{
config(['hyde' => []]);

$this->file('hyde.yml', <<<'YAML'
foo:
bar: baz
hyde:
some: thing
YAML);

$this->runBootstrapper();

$this->assertNull(Config::get('foo.bar'));
}

public function testHydeNamespaceCanBeEmpty()
{
config(['hyde' => []]);

$this->file('hyde.yml', <<<'YAML'
hyde:
foo:
bar: baz
YAML);

$this->runBootstrapper();

$this->assertSame('baz', Config::get('foo.bar'));
Expand All @@ -201,27 +191,25 @@ public function testHydeNamespaceCanBeEmpty()
public function testHydeNamespaceCanBeNull()
{
// This is essentially the same as the empty state test above, at least according to the YAML spec.
config(['hyde' => []]);

$this->file('hyde.yml', <<<'YAML'
hyde: null
foo:
bar: baz
YAML);

$this->runBootstrapper();

$this->assertSame('baz', Config::get('foo.bar'));
}

public function testHydeNamespaceCanBlank()
{
config(['hyde' => []]);

$this->file('hyde.yml', <<<'YAML'
hyde: ''
foo:
bar: baz
YAML);

$this->runBootstrapper();

$this->assertSame('baz', Config::get('foo.bar'));
Expand All @@ -234,6 +222,7 @@ public function testDotNotationCanBeUsed()
$this->file('hyde.yml', <<<'YAML'
foo.bar.baz: qux
YAML);

$this->runBootstrapper();

$this->assertSame(['foo' => ['bar' => ['baz' => 'qux']]], Config::get('hyde'));
Expand All @@ -254,14 +243,119 @@ public function testDotNotationCanBeUsedWithNamespaces()
two:
foo.bar.baz: qux
YAML);

$this->runBootstrapper();

$expected = ['foo' => ['bar' => ['baz' => 'qux']]];

$this->assertSame($expected, Config::get('hyde'));
$this->assertSame($expected, Config::get('one'));
$this->assertSame($expected, Config::get('two'));
}

public function testSettingSiteNameSetsSidebarHeader()
{
$this->file('hyde.yml', <<<'YAML'
name: Example
YAML);

$this->runBootstrapper();

$this->assertSame('Example Docs', Config::get('docs.sidebar.header'));
}

public function testSettingSiteNameSetsSidebarHeaderWhenUsingHydeNamespace()
{
$this->file('hyde.yml', <<<'YAML'
hyde:
name: Example
YAML);

$this->runBootstrapper();

$this->assertSame('Example Docs', Config::get('docs.sidebar.header'));
}

public function testSettingSiteNameSetsSidebarHeaderUnlessAlreadySpecifiedInYamlConfig()
{
$this->file('hyde.yml', <<<'YAML'
hyde:
name: Example
docs:
sidebar:
header: Custom
YAML);

$this->runBootstrapper();

$this->assertSame('Custom', Config::get('docs.sidebar.header'));
}

public function testSettingSiteNameSetsSidebarHeaderUnlessAlreadySpecifiedInStandardConfig()
{
config(['docs.sidebar.header' => 'Custom']);

$this->file('hyde.yml', <<<'YAML'
hyde:
name: Example
YAML);

$this->runBootstrapper();

$this->assertSame('Custom', Config::get('docs.sidebar.header'));
}

public function testSettingSiteNameSetsRssFeedSiteName()
{
$this->file('hyde.yml', <<<'YAML'
name: Example
YAML);

$this->runBootstrapper();

$this->assertSame('Example RSS Feed', Config::get('hyde.rss.description'));
}

public function testSettingSiteNameSetsRssFeedSiteNameWhenUsingHydeNamespace()
{
$this->file('hyde.yml', <<<'YAML'
hyde:
name: Example
YAML);

$this->runBootstrapper();

$this->assertSame('Example RSS Feed', Config::get('hyde.rss.description'));
}

public function testSettingSiteNameSetsRssFeedSiteNameUnlessAlreadySpecifiedInYamlConfig()
{
$this->file('hyde.yml', <<<'YAML'
hyde:
name: Example
rss:
description: Custom
YAML);

$this->runBootstrapper();

$this->assertSame('Custom', Config::get('hyde.rss.description'));
}

public function testSettingSiteNameSetsRssFeedSiteNameUnlessAlreadySpecifiedInStandardConfig()
{
config(['hyde.rss.description' => 'Custom']);

$this->file('hyde.yml', <<<'YAML'
hyde:
name: Example
YAML);

$this->runBootstrapper();

$this->assertSame('Custom', Config::get('hyde.rss.description'));
}

protected function runBootstrapper(): void
{
$this->app->bootstrapWith([LoadYamlConfiguration::class]);
Expand Down

0 comments on commit 1ac6ce6

Please sign in to comment.