sandrokeil/interop-config
Framework-agnostic PHP library for configuration-driven factories. Validates config structure, merges defaults, enforces required options, reduces factory boilerplate, supports auto-discovery of factories, and can generate configuration files from factory classes.
Purpose Alignment:
The package (interop-config) remains a strong fit for Laravel’s ecosystem, particularly with PHP 8 support in v2.2.0, which aligns with Laravel 9+ and PHP 8.x/8.1/8.2. The addition of PHP 8 features (e.g., union types, named arguments) can enhance type safety and modernize the package’s integration with Laravel’s service container and dependency injection. The package’s factory-based validation and mandatory option enforcement continue to complement Laravel’s native config system by introducing runtime schema validation, reducing runtime errors in complex configurations (e.g., third-party APIs, microservices, or multi-tenant setups).
Separation of Concerns:
The package’s focus on factory-based instantiation with validation remains aligned with Laravel’s ServiceProvider bootstrapping. PHP 8’s improvements (e.g., attributes, constructor property promotion) could further refine how factories are defined and resolved. For example:
@ConfigSchema).class MyServiceFactory {
public function __construct(
private readonly ConfigValidatorInterface $validator,
) {}
public function create(array $config): MyService {
$this->validator->validate($config);
return new MyService($config['api_key'], $config['timeout']);
}
}
Laravel-Specific Gaps: The package still bridges critical gaps in Laravel’s native config system, such as:
required fields in config/*.php).string|int $timeout).Core Compatibility:
$this->app->bind(MyServiceFactory::class);
$this->app->bind(MyService::class, fn($c) => $c->make(MyServiceFactory::class)->create($c['config']['services.my_service']));
Potential Friction Points:
config:cache. Mitigation:
never return type in validators to enforce exhaustive validation:
public function validate(array $config): never {
if (!isset($config['required_field'])) {
throw new ConfigException('Missing required field');
}
// ...
}
ConfigCache decorator).#[Deprecated]) can ease migration:
#[Deprecated('Use interop-config instead', '2023-01-01')]
function oldConfigHelper() { ... }
php -d opcache.jit_buffer_size=100M artisan config:benchmark
Technical Risk:
| Risk Area | Severity | Mitigation Strategy |
|---|---|---|
| Schema Drift | High | Use PHP 8’s attributes to version schemas (e.g., @SchemaVersion("2.0")). |
| Service Container Pollution | Medium | Scope bindings with PHP 8’s readonly properties and contextual binding (Laravel 10+). |
| Validation Overhead | Low | Optimize with lazy validation (e.g., validate only when config changes). |
| Dependency Bloat | Low | Package remains lightweight; PHP 8 reduces LOC via constructor promotion. |
| Testing Complexity | Medium | Use PHP 8’s data provider attributes (#[DataProvider]) for validation tests. |
readonly properties)?final readonly class MyServiceConfig {
public function __construct(
public string $apiKey,
public int $timeout,
) {}
}
array<string, int|string> $mapping)?app:resolve (Laravel 10+) for contextual configs?$config = app()->resolve(ConfigurableInterface::class, ['key' => 'my_service']);
ConfigException leverage PHP 8’s exception interfaces (e.g., Throwable) for better error handling?array_key_first/array_key_last for config traversal?Laravel Ecosystem Synergy:
class MyServiceFactory {
public function __construct(
private ConfigValidatorInterface $validator,
) {}
public function create(array $config): MyService {
$this->validator->validate($config);
return new MyService(...$config);
}
}
public function validate(array $config): void {
if ($config['timeout'] is int) {
// ...
}
}
@ConfigSchema:
#[ConfigSchema([
'api_key' => 'required|string',
'timeout' => 'required|int',
])]
class MyServiceConfig {}
readonly:
$this->app->bind(MyService::class, fn($c) => $c->make(MyServiceFactory::class)->create($c['config']['services.my_service']));
Alternative Stacks:
string|int props).finally clauses:
try {
$config = app(ConfigFactory::class)->create($rawConfig);
ProcessJob::dispatch($config);
} finally {
// Cleanup
}
// Before (PHP 7.4)
public function __construct(ConfigValidatorInterface $validator) {
$this->validator = $validator;
}
// After (PHP 8)
public function __construct(
private ConfigValidatorInterface $validator,
) {}
config() calls with factory-resolved configs using PHP 8’s readonly:
$this->app->bind(MyServiceConfig::class, fn($c) => new MyServiceConfig(
$c['config']['services.my_service']['api_key'],
$c['config']['services.my_service']['timeout'],
));
#[ConfigSchema(['timeout' => 'required|int'])]
class MyServiceConfig {}
How can I help you explore Laravel packages today?