Skip to content

Instantly share code, notes, and snippets.

@dazz
Last active April 11, 2026 20:50
Show Gist options
  • Select an option

  • Save dazz/9951e311cf34fd50f540dad214264251 to your computer and use it in GitHub Desktop.

Select an option

Save dazz/9951e311cf34fd50f540dad214264251 to your computer and use it in GitHub Desktop.
Shared Configuration

Simple Shared Project Configuration

A lightweight, type-safe configuration wrapper for Symfony applications. It provides dot-notation access to validated configuration values with runtime type assertions.

Problem

Symfony's parameter bag returns mixed values without type guarantees. Accessing nested configuration requires verbose array key handling and manual type checks scattered across the codebase.

Solution

Configuration wraps a single validated parameter array (dazzhub) and exposes it through a clean API with dot-notation paths and ConfigValueType assertions.

Files

File Purpose
Configuration.php Read-only service with has(), get(), and all() methods
ConfigValueType.php Backed enum for type assertions (STRING, INT, FLOAT, BOOL, ARRAY)
dazzhub_preferences.yaml Configuration values under the dazzhub parameter
services.yaml Imports the preferences file

Usage

// Injected via autowiring — no manual setup needed
public function __construct(
    private readonly Configuration $config,
) {}

// Read a string (default type)
$model = $this->config->get('assessment.options.model');

// Read with explicit type assertion
$temp = $this->config->get('assessment.options.temperature', ConfigValueType::FLOAT);

// Check existence
if ($this->config->has('assessment.options.temperature')) { /* ... */ }

// With default value
$timeout = $this->config->get('http.timeout', ConfigValueType::INT, 30);

// Get everything
$all = $this->config->all();

How It Works

  • The dazzhub parameter is injected via #[Autowire(param: 'dazzhub')].
  • Dot-notation paths (e.g. a.b.c) are converted to Symfony PropertyAccess syntax ([a][b][c]).
  • get() asserts the retrieved value matches the expected ConfigValueType using webmozart/assert. If the path doesn't exist and no default was provided, an OutOfBoundsException is thrown.

Requirements

  • PHP 8.2+
  • Symfony 7.x (symfony/property-access, symfony/dependency-injection)
  • webmozart/assert

Configuration

Define your values in dazzhub_preferences.yaml:

parameters:
    dazzhub:
        pi: 3.14
        assessment:
            options:
                model: "gpt-4"
                temperature: 0.7

Import it in services.yaml:

imports:
    - { resource: dazzhub_preferences.yaml }

The Configuration service is auto-registered via Symfony's autowiring — no additional service definition required.

<?php
declare(strict_types=1);
namespace App\Shared\Configuration;
use Exception;
use OutOfBoundsException;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Webmozart\Assert\Assert;
final readonly class Configuration
{
/**
* @param array $config the complete validated configuration array
*/
public function __construct(
#[Autowire(param: 'dazzhub')]
private array $config,
) {}
/**
* Check if a configuration value exists at the given path.
* Uses dot-notation (e.g. 'assessment.options.temperature').
*/
public function has(string $path): bool
{
// Simple check for top-level keys
if (\array_key_exists($path, $this->config)) {
return true;
}
// For nested paths we use PropertyAccess which handles dot-notation well
try {
$accessor = PropertyAccess::createPropertyAccessor();
// PropertyAccess requires the path in the form '[key1][key2]'
$symfonyPath = '[' . \str_replace('.', '][', $path) . ']';
// If access fails, the path doesn't exist
$accessor->getValue($this->config, $symfonyPath);
return true;
} catch (Exception $e) {
return false;
}
}
/**
* Retrieve a configuration value.
* Uses dot-notation (e.g. 'assessment.options.temperature').
*
* @param string $path the path in dot-notation
* @param ConfigValueType $type expected type for assertion (rarely needed for strings)
* @param mixed $default optional default value (use sparingly, only when needed)
*
* @throws OutOfBoundsException if the path doesn't exist and no default is provided
*/
public function get(string $path, ConfigValueType $type = ConfigValueType::STRING, mixed $default = null): mixed
{
if ($this->has($path)) {
$accessor = PropertyAccess::createPropertyAccessor();
$symfonyPath = '[' . \str_replace('.', '][', $path) . ']';
// Since we already checked with 'has()', this should succeed
$value = $accessor->getValue($this->config, $symfonyPath);
match ($type) {
ConfigValueType::FLOAT => Assert::float($value, \sprintf('Configuration value at "%s" must be float, got %s', $path, \get_debug_type($value))),
ConfigValueType::INT => Assert::integer($value, \sprintf('Configuration value at "%s" must be int, got %s', $path, \get_debug_type($value))),
ConfigValueType::STRING => Assert::string($value, \sprintf('Configuration value at "%s" must be string, got %s', $path, \get_debug_type($value))),
ConfigValueType::ARRAY => Assert::isArray($value, \sprintf('Configuration value at "%s" must be array, got %s', $path, \get_debug_type($value))),
ConfigValueType::BOOL => Assert::boolean($value, \sprintf('Configuration value at "%s" must be bool, got %s', $path, \get_debug_type($value))),
};
return $value;
}
// If more than 2 arguments were passed, a default was provided
if (\func_num_args() > 2) {
return $default;
}
throw new OutOfBoundsException(\sprintf('The configuration key "%s" does not exist.', $path));
}
/**
* Return the entire configuration array.
*/
public function all(): array
{
return $this->config;
}
}
<?php
declare(strict_types=1);
namespace App\Shared\Configuration;
/**
* Enum for configuration value types used in type assertions.
*/
enum ConfigValueType: string
{
case FLOAT = 'float';
case INT = 'int';
case STRING = 'string';
case ARRAY = 'array';
case BOOL = 'bool';
}
---
parameters:
dazzhub:
pi: 3.14
assessment:
options:
model: "gpt-4"
temperature: 0.7
imports:
- { resource: dazzhub_preferences.yaml }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment