Skip to content

Commit 592d5f5

Browse files
committed
efactor(configurator): Rename Config to Configuration and improve overall design
- Rename main class from Config to Configuration for clarity - Implement strict typing and improve type hints - Add auto-validation with new Validator interface and AutoValidator class - Refactor to better adhere to SOLID principles - Improve dependency injection and separation of concerns - Optimize performance with PHP 8 features (match expression) - Enhance extensibility for custom validation rules
1 parent 3e4dd49 commit 592d5f5

14 files changed

+156
-72
lines changed
File renamed without changes.

src/Config.php renamed to src/Configuration.php

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,50 @@
44

55
namespace KaririCode\Configurator;
66

7-
use KaririCode\Configurator\Contract\Configurator\ConfigManager;
7+
use KaririCode\Configurator\Contract\Configurator\ConfigurationManager;
88
use KaririCode\Configurator\Contract\Configurator\Loader;
99
use KaririCode\Configurator\Contract\Configurator\MergeStrategy;
1010
use KaririCode\Configurator\Contract\Configurator\Storage;
11-
use KaririCode\Configurator\Exception\ConfigException;
11+
use KaririCode\Configurator\Contract\Validator\Validator;
12+
use KaririCode\Configurator\Exception\ConfigurationException;
13+
use KaririCode\Configurator\MergeStrategy\OverwriteMerge;
14+
use KaririCode\Configurator\Storage\TreeMapStorage;
15+
use KaririCode\Configurator\Validator\AutoValidator;
1216

13-
final class Config implements ConfigManager
17+
final class Configuration implements ConfigurationManager
1418
{
1519
/**
1620
* @var array<string, Loader>
1721
*/
1822
private array $loaders = [];
1923

2024
public function __construct(
21-
private readonly Storage $storage,
22-
private readonly MergeStrategy $mergeStrategy
25+
private readonly Storage $storage = new TreeMapStorage(),
26+
private readonly Validator $validator = new AutoValidator(),
27+
private readonly MergeStrategy $mergeStrategy = new OverwriteMerge(),
2328
) {
2429
}
2530

2631
public function load(string $path): void
2732
{
2833
$loader = $this->getLoaderForFile($path);
2934
$config = $loader->load($path);
30-
$this->mergeStrategy->merge($this->storage, $config);
35+
$prefix = pathinfo($path, PATHINFO_FILENAME);
36+
37+
$this->validateConfig($config, $prefix);
38+
$this->loadRecursive($config, $prefix);
39+
}
40+
41+
private function loadRecursive(array $config, string $prefix = ''): void
42+
{
43+
foreach ($config as $key => $value) {
44+
$fullKey = $prefix ? $prefix . '.' . $key : $key;
45+
if (is_array($value)) {
46+
$this->loadRecursive($value, $fullKey);
47+
} else {
48+
$this->storage->set($fullKey, $value);
49+
}
50+
}
3151
}
3252

3353
public function loadDirectory(string $directory): void
@@ -60,40 +80,39 @@ public function all(): array
6080

6181
public function registerLoader(Loader $loader): void
6282
{
63-
$this->loaders[$loader->getType()] = $loader;
83+
$types = $loader->getTypes();
84+
foreach ($types as $type) {
85+
$this->loaders[$type] = $loader;
86+
}
6487
}
6588

66-
/**
67-
* Get the appropriate loader for a given file.
68-
*
69-
* @throws ConfigException
70-
*/
7189
private function getLoaderForFile(string $path): Loader
7290
{
7391
$extension = pathinfo($path, PATHINFO_EXTENSION);
7492
if (!isset($this->loaders[$extension])) {
75-
throw new ConfigException("No loader registered for file type: {$extension}");
93+
throw new ConfigurationException("No loader registered for file type: {$extension}");
7694
}
7795

7896
return $this->loaders[$extension];
7997
}
8098

81-
/**
82-
* Get all configuration files from a directory.
83-
*
84-
* @throws ConfigException
85-
*
86-
* @return array<string>
87-
*/
8899
private function getConfigFilesFromDirectory(string $directory): array
89100
{
90101
if (!is_dir($directory)) {
91-
throw new ConfigException("Directory not found: {$directory}");
102+
throw new ConfigurationException("Directory not found: {$directory}");
92103
}
93104

94105
return array_filter(
95106
glob($directory . '/*') ?: [],
96107
fn ($file) => is_file($file) && in_array(pathinfo($file, PATHINFO_EXTENSION), array_keys($this->loaders), true)
97108
);
98109
}
110+
111+
private function validateConfig(array $config, string $prefix = ''): void
112+
{
113+
foreach ($config as $key => $value) {
114+
$fullKey = $prefix ? "$prefix.$key" : $key;
115+
$this->validator->validate($value, $fullKey);
116+
}
117+
}
99118
}

