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 enhances array_replace with a robust options system: define required options, set defaults, validate types and values, and normalize inputs. Ideal for building configurable APIs, form components, and reusable libraries with strict option handling.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package:
    composer require symfony/options-resolver
    
  2. Basic usage in Laravel:
    use Symfony\Component\OptionsResolver\OptionsResolver;
    
    $resolver = new OptionsResolver();
    $resolver->setDefaults([
        'timeout' => 30,
        'retry' => false,
    ]);
    
    $resolved = $resolver->resolve([
        'timeout' => 45,
        'retry' => true,
    ]);
    // Returns: ['timeout' => 45, 'retry' => true]
    

First Use Case: Validating API Client Configs

use Symfony\Component\OptionsResolver\OptionsResolver;

class StripeClientConfigurator
{
    public function __construct()
    {
        $this->resolver = new OptionsResolver();
        $this->configureResolver();
    }

    protected function configureResolver(): void
    {
        $this->resolver
            ->setRequired(['api_key'])
            ->setDefaults([
                'timeout' => 30,
                'endpoint' => 'https://api.stripe.com/v1',
            ])
            ->setAllowedTypes('timeout', ['int', 'null'])
            ->setNormalizer('endpoint', fn($value) => rtrim($value, '/'));
    }

    public function resolve(array $config): array
    {
        return $this->resolver->resolve($config);
    }
}

Where to Look First

  • Official Documentation (covers all features and migration guides).
  • OptionsResolver class (core class for defining rules).
  • Options class (for nested option definitions in Symfony 7.4+).
  • Laravel Integration: Use app(OptionsResolver::class) or bind it in AppServiceProvider.

Implementation Patterns

1. Service Configuration Validation

Pattern: Validate service-specific configurations (e.g., queues, caches, APIs). Example:

use Symfony\Component\OptionsResolver\OptionsResolver;

class QueueConfigurator
{
    public function __invoke(OptionsResolver $resolver): void
    {
        $resolver
            ->setDefaults([
                'driver' => 'sync',
                'options' => [],
                'retry_after' => 90,
            ])
            ->setAllowedValues('driver', ['sync', 'database', 'redis'])
            ->setAllowedTypes('retry_after', ['int', 'null'])
            ->setNormalizer('options', fn($options) => is_array($options) ? $options : []);
    }
}

// Usage in a service provider:
$resolver = app(OptionsResolver::class);
$resolver->configure([$this, 'configureQueue']);
$config = $resolver->resolve(config('queue.default'));

2. Nested Option Handling

Pattern: Define hierarchical configurations (e.g., database connections, API endpoints). Example:

use Symfony\Component\OptionsResolver\Options;

$resolver = new OptionsResolver();
$resolver->setDefaults([
    'database' => new Options([
        'host' => 'localhost',
        'port' => 3306,
        'ssl' => false,
    ]),
    'timeout' => 30,
]);

$resolved = $resolver->resolve([
    'database' => [
        'host' => 'db.example.com',
        'ssl' => true,
    ],
]);
// Returns:
// [
//     'database' => [
//         'host' => 'db.example.com',
//         'port' => 3306,
//         'ssl' => true,
//     ],
//     'timeout' => 30,
// ]

3. Dynamic Defaults with Closures

Pattern: Use closures for computed defaults (e.g., fetch from config or environment). Example:

$resolver = new OptionsResolver();
$resolver->setDefaults([
    'cache_ttl' => fn() => config('app.cache_ttl', 60),
    'log_path' => fn() => storage_path('logs/app.log'),
]);

4. Integration with Laravel’s Service Container

Pattern: Bind OptionsResolver as a singleton and reuse it across services. Example (in AppServiceProvider):

public function register(): void
{
    $this->app->singleton(OptionsResolver::class, fn() => new OptionsResolver());
}

public function boot(): void
{
    $this->app->resolving(QueueWorker::class, function ($worker, $app) {
        $resolver = $app->make(OptionsResolver::class);
        $resolver->configure([$this, 'configureQueueWorker']);
        $worker->setConfig($resolver->resolve(config('queue.worker')));
    });
}

5. Conditional Validation

Pattern: Use setValidator for custom logic (e.g., validate retry only if max_retries is set). Example:

$resolver->setValidator('retry', fn($value, $options) => [
    $options['max_retries'] ?? null,
    $value >= 0 && $value <= ($options['max_retries'] ?? 5),
]);

6. Deprecation Warnings

Pattern: Mark legacy options for deprecation (Symfony 7.3+). Example:

$resolver->setDeprecated('old_option', 'Use `new_option` instead.');

7. Union/Intersection Types (PHP 8.2+)

Pattern: Leverage modern PHP types for stricter validation. Example:

$resolver->setAllowedTypes('status', ['string', 'int', 'null']);
$resolver->setAllowedTypes('roles', ['array', 'string']);

Gotchas and Tips

Pitfalls

  1. Nested Options in Symfony <7.4:

    • Gotcha: Before Symfony 7.4, nested options required manual recursion or setDefault() hacks.
    • Fix: Use setOptions() for nested structures in newer versions:
      $resolver->setOptions([
          'database' => new Options([
              'host' => 'localhost',
          ]),
      ]);
      
  2. Closure Scope Issues:

    • Gotcha: Closures in setDefaults or setNormalizer lose scope (e.g., $this).
    • Fix: Use fn() or bind the closure:
      $resolver->setDefaults([
          'path' => fn() => $this->getDefaultPath(),
      ]);
      
  3. Overwriting vs. Merging:

    • Gotcha: resolve() replaces existing options with defaults, not merges.
    • Fix: Use resolveWithDefaults() for partial overrides:
      $resolver->resolveWithDefaults(['timeout' => 60]); // Merges with defaults
      
  4. Error Paths in Nested Structures:

    • Gotcha: Error paths for nested options may be unclear (e.g., database.host).
    • Fix: Use setPrototypeValue() or setPrototypeNormalizer() for nested validation:
      $resolver->setPrototypeValue('database', new Options([
          'host' => 'localhost',
      ]));
      
  5. Performance with Large Configs:

    • Gotcha: Deeply nested or complex resolvers may impact performance.
    • Fix: Cache resolved configs or use resolve() sparingly in hot paths.
  6. Deprecation Warnings:

    • Gotcha: Deprecation warnings are logged but not thrown as exceptions.
    • Fix: Combine with setValidator to enforce deprecation:
      $resolver->setDeprecated('old_key', 'Use `new_key` instead.');
      $resolver->setValidator('old_key', fn($v) => throw new \RuntimeException('Deprecated!'));
      

Debugging Tips

  1. Enable Debug Mode:

    $resolver->setDebug(true); // Shows detailed error paths
    

    Example error:

    Invalid configuration for path "database.ssl": The option "ssl" with value "yes" is expected to be of type "bool", but is of type "string".
    
  2. Inspect Resolver State:

    $resolver->getDefinedOptions(); // List all defined options
    $resolver->getDefaults();       // Show current defaults
    
  3. Test Edge Cases:

    • Test with null, empty arrays, and invalid types.
    • Use resolve() with partial configs to simulate real-world usage.

Extension Points

  1. Custom Normalizers:

    $resolver->setNormalizer('enum_value', fn($value) => YourEnum::from($value));
    
  2. Dynamic Option Definitions:

    $resolver->setDefaults(fn() => [
        'timeout' => config('app.timeout'),
    ]);
    
  3. Integration with Laravel Validation:

    use Illuminate\Support\Facades\Validator;
    
    $resolver = new
    
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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport