Skip to content

Commit

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

Improve the Yaml configuration file feature to support multiple config namespaces
  • Loading branch information
caendesilva authored Mar 22, 2023
2 parents c63ee87 + f002564 commit afbe691
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 11 deletions.
23 changes: 23 additions & 0 deletions docs/digging-deeper/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -440,3 +440,26 @@ rss:
language: en
output_directory: _site
```
### Namespaced YAML Configuration
If you are running `v1.2` or higher, you can also use namespaced configuration options in the YAML file.

This allows you to set the settings of **any** configuration file normally found in the `config` directory.

This feature is automatically enabled when you have a `hyde:` entry **first** in your `hyde.yml` file

```yaml
# filepath hyde.yml
hyde:
name: HydePHP
docs:
sidebar:
header: "My Docs"
```

This would set the `name` setting in the `config/hyde.php` file, and the `sidebar.header` setting in the `config/docs.php` file.

Each top level key in the YAML file is treated as a namespace, and the settings are set in the corresponding configuration file.
You can of course use arrays like normal even in namespaced configuration.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Hyde\Facades\Config;
use Symfony\Component\Yaml\Yaml;

use function array_key_first;
use function file_get_contents;
use function array_merge;
use function file_exists;
Expand All @@ -16,6 +17,14 @@
* @internal Bootstrap service that loads the YAML configuration file.
*
* @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.
*/
class LoadYamlConfiguration
{
Expand All @@ -36,14 +45,6 @@ protected function hasYamlConfigFile(): bool
|| file_exists(Hyde::path('hyde.yaml'));
}

protected function mergeParsedConfiguration(): void
{
Config::set('hyde', array_merge(
Config::getArray('hyde', []),
$this->getYaml()
));
}

protected function getYaml(): array
{
return (array) Yaml::parse(file_get_contents($this->getFile()));
Expand All @@ -55,4 +56,35 @@ protected function getFile(): string
? Hyde::path('hyde.yml')
: Hyde::path('hyde.yaml');
}

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

// 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)) {
foreach ($yaml as $namespace => $data) {
$this->mergeConfiguration($namespace, (array) $data);
}

return;
}

// 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, []),
$yamlData
));
}

protected function configurationContainsNamespaces(array $yaml): bool
{
return array_key_first($yaml) === 'hyde';
}
}
129 changes: 126 additions & 3 deletions packages/framework/tests/Feature/LoadYamlConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@

namespace Hyde\Framework\Testing\Feature;

use Hyde\Foundation\Internal\LoadYamlConfiguration;
use Hyde\Testing\TestCase;
use Hyde\Foundation\Internal\LoadYamlConfiguration;
use Illuminate\Support\Facades\Config;

use function config;

/**
* @covers \Hyde\Foundation\Internal\LoadYamlConfiguration
*/
Expand Down Expand Up @@ -44,6 +42,27 @@ public function testCanDefineHydeConfigSettingsInHydeYmlFile()
$this->assertSame('_site', Config::get('hyde.output_directory'));
}

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

$this->file('hyde.yml', <<<'YAML'
hyde:
name: HydePHP
url: "http://localhost"
docs:
sidebar:
header: "My Docs"
YAML);

$this->runBootstrapper();

$this->assertSame('HydePHP', Config::get('hyde.name'));
$this->assertSame('http://localhost', Config::get('hyde.url'));
$this->assertSame('My Docs', Config::get('docs.sidebar.header'));
}

public function testBootstrapperAppliesYamlConfigurationWhenPresent()
{
$this->file('hyde.yml', 'name: Foo');
Expand Down Expand Up @@ -104,6 +123,110 @@ public function testConfigurationOptionsAreMerged()
$this->assertSame('bar', Config::get('hyde.foo'));
}

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'));
$this->assertSame('bar', Config::get('hyde.foo'));
$this->assertSame('qux', Config::get('hyde.bar.baz'));
}

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'));
}

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'));
}

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

0 comments on commit afbe691

Please sign in to comment.