Files
guides/projects/priceservice/vendor/symfony/rate-limiter/RateLimiterFactory.php
2026-06-03 22:05:20 +02:00

103 lines
4.2 KiB
PHP

<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\RateLimiter;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\RateLimiter\Policy\FixedWindowLimiter;
use Symfony\Component\RateLimiter\Policy\NoLimiter;
use Symfony\Component\RateLimiter\Policy\Rate;
use Symfony\Component\RateLimiter\Policy\SlidingWindowLimiter;
use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter;
use Symfony\Component\RateLimiter\Storage\StorageInterface;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*/
final class RateLimiterFactory
{
private array $config;
private StorageInterface $storage;
private ?LockFactory $lockFactory;
public function __construct(array $config, StorageInterface $storage, ?LockFactory $lockFactory = null)
{
$this->storage = $storage;
$this->lockFactory = $lockFactory;
$options = new OptionsResolver();
self::configureOptions($options);
$this->config = $options->resolve($config);
}
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) {
if (!isset($value['interval'])) {
return null;
}
return new Rate($value['interval'], $value['amount']);
})
;
}
}