master

laravel/framework

Last updated at: 29/12/2023 09:20

RateLimiter.php

TLDR

The file RateLimiter.php is a part of the Illuminate\Cache namespace in the Demo Projects project. This file contains the RateLimiter class that provides functionality for rate limiting in a web application.

Methods

__construct(Cache $cache)

This is the constructor method of the RateLimiter class. It takes an instance of Cache and assigns it to the cache property of the class.

for(string $name, Closure $callback)

This method is used to register a named limiter configuration. It takes a string $name and a closure $callback as parameters. The limiter configuration is stored in the $limiters array of the class.

limiter(string $name)

This method is used to get a named rate limiter from the registered limiters. It takes a string $name as a parameter and returns the corresponding limiter closure if it exists, otherwise it returns null.

attempt($key, $maxAttempts, Closure $callback, $decaySeconds = 60)

This method is used to execute a callback if it is not limited. It takes a $key, $maxAttempts, $callback, and an optional $decaySeconds parameter. If the number of attempts for the key is less than the maximum allowed attempts, the callback is executed. The method returns the result of the callback.

tooManyAttempts($key, $maxAttempts)

This method is used to determine if the given key has been "accessed" too many times. It takes a $key and $maxAttempts as parameters. If the number of attempts for the key is greater than or equal to the maximum allowed attempts, and a timer is set in the cache for the key, the method returns true, otherwise it returns false.

hit($key, $decaySeconds = 60)

This method is used to increment the counter for a given key for a given decay time. It takes a $key and an optional $decaySeconds parameter. The method adds a timer and a counter for the key in the cache and increments the counter. If the counter was not previously set and the counter value after increment is 1, the counter is set to 1 in the cache. The method returns the incremented counter value.

attempts($key)

This method is used to get the number of attempts for the given key. It takes a $key as a parameter and returns the number of attempts stored in the cache for that key.

resetAttempts($key)

This method is used to reset the number of attempts for the given key. It takes a $key as a parameter and removes the key from the cache.

remaining($key, $maxAttempts)

This method is used to get the number of retries left for the given key. It takes a $key and $maxAttempts as parameters and returns the remaining number of retries calculated as the difference between the maximum allowed attempts and the number of attempts for the key.

retriesLeft($key, $maxAttempts)

This method is an alias for the remaining method. It takes a $key and $maxAttempts as parameters and returns the remaining number of retries.

clear($key)

This method is used to clear the hits and lockout timer for the given key. It takes a $key as a parameter and removes the key and its timer from the cache.

availableIn($key)

This method is used to get the number of seconds until the "key" is accessible again. It takes a $key as a parameter and calculates the remaining time in seconds by subtracting the current time from the timer value stored in the cache for the key. The method returns the remaining time in seconds.

cleanRateLimiterKey($key)

This method is used to clean the rate limiter key from unicode characters. It takes a $key as a parameter and removes any unicode characters from the key using regular expressions and the htmlentities function. The method returns the cleaned key.

Classes

There are no other classes defined in this file.

<?php

namespace Illuminate\Cache;

use Closure;
use Illuminate\Contracts\Cache\Repository as Cache;
use Illuminate\Support\InteractsWithTime;

class RateLimiter
{
    use InteractsWithTime;

    /**
     * The cache store implementation.
     *
     * @var \Illuminate\Contracts\Cache\Repository
     */
    protected $cache;

    /**
     * The configured limit object resolvers.
     *
     * @var array
     */
    protected $limiters = [];

    /**
     * Create a new rate limiter instance.
     *
     * @param  \Illuminate\Contracts\Cache\Repository  $cache
     * @return void
     */
    public function __construct(Cache $cache)
    {
        $this->cache = $cache;
    }

    /**
     * Register a named limiter configuration.
     *
     * @param  string  $name
     * @param  \Closure  $callback
     * @return $this
     */
    public function for(string $name, Closure $callback)
    {
        $this->limiters[$name] = $callback;

        return $this;
    }

    /**
     * Get the given named rate limiter.
     *
     * @param  string  $name
     * @return \Closure|null
     */
    public function limiter(string $name)
    {
        return $this->limiters[$name] ?? null;
    }

    /**
     * Attempts to execute a callback if it's not limited.
     *
     * @param  string  $key
     * @param  int  $maxAttempts
     * @param  \Closure  $callback
     * @param  int  $decaySeconds
     * @return mixed
     */
    public function attempt($key, $maxAttempts, Closure $callback, $decaySeconds = 60)
    {
        if ($this->tooManyAttempts($key, $maxAttempts)) {
            return false;
        }

        if (is_null($result = $callback())) {
            $result = true;
        }

        return tap($result, function () use ($key, $decaySeconds) {
            $this->hit($key, $decaySeconds);
        });
    }

    /**
     * Determine if the given key has been "accessed" too many times.
     *
     * @param  string  $key
     * @param  int  $maxAttempts
     * @return bool
     */
    public function tooManyAttempts($key, $maxAttempts)
    {
        if ($this->attempts($key) >= $maxAttempts) {
            if ($this->cache->has($this->cleanRateLimiterKey($key).':timer')) {
                return true;
            }

            $this->resetAttempts($key);
        }

        return false;
    }

    /**
     * Increment the counter for a given key for a given decay time.
     *
     * @param  string  $key
     * @param  int  $decaySeconds
     * @return int
     */
    public function hit($key, $decaySeconds = 60)
    {
        $key = $this->cleanRateLimiterKey($key);

        $this->cache->add(
            $key.':timer', $this->availableAt($decaySeconds), $decaySeconds
        );

        $added = $this->cache->add($key, 0, $decaySeconds);

        $hits = (int) $this->cache->increment($key);

        if (! $added && $hits == 1) {
            $this->cache->put($key, 1, $decaySeconds);
        }

        return $hits;
    }

    /**
     * Get the number of attempts for the given key.
     *
     * @param  string  $key
     * @return mixed
     */
    public function attempts($key)
    {
        $key = $this->cleanRateLimiterKey($key);

        return $this->cache->get($key, 0);
    }

    /**
     * Reset the number of attempts for the given key.
     *
     * @param  string  $key
     * @return mixed
     */
    public function resetAttempts($key)
    {
        $key = $this->cleanRateLimiterKey($key);

        return $this->cache->forget($key);
    }

    /**
     * Get the number of retries left for the given key.
     *
     * @param  string  $key
     * @param  int  $maxAttempts
     * @return int
     */
    public function remaining($key, $maxAttempts)
    {
        $key = $this->cleanRateLimiterKey($key);

        $attempts = $this->attempts($key);

        return $maxAttempts - $attempts;
    }

    /**
     * Get the number of retries left for the given key.
     *
     * @param  string  $key
     * @param  int  $maxAttempts
     * @return int
     */
    public function retriesLeft($key, $maxAttempts)
    {
        return $this->remaining($key, $maxAttempts);
    }

    /**
     * Clear the hits and lockout timer for the given key.
     *
     * @param  string  $key
     * @return void
     */
    public function clear($key)
    {
        $key = $this->cleanRateLimiterKey($key);

        $this->resetAttempts($key);

        $this->cache->forget($key.':timer');
    }

    /**
     * Get the number of seconds until the "key" is accessible again.
     *
     * @param  string  $key
     * @return int
     */
    public function availableIn($key)
    {
        $key = $this->cleanRateLimiterKey($key);

        return max(0, $this->cache->get($key.':timer') - $this->currentTime());
    }

    /**
     * Clean the rate limiter key from unicode characters.
     *
     * @param  string  $key
     * @return string
     */
    public function cleanRateLimiterKey($key)
    {
        return preg_replace('/&([a-z])[a-z]+;/i', '$1', htmlentities($key));
    }
}