symfony/options-resolver
OptionsResolver is array_replace on steroids: build option systems with required options, defaults, type and value validation, and normalization, with clear exceptions when configuration is invalid.
isset()/is_array()/is_numeric() checks. This is a foundational decision: it doesn't ship a user-facing feature, but it removes a recurring class of defects and support load.symfony/options-resolver — MIT, Symfony-maintained, mature (7.x/8.x lines), battle-tested by the framework ecosystem. The buy decision is low-risk: no licensing review, no vendor lock-in, and it is a small, dependency-light component. Adopt it as the internal default for option handling rather than green-lighting one-off validation code.setDeprecated() plus normalizers/fallbacks supports phased configuration changes (e.g., renaming a driver option) without breaking existing deployments. This informs how aggressively the roadmap can ship breaking config changes and how much migration tooling to invest in.debug, timeouts, feature flags) without duplicating config files — useful for decisions about environment parity and config sprawl.Adopt when:
database.ssl.cert).Look elsewhere when:
For executives: This is a small, MIT-licensed, Symfony-maintained component that standardizes how our services validate their configuration. It doesn't add a user-facing feature — it reduces a specific, recurring failure mode: services breaking in production because a config value was missing, the wrong type, or silently ignored. The business case is operational: fewer config-related incidents (especially in payment and integration paths), lower support load, and faster onboarding because configuration rules become explicit rather than tribal knowledge. Risk is low: mature package, permissive license, no vendor lock-in, incremental adoption. Recommendation: approve it as the internal standard for option handling and pilot it on one or two high-risk integrations before broad rollout.
For engineering leaders: OptionsResolver replaces hand-rolled validation with a single declarative API: required options, defaults, allowed types/values, normalizers, nested option sets, and deprecation warnings. Benefits are consolidation (one pattern instead of N bespoke helpers), earlier failure (misconfiguration surfaces at construction, not mid-request), and a clean migration path for config changes via setDeprecated(). Watch two things in planning: the major-version PHP floor (the 8.x line targets PHP 8.4; stay on 7.4.x otherwise) and scope discipline — it resolves option arrays, not user input or DTOs. Suggested rollout: pilot on the payment/integration config and one shared package, measure config-related incidents and time-to-configure, then codify as a team convention.
For developers: Define the contract once and let the resolver enforce it:
$resolver = new OptionsResolver();
$resolver->setRequired(['driver', 'timeout'])
->setAllowedTypes('timeout', ['int', 'null'])
->setAllowedValues('driver', ['mail', 'slack', 'sms'])
->setDefault('retries', 3)
->setNormalizer('retries', fn ($v) => max(0, (int) $v));
$options = $resolver->resolve($config); // throws with a precise message on bad input
Nested structures use a sub-resolver via setOptions() rather than nested setDefault() (the latter is deprecated in the 7.3 line and removed in 8.0). No more if (!is_numeric(...)) scattered through service providers.
How can I help you explore Laravel packages today?