src/Contract/Configurator/ConfigManager.php renamed to src/Contract/Configurator/ConfigurationManager.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44

55
namespace KaririCode\Configurator\Contract\Configurator;
66

7-
/**
8-
* Interface for configuration management.
9-
*/
10-
interface ConfigManager
7+
interface ConfigurationManager
118
{
129
/**
1310
* Load configuration from a file.

src/Contract/Configurator/Loader.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ public function load(string $path): array;
1616
/**
1717
* Get the file type this loader handles.
1818
*/
19-
public function getType(): string;
19+
public function getTypes(): array;
2020
}

src/Contract/Validator/Validator.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace KaririCode\Configurator\Contract\Validator;
6+
7+
interface Validator
8+
{
9+
public function validate(mixed $value, string $key): void;
10+
}

src/Exception/ConfigException.php renamed to src/Exception/ConfigurationException.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44

55
namespace KaririCode\Configurator\Exception;
66

7-
class ConfigException extends \Exception
7+
class ConfigurationException extends \Exception
88
{
99
}

src/Loader/FileLoader.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,23 @@
55
namespace KaririCode\Configurator\Loader;
66

77
use KaririCode\Configurator\Contract\Configurator\Loader;
8-
use KaririCode\Configurator\Exception\ConfigException;
8+
use KaririCode\Configurator\Exception\ConfigurationException;
99

1010
abstract class FileLoader implements Loader
1111
{
1212
/**
1313
* Validate that the file exists and is readable.
1414
*
15-
* @throws ConfigException
15+
* @throws ConfigurationException
1616
*/
1717
protected function validateFile(string $path): void
1818
{
1919
if (!file_exists($path)) {
20-
throw new ConfigException("Configuration file not found: {$path}");
20+
throw new ConfigurationException("Configuration file not found: {$path}");
2121
}
2222

2323
if (!is_readable($path)) {
24-
throw new ConfigException("Configuration file is not readable: {$path}");
24+
throw new ConfigurationException("Configuration file is not readable: {$path}");
2525
}
2626
}
2727
}

src/Loader/JsonLoader.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace KaririCode\Configurator\Loader;
66

7-
use KaririCode\Configurator\Exception\ConfigException;
7+
use KaririCode\Configurator\Exception\ConfigurationException;
88

99
/**
1010
* Loader for JSON configuration files.
@@ -18,20 +18,20 @@ public function load(string $path): array
1818
$content = file_get_contents($path);
1919

2020
if (false === $content) {
21-
throw new ConfigException("Failed to read JSON file: {$path}");
21+
throw new ConfigurationException("Failed to read JSON file: {$path}");
2222
}
2323

2424
$config = json_decode($content, true);
2525

2626
if (JSON_ERROR_NONE !== json_last_error()) {
27-
throw new ConfigException('Failed to parse JSON file: ' . json_last_error_msg());
27+
throw new ConfigurationException('Failed to parse JSON file: ' . json_last_error_msg());
2828
}
2929

3030
return $config;
3131
}
3232

33-
public function getType(): string
33+
public function getTypes(): array
3434
{
35-
return 'json';
35+
return ['json'];
3636
}
3737
}

src/Loader/PhpLoader.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace KaririCode\Configurator\Loader;
66

7-
use KaririCode\Configurator\Exception\ConfigException;
7+
use KaririCode\Configurator\Exception\ConfigurationException;
88

99
class PhpLoader extends FileLoader
1010
{
@@ -15,14 +15,14 @@ public function load(string $path): array
1515
$config = require $path;
1616

1717
if (!is_array($config)) {
18-
throw new ConfigException("PHP configuration file must return an array: {$path}");
18+
throw new ConfigurationException("PHP configuration file must return an array: {$path}");
1919
}
2020

2121
return $config;
2222
}
2323

24-
public function getType(): string
24+
public function getTypes(): array
2525
{
26-
return 'php';
26+
return ['php'];
2727
}
2828
}

src/Loader/YamlLoader.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace KaririCode\Configurator\Loader;
66

7-
use KaririCode\Configurator\Exception\ConfigException;
7+
use KaririCode\Configurator\Exception\ConfigurationException;
88

99
/**
1010
* Loader for YAML configuration files.
@@ -16,20 +16,20 @@ public function load(string $path): array
1616
$this->validateFile($path);
1717

1818
if (!extension_loaded('yaml')) {
19-
throw new ConfigException('YAML extension is not loaded');
19+
throw new ConfigurationException('YAML extension is not loaded');
2020
}
2121

2222
$config = yaml_parse_file($path);
2323

2424
if (false === $config) {
25-
throw new ConfigException("Failed to parse YAML file: {$path}");
25+
throw new ConfigurationException("Failed to parse YAML file: {$path}");
2626
}
2727

2828
return $config;
2929
}
3030

31-
public function getType(): string
31+
public function getTypes(): array
3232
{
33-
return 'yaml';
33+
return ['yml', 'yaml'];
3434
}
3535
}

src/MergeStrategy/StrictMerge.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66

77
use KaririCode\Configurator\Contract\Configurator\MergeStrategy;
88
use KaririCode\Configurator\Contract\Configurator\Storage;
9-
use KaririCode\Configurator\Exception\ConfigException;
9+
use KaririCode\Configurator\Exception\ConfigurationException;
1010

1111
class StrictMerge implements MergeStrategy
1212
{
1313
public function merge(Storage $storage, array $newConfig): void
1414
{
1515
foreach ($newConfig as $key => $value) {
1616
if ($storage->has($key)) {
17-
throw new ConfigException("Configuration key '{$key}' already exists and cannot be overwritten in strict mode.");
17+
throw new ConfigurationException("Configuration key '{$key}' already exists and cannot be overwritten in strict mode.");
1818
}
1919
$storage->set($key, $value);
2020
}

src/Storage/TreeMapStorage.php

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,17 @@ public function __construct(
1414
) {
1515
}
1616

17-
public function get(string $key, mixed $default = null): TreeMap
17+
public function get(string $key, mixed $default = null): mixed
1818
{
1919
$keys = explode('.', $key);
2020
$current = $this->treeMap;
2121

22-
foreach ($keys as $subKey) {
23-
if (!$current->containsKey($subKey)) {
22+
foreach ($keys as $_ => $subKey) {
23+
if (!$current instanceof TreeMap || !$current->containsKey($subKey)) {
2424
return $default;
2525
}
26+
2627
$current = $current->get($subKey);
27-
if (!$current instanceof TreeMap) {
28-
return $current;
29-
}
3028
}
3129

3230
return $current;
@@ -41,7 +39,7 @@ public function set(string $key, mixed $value): void
4139
if ($i === count($keys) - 1) {
4240
$current->put($subKey, $value);
4341
} else {
44-
if (!$current->containsKey($subKey) || !$current->get($subKey) instanceof TreeMap) {
42+
if (!$current->containsKey($subKey)) {
4543
$current->put($subKey, new TreeMap());
4644
}
4745
$current = $current->get($subKey);
@@ -72,11 +70,6 @@ public function all(): array
7270
return $this->flattenTreeMap($this->treeMap);
7371
}
7472

75-
/**
76-
* Flatten a TreeMap into an associative array.
77-
*
78-
* @return array<string, mixed>
79-
*/
8073
private function flattenTreeMap(TreeMap $treeMap, string $prefix = ''): array
8174
{
8275
$result = [];

src/Validator/AutoValidator.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace KaririCode\Configurator\Validator;
6+
7+
use KaririCode\Configurator\Contract\Validator\Validator;
8+
use KaririCode\Configurator\Exception\ConfigurationException;
9+
10+
class AutoValidator implements Validator
11+
{
12+
public function validate(mixed $value, string $key): void
13+
{
14+
$type = $this->detectType($value);
15+
$this->validateByType($value, $type, $key);
16+
}
17+
18+
private function detectType(mixed $value): string
19+
{
20+
return match (true) {
21+
is_bool($value) => 'boolean',
22+
is_int($value) => 'integer',
23+
is_float($value) => 'float',
24+
is_string($value) => 'string',
25+
is_array($value) => 'array',
26+
is_null($value) => 'null',
27+
default => 'mixed',
28+
};
29+
}
30+
31+
private function validateByType(mixed $value, string $type, string $key): void
32+
{
33+
$typeValidators = [
34+
'boolean' => fn ($v): bool => is_bool($v),
35+
'integer' => fn ($v): bool => is_int($v),
36+
'float' => fn ($v): bool => is_float($v),
37+
'string' => fn ($v): bool => is_string($v),
38+
'array' => fn ($v): bool => is_array($v),
39+
'null' => fn ($v): bool => is_null($v),
40+
];
41+
42+
if (isset($typeValidators[$type]) && !$typeValidators[$type]($value)) {
43+
throw new ConfigurationException("Configuration '$key' must be a $type.");
44+
}
45+
46+
if ('array' === $type && is_array($value)) {
47+
foreach ($value as $subKey => $subValue) {
48+
$this->validate($subValue, "$key.$subKey");
49+
}
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)