Файл: vendor/laravel/framework/src/Illuminate/Queue/Middleware/ThrottlesExceptions.php
Строк: 207
<?php
namespace IlluminateQueueMiddleware;
use Closure;
use IlluminateCacheRateLimiter;
use IlluminateContainerContainer;
use Throwable;
class ThrottlesExceptions
{
/**
* The developer specified key that the rate limiter should use.
*
* @var string
*/
protected $key;
/**
* Indicates whether the throttle key should use the job's UUID.
*
* @var bool
*/
protected $byJob = false;
/**
* The maximum number of attempts allowed before rate limiting applies.
*
* @var int
*/
protected $maxAttempts;
/**
* The number of seconds until the maximum attempts are reset.
*
* @var int
*/
protected $decaySeconds;
/**
* The number of minutes to wait before retrying the job after an exception.
*
* @var int|(Closure(Throwable): int)
*/
protected $retryAfterMinutes = 0;
/**
* The callback that determines if the exception should be reported.
*
* @var callable
*/
protected $reportCallback;
/**
* The callback that determines if rate limiting should apply.
*
* @var callable
*/
protected $whenCallback;
/**
* The callbacks that determine if the job should be deleted.
*
* @var callable[]
*/
protected array $deleteWhenCallbacks = [];
/**
* The callbacks that determine if the job should be failed.
*
* @var callable[]
*/
protected array $failWhenCallbacks = [];
/**
* The prefix of the rate limiter key.
*
* @var string
*/
protected $prefix = 'laravel_throttles_exceptions:';
/**
* The rate limiter instance.
*
* @var IlluminateCacheRateLimiter
*/
protected $limiter;
/**
* Create a new middleware instance.
*
* @param int $maxAttempts
* @param int $decaySeconds
*/
public function __construct($maxAttempts = 10, $decaySeconds = 600)
{
$this->maxAttempts = $maxAttempts;
$this->decaySeconds = $decaySeconds;
}
/**
* Process the job.
*
* @param mixed $job
* @param callable $next
* @return mixed
*
* @throws Throwable
*/
public function handle($job, $next)
{
$this->limiter = Container::getInstance()->make(RateLimiter::class);
if ($this->limiter->tooManyAttempts($jobKey = $this->getKey($job), $this->maxAttempts)) {
return $job->release($this->getTimeUntilNextRetry($jobKey));
}
try {
$next($job);
$this->limiter->clear($jobKey);
} catch (Throwable $throwable) {
if ($this->whenCallback && ! call_user_func($this->whenCallback, $throwable, $this->limiter)) {
throw $throwable;
}
if ($this->reportCallback && call_user_func($this->reportCallback, $throwable, $this->limiter)) {
report($throwable);
}
if ($this->shouldDelete($throwable)) {
return $job->delete();
}
if ($this->shouldFail($throwable)) {
return $job->fail($throwable);
}
$this->limiter->hit($jobKey, $this->decaySeconds);
return $job->release($this->getTimeUntilNextRetryAfterException($throwable));
}
}
/**
* Specify a callback that should determine if rate limiting behavior should apply.
*
* @param callable $callback
* @return $this
*/
public function when(callable $callback)
{
$this->whenCallback = $callback;
return $this;
}
/**
* Add a callback that should determine if the job should be deleted.
*
* @param callable|string $callback
* @return $this
*/
public function deleteWhen(callable|string $callback)
{
$this->deleteWhenCallbacks[] = is_string($callback)
? fn (Throwable $e) => $e instanceof $callback
: $callback;
return $this;
}
/**
* Add a callback that should determine if the job should be failed.
*
* @param callable|string $callback
* @return $this
*/
public function failWhen(callable|string $callback)
{
$this->failWhenCallbacks[] = is_string($callback)
? fn (Throwable $e) => $e instanceof $callback
: $callback;
return $this;
}
/**
* Run the skip / delete callbacks to determine if the job should be deleted for the given exception.
*
* @param Throwable $throwable
* @return bool
*/
protected function shouldDelete(Throwable $throwable): bool
{
foreach ($this->deleteWhenCallbacks as $callback) {
if (call_user_func($callback, $throwable)) {
return true;
}
}
return false;
}
/**
* Run the skip / fail callbacks to determine if the job should be failed for the given exception.
*
* @param Throwable $throwable
* @return bool
*/
protected function shouldFail(Throwable $throwable): bool
{
foreach ($this->failWhenCallbacks as $callback) {
if (call_user_func($callback, $throwable)) {
return true;
}
}
return false;
}
/**
* Set the prefix of the rate limiter key.
*
* @param string $prefix
* @return $this
*/
public function withPrefix(string $prefix)
{
$this->prefix = $prefix;
return $this;
}
/**
* Specify the number of minutes a job should be delayed when it is released (before it has reached its max exceptions).
*
* @param int|(Closure(Throwable): int) $backoff
* @return $this
*/
public function backoff($backoff)
{
$this->retryAfterMinutes = $backoff;
return $this;
}
/**
* Get the number of seconds that should elapse before the job is retried after an exception.
*
* @param Throwable $throwable
* @return int
*/
protected function getTimeUntilNextRetryAfterException(Throwable $throwable)
{
$backoff = $this->retryAfterMinutes instanceof Closure
? call_user_func($this->retryAfterMinutes, $throwable)
: $this->retryAfterMinutes;
return $backoff * 60;
}
/**
* Get the cache key associated for the rate limiter.
*
* @param mixed $job
* @return string
*/
protected function getKey($job)
{
if ($this->key) {
return $this->prefix.$this->key;
} elseif ($this->byJob) {
return $this->prefix.$job->job->uuid();
}
$jobName = method_exists($job, 'displayName')
? $job->displayName()
: get_class($job);
return $this->prefix.hash('xxh128', $jobName);
}
/**
* Set the value that the rate limiter should be keyed by.
*
* @param string $key
* @return $this
*/
public function by($key)
{
$this->key = $key;
return $this;
}
/**
* Indicate that the throttle key should use the job's UUID.
*
* @return $this
*/
public function byJob()
{
$this->byJob = true;
return $this;
}
/**
* Report exceptions and optionally specify a callback that determines if the exception should be reported.
*
* @param callable|null $callback
* @return $this
*/
public function report(?callable $callback = null)
{
$this->reportCallback = $callback ?? fn () => true;
return $this;
}
/**
* Get the number of seconds that should elapse before the job is retried.
*
* @param string $key
* @return int
*/
protected function getTimeUntilNextRetry($key)
{
return $this->limiter->availableIn($key) + 3;
}
}