Repository: symfony/symfony
File: src/Symfony/Component/RateLimiter/RateLimiterFactory.php (PHP)

Code before:

    public function create(?string $key = null): LimiterInterface
    {
        $id = $this->config['id'].'-'.$key;
        $lock = $this->lockFactory?->createLock($id);

        return match ($this->config['policy']) {
            'token_bucket' => new TokenBucketLimiter($id, $this->config['limit'], $this->config['rate'], $this->storage, $lock),
            'fixed_window' => new FixedWindowLimiter($id, $this->config['limit'], $this->config['interval'], $this->storage, $lock),
            'sliding_window' => new SlidingWindowLimiter($id, $this->config['limit'], $this->config['interval'], $this->storage, $lock),
            'no_limit' => new NoLimiter(),
            default => throw new \LogicException(sprintf('Limiter policy "%s" does not exists, it must be either "token_bucket", "sliding_window", "fixed_window" or "no_limit".', $this->config['policy'])),
        };
    }

    protected static function configureOptions(OptionsResolver $options): void
    {
        $intervalNormalizer = static function (Options $options, string $interval): \DateInterval {
            // Create DateTimeImmutable from unix timesatmp, so the default timezone is ignored and we don't need to
            // deal with quirks happening when modifying dates using a timezone with DST.
            $now = \DateTimeImmutable::createFromFormat('U', time());

            try {
                $nowPlusInterval = @$now->modify('+' . $interval);
            } catch (\DateMalformedStringException $e) {
                throw new \LogicException(\sprintf('Cannot parse interval "%s", please use a valid unit as described on https://www.php.net/manual/en/datetime.formats.php#datetime.formats.relative', $interval), 0, $e);
            }

            if (!$nowPlusInterval) {
                throw new \LogicException(\sprintf('Cannot parse interval "%s", please use a valid unit as described on https://www.php.net/manual/en/datetime.formats.php#datetime.formats.relative', $interval));
            }

            return $now->diff($nowPlusInterval);
        };

        $options
            ->define('id')->required()
            ->define('policy')
                ->required()
                ->allowedValues('token_bucket', 'fixed_window', 'sliding_window', 'no_limit')

            ->define('limit')->allowedTypes('int')
            ->define('interval')->allowedTypes('string')->normalize($intervalNormalizer)
            ->define('rate')
                ->default(function (OptionsResolver $rate) use ($intervalNormalizer) {
                    $rate
                        ->define('amount')->allowedTypes('int')->default(1)
                        ->define('interval')->allowedTypes('string')->normalize($intervalNormalizer)
                    ;
                })
                ->normalize(function (Options $options, $value) {

Review comment (suggestion):
```suggestion
                throw new \LogicException(\sprintf('Cannot parse interval "%s", please use a valid unit as described on https://www.php.net/datetime.formats#datetime.formats.relative', $interval), 0, $e);
```

Code after:

    public function create(?string $key = null): LimiterInterface
    {
        $id = $this->config['id'].'-'.$key;
        $lock = $this->lockFactory?->createLock($id);

        return match ($this->config['policy']) {
            'token_bucket' => new TokenBucketLimiter($id, $this->config['limit'], $this->config['rate'], $this->storage, $lock),
            'fixed_window' => new FixedWindowLimiter($id, $this->config['limit'], $this->config['interval'], $this->storage, $lock),
            'sliding_window' => new SlidingWindowLimiter($id, $this->config['limit'], $this->config['interval'], $this->storage, $lock),
            'no_limit' => new NoLimiter(),
            default => throw new \LogicException(sprintf('Limiter policy "%s" does not exists, it must be either "token_bucket", "sliding_window", "fixed_window" or "no_limit".', $this->config['policy'])),
        };
    }

    protected static function configureOptions(OptionsResolver $options): void
    {
        $intervalNormalizer = static function (Options $options, string $interval): \DateInterval {
            // Create DateTimeImmutable from unix timesatmp, so the default timezone is ignored and we don't need to
            // deal with quirks happening when modifying dates using a timezone with DST.
            $now = \DateTimeImmutable::createFromFormat('U', time());

            try {
                $nowPlusInterval = @$now->modify('+' . $interval);
            } catch (\DateMalformedStringException $e) {
                throw new \LogicException(\sprintf('Cannot parse interval "%s", please use a valid unit as described on https://php.net/datetime.formats#datetime.formats.relative', $interval), 0, $e);
            }

            if (!$nowPlusInterval) {
                throw new \LogicException(\sprintf('Cannot parse interval "%s", please use a valid unit as described on https://php.net/datetime.formats#datetime.formats.relative', $interval));
            }

            return $now->diff($nowPlusInterval);
        };

        $options
            ->define('id')->required()
            ->define('policy')
                ->required()
                ->allowedValues('token_bucket', 'fixed_window', 'sliding_window', 'no_limit')

            ->define('limit')->allowedTypes('int')
            ->define('interval')->allowedTypes('string')->normalize($intervalNormalizer)
            ->define('rate')
                ->default(function (OptionsResolver $rate) use ($intervalNormalizer) {
                    $rate
                        ->define('amount')->allowedTypes('int')->default(1)
                        ->define('interval')->allowedTypes('string')->normalize($intervalNormalizer)
                    ;
                })
                ->normalize(function (Options $options, $value) {