detain/rate-limit

View on GitHub
src/RateLimit.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

namespace Detain\RateLimit;

/**
 * @author Peter Chung <touhonoob@gmail.com>
 * @date May 16, 2015
 */
class RateLimit
{
    /**
     *
     * @var string
     */
    protected $name;

    /**
     *
     * @var int
     */
    protected $maxRequests;

    /**
     *
     * @var int
     */
    protected $period;

    /**
     * @var Adapter
     */
    private $adapter;

    /**
     * RateLimit constructor.
     * @param string $name - prefix used in storage keys.
     * @param int $maxRequests
     * @param int $period seconds
     * @param Adapter $adapter - storage adapter
     */
    public function __construct($name, $maxRequests, $period, Adapter $adapter)
    {
        $this->name = $name;
        $this->maxRequests = $maxRequests;
        $this->period = $period;
        $this->adapter = $adapter;
    }

    /**
     * Rate Limiting
     * http://stackoverflow.com/a/668327/670662
     * @param string $id
     * @param float $use
     * @return boolean
     */
    public function check($id, $use = 1.0)
    {
        $rate = $this->maxRequests / $this->period;

        $t_key = $this->keyTime($id);
        $a_key = $this->keyAllow($id);

        if (!$this->adapter->exists($t_key)) {
            // first hit; setup storage; allow.
            $this->adapter->set($t_key, time(), $this->period);
            $this->adapter->set($a_key, ($this->maxRequests - $use), $this->period);
            return true;
        }

        $c_time = time();

        $time_passed = $c_time - $this->adapter->get($t_key);
        $this->adapter->set($t_key, $c_time, $this->period);

        $allowance = $this->adapter->get($a_key);
        $allowance += $time_passed * $rate;

        if ($allowance > $this->maxRequests) {
            $allowance = $this->maxRequests; // throttle
        }


        if ($allowance < $use) {
            // need to wait for more 'tokens' to be in the bucket.
            $this->adapter->set($a_key, $allowance, $this->period);
            return false;
        }


        $this->adapter->set($a_key, $allowance - $use, $this->period);
        return true;
    }

    /**
     * @deprecated use getAllowance() instead.
     * @param string $id
     * @return int
     */
    public function getAllow($id)
    {
        return $this->getAllowance($id);
    }


    /**
     * Get allowance left.
     *
     * @param string $id
     * @return int number of requests that can be made before hitting a limit.
     */
    public function getAllowance($id)
    {
        $this->check($id, 0.0);

        $a_key = $this->keyAllow($id);

        if (!$this->adapter->exists($a_key)) {
            return $this->maxRequests;
        }
        return (int) max(0, floor($this->adapter->get($a_key)));
    }

    /**
     * Purge rate limit record for $id
     * @param string $id
     * @return void
     */
    public function purge($id)
    {
        $this->adapter->del($this->keyTime($id));
        $this->adapter->del($this->keyAllow($id));
    }

    /**
     * @return string
     * @param string $id
     */
    private function keyTime($id)
    {
        return $this->name . ":" . $id . ":time";
    }

    /**
     * @return string
     * @param string $id
     */
    private function keyAllow($id)
    {
        return $this->name . ":" . $id . ":allow";
    }

    /**
     * @param string $name
     * @return void
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * @param int $maxRequests
     * @return void
     */
    public function setMaxRequests($maxRequests)
    {
        $this->maxRequests = $maxRequests;
    }

    /**
     * @param int $period
     * @return void
     */
    public function setPeriod($period)
    {
        $this->period = $period;
    }

    /**
     * @param Adapter $adapter
     * @return void
     */
    public function setAdapter(Adapter $adapter)
    {
        $this->adapter = $adapter;
    }
}