Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Options Resolver Laravel Package

symfony/options-resolver

Symfony OptionsResolver is array_replace on steroids: define required options, defaults, allowed types/values, normalizers, and validation for robust option/config handling in your PHP code. Great for APIs, components, and reusable libraries.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package:
    composer require symfony/options-resolver
    
  2. Basic usage (replace array_replace_recursive):
    use Symfony\Component\OptionsResolver\OptionsResolver;
    
    $resolver = new OptionsResolver();
    $resolver->setDefaults([
        'timeout' => 30,
        'retries' => 3,
    ]);
    
    $config = $resolver->resolve([
        'timeout' => 60, // Overrides default
        'retries' => 0,  // Invalid (will be normalized)
    ]);
    // Returns: ['timeout' => 60, 'retries' => 3] (retries normalized to min(3))
    

First Use Case: Laravel Service Configuration

// app/Services/QueueService.php
use Symfony\Component\OptionsResolver\OptionsResolver;

class QueueService
{
    public function __construct(array $config)
    {
        $resolver = new OptionsResolver();
        $resolver->setRequired(['driver'])
                 ->setAllowedValues('driver', ['database', 'beanstalkd', 'redis'])
                 ->setDefaults(['timeout' => 60])
                 ->setNormalizer('retries', fn($val) => max(0, $val));

        $this->config = $resolver->resolve($config);
    }
}

Call it:

$service = new QueueService([
    'driver' => 'redis',
    'retries' => -5, // Normalized to 0
]);

Where to Look First

  • Official Docs (API reference + examples).
  • OptionsResolver class (core methods: setDefaults(), setRequired(), setAllowedTypes(), resolve()).
  • Options class (for nested configurations, e.g., setOptions() in Symfony 8+).

Implementation Patterns

1. Service Configuration Validation

Pattern: Centralize validation for Laravel services (e.g., Mailer, Logger).

// app/Providers/AppServiceProvider.php
use Symfony\Component\OptionsResolver\OptionsResolver;

public function register()
{
    $this->app->singleton(QueueService::class, function ($app) {
        $config = $app['config']['queue'];
        $resolver = new OptionsResolver();
        $resolver->setRequired(['driver'])
                 ->setAllowedTypes('timeout', ['int', 'null'])
                 ->setDefaults(['timeout' => 60]);

        return new QueueService($resolver->resolve($config));
    });
}

2. Nested Configurations

Pattern: Validate complex structures (e.g., database connections).

// app/Services/DatabaseService.php
$resolver = new OptionsResolver();
$dbResolver = new OptionsResolver();
$dbResolver->setRequired(['host', 'port'])
           ->setAllowedTypes('port', 'int');

$resolver->setDefaults([
    'default' => $dbResolver->resolve([
        'host' => 'localhost',
        'port' => 3306,
    ]),
    'replicas' => [],
]);

3. Dynamic Defaults (Closures)

Pattern: Runtime defaults (e.g., environment-aware configs).

$resolver->setDefaults([
    'debug' => fn() => app()->environment('local'),
    'api_key' => fn() => config('services.stripe.key'),
]);

4. Deprecation Handling

Pattern: Phase out legacy configs with warnings.

$resolver->setDeprecated('old_driver', '2.0', 'Use `new_driver` instead.');
$resolver->setDefault('new_driver', fn() => $config['old_driver'] ?? 'default');

5. Reusable Resolvers for Packages

Pattern: Export resolvers in custom Laravel packages.

// vendor/package/src/Resolver/NotifierResolver.php
namespace Package\Resolver;

use Symfony\Component\OptionsResolver\OptionsResolver;

class NotifierResolver
{
    public static function configure(OptionsResolver $resolver): void
    {
        $resolver->setRequired(['driver'])
                 ->setAllowedValues('driver', ['mail', 'slack', 'sms']);
    }
}

Usage:

$resolver = new OptionsResolver();
NotifierResolver::configure($resolver);
$config = $resolver->resolve(['driver' => 'slack']);

6. Integration with Laravel Bindings

Pattern: Bind resolvers to container for dependency injection.

// app/Providers/AppServiceProvider.php
$this->app->bind(QueueService::class, function ($app) {
    $config = $app['config']['queue'];
    $resolver = $app->make(QueueResolver::class);
    return new QueueService($resolver->resolve($config));
});

7. Validation in Form Requests

Pattern: Validate API request payloads.

// app/Http/Requests/StoreQueueJobRequest.php
use Symfony\Component\OptionsResolver\OptionsResolver;

public function rules()
{
    $resolver = new OptionsResolver();
    $resolver->setRequired(['driver', 'payload'])
             ->setAllowedValues('driver', ['database', 'redis']);

    $this->resolvedConfig = $resolver->resolve($this->all());
    return [];
}

8. Testing Configurations

Pattern: Test resolvers in isolation.

// tests/Unit/Services/QueueServiceTest.php
public function testResolver()
{
    $resolver = new OptionsResolver();
    $resolver->setRequired(['driver'])
             ->setAllowedValues('driver', ['database']);

    $this->assertFalse($resolver->isValid(['driver' => 'redis']));
    $this->assertTrue($resolver->isValid(['driver' => 'database']));
}

Gotchas and Tips

Pitfalls

  1. Nested Options in Symfony 8+:

    • Issue: setDefault() for nested options was deprecated in v7.3.0 and removed in v8.0.0.
    • Fix: Use setOptions() for nested structures:
      $resolver->setOptions([
          'database' => new OptionsResolver(),
      ]);
      
  2. Closure Evaluation Timing:

    • Issue: Default closures are evaluated once during resolution, not per-call.
    • Fix: Use fn() => ... for dynamic values (e.g., environment vars):
      $resolver->setDefaults(['timeout' => fn() => env('QUEUE_TIMEOUT', 60)]);
      
  3. Error Paths in Nested Resolvers:

    • Issue: Error paths for nested options may omit the prototype key (fixed in v7.3.8).
    • Fix: Update to Symfony 7.3.8+ or manually handle errors:
      try {
          $resolver->resolve($input);
      } catch (\InvalidArgumentException $e) {
          $path = $e->getPath(); // e.g., 'database.ssl.cert'
      }
      
  4. Performance with Large Configs:

    • Issue: Resolvers validate all options on every call, even if unchanged.
    • Fix: Cache resolved configs if immutable:
      private $resolvedConfig;
      public function __construct(array $config)
      {
          $this->resolvedConfig = $this->resolver->resolve($config);
      }
      
  5. Deprecation Warnings:

    • Issue: Deprecated options trigger warnings only once (per resolver instance).
    • Fix: Recreate the resolver for repeated checks:
      $resolver = new OptionsResolver();
      $resolver->setDeprecated('old_key', '1.0', 'Use `new_key`');
      $resolver->resolve($config); // Warning triggered
      
  6. Type Validation Quirks:

    • Issue: setAllowedTypes() rejects null unless explicitly allowed:
      $resolver->setAllowedTypes('timeout', ['int', 'null']);
      
    • Fix: Always include null for optional nullable fields.
  7. Normalizer Side Effects:

    • Issue: Normalizers run after validation but before defaults.
    • Fix: Use setNormalizer() for transformations, not validation:
      $resolver->setNormalizer('retries', fn($val) => max(0, $val));
      

Debugging Tips

  1. Inspect Resolved Configs:

    $resolver->resolve($input); // Returns final config
    $resolver->getDefinedOptions(); // List of defined options
    $resolver->getDefinedOptionNames(); // Array of option names
    
  2. Validate Without Resolving:

    if (!$resolver->isValid($input)) {
        $errors = $resolver->getErrors($input);
        // $errors = ['timeout' => ['This value should be of type int.']]
    }
    
  3. Enable Deprecation Warnings:

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope