[
  {
    "path": ".gitignore",
    "content": "/vendor\n/phpunit.xml\n/composer.lock\n/.idea/\n"
  },
  {
    "path": "LICENCE",
    "content": "Copyright (C) 2012 Dave Marshall <dave.marshall@atstsolutions.co.uk>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "Stiphle\n======\n\nInstall via Composer\n-------\n\n```\ncomposer require davedevelopment/stiphle\n```\n\nWhat is it?\n-----------\n\nStiphle is a little library to try and provide an easy way of throttling/rate limit requests, for those without fancy hardware etc.\n\nHow does it work?\n-----------------\n\nYou create a throttle, and ask it how long you should wait. For example, given\nthat $identifier is some means of identifying whatever it is you're throttling,\nand you want to throttle it to 5 requests per second:\n\n``` php\n<?php\n\n$throttle = new Stiphle\\Throttle\\LeakyBucket;\n$identifier = 'dave';\nwhile(true) {\n    // the throttle method returns the amount of milliseconds it slept for\n    echo $throttle->throttle($identifier, 5, 1000);\n}\n# 0 0 0 0 0 200 200....\n\n```\n\nUse combinations of values to provide bursting etc, though use carefully as it\nscrews with your mind\n\n``` php\n<?php\n\n$throttle = new Stiphle\\Throttle\\LeakyBucket;\n$identifier = 'dave';\nfor(;;) {\n    /**\n     * Allow upto 5 per second, but limit to 20 a minute - I think\n     **/\n    echo \"a:\" . $throttle->throttle($identifier, 5, 1000);\n    echo \" b:\" . $throttle->throttle($identifier, 20, 60000);\n    echo \"\\n\";\n}\n#a:0 b:0\n#a:0 b:0\n#a:0 b:0\n#a:0 b:0\n#a:0 b:0\n#a:199 b:0\n#a:200 b:0\n#a:199 b:0\n#a:200 b:0\n#a:200 b:0\n#a:199 b:0\n#a:200 b:0\n#a:199 b:0\n#a:200 b:0\n#a:200 b:0\n#a:199 b:0\n#a:200 b:0\n#a:200 b:0\n#a:199 b:0\n#a:200 b:0\n#a:199 b:0\n#a:200 b:2600\n#a:0 b:3000\n#a:0 b:2999\n\n\n```\n\nThrottle Strategies\n-------------------\n\nThere are currently two types of throttles, [Leaky\nBucket](http://en.wikipedia.org/wiki/Leaky_bucket) and a simple fixed time\nwindow.\n\n``` php\n\n/**\n * Throttle to 1000 per *rolling* 24 hours, e.g. the counter will not reset at\n * midnight\n */\n$throttle = new Stiphle\\Throttle\\LeakyBucket;\n$throttle->throttle('api.request', 1000, 86400000);\n\n/**\n * Throttle to 1000 per calendar day, counter will reset at midnight\n */\n$throttle = new Stiphle\\Throttle\\TimeWindow;\n$throttle->throttle('api.request', 1000, 86400000);\n\n```\n\n__NB:__ The current implementation of the `TimeWindow` throttle will only work on 64-bit architectures!\n\nStorage\n-------\n\nStiphle currently ships with 5 storage engines\n\n* In process\n* APC\n* Memcached\n* Doctrine Cache\n* Redis\n\nStiphle uses the in process storage by default. A different storage engine can\nbe injected after object creation.\n\n``` php\n$throttle = new Stiphle\\Throttle\\LeakyBucket();\n$storage = new \\Stiphle\\Storage\\Memcached(new \\Memcached());\n$throttle->setStorage($storage);\n```\n\nTodo\n----\n\n* More Tests!\n* Decent *Unit* tests\n* More throttling methods\n* More storage adapters, the current ones are a little volatile, Mongo,\n  Cassandra, MemcacheDB etc\n\nCopyright\n---------\n\nCopyright (c) 2011 Dave Marshall. See LICENCE for further details\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"davedevelopment/stiphle\",\n    \"type\": \"library\",\n    \"description\": \"Simple rate limiting/throttling for php\",\n    \"keywords\": [\"throttle\", \"throttling\", \"rate limiting\", \"rate limit\"],\n    \"homepage\": \"http://github.com/davedevelopment/stiphle\",\n    \"license\": \"MIT\",\n    \"authors\": [{\n        \"name\": \"Dave Marshall\",\n        \"email\": \"dave.marshall@atstsolutions.co.uk\",\n        \"homepage\": \"http://davedevelopment.co.uk\"\n    }],\n\n    \"require\": {\n        \"php\": \"^5.6.0|^7.0|^8.0\"\n    },\n\n    \"suggest\": {\n        \"doctrine/cache\": \"~1.0\",\n        \"predis/predis\": \"~1.1\",\n        \"zendframework/zend-cache\": \"^2.8\"\n    },\n\n    \"autoload\": {\n        \"psr-0\": {\n            \"Stiphle\": \"src/\"\n        }\n    },\n\n    \"require-dev\": {\n        \"phpunit/phpunit\": \"^6.5|^7.5|^8.4\",\n        \"predis/predis\": \"^1.1\",\n        \"doctrine/cache\": \"^1.0\",\n        \"zendframework/zend-cache\": \"^2.8\"\n    }\n}\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- http://www.phpunit.de/manual/current/en/appendixes.configuration.html -->\n<phpunit\n    backupGlobals = \"false\"\n    backupStaticAttributes = \"false\"\n    colors = \"true\"\n    convertErrorsToExceptions = \"true\"\n    convertNoticesToExceptions = \"true\"\n    convertWarningsToExceptions = \"true\"\n    processIsolation = \"false\"\n    stopOnFailure = \"false\"\n    syntaxCheck = \"false\"\n    bootstrap = \"./vendor/autoload.php\" >\n\n    <testsuites>\n        <testsuite name=\"Stiphle\">\n            <directory>./tests</directory>\n        </testsuite>\n    </testsuites>\n\n    <filter>\n        <whitelist processUncoveredFilesFromWhitelist=\"true\">\n            <directory suffix=\".php\">src</directory>\n        </whitelist>\n    </filter>\n</phpunit>\n"
  },
  {
    "path": "src/Stiphle/Storage/Apc.php",
    "content": "<?php\n/**\n * @package    Stiphle\n * @subpackage Stiphle\\Throttle\\LeakyBucket\\Storage\n */\nnamespace Stiphle\\Storage;\n\n/**\n * This file is part of Stiphle\n *\n * Copyright (c) 2011 Dave Marshall <dave.marshall@atstsolutuions.co.uk>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\n/**\n * Use Apc as the storage, I hope apc_add is atomic and therefore we wont get\n * any race conditions with the locking....\n *\n * @author      Dave Marshall <david.marshall@atstsolutions.co.uk>\n */\nclass Apc implements StorageInterface\n{\n    /**\n     * @var int\n     */\n    protected $lockWaitTimeout = 1000;\n\n    /**\n     * @var int  Time to sleep when attempting to get lock in microseconds\n     */\n    protected $sleep = 100;\n\n    /**\n     * @var int \n     */\n    protected $ttl = 10000000;\n\n    /**\n     * Set lock wait timeout\n     *\n     * @param int $milliseconds\n     */\n    public function setLockWaitTimeout($milliseconds)\n    {\n        $this->lockWaitTimeout = $milliseconds;\n        return;\n    }\n\n    /**\n     * Set the sleep time in microseconds\n     *\n     * @param int \n     * @return void\n     */\n    public function setSleep($microseconds)\n    {\n        $this->sleep = $microseconds;\n        return;\n    }\n\n    /**\n     * Set the ttl for the apc records in seconds\n     *\n     * @param int $seconds\n     * @return void\n     */\n    public function setTtl($microseconds)\n    {\n        $this->ttl = $microseconds;\n        return;\n    }\n\n    /**\n     * Lock \n     *\n     * If we're using storage, we might have multiple requests coming in at\n     * once, so we lock the storage\n     *\n     * @return void\n     */\n    public function lock($key)\n    {\n        $key = $key . \"::LOCK\";\n        $start = microtime(true);\n        while(!apc_add($key, true, $this->ttl)) {\n            $passed = (microtime(true) - $start) * 1000;\n            if ($passed > $this->lockWaitTimeout) {\n                throw new LockWaitTimeoutException();\n            }\n            usleep($this->sleep);\n        }\n\n        return;\n    }\n\n    /**\n     * Unlock\n     *\n     * @return void\n     */\n    public function unlock($key)\n    {\n        $key = $key . \"::LOCK\";\n        apc_delete($key);\n    }\n\n    /**\n     * Get last modified\n     *\n     * @param string $key\n     * @return int\n     */\n    public function get($key)\n    {\n        return apc_fetch($key);\n    }\n\n    /**\n     * set \n     *\n     * @param string $key\n     * @param mixed $value\n     * @return void\n     */\n    public function set($key, $value)\n    {\n        apc_store($key, $value, $this->ttl);\n        return;\n    }\n\n}\n\n\n\n"
  },
  {
    "path": "src/Stiphle/Storage/Apcu.php",
    "content": "<?php\n/**\n * @package    Stiphle\n * @subpackage Stiphle\\Throttle\\LeakyBucket\\Storage\n */\nnamespace Stiphle\\Storage;\n\n/**\n * This file is part of Stiphle\n *\n * Copyright (c) 2011 Dave Marshall <dave.marshall@atstsolutuions.co.uk>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nclass Apcu implements StorageInterface\n{\n    /**\n     * @var int\n     */\n    protected $lockWaitTimeout = 1000;\n\n    /**\n     * @var int  Time to sleep when attempting to get lock in microseconds\n     */\n    protected $sleep = 100;\n\n    /**\n     * @var int \n     */\n    protected $ttl = 10000000;\n\n    /**\n     * Set lock wait timeout\n     *\n     * @param int $milliseconds\n     */\n    public function setLockWaitTimeout($milliseconds)\n    {\n        $this->lockWaitTimeout = $milliseconds;\n        return;\n    }\n\n    /**\n     * Set the sleep time in microseconds\n     *\n     * @param int \n     * @return void\n     */\n    public function setSleep($microseconds)\n    {\n        $this->sleep = $microseconds;\n        return;\n    }\n\n    /**\n     * Set the ttl for the apc records in seconds\n     *\n     * @param int $seconds\n     * @return void\n     */\n    public function setTtl($microseconds)\n    {\n        $this->ttl = $microseconds;\n        return;\n    }\n\n    /**\n     * Lock \n     *\n     * If we're using storage, we might have multiple requests coming in at\n     * once, so we lock the storage\n     *\n     * @return void\n     */\n    public function lock($key)\n    {\n        $key = $key . \"::LOCK\";\n        $start = microtime(true);\n        while(!apcu_add($key, true, $this->ttl)) {\n            $passed = (microtime(true) - $start) * 1000;\n            if ($passed > $this->lockWaitTimeout) {\n                throw new LockWaitTimeoutException();\n            }\n            usleep($this->sleep);\n        }\n\n        return;\n    }\n\n    /**\n     * Unlock\n     *\n     * @return void\n     */\n    public function unlock($key)\n    {\n        $key = $key . \"::LOCK\";\n        apcu_delete($key);\n    }\n\n    /**\n     * Get last modified\n     *\n     * @param string $key\n     * @return int\n     */\n    public function get($key)\n    {\n        return apcu_fetch($key);\n    }\n\n    /**\n     * set \n     *\n     * @param string $key\n     * @param mixed $value\n     * @return void\n     */\n    public function set($key, $value)\n    {\n        apcu_store($key, $value, $this->ttl);\n        return;\n    }\n\n}"
  },
  {
    "path": "src/Stiphle/Storage/DoctrineCache.php",
    "content": "<?php\n\nnamespace Stiphle\\Storage;\n\nuse Doctrine\\Common\\Cache\\Cache;\n\n/**\n * This file is part of Stiphle\n *\n * Copyright (c) 2011 Dave Marshall <dave.marshall@atstsolutuions.co.uk>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nclass DoctrineCache implements StorageInterface\n{\n    /** @var Cache */\n    protected $cache;\n\n    /** @var int */\n    protected $lockWaitTimeout;\n\n    /** @var int */\n    protected $lockWaitInterval;\n\n    public function __construct(Cache $cache, $lockWaitTimeout = 1000, $lockWaitInterval = 100)\n    {\n        $this->cache = $cache;\n        $this->lockWaitTimeout = $lockWaitTimeout;\n        $this->lockWaitInterval = $lockWaitInterval;\n    }\n\n    public function setLockWaitTimeout($milliseconds)\n    {\n        $this->lockWaitTimeout = $milliseconds;\n    }\n\n    public function setSleep($microseconds)\n    {\n        $this->lockWaitInterval = $microseconds;\n    }\n\n    public function lock($key)\n    {\n        $key = $key . \"::LOCK\";\n        $start = microtime(true);\n        while ($this->cache->contains($key)) {\n            $passed = (microtime(true) - $start) * 1000;\n            if ($passed > $this->lockWaitTimeout) {\n                throw new LockWaitTimeoutException();\n            }\n            usleep($this->lockWaitInterval);\n        }\n        $this->cache->save($key, true);\n    }\n\n    public function unlock($key)\n    {\n        $key = $key . \"::LOCK\";\n        $this->cache->delete($key);\n    }\n\n    public function get($key)\n    {\n        return $this->cache->fetch($key);\n    }\n\n    public function set($key, $value)\n    {\n        $this->cache->save($key, $value);\n    }\n}\n"
  },
  {
    "path": "src/Stiphle/Storage/LockWaitTimeoutException.php",
    "content": "<?php\n/**\n * @package    Stiphle\n * @subpackage Stiphle\\Storage\n */\nnamespace Stiphle\\Storage;\n\n/**\n * This file is part of Stiphle\n *\n * Copyright (c) 2011 Dave Marshall <dave.marshall@atstsolutuions.co.uk>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\n/**\n * Thrown when a request for a lock timesout\n *\n * @author      Dave Marshall <david.marshall@atstsolutions.co.uk>\n */\nclass LockWaitTimeoutException extends \\Exception {}\n"
  },
  {
    "path": "src/Stiphle/Storage/Memcached.php",
    "content": "<?php\n/**\n * @package    Stiphle\n * @subpackage Stiphle\\Storage\n */\nnamespace Stiphle\\Storage;\n\n/**\n * This file is part of Stiphle\n *\n * Copyright (c) 2011 Dave Marshall <dave.marshall@atstsolutuions.co.uk>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\n/**\n * Use memcached via PHP's memcached extension\n *\n * @author      Dave Marshall <david.marshall@atstsolutions.co.uk>\n */\nclass Memcached implements StorageInterface\n{\n    /**\n     * @var int\n     */\n    protected $lockWaitTimeout = 1000;\n\n    /**\n     * @var int  Time to sleep when attempting to get lock in microseconds\n     */\n    protected $sleep = 100;\n\n    /**\n     * @var int \n     */\n    protected $ttl = 3600;\n\n    /**\n     * Memcached instance\n     */\n    protected $memcached;\n\n    /**\n     * Constructor\n     *\n     */\n    public function __construct(\\Memcached $memcached)\n    {\n        $this->memcached = $memcached;\n    }\n\n    /**\n     * Set lock wait timeout\n     *\n     * @param int $milliseconds\n     */\n    public function setLockWaitTimeout($milliseconds)\n    {\n        $this->lockWaitTimeout = $milliseconds;\n        return;\n    }\n\n    /**\n     * Set the sleep time in microseconds\n     *\n     * @param int \n     * @return void\n     */\n    public function setSleep($microseconds)\n    {\n        $this->sleep = $microseconds;\n        return;\n    }\n\n    /**\n     * Set the ttl for the apc records in seconds\n     *\n     * @param int $seconds\n     * @return void\n     */\n    public function setTtl($microseconds)\n    {\n        $this->ttl = $microseconds;\n        return;\n    }\n\n    /**\n     * Lock \n     *\n     * If we're using storage, we might have multiple requests coming in at\n     * once, so we lock the storage\n     *\n     * @return void\n     */\n    public function lock($key)\n    {\n        $key = $key . \"::LOCK\";\n        $start = microtime(true);\n        \n        while(!$this->memcached->add($key, true, $this->ttl)) {\n            $passed = (microtime(true) - $start) * 1000;\n            if ($passed > $this->lockWaitTimeout) {\n                throw new LockWaitTimeoutException();\n            }\n            usleep($this->sleep);\n        }\n\n        return;\n    }\n\n    /**\n     * Unlock\n     *\n     * @return void\n     */\n    public function unlock($key)\n    {\n        $key = $key . \"::LOCK\";\n        $this->memcached->delete($key);\n    }\n\n    /**\n     * Get last modified\n     *\n     * @param string $key\n     * @return int\n     */\n    public function get($key)\n    {\n        return $this->memcached->get($key);\n    }\n\n    /**\n     * set \n     *\n     * @param string $key\n     * @param mixed $value\n     * @return void\n     */\n    public function set($key, $value)\n    {\n        $this->memcached->set($key, $value, $this->ttl);\n        return;\n    }\n\n}\n\n\n\n"
  },
  {
    "path": "src/Stiphle/Storage/Process.php",
    "content": "<?php\n/**\n * @package    Stiphle\n * @subpackage Stiphle\\Storage\n */\nnamespace Stiphle\\Storage;\n\n/**\n * This file is part of Stiphle\n *\n * Copyright (c) 2011 Dave Marshall <dave.marshall@atstsolutuions.co.uk>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\n/**\n * Basic in-process storage of values\n *\n * @author      Dave Marshall <david.marshall@atstsolutions.co.uk>\n */\nclass Process implements StorageInterface\n{\n    /**\n     * @var int\n     */\n    protected $lockWaitTimeout = 1000;\n\n    /**\n     * @var array\n     */\n    protected $locked = array();\n\n    /**\n     * @var array\n     */\n    protected $values = array();\n\n    /**\n     * Set lock wait timeout\n     *\n     * @param int $milliseconds\n     */\n    public function setLockWaitTimeout($milliseconds)\n    {\n        $this->lockWaitTimeout = $milliseconds;\n    }\n\n    /**\n     * Lock \n     *\n     * If we're using storage, we might have multiple requests coming in at\n     * once, so we lock the storage\n     *\n     * @return void\n     */\n    public function lock($key)\n    {\n        if (!isset($this->locked[$key])) {\n            $this->locked[$key] = false;\n        }\n\n        $start = microtime(true);\n        while($this->locked[$key]) {\n            $passed = (microtime(true) - $start) * 1000;\n            if ($passed > $this->lockWaitTimeout) {\n                throw new LockWaitTimeoutException();\n            }\n        }\n\n        $this->locked[$key] = true;\n\n        return;\n    }\n\n    /**\n     * Unlock\n     *\n     * @return void\n     */\n    public function unlock($key)\n    {\n        $this->locked[$key] = false;\n    }\n\n    /**\n     * Get \n     *\n     * @param string $key\n     * @return int\n     */\n    public function get($key)\n    {\n        if (isset($this->values[$key])) {\n            return $this->values[$key];\n        }\n\n        return null;\n    }\n\n    /**\n     * set \n     *\n     * @param string $key\n     * @param mixed $value\n     * @return void\n     */\n    public function set($key, $value)\n    {\n        $this->values[$key] = $value;\n    }\n}\n\n\n\n"
  },
  {
    "path": "src/Stiphle/Storage/Redis.php",
    "content": "<?php\nnamespace Stiphle\\Storage;\n\n/**\n * Redis storage via Predis package\n *\n * @author Jacob Christiansen <jacob@colourbox.com>\n */\nclass Redis implements StorageInterface\n{\n    protected $lockWaitTimeout = 1000;\n    protected $redisClient;\n\n    public function __construct(\\Predis\\Client $redisClient)\n    {\n        $this->redisClient = $redisClient;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public function setLockWaitTimeout($milliseconds)\n    {\n        $this->lockWaitTimeout = $milliseconds;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public function lock($key)\n    {\n        $start = microtime(true);\n\n        while (is_null($this->redisClient->set($this->getLockKey($key), 'LOCKED', 'PX', 3600, 'NX'))) {\n            $passed = (microtime(true) - $start) * 1000;\n            if ($passed > $this->lockWaitTimeout) {\n                throw new LockWaitTimeoutException();\n            }\n            usleep(100);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public function unlock($key)\n    {\n        $this->redisClient->del($this->getLockKey($key));\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public function get($key)\n    {\n        return $this->redisClient->get($key);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public function set($key, $value)\n    {\n        $this->redisClient->set($key, $value);\n    }\n\n    private function getLockKey($key)\n    {\n        return $key . \"::LOCK\";\n    }\n}\n"
  },
  {
    "path": "src/Stiphle/Storage/StorageInterface.php",
    "content": "<?php\n/**\n * @package    Stiphle\n * @subpackage Stiphle\\Throttle\\LeakyBucket\\Storage\n */\nnamespace Stiphle\\Storage;\n\n/**\n * This file is part of Stiphle\n *\n * Copyright (c) 2011 Dave Marshall <dave.marshall@atstsolutuions.co.uk>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\n/**\n * Interface describing a persistant storage mechanism for the LeakyBucket\n * throttle\n *\n * @author      Dave Marshall <david.marshall@atstsolutions.co.uk>\n */\ninterface StorageInterface\n{\n    /**\n     * Set lock wait timout\n     *\n     * @param int $milliseconds\n     */\n    public function setLockWaitTimeout($milliseconds);\n\n    /**\n     * Lock \n     *\n     * We might have multiple requests coming in at once, so we lock the storage\n     *\n     * @return void\n     */\n    public function lock($key);\n\n    /**\n     * Unlock\n     *\n     * @return void\n     */\n    public function unlock($key);\n\n    /**\n     * Get \n     *\n     * @param string $key\n     * @return int\n     */\n    public function get($key);\n\n    /**\n     * set last modified\n     *\n     * @param string $key\n     * @param mixed $value\n     * @return void\n     */\n    public function set($key, $value);\n}\n\n\n\n"
  },
  {
    "path": "src/Stiphle/Storage/ZendStorage.php",
    "content": "<?php\n\nuse Stiphle\\Storage\\LockWaitTimeoutException;\nuse Stiphle\\Storage\\StorageInterface;\nuse Zend\\Cache\\Storage\\StorageInterface as ZendStorageInterface;\n\nclass ZendStorage implements StorageInterface\n{\n    /** @var ZendStorageInterface $cache */\n    protected $cache;\n\n    /** @var int */\n    protected $lockWaitTimeout;\n\n    /** @var int */\n    protected $lockWaitInterval;\n\n    public function __construct(ZendStorageInterface $cache, $lockWaitTimeout = 1000, $lockWaitInterval = 100)\n    {\n        $this->cache = $cache;\n        $this->lockWaitTimeout = $lockWaitTimeout;\n        $this->lockWaitInterval = $lockWaitInterval;\n    }\n\n    public function setLockWaitTimeout($lockWaitTimeout)\n    {\n        $this->lockWaitTimeout = $lockWaitTimeout;\n    }\n\n    public function lock($key)\n    {\n        $key = sprintf('%s::LOCK', $key);\n        $start = microtime(true);\n\n        while ($this->cache->hasItem($key)) {\n            $passed = (microtime(true) - $start) * 1000;\n            if ($passed > $this->lockWaitTimeout) {\n                throw new LockWaitTimeoutException();\n            }\n\n            usleep($this->lockWaitInterval);\n        }\n\n        $this->cache->setItem($key, true);\n    }\n\n    public function unlock($key)\n    {\n        $key = sprintf('%s::LOCK', $key);\n        $this->cache->removeItem($key);\n    }\n\n    public function get($key)\n    {\n        return $this->cache->getItem($key);\n    }\n\n    public function set($key, $value)\n    {\n        $this->cache->setItem($key, $value);\n    }\n}\n"
  },
  {
    "path": "src/Stiphle/Throttle/LeakyBucket.php",
    "content": "<?php\n/**\n * @package    Stiphle\n * @subpackage Stiphle\\Throttle\n */\nnamespace Stiphle\\Throttle;\n\nuse Stiphle\\Storage\\LockWaitTimeoutException;\nuse Stiphle\\Storage\\Process;\nuse Stiphle\\Storage\\StorageInterface;\n\n/**\n * This file is part of Stiphle\n *\n * Copyright (c) 2011 Dave Marshall <dave.marshall@atstsolutuions.co.uk>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\n/**\n * A 'leaky bucket' style rate limiter\n *\n * @see http://stackoverflow.com/questions/1375501/how-do-i-throttle-my-sites-api-users\n * @author      Dave Marshall <david.marshall@atstsolutions.co.uk>\n */\nclass LeakyBucket implements ThrottleInterface\n{\n    /**\n     * @var StorageInterface\n     */\n    protected $storage;\n\n    /**\n     *\n     */\n    public function __construct()\n    {\n        $this->storage = new Process();\n    }\n\n    /**\n     * Throttle\n     *\n     * @param string $key  - A unique key for what we're throttling\n     * @param int $limit   - How many are allowed\n     * @param int $milliseconds - In this many milliseconds\n     * @return int\n     * @throws LockWaitTimeoutException\n     */\n    public function throttle($key, $limit, $milliseconds)\n    {\n        /**\n         * Try and do our waiting without a lock\n         */\n        $key = $this->getStorageKey($key, $limit, $milliseconds); \n        $wait     = 0;\n        $newRatio = $this->getNewRatio($key, $limit, $milliseconds);\n\n        if ($newRatio > $milliseconds) {\n            $wait = ceil($newRatio - $milliseconds);\n        }\n        usleep($wait * 1000);\n\n        /**\n         * Lock, record and release \n         */\n        $this->storage->lock($key);\n        $newRatio = $this->getNewRatio($key, $limit, $milliseconds);\n        $this->setLastRatio($key, $newRatio);\n        $this->setLastRequest($key, microtime(1));\n        $this->storage->unlock($key);\n        return $wait;\n    }\n\n    /**\n     * Get Estimate (doesn't require lock)\n     *\n     * How long would I have to wait to make a request?\n     *\n     * @param string $key  - A unique key for what we're throttling\n     * @param int $limit   - How many are allowed\n     * @param int $milliseconds - In this many milliseconds\n     * @return int - the number of milliseconds before this request should be allowed\n     * to pass\n     */\n    public function getEstimate($key, $limit, $milliseconds)\n    {\n        $key = $this->getStorageKey($key, $limit, $milliseconds); \n        $newRatio = $this->getNewRatio($key, $limit, $milliseconds);\n        $wait     = 0;\n        if ($newRatio > $milliseconds) {\n            $wait = ceil($newRatio - $milliseconds);\n        }\n        return $wait;\n    }\n\n    /**\n     * Get new ratio\n     *\n     * Assuming we're making a request, get the ratio of requests made to\n     * requests allowed\n     *\n     * @param string $key  - A unique key for what we're throttling\n     * @param int $limit   - How many are allowed\n     * @param int $milliseconds - In this many milliseconds\n     * @return float\n     */\n    protected function getNewRatio($key, $limit, $milliseconds)\n    {\n        $lastRequest = $this->getLastRequest($key) ?: 0;\n        $lastRatio   = $this->getLastRatio($key) ?: 0;\n\n        $diff = (microtime(1) - $lastRequest) * 1000;\n\n        $newRatio = $lastRatio - $diff;\n        $newRatio = $newRatio < 0 ? 0 : $newRatio;\n        $newRatio+= $milliseconds/$limit;\n\n        return $newRatio;\n    }\n\n    /**\n     * Get storage key\n     *\n     * @param string $key  - A unique key for what we're throttling\n     * @param int $limit   - How many are allowed\n     * @param int $milliseconds - In this many milliseconds\n     * @return string\n     */\n    protected function getStorageKey($key, $limit, $milliseconds)\n    {\n        return $key . '::' . $limit . '::' . $milliseconds; \n    }\n\n    /**\n     * Set Storage\n     *\n     * @param StorageInterface $storage\n     * @return LeakyBucket\n     */\n    public function setStorage(StorageInterface $storage)\n    {\n        $this->storage = $storage;\n        return $this;\n    }\n\n    /**\n     * Get Last Ratio\n     *\n     * @param string $key\n     * @return float\n     */\n    protected function getLastRatio($key)\n    {\n        return $this->storage->get($key . '::LASTRATIO');\n    }\n\n    /**\n     * Set Last Ratio\n     *\n     * @param string $key\n     * @param float $ratio\n     * @return void\n     */\n    protected function setLastRatio($key, $ratio)\n    {\n        $this->storage->set($key . '::LASTRATIO', $ratio);\n    }\n\n    /**\n     * Get Last Request\n     *\n     * @param string $key\n     * @return float\n     */\n    protected function getLastRequest($key)\n    {\n        return $this->storage->get($key . '::LASTREQUEST');\n    }\n\n    /**\n     * Set Last Request\n     *\n     * @param string $key\n     * @param float $request\n     * @return void\n     */\n    protected function setLastRequest($key, $request)\n    {\n        $this->storage->set($key . '::LASTREQUEST', $request);\n    }\n}\n"
  },
  {
    "path": "src/Stiphle/Throttle/ThrottleInterface.php",
    "content": "<?php\n/**\n * @package    Stiphle\n * @subpackage \n */\n\nnamespace Stiphle\\Throttle;\n\nuse Stiphle\\Storage\\LockWaitTimeoutException;\n\n/**\n * This file is part of Stiphle\n *\n * Copyright (c) 2011 Dave Marshall <dave.marshall@atstsolutuions.co.uk>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\n/**\n * Interface describing a throttle\n *\n * @author      Dave Marshall <david.marshall@atstsolutions.co.uk>\n */\ninterface ThrottleInterface\n{\n\n    /**\n     * Throttle\n     *\n     * @param string $key  - A unique key for what we're throttling\n     * @param int $limit   - How many are allowed\n     * @param int $milliseconds - In this many milliseconds\n     * @return int\n     * @throws LockWaitTimeoutException\n     */\n    public function throttle($key, $limit, $milliseconds);\n\n    /**\n     * Get Estimate\n     *\n     * If I were to throttle now, how long would I be waiting\n     *\n     * @param string $key  - A unique key for what we're throttling\n     * @param int $limit   - How many are allowed\n     * @param int $milliseconds - In this many milliseconds\n     * @return int - the number of milliseconds before this request should be allowed\n     */\n    public function getEstimate($key, $limit, $milliseconds);\n}\n\n\n\n"
  },
  {
    "path": "src/Stiphle/Throttle/TimeWindow.php",
    "content": "<?php\n\nnamespace Stiphle\\Throttle;\n\nuse Stiphle\\Storage\\LockWaitTimeoutException;\nuse Stiphle\\Storage\\StorageInterface;\nuse Stiphle\\Storage\\Process;\n\n/**\n * This file is part of Stiphle\n *\n * Copyright (c) 2011 Dave Marshall <dave.marshall@atstsolutuions.co.uk>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\n/**\n * A throttle based on a fixed time window\n *\n * @author      Dave Marshall <david.marshall@atstsolutions.co.uk>\n */\nclass TimeWindow implements ThrottleInterface\n{\n    /**\n     * @var StorageInterface\n     */\n    protected $storage;\n\n    /**\n     *\n     */\n    public function __construct()\n    {\n        $this->storage = new Process();\n    }\n\n    /**\n     * Throttle\n     *\n     * @param string $key  - A unique key for what we're throttling\n     * @param int $limit   - How many are allowed\n     * @param int $milliseconds - In this many milliseconds\n     * @return int\n     * @throws LockWaitTimeoutException\n     */\n    public function throttle($key, $limit, $milliseconds)\n    {\n        /**\n         * Try do our waiting without a lock, so may sneak through because of\n         * this...\n         */\n        $wait = $this->getEstimate($key, $limit, $milliseconds);\n        if ($wait > 0) {\n            usleep($wait * 1000);\n        }\n\n        $key = $this->getStorageKey($key, $limit, $milliseconds); \n        $this->storage->lock($key);\n        $count = $this->storage->get($key);\n        $count++;\n        $this->storage->set($key, $count);\n        $this->storage->unlock($key);\n        return $wait;\n    }\n\n    /**\n     * Get Estimate (doesn't require lock)\n     *\n     * How long would I have to wait to make a request?\n     *\n     * @param string $key  - A unique key for what we're throttling\n     * @param int $limit   - How many are allowed\n     * @param int $milliseconds - In this many milliseconds\n     * @return int - the number of milliseconds before this request should be allowed\n     * to pass\n     */\n    public function getEstimate($key, $limit, $milliseconds)\n    {\n        $key = $this->getStorageKey($key, $limit, $milliseconds); \n        $count = $this->storage->get($key);\n        if ($count < $limit) {\n            return 0;\n        }\n\n        return $milliseconds - ((microtime(1) * 1000) % (float) $milliseconds);\n    }\n\n    /**\n     * Get storage key\n     *\n     * @param string $key  - A unique key for what we're throttling\n     * @param int $limit   - How many are allowed\n     * @param int $milliseconds - In this many milliseconds\n     * @return string\n     */\n    protected function getStorageKey($key, $limit, $milliseconds)\n    {\n        $window = $milliseconds * (floor((microtime(1) * 1000)/$milliseconds));\n        $date = date('YmdHis', $window/1000);\n        return $date . '::' . $key . '::' . $limit . '::' . $milliseconds . '::COUNT'; \n    }\n\n    /**\n     * Set Storage\n     *\n     * @param StorageInterface $storage\n     * @return TimeWindow\n     */\n    public function setStorage(StorageInterface $storage)\n    {\n        $this->storage = $storage;\n        return $this;\n    }\n\n}\n"
  },
  {
    "path": "tests/src/Stiphle/Storage/ApcTest.php",
    "content": "<?php\n/**\n * @package\n * @subpackage\n */\nnamespace Stiphle\\Storage;\n\nuse \\PHPUnit_Framework_TestCase;\n\n/**\n * This file is part of Stiphle\n *\n * Copyright (c) 2011 Dave Marshall <dave.marshall@atstsolutuions.co.uk>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\n/**\n * TITLE\n *\n * DESCRIPTION\n *\n * @author      Dave Marshall <david.marshall@atstsolutions.co.uk>\n */\nclass ApcTest extends PHPUnit_Framework_TestCase\n{\n    protected $storage = null;\n\n    public function setup()\n    {\n        $this->storage = new Apc();\n    }\n\n    public function tearDown()\n    {\n        apc_delete('dave::LOCK');\n    }\n\n    /**\n     * @expectedException Stiphle\\Storage\\LockWaitTimeoutException\n     */\n    public function testLockThrowsLockWaitTimeoutException()\n    {\n        if (!ini_get('apc.enable_cli') && !ini_get('apcu.enable_cli')) {\n            $this->markTestSkipped('APC and APCu needs enabling for the cli via apc.enable_cli=1 or apcu.enable_cli=1');\n        }\n\n        $this->storage->lock('dave');        \n        $this->storage->lock('dave');\n    }\n\n\n    public function testLockRespectsLockWaitTimeoutValue()\n    {\n        if (!ini_get('apc.enable_cli') && !ini_get('apcu.enable_cli')) {\n            $this->markTestSkipped('APC and APCu needs enabling for the cli via apc.enable_cli=1 or apcu.enable_cli=1');\n        }\n\n        /**\n         * Test we can do this \n         */\n        $this->storage->lock('dave');\n        try {\n            $start = microtime(1);\n            $this->storage->lock('dave');\n        } catch (LockWaitTimeoutException $e) {\n            $caught = microtime(1);\n            $diff   = $caught - $start;\n            if (round($diff) != 1) {\n                $this->markTestSkipped(\"Don't think the timings will be accurate enough, expected exception after 1 second, was $diff\");\n            }\n        }\n\n        $this->storage->setLockWaitTimeout(2000);\n        try {\n            $start = microtime(1);\n            $this->storage->lock('dave');\n            $this->fail(\"should not get to this point\");\n        } catch (LockWaitTimeoutException $e) {\n            $caught = microtime(1);\n            $diff   = $caught - $start;\n            $this->assertEquals(2, round($diff), \"Exception thrown after approximately 2000 milliseconds\");\n        }       \n    }\n}\n\n\n"
  },
  {
    "path": "tests/src/Stiphle/Storage/ProcessTest.php",
    "content": "<?php\n/**\n * @package\n * @subpackage\n */\nnamespace Stiphle\\Storage;\n\nuse \\PHPUnit_Framework_TestCase;\n\n/**\n * This file is part of Stiphle\n *\n * Copyright (c) 2011 Dave Marshall <dave.marshall@atstsolutuions.co.uk>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\n/**\n * TITLE\n *\n * DESCRIPTION\n *\n * @author      Dave Marshall <david.marshall@atstsolutions.co.uk>\n */\nclass ProcessTest extends PHPUnit_Framework_TestCase\n{\n    protected $storage = null;\n\n    public function setup()\n    {\n        $this->storage = new Process();\n    }\n\n    /**\n     * @expectedException Stiphle\\Storage\\LockWaitTimeoutException\n     */\n    public function testLockThrowsLockWaitTimeoutException()\n    {\n        $this->storage->lock('dave');        \n        $this->storage->lock('dave');\n    }\n\n\n    public function testLockRespectsLockWaitTimeoutValue()\n    {\n        /**\n         * Test we can do this \n         */\n        $this->storage->lock('dave');\n        try {\n            $start = microtime(1);\n            $this->storage->lock('dave');\n        } catch (LockWaitTimeoutException $e) {\n            $caught = microtime(1);\n            $diff   = $caught - $start;\n            if (round($diff) != 1) {\n                $this->markTestSkipped(\"Don't think the timings will be accurate enough, expected exception after 1 second, was $diff\");\n            }\n        }\n\n        $this->storage->setLockWaitTimeout(2000);\n        try {\n            $start = microtime(1);\n            $this->storage->lock('dave');\n            $this->fail(\"should not get to this point\");\n        } catch (LockWaitTimeoutException $e) {\n            $caught = microtime(1);\n            $diff   = $caught - $start;\n            $this->assertEquals(2, round($diff), \"Exception thrown after approximately 2000 milliseconds\");\n        }       \n    }\n}\n\n\n"
  },
  {
    "path": "tests/src/Stiphle/Storage/RedisTest.php",
    "content": "<?php\n/* vim: set ts=4 sw=4 tw=0 et :*/\n\nnamespace Stiphle\\Storage;\n\nuse \\PHPUnit_Framework_TestCase;\n\nclass RedisTest extends PHPUnit_Framework_TestCase\n{\n    public function testLockThrowsLockWaitTimeoutException()\n    {\n        $redisClient = $this->getMockBuilder(\\Predis\\Client::class)\n                            ->setMethods(['set'])\n                            ->getMock();\n\n        $redisClient->expects($this->at(0))\n            ->method('set')\n            ->with('dave::LOCK', 'LOCKED', 'PX', 3600, 'NX')\n            ->will($this->returnValue(1));\n\n        $redisClient->expects($this->any())\n            ->method('set')\n            ->with('dave::LOCK', 'LOCKED', 'PX', 3600, 'NX')\n            ->will($this->returnValue(null));\n\n        $this->expectException(\\Stiphle\\Storage\\LockWaitTimeoutException::class);\n\n        $storage = new Redis($redisClient);\n\n        $storage->lock('dave');\n        $storage->lock('dave');\n    }\n\n    public function testStorageCanBeUnlocked()\n    {\n        $redisClient = $this->getMockBuilder(\\Predis\\Client::class)\n                            ->setMethods(['del'])\n                            ->getMock();\n\n        $redisClient->expects($this->once())\n            ->method('del')\n            ->with('dave::LOCK');\n\n        $storage = new Redis($redisClient);\n\n        $storage->unlock('dave');\n    }\n}\n"
  },
  {
    "path": "tests/src/Stiphle/Throttle/LeakyBucketTest.php",
    "content": "<?php\n/**\n * @package\n * @subpackage\n */\nnamespace Stiphle\\Throttle;\n\nuse \\PHPUnit_Framework_TestCase;\n\n/**\n * This file is part of Stiphle\n *\n * Copyright (c) 2011 Dave Marshall <dave.marshall@atstsolutuions.co.uk>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\n/**\n * TITLE\n *\n * DESCRIPTION\n *\n * @author      Dave Marshall <david.marshall@atstsolutions.co.uk>\n */\nclass LeakyBucketTest extends PHPUnit_Framework_TestCase\n{\n    /** @var LeakyBucket */\n    protected $throttle;\n\n    public function setup()\n    {\n        $this->throttle = new LeakyBucket();\n    }\n\n    /**\n     * This test assumes your machine is capable of processing the first five\n     * calls in less that a second :)\n     *\n     * Nothing special here, ideally we need to mock the storage out and test it\n     * with different values etc\n     */\n    public function testGetEstimate()\n    {\n        $this->assertEquals(0, $this->throttle->throttle('dave', 5, 1000));\n        $this->assertEquals(0, $this->throttle->throttle('dave', 5, 1000));\n        $this->assertEquals(0, $this->throttle->throttle('dave', 5, 1000));\n        $this->assertEquals(0, $this->throttle->throttle('dave', 5, 1000));\n        $this->assertEquals(0, $this->throttle->throttle('dave', 5, 1000));\n        $this->assertGreaterThan(0, $this->throttle->getEstimate('dave', 5, 1000));\n        $this->assertGreaterThan(0, $this->throttle->throttle('dave', 5, 1000));\n    }\n}\n\n\n"
  },
  {
    "path": "tests/src/Stiphle/Throttle/TimeWindowTest.php",
    "content": "<?php\n\nnamespace Stiphle\\Throttle;\n\nuse \\PHPUnit_Framework_TestCase;\n\n/**\n * This file is part of Stiphle\n *\n * Copyright (c) 2011 Dave Marshall <dave.marshall@atstsolutuions.co.uk>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\n/**\n * TITLE\n *\n * DESCRIPTION\n *\n * @author      Dave Marshall <david.marshall@atstsolutions.co.uk>\n */\nclass TimeWindowTest extends PHPUnit_Framework_TestCase\n{\n    /** @var TimeWindow */\n    protected $throttle;\n\n    public function setup()\n    {\n        $this->throttle = new TimeWindow();\n    }\n\n    /**\n     * Really crap test here, without mocking the system time, it's difficult to\n     * know when you're going to throttled...\n     */\n    public function testGetEstimate()\n    {\n        $timeout = strtotime('+5 seconds', microtime(1));\n        $count = 0;\n        while (microtime(1) < $timeout) {\n            $wait = $this->throttle->throttle('dave', 5, 1000);\n            if (microtime(1) < $timeout) {\n                $count++;\n            }\n        }\n\n        $this->assertEquals(25, $count);\n    }\n}\n\n\n"
  }
]