├── .coveralls.yml ├── .travis.yml ├── LICENSE.md ├── README.md ├── composer.json ├── composer.lock ├── logo.png ├── phpunit.xml ├── src ├── Backtrace.php ├── Bucket.php ├── Exceptions │ └── NotIterableException.php ├── Iterator.php ├── MethodThrottler.php ├── Throttler.php └── functions.php └── tests ├── BucketTest.php ├── IteratorCountingTest.php ├── IteratorTest.php ├── MethodThrottlerTest.php ├── ThrottlerTest.php └── helpers ├── IterableCountableClass.php ├── IterableNotCountableClass.php └── MockQueryBuilder.php /.coveralls.yml: -------------------------------------------------------------------------------- 1 | coverage_clover: build/logs/clover.xml 2 | json_path: build/logs/coveralls-upload.json 3 | service_name: travis-ci -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.3 5 | 6 | before_script: 7 | - mkdir -p build/logs 8 | - composer self-update 9 | - composer install --prefer-source --no-interaction --dev 10 | 11 | script: 12 | - vendor/bin/phpunit --coverage-clover build/logs/clover.xml 13 | 14 | after_script: 15 | - php vendor/bin/coveralls -v 16 | 17 | after_success: 18 | - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then php vendor/bin/coveralls -v; fi;' -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) Rackbeat & Lasse Rafn 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Rackbeat throttler 3 |
Throttle PHP actions to only run a set amount of times within a given timeframe. 4 |
Especially useful to avoid rate limiting, high CPU usage or DOS'ing your own services (database etc.) 5 |

6 | 7 |

8 | Build Status 9 | Coverage 10 | Total Downloads 11 | Latest Stable Version 12 | License 13 |

14 | 15 | ## Installation 16 | 17 | You just require using composer and you're good to go! 18 | 19 | ```bash 20 | composer require rackbeat/php-throttler 21 | ``` 22 | 23 | ## Usage 24 | 25 | ### Basic example 26 | 27 | ```php 28 | use Rackbeat\Throttler\Throttler; 29 | 30 | Throttler::make( range(1,100) )->allow(10)->every(1)->run( function($value, $key) { 31 | // do something with $value 32 | } ); 33 | ``` 34 | 35 | ## Requirements 36 | * PHP >= 7.3 37 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rackbeat/php-throttler", 3 | "description": "Throttle PHP function calls and iteration runs by X executions per second.", 4 | "keywords": [ 5 | "laravel", 6 | "php", 7 | "throttle", 8 | "rate-limit" 9 | ], 10 | "type": "library", 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Lasse Rafn", 15 | "email": "lasserafn@gmail.com" 16 | }, 17 | { 18 | "name": "Rackbeat", 19 | "email": "open-source@rackbeat.com" 20 | } 21 | ], 22 | "require": { 23 | "php": ">=7.3" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "^7.0", 27 | "php-coveralls/php-coveralls": "^1.0" 28 | }, 29 | "autoload": { 30 | "files": [ 31 | "src/functions.php" 32 | ], 33 | "psr-4": { 34 | "Rackbeat\\Throttler\\": "src/" 35 | } 36 | }, 37 | "autoload-dev": { 38 | "psr-4": { 39 | "Throttler\\Tests\\": "tests/" 40 | } 41 | }, 42 | "config": { 43 | "sort-packages": true 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "88ccb51ed0179e4ae76ba88894b8f08d", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "doctrine/instantiator", 12 | "version": "1.3.0", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/doctrine/instantiator.git", 16 | "reference": "ae466f726242e637cebdd526a7d991b9433bacf1" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1", 21 | "reference": "ae466f726242e637cebdd526a7d991b9433bacf1", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": "^7.1" 26 | }, 27 | "require-dev": { 28 | "doctrine/coding-standard": "^6.0", 29 | "ext-pdo": "*", 30 | "ext-phar": "*", 31 | "phpbench/phpbench": "^0.13", 32 | "phpstan/phpstan-phpunit": "^0.11", 33 | "phpstan/phpstan-shim": "^0.11", 34 | "phpunit/phpunit": "^7.0" 35 | }, 36 | "type": "library", 37 | "extra": { 38 | "branch-alias": { 39 | "dev-master": "1.2.x-dev" 40 | } 41 | }, 42 | "autoload": { 43 | "psr-4": { 44 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 45 | } 46 | }, 47 | "notification-url": "https://packagist.org/downloads/", 48 | "license": [ 49 | "MIT" 50 | ], 51 | "authors": [ 52 | { 53 | "name": "Marco Pivetta", 54 | "email": "ocramius@gmail.com", 55 | "homepage": "http://ocramius.github.com/" 56 | } 57 | ], 58 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 59 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 60 | "keywords": [ 61 | "constructor", 62 | "instantiate" 63 | ], 64 | "time": "2019-10-21T16:45:58+00:00" 65 | }, 66 | { 67 | "name": "guzzle/guzzle", 68 | "version": "v3.9.3", 69 | "source": { 70 | "type": "git", 71 | "url": "https://github.com/guzzle/guzzle3.git", 72 | "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" 73 | }, 74 | "dist": { 75 | "type": "zip", 76 | "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", 77 | "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", 78 | "shasum": "" 79 | }, 80 | "require": { 81 | "ext-curl": "*", 82 | "php": ">=5.3.3", 83 | "symfony/event-dispatcher": "~2.1" 84 | }, 85 | "replace": { 86 | "guzzle/batch": "self.version", 87 | "guzzle/cache": "self.version", 88 | "guzzle/common": "self.version", 89 | "guzzle/http": "self.version", 90 | "guzzle/inflection": "self.version", 91 | "guzzle/iterator": "self.version", 92 | "guzzle/log": "self.version", 93 | "guzzle/parser": "self.version", 94 | "guzzle/plugin": "self.version", 95 | "guzzle/plugin-async": "self.version", 96 | "guzzle/plugin-backoff": "self.version", 97 | "guzzle/plugin-cache": "self.version", 98 | "guzzle/plugin-cookie": "self.version", 99 | "guzzle/plugin-curlauth": "self.version", 100 | "guzzle/plugin-error-response": "self.version", 101 | "guzzle/plugin-history": "self.version", 102 | "guzzle/plugin-log": "self.version", 103 | "guzzle/plugin-md5": "self.version", 104 | "guzzle/plugin-mock": "self.version", 105 | "guzzle/plugin-oauth": "self.version", 106 | "guzzle/service": "self.version", 107 | "guzzle/stream": "self.version" 108 | }, 109 | "require-dev": { 110 | "doctrine/cache": "~1.3", 111 | "monolog/monolog": "~1.0", 112 | "phpunit/phpunit": "3.7.*", 113 | "psr/log": "~1.0", 114 | "symfony/class-loader": "~2.1", 115 | "zendframework/zend-cache": "2.*,<2.3", 116 | "zendframework/zend-log": "2.*,<2.3" 117 | }, 118 | "suggest": { 119 | "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." 120 | }, 121 | "type": "library", 122 | "extra": { 123 | "branch-alias": { 124 | "dev-master": "3.9-dev" 125 | } 126 | }, 127 | "autoload": { 128 | "psr-0": { 129 | "Guzzle": "src/", 130 | "Guzzle\\Tests": "tests/" 131 | } 132 | }, 133 | "notification-url": "https://packagist.org/downloads/", 134 | "license": [ 135 | "MIT" 136 | ], 137 | "authors": [ 138 | { 139 | "name": "Michael Dowling", 140 | "email": "mtdowling@gmail.com", 141 | "homepage": "https://github.com/mtdowling" 142 | }, 143 | { 144 | "name": "Guzzle Community", 145 | "homepage": "https://github.com/guzzle/guzzle/contributors" 146 | } 147 | ], 148 | "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", 149 | "homepage": "http://guzzlephp.org/", 150 | "keywords": [ 151 | "client", 152 | "curl", 153 | "framework", 154 | "http", 155 | "http client", 156 | "rest", 157 | "web service" 158 | ], 159 | "abandoned": "guzzlehttp/guzzle", 160 | "time": "2015-03-18T18:23:50+00:00" 161 | }, 162 | { 163 | "name": "myclabs/deep-copy", 164 | "version": "1.9.3", 165 | "source": { 166 | "type": "git", 167 | "url": "https://github.com/myclabs/DeepCopy.git", 168 | "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" 169 | }, 170 | "dist": { 171 | "type": "zip", 172 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", 173 | "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", 174 | "shasum": "" 175 | }, 176 | "require": { 177 | "php": "^7.1" 178 | }, 179 | "replace": { 180 | "myclabs/deep-copy": "self.version" 181 | }, 182 | "require-dev": { 183 | "doctrine/collections": "^1.0", 184 | "doctrine/common": "^2.6", 185 | "phpunit/phpunit": "^7.1" 186 | }, 187 | "type": "library", 188 | "autoload": { 189 | "psr-4": { 190 | "DeepCopy\\": "src/DeepCopy/" 191 | }, 192 | "files": [ 193 | "src/DeepCopy/deep_copy.php" 194 | ] 195 | }, 196 | "notification-url": "https://packagist.org/downloads/", 197 | "license": [ 198 | "MIT" 199 | ], 200 | "description": "Create deep copies (clones) of your objects", 201 | "keywords": [ 202 | "clone", 203 | "copy", 204 | "duplicate", 205 | "object", 206 | "object graph" 207 | ], 208 | "time": "2019-08-09T12:45:53+00:00" 209 | }, 210 | { 211 | "name": "phar-io/manifest", 212 | "version": "1.0.3", 213 | "source": { 214 | "type": "git", 215 | "url": "https://github.com/phar-io/manifest.git", 216 | "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" 217 | }, 218 | "dist": { 219 | "type": "zip", 220 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", 221 | "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", 222 | "shasum": "" 223 | }, 224 | "require": { 225 | "ext-dom": "*", 226 | "ext-phar": "*", 227 | "phar-io/version": "^2.0", 228 | "php": "^5.6 || ^7.0" 229 | }, 230 | "type": "library", 231 | "extra": { 232 | "branch-alias": { 233 | "dev-master": "1.0.x-dev" 234 | } 235 | }, 236 | "autoload": { 237 | "classmap": [ 238 | "src/" 239 | ] 240 | }, 241 | "notification-url": "https://packagist.org/downloads/", 242 | "license": [ 243 | "BSD-3-Clause" 244 | ], 245 | "authors": [ 246 | { 247 | "name": "Arne Blankerts", 248 | "email": "arne@blankerts.de", 249 | "role": "Developer" 250 | }, 251 | { 252 | "name": "Sebastian Heuer", 253 | "email": "sebastian@phpeople.de", 254 | "role": "Developer" 255 | }, 256 | { 257 | "name": "Sebastian Bergmann", 258 | "email": "sebastian@phpunit.de", 259 | "role": "Developer" 260 | } 261 | ], 262 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 263 | "time": "2018-07-08T19:23:20+00:00" 264 | }, 265 | { 266 | "name": "phar-io/version", 267 | "version": "2.0.1", 268 | "source": { 269 | "type": "git", 270 | "url": "https://github.com/phar-io/version.git", 271 | "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" 272 | }, 273 | "dist": { 274 | "type": "zip", 275 | "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", 276 | "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", 277 | "shasum": "" 278 | }, 279 | "require": { 280 | "php": "^5.6 || ^7.0" 281 | }, 282 | "type": "library", 283 | "autoload": { 284 | "classmap": [ 285 | "src/" 286 | ] 287 | }, 288 | "notification-url": "https://packagist.org/downloads/", 289 | "license": [ 290 | "BSD-3-Clause" 291 | ], 292 | "authors": [ 293 | { 294 | "name": "Arne Blankerts", 295 | "email": "arne@blankerts.de", 296 | "role": "Developer" 297 | }, 298 | { 299 | "name": "Sebastian Heuer", 300 | "email": "sebastian@phpeople.de", 301 | "role": "Developer" 302 | }, 303 | { 304 | "name": "Sebastian Bergmann", 305 | "email": "sebastian@phpunit.de", 306 | "role": "Developer" 307 | } 308 | ], 309 | "description": "Library for handling version information and constraints", 310 | "time": "2018-07-08T19:19:57+00:00" 311 | }, 312 | { 313 | "name": "php-coveralls/php-coveralls", 314 | "version": "v1.1.0", 315 | "source": { 316 | "type": "git", 317 | "url": "https://github.com/php-coveralls/php-coveralls.git", 318 | "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad" 319 | }, 320 | "dist": { 321 | "type": "zip", 322 | "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", 323 | "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", 324 | "shasum": "" 325 | }, 326 | "require": { 327 | "ext-json": "*", 328 | "ext-simplexml": "*", 329 | "guzzle/guzzle": "^2.8 || ^3.0", 330 | "php": "^5.3.3 || ^7.0", 331 | "psr/log": "^1.0", 332 | "symfony/config": "^2.1 || ^3.0 || ^4.0", 333 | "symfony/console": "^2.1 || ^3.0 || ^4.0", 334 | "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0", 335 | "symfony/yaml": "^2.0 || ^3.0 || ^4.0" 336 | }, 337 | "require-dev": { 338 | "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0" 339 | }, 340 | "suggest": { 341 | "symfony/http-kernel": "Allows Symfony integration" 342 | }, 343 | "bin": [ 344 | "bin/coveralls" 345 | ], 346 | "type": "library", 347 | "autoload": { 348 | "psr-4": { 349 | "Satooshi\\": "src/Satooshi/" 350 | } 351 | }, 352 | "notification-url": "https://packagist.org/downloads/", 353 | "license": [ 354 | "MIT" 355 | ], 356 | "authors": [ 357 | { 358 | "name": "Kitamura Satoshi", 359 | "email": "with.no.parachute@gmail.com", 360 | "homepage": "https://www.facebook.com/satooshi.jp" 361 | } 362 | ], 363 | "description": "PHP client library for Coveralls API", 364 | "homepage": "https://github.com/php-coveralls/php-coveralls", 365 | "keywords": [ 366 | "ci", 367 | "coverage", 368 | "github", 369 | "test" 370 | ], 371 | "time": "2017-12-06T23:17:56+00:00" 372 | }, 373 | { 374 | "name": "phpdocumentor/reflection-common", 375 | "version": "2.0.0", 376 | "source": { 377 | "type": "git", 378 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 379 | "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" 380 | }, 381 | "dist": { 382 | "type": "zip", 383 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", 384 | "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", 385 | "shasum": "" 386 | }, 387 | "require": { 388 | "php": ">=7.1" 389 | }, 390 | "require-dev": { 391 | "phpunit/phpunit": "~6" 392 | }, 393 | "type": "library", 394 | "extra": { 395 | "branch-alias": { 396 | "dev-master": "2.x-dev" 397 | } 398 | }, 399 | "autoload": { 400 | "psr-4": { 401 | "phpDocumentor\\Reflection\\": "src/" 402 | } 403 | }, 404 | "notification-url": "https://packagist.org/downloads/", 405 | "license": [ 406 | "MIT" 407 | ], 408 | "authors": [ 409 | { 410 | "name": "Jaap van Otterdijk", 411 | "email": "opensource@ijaap.nl" 412 | } 413 | ], 414 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 415 | "homepage": "http://www.phpdoc.org", 416 | "keywords": [ 417 | "FQSEN", 418 | "phpDocumentor", 419 | "phpdoc", 420 | "reflection", 421 | "static analysis" 422 | ], 423 | "time": "2018-08-07T13:53:10+00:00" 424 | }, 425 | { 426 | "name": "phpdocumentor/reflection-docblock", 427 | "version": "4.3.2", 428 | "source": { 429 | "type": "git", 430 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 431 | "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e" 432 | }, 433 | "dist": { 434 | "type": "zip", 435 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e", 436 | "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e", 437 | "shasum": "" 438 | }, 439 | "require": { 440 | "php": "^7.0", 441 | "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", 442 | "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", 443 | "webmozart/assert": "^1.0" 444 | }, 445 | "require-dev": { 446 | "doctrine/instantiator": "^1.0.5", 447 | "mockery/mockery": "^1.0", 448 | "phpunit/phpunit": "^6.4" 449 | }, 450 | "type": "library", 451 | "extra": { 452 | "branch-alias": { 453 | "dev-master": "4.x-dev" 454 | } 455 | }, 456 | "autoload": { 457 | "psr-4": { 458 | "phpDocumentor\\Reflection\\": [ 459 | "src/" 460 | ] 461 | } 462 | }, 463 | "notification-url": "https://packagist.org/downloads/", 464 | "license": [ 465 | "MIT" 466 | ], 467 | "authors": [ 468 | { 469 | "name": "Mike van Riel", 470 | "email": "me@mikevanriel.com" 471 | } 472 | ], 473 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 474 | "time": "2019-09-12T14:27:41+00:00" 475 | }, 476 | { 477 | "name": "phpdocumentor/type-resolver", 478 | "version": "1.0.1", 479 | "source": { 480 | "type": "git", 481 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 482 | "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" 483 | }, 484 | "dist": { 485 | "type": "zip", 486 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", 487 | "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", 488 | "shasum": "" 489 | }, 490 | "require": { 491 | "php": "^7.1", 492 | "phpdocumentor/reflection-common": "^2.0" 493 | }, 494 | "require-dev": { 495 | "ext-tokenizer": "^7.1", 496 | "mockery/mockery": "~1", 497 | "phpunit/phpunit": "^7.0" 498 | }, 499 | "type": "library", 500 | "extra": { 501 | "branch-alias": { 502 | "dev-master": "1.x-dev" 503 | } 504 | }, 505 | "autoload": { 506 | "psr-4": { 507 | "phpDocumentor\\Reflection\\": "src" 508 | } 509 | }, 510 | "notification-url": "https://packagist.org/downloads/", 511 | "license": [ 512 | "MIT" 513 | ], 514 | "authors": [ 515 | { 516 | "name": "Mike van Riel", 517 | "email": "me@mikevanriel.com" 518 | } 519 | ], 520 | "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", 521 | "time": "2019-08-22T18:11:29+00:00" 522 | }, 523 | { 524 | "name": "phpspec/prophecy", 525 | "version": "1.9.0", 526 | "source": { 527 | "type": "git", 528 | "url": "https://github.com/phpspec/prophecy.git", 529 | "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203" 530 | }, 531 | "dist": { 532 | "type": "zip", 533 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/f6811d96d97bdf400077a0cc100ae56aa32b9203", 534 | "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203", 535 | "shasum": "" 536 | }, 537 | "require": { 538 | "doctrine/instantiator": "^1.0.2", 539 | "php": "^5.3|^7.0", 540 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", 541 | "sebastian/comparator": "^1.1|^2.0|^3.0", 542 | "sebastian/recursion-context": "^1.0|^2.0|^3.0" 543 | }, 544 | "require-dev": { 545 | "phpspec/phpspec": "^2.5|^3.2", 546 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" 547 | }, 548 | "type": "library", 549 | "extra": { 550 | "branch-alias": { 551 | "dev-master": "1.8.x-dev" 552 | } 553 | }, 554 | "autoload": { 555 | "psr-4": { 556 | "Prophecy\\": "src/Prophecy" 557 | } 558 | }, 559 | "notification-url": "https://packagist.org/downloads/", 560 | "license": [ 561 | "MIT" 562 | ], 563 | "authors": [ 564 | { 565 | "name": "Konstantin Kudryashov", 566 | "email": "ever.zet@gmail.com", 567 | "homepage": "http://everzet.com" 568 | }, 569 | { 570 | "name": "Marcello Duarte", 571 | "email": "marcello.duarte@gmail.com" 572 | } 573 | ], 574 | "description": "Highly opinionated mocking framework for PHP 5.3+", 575 | "homepage": "https://github.com/phpspec/prophecy", 576 | "keywords": [ 577 | "Double", 578 | "Dummy", 579 | "fake", 580 | "mock", 581 | "spy", 582 | "stub" 583 | ], 584 | "time": "2019-10-03T11:07:50+00:00" 585 | }, 586 | { 587 | "name": "phpunit/php-code-coverage", 588 | "version": "6.1.4", 589 | "source": { 590 | "type": "git", 591 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 592 | "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" 593 | }, 594 | "dist": { 595 | "type": "zip", 596 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", 597 | "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", 598 | "shasum": "" 599 | }, 600 | "require": { 601 | "ext-dom": "*", 602 | "ext-xmlwriter": "*", 603 | "php": "^7.1", 604 | "phpunit/php-file-iterator": "^2.0", 605 | "phpunit/php-text-template": "^1.2.1", 606 | "phpunit/php-token-stream": "^3.0", 607 | "sebastian/code-unit-reverse-lookup": "^1.0.1", 608 | "sebastian/environment": "^3.1 || ^4.0", 609 | "sebastian/version": "^2.0.1", 610 | "theseer/tokenizer": "^1.1" 611 | }, 612 | "require-dev": { 613 | "phpunit/phpunit": "^7.0" 614 | }, 615 | "suggest": { 616 | "ext-xdebug": "^2.6.0" 617 | }, 618 | "type": "library", 619 | "extra": { 620 | "branch-alias": { 621 | "dev-master": "6.1-dev" 622 | } 623 | }, 624 | "autoload": { 625 | "classmap": [ 626 | "src/" 627 | ] 628 | }, 629 | "notification-url": "https://packagist.org/downloads/", 630 | "license": [ 631 | "BSD-3-Clause" 632 | ], 633 | "authors": [ 634 | { 635 | "name": "Sebastian Bergmann", 636 | "email": "sebastian@phpunit.de", 637 | "role": "lead" 638 | } 639 | ], 640 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 641 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 642 | "keywords": [ 643 | "coverage", 644 | "testing", 645 | "xunit" 646 | ], 647 | "time": "2018-10-31T16:06:48+00:00" 648 | }, 649 | { 650 | "name": "phpunit/php-file-iterator", 651 | "version": "2.0.2", 652 | "source": { 653 | "type": "git", 654 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 655 | "reference": "050bedf145a257b1ff02746c31894800e5122946" 656 | }, 657 | "dist": { 658 | "type": "zip", 659 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", 660 | "reference": "050bedf145a257b1ff02746c31894800e5122946", 661 | "shasum": "" 662 | }, 663 | "require": { 664 | "php": "^7.1" 665 | }, 666 | "require-dev": { 667 | "phpunit/phpunit": "^7.1" 668 | }, 669 | "type": "library", 670 | "extra": { 671 | "branch-alias": { 672 | "dev-master": "2.0.x-dev" 673 | } 674 | }, 675 | "autoload": { 676 | "classmap": [ 677 | "src/" 678 | ] 679 | }, 680 | "notification-url": "https://packagist.org/downloads/", 681 | "license": [ 682 | "BSD-3-Clause" 683 | ], 684 | "authors": [ 685 | { 686 | "name": "Sebastian Bergmann", 687 | "email": "sebastian@phpunit.de", 688 | "role": "lead" 689 | } 690 | ], 691 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 692 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 693 | "keywords": [ 694 | "filesystem", 695 | "iterator" 696 | ], 697 | "time": "2018-09-13T20:33:42+00:00" 698 | }, 699 | { 700 | "name": "phpunit/php-text-template", 701 | "version": "1.2.1", 702 | "source": { 703 | "type": "git", 704 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 705 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 706 | }, 707 | "dist": { 708 | "type": "zip", 709 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 710 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 711 | "shasum": "" 712 | }, 713 | "require": { 714 | "php": ">=5.3.3" 715 | }, 716 | "type": "library", 717 | "autoload": { 718 | "classmap": [ 719 | "src/" 720 | ] 721 | }, 722 | "notification-url": "https://packagist.org/downloads/", 723 | "license": [ 724 | "BSD-3-Clause" 725 | ], 726 | "authors": [ 727 | { 728 | "name": "Sebastian Bergmann", 729 | "email": "sebastian@phpunit.de", 730 | "role": "lead" 731 | } 732 | ], 733 | "description": "Simple template engine.", 734 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 735 | "keywords": [ 736 | "template" 737 | ], 738 | "time": "2015-06-21T13:50:34+00:00" 739 | }, 740 | { 741 | "name": "phpunit/php-timer", 742 | "version": "2.1.2", 743 | "source": { 744 | "type": "git", 745 | "url": "https://github.com/sebastianbergmann/php-timer.git", 746 | "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" 747 | }, 748 | "dist": { 749 | "type": "zip", 750 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", 751 | "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", 752 | "shasum": "" 753 | }, 754 | "require": { 755 | "php": "^7.1" 756 | }, 757 | "require-dev": { 758 | "phpunit/phpunit": "^7.0" 759 | }, 760 | "type": "library", 761 | "extra": { 762 | "branch-alias": { 763 | "dev-master": "2.1-dev" 764 | } 765 | }, 766 | "autoload": { 767 | "classmap": [ 768 | "src/" 769 | ] 770 | }, 771 | "notification-url": "https://packagist.org/downloads/", 772 | "license": [ 773 | "BSD-3-Clause" 774 | ], 775 | "authors": [ 776 | { 777 | "name": "Sebastian Bergmann", 778 | "email": "sebastian@phpunit.de", 779 | "role": "lead" 780 | } 781 | ], 782 | "description": "Utility class for timing", 783 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 784 | "keywords": [ 785 | "timer" 786 | ], 787 | "time": "2019-06-07T04:22:29+00:00" 788 | }, 789 | { 790 | "name": "phpunit/php-token-stream", 791 | "version": "3.1.1", 792 | "source": { 793 | "type": "git", 794 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 795 | "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" 796 | }, 797 | "dist": { 798 | "type": "zip", 799 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", 800 | "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", 801 | "shasum": "" 802 | }, 803 | "require": { 804 | "ext-tokenizer": "*", 805 | "php": "^7.1" 806 | }, 807 | "require-dev": { 808 | "phpunit/phpunit": "^7.0" 809 | }, 810 | "type": "library", 811 | "extra": { 812 | "branch-alias": { 813 | "dev-master": "3.1-dev" 814 | } 815 | }, 816 | "autoload": { 817 | "classmap": [ 818 | "src/" 819 | ] 820 | }, 821 | "notification-url": "https://packagist.org/downloads/", 822 | "license": [ 823 | "BSD-3-Clause" 824 | ], 825 | "authors": [ 826 | { 827 | "name": "Sebastian Bergmann", 828 | "email": "sebastian@phpunit.de" 829 | } 830 | ], 831 | "description": "Wrapper around PHP's tokenizer extension.", 832 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 833 | "keywords": [ 834 | "tokenizer" 835 | ], 836 | "time": "2019-09-17T06:23:10+00:00" 837 | }, 838 | { 839 | "name": "phpunit/phpunit", 840 | "version": "7.5.17", 841 | "source": { 842 | "type": "git", 843 | "url": "https://github.com/sebastianbergmann/phpunit.git", 844 | "reference": "4c92a15296e58191a4cd74cff3b34fc8e374174a" 845 | }, 846 | "dist": { 847 | "type": "zip", 848 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4c92a15296e58191a4cd74cff3b34fc8e374174a", 849 | "reference": "4c92a15296e58191a4cd74cff3b34fc8e374174a", 850 | "shasum": "" 851 | }, 852 | "require": { 853 | "doctrine/instantiator": "^1.1", 854 | "ext-dom": "*", 855 | "ext-json": "*", 856 | "ext-libxml": "*", 857 | "ext-mbstring": "*", 858 | "ext-xml": "*", 859 | "myclabs/deep-copy": "^1.7", 860 | "phar-io/manifest": "^1.0.2", 861 | "phar-io/version": "^2.0", 862 | "php": "^7.1", 863 | "phpspec/prophecy": "^1.7", 864 | "phpunit/php-code-coverage": "^6.0.7", 865 | "phpunit/php-file-iterator": "^2.0.1", 866 | "phpunit/php-text-template": "^1.2.1", 867 | "phpunit/php-timer": "^2.1", 868 | "sebastian/comparator": "^3.0", 869 | "sebastian/diff": "^3.0", 870 | "sebastian/environment": "^4.0", 871 | "sebastian/exporter": "^3.1", 872 | "sebastian/global-state": "^2.0", 873 | "sebastian/object-enumerator": "^3.0.3", 874 | "sebastian/resource-operations": "^2.0", 875 | "sebastian/version": "^2.0.1" 876 | }, 877 | "conflict": { 878 | "phpunit/phpunit-mock-objects": "*" 879 | }, 880 | "require-dev": { 881 | "ext-pdo": "*" 882 | }, 883 | "suggest": { 884 | "ext-soap": "*", 885 | "ext-xdebug": "*", 886 | "phpunit/php-invoker": "^2.0" 887 | }, 888 | "bin": [ 889 | "phpunit" 890 | ], 891 | "type": "library", 892 | "extra": { 893 | "branch-alias": { 894 | "dev-master": "7.5-dev" 895 | } 896 | }, 897 | "autoload": { 898 | "classmap": [ 899 | "src/" 900 | ] 901 | }, 902 | "notification-url": "https://packagist.org/downloads/", 903 | "license": [ 904 | "BSD-3-Clause" 905 | ], 906 | "authors": [ 907 | { 908 | "name": "Sebastian Bergmann", 909 | "email": "sebastian@phpunit.de", 910 | "role": "lead" 911 | } 912 | ], 913 | "description": "The PHP Unit Testing framework.", 914 | "homepage": "https://phpunit.de/", 915 | "keywords": [ 916 | "phpunit", 917 | "testing", 918 | "xunit" 919 | ], 920 | "time": "2019-10-28T10:37:36+00:00" 921 | }, 922 | { 923 | "name": "psr/container", 924 | "version": "1.0.0", 925 | "source": { 926 | "type": "git", 927 | "url": "https://github.com/php-fig/container.git", 928 | "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" 929 | }, 930 | "dist": { 931 | "type": "zip", 932 | "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", 933 | "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", 934 | "shasum": "" 935 | }, 936 | "require": { 937 | "php": ">=5.3.0" 938 | }, 939 | "type": "library", 940 | "extra": { 941 | "branch-alias": { 942 | "dev-master": "1.0.x-dev" 943 | } 944 | }, 945 | "autoload": { 946 | "psr-4": { 947 | "Psr\\Container\\": "src/" 948 | } 949 | }, 950 | "notification-url": "https://packagist.org/downloads/", 951 | "license": [ 952 | "MIT" 953 | ], 954 | "authors": [ 955 | { 956 | "name": "PHP-FIG", 957 | "homepage": "http://www.php-fig.org/" 958 | } 959 | ], 960 | "description": "Common Container Interface (PHP FIG PSR-11)", 961 | "homepage": "https://github.com/php-fig/container", 962 | "keywords": [ 963 | "PSR-11", 964 | "container", 965 | "container-interface", 966 | "container-interop", 967 | "psr" 968 | ], 969 | "time": "2017-02-14T16:28:37+00:00" 970 | }, 971 | { 972 | "name": "psr/log", 973 | "version": "1.1.2", 974 | "source": { 975 | "type": "git", 976 | "url": "https://github.com/php-fig/log.git", 977 | "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" 978 | }, 979 | "dist": { 980 | "type": "zip", 981 | "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", 982 | "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", 983 | "shasum": "" 984 | }, 985 | "require": { 986 | "php": ">=5.3.0" 987 | }, 988 | "type": "library", 989 | "extra": { 990 | "branch-alias": { 991 | "dev-master": "1.1.x-dev" 992 | } 993 | }, 994 | "autoload": { 995 | "psr-4": { 996 | "Psr\\Log\\": "Psr/Log/" 997 | } 998 | }, 999 | "notification-url": "https://packagist.org/downloads/", 1000 | "license": [ 1001 | "MIT" 1002 | ], 1003 | "authors": [ 1004 | { 1005 | "name": "PHP-FIG", 1006 | "homepage": "http://www.php-fig.org/" 1007 | } 1008 | ], 1009 | "description": "Common interface for logging libraries", 1010 | "homepage": "https://github.com/php-fig/log", 1011 | "keywords": [ 1012 | "log", 1013 | "psr", 1014 | "psr-3" 1015 | ], 1016 | "time": "2019-11-01T11:05:21+00:00" 1017 | }, 1018 | { 1019 | "name": "sebastian/code-unit-reverse-lookup", 1020 | "version": "1.0.1", 1021 | "source": { 1022 | "type": "git", 1023 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 1024 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" 1025 | }, 1026 | "dist": { 1027 | "type": "zip", 1028 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", 1029 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", 1030 | "shasum": "" 1031 | }, 1032 | "require": { 1033 | "php": "^5.6 || ^7.0" 1034 | }, 1035 | "require-dev": { 1036 | "phpunit/phpunit": "^5.7 || ^6.0" 1037 | }, 1038 | "type": "library", 1039 | "extra": { 1040 | "branch-alias": { 1041 | "dev-master": "1.0.x-dev" 1042 | } 1043 | }, 1044 | "autoload": { 1045 | "classmap": [ 1046 | "src/" 1047 | ] 1048 | }, 1049 | "notification-url": "https://packagist.org/downloads/", 1050 | "license": [ 1051 | "BSD-3-Clause" 1052 | ], 1053 | "authors": [ 1054 | { 1055 | "name": "Sebastian Bergmann", 1056 | "email": "sebastian@phpunit.de" 1057 | } 1058 | ], 1059 | "description": "Looks up which function or method a line of code belongs to", 1060 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 1061 | "time": "2017-03-04T06:30:41+00:00" 1062 | }, 1063 | { 1064 | "name": "sebastian/comparator", 1065 | "version": "3.0.2", 1066 | "source": { 1067 | "type": "git", 1068 | "url": "https://github.com/sebastianbergmann/comparator.git", 1069 | "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" 1070 | }, 1071 | "dist": { 1072 | "type": "zip", 1073 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", 1074 | "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", 1075 | "shasum": "" 1076 | }, 1077 | "require": { 1078 | "php": "^7.1", 1079 | "sebastian/diff": "^3.0", 1080 | "sebastian/exporter": "^3.1" 1081 | }, 1082 | "require-dev": { 1083 | "phpunit/phpunit": "^7.1" 1084 | }, 1085 | "type": "library", 1086 | "extra": { 1087 | "branch-alias": { 1088 | "dev-master": "3.0-dev" 1089 | } 1090 | }, 1091 | "autoload": { 1092 | "classmap": [ 1093 | "src/" 1094 | ] 1095 | }, 1096 | "notification-url": "https://packagist.org/downloads/", 1097 | "license": [ 1098 | "BSD-3-Clause" 1099 | ], 1100 | "authors": [ 1101 | { 1102 | "name": "Jeff Welch", 1103 | "email": "whatthejeff@gmail.com" 1104 | }, 1105 | { 1106 | "name": "Volker Dusch", 1107 | "email": "github@wallbash.com" 1108 | }, 1109 | { 1110 | "name": "Bernhard Schussek", 1111 | "email": "bschussek@2bepublished.at" 1112 | }, 1113 | { 1114 | "name": "Sebastian Bergmann", 1115 | "email": "sebastian@phpunit.de" 1116 | } 1117 | ], 1118 | "description": "Provides the functionality to compare PHP values for equality", 1119 | "homepage": "https://github.com/sebastianbergmann/comparator", 1120 | "keywords": [ 1121 | "comparator", 1122 | "compare", 1123 | "equality" 1124 | ], 1125 | "time": "2018-07-12T15:12:46+00:00" 1126 | }, 1127 | { 1128 | "name": "sebastian/diff", 1129 | "version": "3.0.2", 1130 | "source": { 1131 | "type": "git", 1132 | "url": "https://github.com/sebastianbergmann/diff.git", 1133 | "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" 1134 | }, 1135 | "dist": { 1136 | "type": "zip", 1137 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", 1138 | "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", 1139 | "shasum": "" 1140 | }, 1141 | "require": { 1142 | "php": "^7.1" 1143 | }, 1144 | "require-dev": { 1145 | "phpunit/phpunit": "^7.5 || ^8.0", 1146 | "symfony/process": "^2 || ^3.3 || ^4" 1147 | }, 1148 | "type": "library", 1149 | "extra": { 1150 | "branch-alias": { 1151 | "dev-master": "3.0-dev" 1152 | } 1153 | }, 1154 | "autoload": { 1155 | "classmap": [ 1156 | "src/" 1157 | ] 1158 | }, 1159 | "notification-url": "https://packagist.org/downloads/", 1160 | "license": [ 1161 | "BSD-3-Clause" 1162 | ], 1163 | "authors": [ 1164 | { 1165 | "name": "Kore Nordmann", 1166 | "email": "mail@kore-nordmann.de" 1167 | }, 1168 | { 1169 | "name": "Sebastian Bergmann", 1170 | "email": "sebastian@phpunit.de" 1171 | } 1172 | ], 1173 | "description": "Diff implementation", 1174 | "homepage": "https://github.com/sebastianbergmann/diff", 1175 | "keywords": [ 1176 | "diff", 1177 | "udiff", 1178 | "unidiff", 1179 | "unified diff" 1180 | ], 1181 | "time": "2019-02-04T06:01:07+00:00" 1182 | }, 1183 | { 1184 | "name": "sebastian/environment", 1185 | "version": "4.2.3", 1186 | "source": { 1187 | "type": "git", 1188 | "url": "https://github.com/sebastianbergmann/environment.git", 1189 | "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368" 1190 | }, 1191 | "dist": { 1192 | "type": "zip", 1193 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368", 1194 | "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368", 1195 | "shasum": "" 1196 | }, 1197 | "require": { 1198 | "php": "^7.1" 1199 | }, 1200 | "require-dev": { 1201 | "phpunit/phpunit": "^7.5" 1202 | }, 1203 | "suggest": { 1204 | "ext-posix": "*" 1205 | }, 1206 | "type": "library", 1207 | "extra": { 1208 | "branch-alias": { 1209 | "dev-master": "4.2-dev" 1210 | } 1211 | }, 1212 | "autoload": { 1213 | "classmap": [ 1214 | "src/" 1215 | ] 1216 | }, 1217 | "notification-url": "https://packagist.org/downloads/", 1218 | "license": [ 1219 | "BSD-3-Clause" 1220 | ], 1221 | "authors": [ 1222 | { 1223 | "name": "Sebastian Bergmann", 1224 | "email": "sebastian@phpunit.de" 1225 | } 1226 | ], 1227 | "description": "Provides functionality to handle HHVM/PHP environments", 1228 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1229 | "keywords": [ 1230 | "Xdebug", 1231 | "environment", 1232 | "hhvm" 1233 | ], 1234 | "time": "2019-11-20T08:46:58+00:00" 1235 | }, 1236 | { 1237 | "name": "sebastian/exporter", 1238 | "version": "3.1.2", 1239 | "source": { 1240 | "type": "git", 1241 | "url": "https://github.com/sebastianbergmann/exporter.git", 1242 | "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" 1243 | }, 1244 | "dist": { 1245 | "type": "zip", 1246 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", 1247 | "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", 1248 | "shasum": "" 1249 | }, 1250 | "require": { 1251 | "php": "^7.0", 1252 | "sebastian/recursion-context": "^3.0" 1253 | }, 1254 | "require-dev": { 1255 | "ext-mbstring": "*", 1256 | "phpunit/phpunit": "^6.0" 1257 | }, 1258 | "type": "library", 1259 | "extra": { 1260 | "branch-alias": { 1261 | "dev-master": "3.1.x-dev" 1262 | } 1263 | }, 1264 | "autoload": { 1265 | "classmap": [ 1266 | "src/" 1267 | ] 1268 | }, 1269 | "notification-url": "https://packagist.org/downloads/", 1270 | "license": [ 1271 | "BSD-3-Clause" 1272 | ], 1273 | "authors": [ 1274 | { 1275 | "name": "Sebastian Bergmann", 1276 | "email": "sebastian@phpunit.de" 1277 | }, 1278 | { 1279 | "name": "Jeff Welch", 1280 | "email": "whatthejeff@gmail.com" 1281 | }, 1282 | { 1283 | "name": "Volker Dusch", 1284 | "email": "github@wallbash.com" 1285 | }, 1286 | { 1287 | "name": "Adam Harvey", 1288 | "email": "aharvey@php.net" 1289 | }, 1290 | { 1291 | "name": "Bernhard Schussek", 1292 | "email": "bschussek@gmail.com" 1293 | } 1294 | ], 1295 | "description": "Provides the functionality to export PHP variables for visualization", 1296 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1297 | "keywords": [ 1298 | "export", 1299 | "exporter" 1300 | ], 1301 | "time": "2019-09-14T09:02:43+00:00" 1302 | }, 1303 | { 1304 | "name": "sebastian/global-state", 1305 | "version": "2.0.0", 1306 | "source": { 1307 | "type": "git", 1308 | "url": "https://github.com/sebastianbergmann/global-state.git", 1309 | "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" 1310 | }, 1311 | "dist": { 1312 | "type": "zip", 1313 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", 1314 | "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", 1315 | "shasum": "" 1316 | }, 1317 | "require": { 1318 | "php": "^7.0" 1319 | }, 1320 | "require-dev": { 1321 | "phpunit/phpunit": "^6.0" 1322 | }, 1323 | "suggest": { 1324 | "ext-uopz": "*" 1325 | }, 1326 | "type": "library", 1327 | "extra": { 1328 | "branch-alias": { 1329 | "dev-master": "2.0-dev" 1330 | } 1331 | }, 1332 | "autoload": { 1333 | "classmap": [ 1334 | "src/" 1335 | ] 1336 | }, 1337 | "notification-url": "https://packagist.org/downloads/", 1338 | "license": [ 1339 | "BSD-3-Clause" 1340 | ], 1341 | "authors": [ 1342 | { 1343 | "name": "Sebastian Bergmann", 1344 | "email": "sebastian@phpunit.de" 1345 | } 1346 | ], 1347 | "description": "Snapshotting of global state", 1348 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1349 | "keywords": [ 1350 | "global state" 1351 | ], 1352 | "time": "2017-04-27T15:39:26+00:00" 1353 | }, 1354 | { 1355 | "name": "sebastian/object-enumerator", 1356 | "version": "3.0.3", 1357 | "source": { 1358 | "type": "git", 1359 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1360 | "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" 1361 | }, 1362 | "dist": { 1363 | "type": "zip", 1364 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", 1365 | "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", 1366 | "shasum": "" 1367 | }, 1368 | "require": { 1369 | "php": "^7.0", 1370 | "sebastian/object-reflector": "^1.1.1", 1371 | "sebastian/recursion-context": "^3.0" 1372 | }, 1373 | "require-dev": { 1374 | "phpunit/phpunit": "^6.0" 1375 | }, 1376 | "type": "library", 1377 | "extra": { 1378 | "branch-alias": { 1379 | "dev-master": "3.0.x-dev" 1380 | } 1381 | }, 1382 | "autoload": { 1383 | "classmap": [ 1384 | "src/" 1385 | ] 1386 | }, 1387 | "notification-url": "https://packagist.org/downloads/", 1388 | "license": [ 1389 | "BSD-3-Clause" 1390 | ], 1391 | "authors": [ 1392 | { 1393 | "name": "Sebastian Bergmann", 1394 | "email": "sebastian@phpunit.de" 1395 | } 1396 | ], 1397 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1398 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1399 | "time": "2017-08-03T12:35:26+00:00" 1400 | }, 1401 | { 1402 | "name": "sebastian/object-reflector", 1403 | "version": "1.1.1", 1404 | "source": { 1405 | "type": "git", 1406 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1407 | "reference": "773f97c67f28de00d397be301821b06708fca0be" 1408 | }, 1409 | "dist": { 1410 | "type": "zip", 1411 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", 1412 | "reference": "773f97c67f28de00d397be301821b06708fca0be", 1413 | "shasum": "" 1414 | }, 1415 | "require": { 1416 | "php": "^7.0" 1417 | }, 1418 | "require-dev": { 1419 | "phpunit/phpunit": "^6.0" 1420 | }, 1421 | "type": "library", 1422 | "extra": { 1423 | "branch-alias": { 1424 | "dev-master": "1.1-dev" 1425 | } 1426 | }, 1427 | "autoload": { 1428 | "classmap": [ 1429 | "src/" 1430 | ] 1431 | }, 1432 | "notification-url": "https://packagist.org/downloads/", 1433 | "license": [ 1434 | "BSD-3-Clause" 1435 | ], 1436 | "authors": [ 1437 | { 1438 | "name": "Sebastian Bergmann", 1439 | "email": "sebastian@phpunit.de" 1440 | } 1441 | ], 1442 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1443 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1444 | "time": "2017-03-29T09:07:27+00:00" 1445 | }, 1446 | { 1447 | "name": "sebastian/recursion-context", 1448 | "version": "3.0.0", 1449 | "source": { 1450 | "type": "git", 1451 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1452 | "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" 1453 | }, 1454 | "dist": { 1455 | "type": "zip", 1456 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", 1457 | "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", 1458 | "shasum": "" 1459 | }, 1460 | "require": { 1461 | "php": "^7.0" 1462 | }, 1463 | "require-dev": { 1464 | "phpunit/phpunit": "^6.0" 1465 | }, 1466 | "type": "library", 1467 | "extra": { 1468 | "branch-alias": { 1469 | "dev-master": "3.0.x-dev" 1470 | } 1471 | }, 1472 | "autoload": { 1473 | "classmap": [ 1474 | "src/" 1475 | ] 1476 | }, 1477 | "notification-url": "https://packagist.org/downloads/", 1478 | "license": [ 1479 | "BSD-3-Clause" 1480 | ], 1481 | "authors": [ 1482 | { 1483 | "name": "Jeff Welch", 1484 | "email": "whatthejeff@gmail.com" 1485 | }, 1486 | { 1487 | "name": "Sebastian Bergmann", 1488 | "email": "sebastian@phpunit.de" 1489 | }, 1490 | { 1491 | "name": "Adam Harvey", 1492 | "email": "aharvey@php.net" 1493 | } 1494 | ], 1495 | "description": "Provides functionality to recursively process PHP variables", 1496 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1497 | "time": "2017-03-03T06:23:57+00:00" 1498 | }, 1499 | { 1500 | "name": "sebastian/resource-operations", 1501 | "version": "2.0.1", 1502 | "source": { 1503 | "type": "git", 1504 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1505 | "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" 1506 | }, 1507 | "dist": { 1508 | "type": "zip", 1509 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", 1510 | "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", 1511 | "shasum": "" 1512 | }, 1513 | "require": { 1514 | "php": "^7.1" 1515 | }, 1516 | "type": "library", 1517 | "extra": { 1518 | "branch-alias": { 1519 | "dev-master": "2.0-dev" 1520 | } 1521 | }, 1522 | "autoload": { 1523 | "classmap": [ 1524 | "src/" 1525 | ] 1526 | }, 1527 | "notification-url": "https://packagist.org/downloads/", 1528 | "license": [ 1529 | "BSD-3-Clause" 1530 | ], 1531 | "authors": [ 1532 | { 1533 | "name": "Sebastian Bergmann", 1534 | "email": "sebastian@phpunit.de" 1535 | } 1536 | ], 1537 | "description": "Provides a list of PHP built-in functions that operate on resources", 1538 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1539 | "time": "2018-10-04T04:07:39+00:00" 1540 | }, 1541 | { 1542 | "name": "sebastian/version", 1543 | "version": "2.0.1", 1544 | "source": { 1545 | "type": "git", 1546 | "url": "https://github.com/sebastianbergmann/version.git", 1547 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" 1548 | }, 1549 | "dist": { 1550 | "type": "zip", 1551 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", 1552 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", 1553 | "shasum": "" 1554 | }, 1555 | "require": { 1556 | "php": ">=5.6" 1557 | }, 1558 | "type": "library", 1559 | "extra": { 1560 | "branch-alias": { 1561 | "dev-master": "2.0.x-dev" 1562 | } 1563 | }, 1564 | "autoload": { 1565 | "classmap": [ 1566 | "src/" 1567 | ] 1568 | }, 1569 | "notification-url": "https://packagist.org/downloads/", 1570 | "license": [ 1571 | "BSD-3-Clause" 1572 | ], 1573 | "authors": [ 1574 | { 1575 | "name": "Sebastian Bergmann", 1576 | "email": "sebastian@phpunit.de", 1577 | "role": "lead" 1578 | } 1579 | ], 1580 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1581 | "homepage": "https://github.com/sebastianbergmann/version", 1582 | "time": "2016-10-03T07:35:21+00:00" 1583 | }, 1584 | { 1585 | "name": "symfony/config", 1586 | "version": "v4.4.0", 1587 | "source": { 1588 | "type": "git", 1589 | "url": "https://github.com/symfony/config.git", 1590 | "reference": "f08e1c48e1f05d07c32f2d8599ed539e62105beb" 1591 | }, 1592 | "dist": { 1593 | "type": "zip", 1594 | "url": "https://api.github.com/repos/symfony/config/zipball/f08e1c48e1f05d07c32f2d8599ed539e62105beb", 1595 | "reference": "f08e1c48e1f05d07c32f2d8599ed539e62105beb", 1596 | "shasum": "" 1597 | }, 1598 | "require": { 1599 | "php": "^7.1.3", 1600 | "symfony/filesystem": "^3.4|^4.0|^5.0", 1601 | "symfony/polyfill-ctype": "~1.8" 1602 | }, 1603 | "conflict": { 1604 | "symfony/finder": "<3.4" 1605 | }, 1606 | "require-dev": { 1607 | "symfony/event-dispatcher": "^3.4|^4.0|^5.0", 1608 | "symfony/finder": "^3.4|^4.0|^5.0", 1609 | "symfony/messenger": "^4.1|^5.0", 1610 | "symfony/service-contracts": "^1.1|^2", 1611 | "symfony/yaml": "^3.4|^4.0|^5.0" 1612 | }, 1613 | "suggest": { 1614 | "symfony/yaml": "To use the yaml reference dumper" 1615 | }, 1616 | "type": "library", 1617 | "extra": { 1618 | "branch-alias": { 1619 | "dev-master": "4.4-dev" 1620 | } 1621 | }, 1622 | "autoload": { 1623 | "psr-4": { 1624 | "Symfony\\Component\\Config\\": "" 1625 | }, 1626 | "exclude-from-classmap": [ 1627 | "/Tests/" 1628 | ] 1629 | }, 1630 | "notification-url": "https://packagist.org/downloads/", 1631 | "license": [ 1632 | "MIT" 1633 | ], 1634 | "authors": [ 1635 | { 1636 | "name": "Fabien Potencier", 1637 | "email": "fabien@symfony.com" 1638 | }, 1639 | { 1640 | "name": "Symfony Community", 1641 | "homepage": "https://symfony.com/contributors" 1642 | } 1643 | ], 1644 | "description": "Symfony Config Component", 1645 | "homepage": "https://symfony.com", 1646 | "time": "2019-11-16T15:22:42+00:00" 1647 | }, 1648 | { 1649 | "name": "symfony/console", 1650 | "version": "v4.2.12", 1651 | "source": { 1652 | "type": "git", 1653 | "url": "https://github.com/symfony/console.git", 1654 | "reference": "fc2e274aade6567a750551942094b2145ade9b6c" 1655 | }, 1656 | "dist": { 1657 | "type": "zip", 1658 | "url": "https://api.github.com/repos/symfony/console/zipball/fc2e274aade6567a750551942094b2145ade9b6c", 1659 | "reference": "fc2e274aade6567a750551942094b2145ade9b6c", 1660 | "shasum": "" 1661 | }, 1662 | "require": { 1663 | "php": "^7.1.3", 1664 | "symfony/contracts": "^1.0", 1665 | "symfony/polyfill-mbstring": "~1.0" 1666 | }, 1667 | "conflict": { 1668 | "symfony/dependency-injection": "<3.4", 1669 | "symfony/process": "<3.3" 1670 | }, 1671 | "provide": { 1672 | "psr/log-implementation": "1.0" 1673 | }, 1674 | "require-dev": { 1675 | "psr/log": "~1.0", 1676 | "symfony/config": "~3.4|~4.0", 1677 | "symfony/dependency-injection": "~3.4|~4.0", 1678 | "symfony/event-dispatcher": "~3.4|~4.0", 1679 | "symfony/lock": "~3.4|~4.0", 1680 | "symfony/process": "~3.4|~4.0" 1681 | }, 1682 | "suggest": { 1683 | "psr/log": "For using the console logger", 1684 | "symfony/event-dispatcher": "", 1685 | "symfony/lock": "", 1686 | "symfony/process": "" 1687 | }, 1688 | "type": "library", 1689 | "extra": { 1690 | "branch-alias": { 1691 | "dev-master": "4.2-dev" 1692 | } 1693 | }, 1694 | "autoload": { 1695 | "psr-4": { 1696 | "Symfony\\Component\\Console\\": "" 1697 | }, 1698 | "exclude-from-classmap": [ 1699 | "/Tests/" 1700 | ] 1701 | }, 1702 | "notification-url": "https://packagist.org/downloads/", 1703 | "license": [ 1704 | "MIT" 1705 | ], 1706 | "authors": [ 1707 | { 1708 | "name": "Fabien Potencier", 1709 | "email": "fabien@symfony.com" 1710 | }, 1711 | { 1712 | "name": "Symfony Community", 1713 | "homepage": "https://symfony.com/contributors" 1714 | } 1715 | ], 1716 | "description": "Symfony Console Component", 1717 | "homepage": "https://symfony.com", 1718 | "time": "2019-07-24T17:13:20+00:00" 1719 | }, 1720 | { 1721 | "name": "symfony/contracts", 1722 | "version": "v1.1.0", 1723 | "source": { 1724 | "type": "git", 1725 | "url": "https://github.com/symfony/contracts.git", 1726 | "reference": "d3636025e8253c6144358ec0a62773cae588395b" 1727 | }, 1728 | "dist": { 1729 | "type": "zip", 1730 | "url": "https://api.github.com/repos/symfony/contracts/zipball/d3636025e8253c6144358ec0a62773cae588395b", 1731 | "reference": "d3636025e8253c6144358ec0a62773cae588395b", 1732 | "shasum": "" 1733 | }, 1734 | "require": { 1735 | "php": "^7.1.3" 1736 | }, 1737 | "require-dev": { 1738 | "psr/cache": "^1.0", 1739 | "psr/container": "^1.0", 1740 | "symfony/polyfill-intl-idn": "^1.10" 1741 | }, 1742 | "suggest": { 1743 | "psr/cache": "When using the Cache contracts", 1744 | "psr/container": "When using the Service contracts", 1745 | "symfony/cache-contracts-implementation": "", 1746 | "symfony/event-dispatcher-implementation": "", 1747 | "symfony/http-client-contracts-implementation": "", 1748 | "symfony/service-contracts-implementation": "", 1749 | "symfony/translation-contracts-implementation": "" 1750 | }, 1751 | "type": "library", 1752 | "extra": { 1753 | "branch-alias": { 1754 | "dev-master": "1.1-dev" 1755 | } 1756 | }, 1757 | "autoload": { 1758 | "psr-4": { 1759 | "Symfony\\Contracts\\": "" 1760 | }, 1761 | "exclude-from-classmap": [ 1762 | "**/Tests/" 1763 | ] 1764 | }, 1765 | "notification-url": "https://packagist.org/downloads/", 1766 | "license": [ 1767 | "MIT" 1768 | ], 1769 | "authors": [ 1770 | { 1771 | "name": "Nicolas Grekas", 1772 | "email": "p@tchwork.com" 1773 | }, 1774 | { 1775 | "name": "Symfony Community", 1776 | "homepage": "https://symfony.com/contributors" 1777 | } 1778 | ], 1779 | "description": "A set of abstractions extracted out of the Symfony components", 1780 | "homepage": "https://symfony.com", 1781 | "keywords": [ 1782 | "abstractions", 1783 | "contracts", 1784 | "decoupling", 1785 | "interfaces", 1786 | "interoperability", 1787 | "standards" 1788 | ], 1789 | "time": "2019-04-27T14:29:50+00:00" 1790 | }, 1791 | { 1792 | "name": "symfony/event-dispatcher", 1793 | "version": "v2.8.52", 1794 | "source": { 1795 | "type": "git", 1796 | "url": "https://github.com/symfony/event-dispatcher.git", 1797 | "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0" 1798 | }, 1799 | "dist": { 1800 | "type": "zip", 1801 | "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a77e974a5fecb4398833b0709210e3d5e334ffb0", 1802 | "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0", 1803 | "shasum": "" 1804 | }, 1805 | "require": { 1806 | "php": ">=5.3.9" 1807 | }, 1808 | "require-dev": { 1809 | "psr/log": "~1.0", 1810 | "symfony/config": "^2.0.5|~3.0.0", 1811 | "symfony/dependency-injection": "~2.6|~3.0.0", 1812 | "symfony/expression-language": "~2.6|~3.0.0", 1813 | "symfony/stopwatch": "~2.3|~3.0.0" 1814 | }, 1815 | "suggest": { 1816 | "symfony/dependency-injection": "", 1817 | "symfony/http-kernel": "" 1818 | }, 1819 | "type": "library", 1820 | "extra": { 1821 | "branch-alias": { 1822 | "dev-master": "2.8-dev" 1823 | } 1824 | }, 1825 | "autoload": { 1826 | "psr-4": { 1827 | "Symfony\\Component\\EventDispatcher\\": "" 1828 | }, 1829 | "exclude-from-classmap": [ 1830 | "/Tests/" 1831 | ] 1832 | }, 1833 | "notification-url": "https://packagist.org/downloads/", 1834 | "license": [ 1835 | "MIT" 1836 | ], 1837 | "authors": [ 1838 | { 1839 | "name": "Fabien Potencier", 1840 | "email": "fabien@symfony.com" 1841 | }, 1842 | { 1843 | "name": "Symfony Community", 1844 | "homepage": "https://symfony.com/contributors" 1845 | } 1846 | ], 1847 | "description": "Symfony EventDispatcher Component", 1848 | "homepage": "https://symfony.com", 1849 | "time": "2018-11-21T14:20:20+00:00" 1850 | }, 1851 | { 1852 | "name": "symfony/filesystem", 1853 | "version": "v5.0.0", 1854 | "source": { 1855 | "type": "git", 1856 | "url": "https://github.com/symfony/filesystem.git", 1857 | "reference": "0bf75c37a71ff41f718b14b9f5a0a7d6a7dd9b84" 1858 | }, 1859 | "dist": { 1860 | "type": "zip", 1861 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/0bf75c37a71ff41f718b14b9f5a0a7d6a7dd9b84", 1862 | "reference": "0bf75c37a71ff41f718b14b9f5a0a7d6a7dd9b84", 1863 | "shasum": "" 1864 | }, 1865 | "require": { 1866 | "php": "^7.2.5", 1867 | "symfony/polyfill-ctype": "~1.8" 1868 | }, 1869 | "type": "library", 1870 | "extra": { 1871 | "branch-alias": { 1872 | "dev-master": "5.0-dev" 1873 | } 1874 | }, 1875 | "autoload": { 1876 | "psr-4": { 1877 | "Symfony\\Component\\Filesystem\\": "" 1878 | }, 1879 | "exclude-from-classmap": [ 1880 | "/Tests/" 1881 | ] 1882 | }, 1883 | "notification-url": "https://packagist.org/downloads/", 1884 | "license": [ 1885 | "MIT" 1886 | ], 1887 | "authors": [ 1888 | { 1889 | "name": "Fabien Potencier", 1890 | "email": "fabien@symfony.com" 1891 | }, 1892 | { 1893 | "name": "Symfony Community", 1894 | "homepage": "https://symfony.com/contributors" 1895 | } 1896 | ], 1897 | "description": "Symfony Filesystem Component", 1898 | "homepage": "https://symfony.com", 1899 | "time": "2019-11-18T17:27:11+00:00" 1900 | }, 1901 | { 1902 | "name": "symfony/polyfill-ctype", 1903 | "version": "v1.12.0", 1904 | "source": { 1905 | "type": "git", 1906 | "url": "https://github.com/symfony/polyfill-ctype.git", 1907 | "reference": "550ebaac289296ce228a706d0867afc34687e3f4" 1908 | }, 1909 | "dist": { 1910 | "type": "zip", 1911 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", 1912 | "reference": "550ebaac289296ce228a706d0867afc34687e3f4", 1913 | "shasum": "" 1914 | }, 1915 | "require": { 1916 | "php": ">=5.3.3" 1917 | }, 1918 | "suggest": { 1919 | "ext-ctype": "For best performance" 1920 | }, 1921 | "type": "library", 1922 | "extra": { 1923 | "branch-alias": { 1924 | "dev-master": "1.12-dev" 1925 | } 1926 | }, 1927 | "autoload": { 1928 | "psr-4": { 1929 | "Symfony\\Polyfill\\Ctype\\": "" 1930 | }, 1931 | "files": [ 1932 | "bootstrap.php" 1933 | ] 1934 | }, 1935 | "notification-url": "https://packagist.org/downloads/", 1936 | "license": [ 1937 | "MIT" 1938 | ], 1939 | "authors": [ 1940 | { 1941 | "name": "Gert de Pagter", 1942 | "email": "BackEndTea@gmail.com" 1943 | }, 1944 | { 1945 | "name": "Symfony Community", 1946 | "homepage": "https://symfony.com/contributors" 1947 | } 1948 | ], 1949 | "description": "Symfony polyfill for ctype functions", 1950 | "homepage": "https://symfony.com", 1951 | "keywords": [ 1952 | "compatibility", 1953 | "ctype", 1954 | "polyfill", 1955 | "portable" 1956 | ], 1957 | "time": "2019-08-06T08:03:45+00:00" 1958 | }, 1959 | { 1960 | "name": "symfony/polyfill-mbstring", 1961 | "version": "v1.12.0", 1962 | "source": { 1963 | "type": "git", 1964 | "url": "https://github.com/symfony/polyfill-mbstring.git", 1965 | "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" 1966 | }, 1967 | "dist": { 1968 | "type": "zip", 1969 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", 1970 | "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", 1971 | "shasum": "" 1972 | }, 1973 | "require": { 1974 | "php": ">=5.3.3" 1975 | }, 1976 | "suggest": { 1977 | "ext-mbstring": "For best performance" 1978 | }, 1979 | "type": "library", 1980 | "extra": { 1981 | "branch-alias": { 1982 | "dev-master": "1.12-dev" 1983 | } 1984 | }, 1985 | "autoload": { 1986 | "psr-4": { 1987 | "Symfony\\Polyfill\\Mbstring\\": "" 1988 | }, 1989 | "files": [ 1990 | "bootstrap.php" 1991 | ] 1992 | }, 1993 | "notification-url": "https://packagist.org/downloads/", 1994 | "license": [ 1995 | "MIT" 1996 | ], 1997 | "authors": [ 1998 | { 1999 | "name": "Nicolas Grekas", 2000 | "email": "p@tchwork.com" 2001 | }, 2002 | { 2003 | "name": "Symfony Community", 2004 | "homepage": "https://symfony.com/contributors" 2005 | } 2006 | ], 2007 | "description": "Symfony polyfill for the Mbstring extension", 2008 | "homepage": "https://symfony.com", 2009 | "keywords": [ 2010 | "compatibility", 2011 | "mbstring", 2012 | "polyfill", 2013 | "portable", 2014 | "shim" 2015 | ], 2016 | "time": "2019-08-06T08:03:45+00:00" 2017 | }, 2018 | { 2019 | "name": "symfony/service-contracts", 2020 | "version": "v2.0.0", 2021 | "source": { 2022 | "type": "git", 2023 | "url": "https://github.com/symfony/service-contracts.git", 2024 | "reference": "9d99e1556417bf227a62e14856d630672bf10eaf" 2025 | }, 2026 | "dist": { 2027 | "type": "zip", 2028 | "url": "https://api.github.com/repos/symfony/service-contracts/zipball/9d99e1556417bf227a62e14856d630672bf10eaf", 2029 | "reference": "9d99e1556417bf227a62e14856d630672bf10eaf", 2030 | "shasum": "" 2031 | }, 2032 | "require": { 2033 | "php": "^7.2.9", 2034 | "psr/container": "^1.0" 2035 | }, 2036 | "suggest": { 2037 | "symfony/service-implementation": "" 2038 | }, 2039 | "type": "library", 2040 | "extra": { 2041 | "branch-alias": { 2042 | "dev-master": "2.0-dev" 2043 | } 2044 | }, 2045 | "autoload": { 2046 | "psr-4": { 2047 | "Symfony\\Contracts\\Service\\": "" 2048 | } 2049 | }, 2050 | "notification-url": "https://packagist.org/downloads/", 2051 | "license": [ 2052 | "MIT" 2053 | ], 2054 | "authors": [ 2055 | { 2056 | "name": "Nicolas Grekas", 2057 | "email": "p@tchwork.com" 2058 | }, 2059 | { 2060 | "name": "Symfony Community", 2061 | "homepage": "https://symfony.com/contributors" 2062 | } 2063 | ], 2064 | "description": "Generic abstractions related to writing services", 2065 | "homepage": "https://symfony.com", 2066 | "keywords": [ 2067 | "abstractions", 2068 | "contracts", 2069 | "decoupling", 2070 | "interfaces", 2071 | "interoperability", 2072 | "standards" 2073 | ], 2074 | "time": "2019-11-09T09:18:34+00:00" 2075 | }, 2076 | { 2077 | "name": "symfony/stopwatch", 2078 | "version": "v4.4.0", 2079 | "source": { 2080 | "type": "git", 2081 | "url": "https://github.com/symfony/stopwatch.git", 2082 | "reference": "5745b514fc56ae1907c6b8ed74f94f90f64694e9" 2083 | }, 2084 | "dist": { 2085 | "type": "zip", 2086 | "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5745b514fc56ae1907c6b8ed74f94f90f64694e9", 2087 | "reference": "5745b514fc56ae1907c6b8ed74f94f90f64694e9", 2088 | "shasum": "" 2089 | }, 2090 | "require": { 2091 | "php": "^7.1.3", 2092 | "symfony/service-contracts": "^1.0|^2" 2093 | }, 2094 | "type": "library", 2095 | "extra": { 2096 | "branch-alias": { 2097 | "dev-master": "4.4-dev" 2098 | } 2099 | }, 2100 | "autoload": { 2101 | "psr-4": { 2102 | "Symfony\\Component\\Stopwatch\\": "" 2103 | }, 2104 | "exclude-from-classmap": [ 2105 | "/Tests/" 2106 | ] 2107 | }, 2108 | "notification-url": "https://packagist.org/downloads/", 2109 | "license": [ 2110 | "MIT" 2111 | ], 2112 | "authors": [ 2113 | { 2114 | "name": "Fabien Potencier", 2115 | "email": "fabien@symfony.com" 2116 | }, 2117 | { 2118 | "name": "Symfony Community", 2119 | "homepage": "https://symfony.com/contributors" 2120 | } 2121 | ], 2122 | "description": "Symfony Stopwatch Component", 2123 | "homepage": "https://symfony.com", 2124 | "time": "2019-11-05T16:11:08+00:00" 2125 | }, 2126 | { 2127 | "name": "symfony/yaml", 2128 | "version": "v4.4.0", 2129 | "source": { 2130 | "type": "git", 2131 | "url": "https://github.com/symfony/yaml.git", 2132 | "reference": "76de473358fe802578a415d5bb43c296cf09d211" 2133 | }, 2134 | "dist": { 2135 | "type": "zip", 2136 | "url": "https://api.github.com/repos/symfony/yaml/zipball/76de473358fe802578a415d5bb43c296cf09d211", 2137 | "reference": "76de473358fe802578a415d5bb43c296cf09d211", 2138 | "shasum": "" 2139 | }, 2140 | "require": { 2141 | "php": "^7.1.3", 2142 | "symfony/polyfill-ctype": "~1.8" 2143 | }, 2144 | "conflict": { 2145 | "symfony/console": "<3.4" 2146 | }, 2147 | "require-dev": { 2148 | "symfony/console": "^3.4|^4.0|^5.0" 2149 | }, 2150 | "suggest": { 2151 | "symfony/console": "For validating YAML files using the lint command" 2152 | }, 2153 | "type": "library", 2154 | "extra": { 2155 | "branch-alias": { 2156 | "dev-master": "4.4-dev" 2157 | } 2158 | }, 2159 | "autoload": { 2160 | "psr-4": { 2161 | "Symfony\\Component\\Yaml\\": "" 2162 | }, 2163 | "exclude-from-classmap": [ 2164 | "/Tests/" 2165 | ] 2166 | }, 2167 | "notification-url": "https://packagist.org/downloads/", 2168 | "license": [ 2169 | "MIT" 2170 | ], 2171 | "authors": [ 2172 | { 2173 | "name": "Fabien Potencier", 2174 | "email": "fabien@symfony.com" 2175 | }, 2176 | { 2177 | "name": "Symfony Community", 2178 | "homepage": "https://symfony.com/contributors" 2179 | } 2180 | ], 2181 | "description": "Symfony Yaml Component", 2182 | "homepage": "https://symfony.com", 2183 | "time": "2019-11-12T14:51:11+00:00" 2184 | }, 2185 | { 2186 | "name": "theseer/tokenizer", 2187 | "version": "1.1.3", 2188 | "source": { 2189 | "type": "git", 2190 | "url": "https://github.com/theseer/tokenizer.git", 2191 | "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" 2192 | }, 2193 | "dist": { 2194 | "type": "zip", 2195 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", 2196 | "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", 2197 | "shasum": "" 2198 | }, 2199 | "require": { 2200 | "ext-dom": "*", 2201 | "ext-tokenizer": "*", 2202 | "ext-xmlwriter": "*", 2203 | "php": "^7.0" 2204 | }, 2205 | "type": "library", 2206 | "autoload": { 2207 | "classmap": [ 2208 | "src/" 2209 | ] 2210 | }, 2211 | "notification-url": "https://packagist.org/downloads/", 2212 | "license": [ 2213 | "BSD-3-Clause" 2214 | ], 2215 | "authors": [ 2216 | { 2217 | "name": "Arne Blankerts", 2218 | "email": "arne@blankerts.de", 2219 | "role": "Developer" 2220 | } 2221 | ], 2222 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 2223 | "time": "2019-06-13T22:48:21+00:00" 2224 | }, 2225 | { 2226 | "name": "webmozart/assert", 2227 | "version": "1.6.0", 2228 | "source": { 2229 | "type": "git", 2230 | "url": "https://github.com/webmozart/assert.git", 2231 | "reference": "573381c0a64f155a0d9a23f4b0c797194805b925" 2232 | }, 2233 | "dist": { 2234 | "type": "zip", 2235 | "url": "https://api.github.com/repos/webmozart/assert/zipball/573381c0a64f155a0d9a23f4b0c797194805b925", 2236 | "reference": "573381c0a64f155a0d9a23f4b0c797194805b925", 2237 | "shasum": "" 2238 | }, 2239 | "require": { 2240 | "php": "^5.3.3 || ^7.0", 2241 | "symfony/polyfill-ctype": "^1.8" 2242 | }, 2243 | "conflict": { 2244 | "vimeo/psalm": "<3.6.0" 2245 | }, 2246 | "require-dev": { 2247 | "phpunit/phpunit": "^4.8.36 || ^7.5.13" 2248 | }, 2249 | "type": "library", 2250 | "autoload": { 2251 | "psr-4": { 2252 | "Webmozart\\Assert\\": "src/" 2253 | } 2254 | }, 2255 | "notification-url": "https://packagist.org/downloads/", 2256 | "license": [ 2257 | "MIT" 2258 | ], 2259 | "authors": [ 2260 | { 2261 | "name": "Bernhard Schussek", 2262 | "email": "bschussek@gmail.com" 2263 | } 2264 | ], 2265 | "description": "Assertions to validate method input/output with nice error messages.", 2266 | "keywords": [ 2267 | "assert", 2268 | "check", 2269 | "validate" 2270 | ], 2271 | "time": "2019-11-24T13:36:37+00:00" 2272 | } 2273 | ], 2274 | "aliases": [], 2275 | "minimum-stability": "stable", 2276 | "stability-flags": [], 2277 | "prefer-stable": false, 2278 | "prefer-lowest": false, 2279 | "platform": { 2280 | "php": ">=7.3" 2281 | }, 2282 | "platform-dev": [] 2283 | } 2284 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rackbeat/php-throttler/54e5669d18f0bef93577184a12a39d1a121c92f1/logo.png -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests/ 14 | 15 | 16 | 17 | 18 | 19 | ./src 20 | 21 | ./vendor 22 | ./tests 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Backtrace.php: -------------------------------------------------------------------------------- 1 | trace = $trace[1]; 18 | $this->zeroStack = $trace[0]; 19 | } 20 | 21 | public function getArguments(): array { 22 | return $this->trace['args']; 23 | } 24 | 25 | public function getFunctionName(): string { 26 | return $this->trace['function']; 27 | } 28 | 29 | /** 30 | * @return mixed 31 | */ 32 | public function getObject() { 33 | return $this->staticCall() ? $this->trace['class'] : $this->trace['object']; 34 | } 35 | 36 | public function getHash( $withArguments = true ): string { 37 | $normalizedArguments = array_map( function ( $argument ) { 38 | return is_object( $argument ) ? spl_object_hash( $argument ) : $argument; 39 | }, $this->getArguments() ); 40 | 41 | $prefix = $this->getFunctionName(); 42 | if ( strpos( $prefix, '{closure}' ) !== false ) { 43 | $prefix = $this->zeroStack['line']; 44 | } 45 | 46 | return md5( $prefix . ( $withArguments ? serialize( $normalizedArguments ) : '' ) ); 47 | } 48 | 49 | protected function staticCall(): bool { 50 | return $this->trace['type'] === '::'; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Bucket.php: -------------------------------------------------------------------------------- 1 | size = $size; 24 | $this->seconds = $seconds; 25 | 26 | $this->reset(); 27 | } 28 | 29 | public function setSize( int $size ): Bucket { 30 | $this->size = $size; 31 | 32 | $this->reset(); 33 | 34 | return $this; 35 | } 36 | 37 | public function makeUnlimited(): Bucket { 38 | $this->size = null; 39 | $this->seconds = null; 40 | 41 | $this->reset(); 42 | 43 | return $this; 44 | } 45 | 46 | public function setSeconds( int $seconds ): Bucket { 47 | $this->seconds = $seconds; 48 | 49 | $this->reset(); 50 | 51 | return $this; 52 | } 53 | 54 | public function setBurstable( bool $burstable ) { 55 | $this->burstable = $burstable; 56 | 57 | return $this; 58 | } 59 | 60 | public function shouldBurst() { 61 | return $this->burstable; 62 | } 63 | 64 | public function getRemaining(): ?int { 65 | return $this->remaining; 66 | } 67 | 68 | public function reset() { 69 | $this->remaining = $this->size; 70 | $this->lastBurst = microtime( true ); 71 | 72 | return $this; 73 | } 74 | 75 | public function pick() { 76 | if ( $this->isUnlimited() ) { 77 | return false; 78 | } 79 | 80 | $this->remaining--; 81 | 82 | if ( $this->secondsSinceLastBurst() >= $this->seconds ) { 83 | $this->reset(); 84 | } 85 | } 86 | 87 | public function expectedTimePerIteration() { 88 | if ( $this->isUnlimited() ) { 89 | return 0; 90 | } 91 | 92 | return $this->seconds * 1000000 / $this->size; 93 | } 94 | 95 | public function expectedSecondsPerIteration() { 96 | if ( $this->isUnlimited() ) { 97 | return 0; 98 | } 99 | 100 | return $this->seconds / $this->size; 101 | } 102 | 103 | public function isExhausted() { 104 | if ( $this->isUnlimited() ) { 105 | return false; 106 | } 107 | 108 | return $this->remaining <= 0; 109 | } 110 | 111 | public function secondsSinceLastBurst() { 112 | return microtime( true ) - $this->lastBurst; 113 | } 114 | 115 | public function exhaustedForMicroseconds() { 116 | return $this->exhaustedForSeconds() * 1000000; 117 | } 118 | 119 | public function exhaustedForSeconds() { 120 | return ( $this->seconds - $this->secondsSinceLastBurst() ); 121 | } 122 | 123 | public function isUnlimited() { 124 | return $this->remaining === null; 125 | } 126 | } -------------------------------------------------------------------------------- /src/Exceptions/NotIterableException.php: -------------------------------------------------------------------------------- 1 | iterable = $iterable; 23 | } 24 | 25 | public function count() { 26 | return method_exists( $this->iterable, 'count' ) 27 | ? $this->iterable->count() 28 | : ( \is_countable( $this->iterable ) ? \count( $this->iterable ) : 0 ); 29 | } 30 | 31 | public function isQueryBuilder() { 32 | return method_exists( $this->iterable, 'each' ); 33 | } 34 | 35 | public function iterate( $callback ) { 36 | if ( $this->isQueryBuilder() ) { 37 | // Handle a eloquent/query builder instance 38 | $this->runForQueryBuilder( $callback ); 39 | } else { 40 | // Handle iterable 41 | $this->runForIterable( $callback ); 42 | } 43 | } 44 | 45 | protected function runForQueryBuilder( $callback ) { 46 | $this->iterable->each( function ( $value, $key ) use ( $callback ) { 47 | $callback( $value, $key ); 48 | } ); 49 | } 50 | 51 | protected function runForIterable( $callback ) { 52 | foreach ( $this->iterable as $key => $value ) { 53 | $callback( $value, $key ); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/MethodThrottler.php: -------------------------------------------------------------------------------- 1 | new Bucket( $executions, $seconds ), 'last_execution' => microtime( true ) ]; 17 | } 18 | 19 | static::delay( static::$buckets[ $objectHash ][ $backtraceHash ] ); 20 | static::$buckets[ $objectHash ][ $backtraceHash ]['last_execution'] = microtime( true ); 21 | } 22 | 23 | protected static function objectHash( $object ): string { 24 | return is_string( $object ) ? $object : spl_object_hash( $object ); 25 | } 26 | 27 | protected static function delay( $object ) { 28 | if ( $object['last_execution'] && ( $iterationCompletionTime = ( microtime( true ) - $object['last_execution'] ) ) < $object['bucket']->expectedTimePerIteration() ) { 29 | usleep( $object['bucket']->expectedTimePerIteration() - $iterationCompletionTime ); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Throttler.php: -------------------------------------------------------------------------------- 1 | setIterable( $iterable ); 24 | $this->bucket = new Bucket(); 25 | } 26 | 27 | /** 28 | * @param array|\Iterator|Illuminate\Database\Query\Builder|Illuminate\Database\Eloquent\Builder\Illuminate\Support\Collection $iterable 29 | * 30 | * @return Throttler 31 | * @throws NotIterableException 32 | */ 33 | public static function make( $iterable ): Throttler { 34 | return new static( $iterable ); 35 | } 36 | 37 | /** 38 | * Set / Override the data to iterate. 39 | * 40 | * @param array|\Iterator|Illuminate\Database\Query\Builder|Illuminate\Database\Eloquent\Builder\Illuminate\Support\Collection $iterable 41 | * 42 | * @return Throttler 43 | * @throws NotIterableException 44 | */ 45 | public function setIterable( $iterable ) { 46 | $this->iterator = new Iterator( $iterable ); 47 | 48 | return $this; 49 | } 50 | 51 | /** 52 | * @return Iterator 53 | */ 54 | public function getIterator(): Iterator { 55 | return $this->iterator; 56 | } 57 | 58 | /** 59 | * @return Bucket 60 | */ 61 | public function getBucket(): Bucket { 62 | return $this->bucket; 63 | } 64 | 65 | /** 66 | * How many $iterations are allowed within set timeframe. 67 | * 68 | * @param int $iterations 69 | * 70 | * @return Throttler 71 | */ 72 | public function allow( int $iterations ): Throttler { 73 | $this->bucket->setSize( $iterations ); 74 | 75 | return $this; 76 | } 77 | 78 | /** 79 | * How many $seconds the timeframe is. 80 | * 81 | * @param int $seconds 82 | * 83 | * @return Throttler 84 | */ 85 | public function every( int $seconds ): Throttler { 86 | $this->bucket->setSeconds( $seconds ); 87 | 88 | return $this; 89 | } 90 | 91 | /** 92 | * How many $minutes the timeframe is. 93 | * 94 | * ( Helper for every($seconds * 60) ) 95 | * 96 | * @param int $minutes 97 | * 98 | * @return Throttler 99 | */ 100 | public function everyMinutes( int $minutes ): Throttler { 101 | return $this->every( $minutes * 60 ); 102 | } 103 | 104 | /** 105 | * How many $iterations are allowed per second. 106 | * 107 | * This is instead of setting a timeframe, you can 108 | * set how many can be run every second. 109 | * 110 | * It simply calculates the timeframe for you, so 111 | * with burst-mode enabled, this won't work as expected. 112 | * 113 | * @param int $iterations 114 | * 115 | * @return Throttler 116 | */ 117 | public function allowPerSecond( int $iterations ): Throttler { 118 | $this->allow( $iterations )->every( 1 ); 119 | 120 | return $this; 121 | } 122 | 123 | /** 124 | * Activate burst mode. 125 | * 126 | * Will run for all $iterations until done, and then wait 127 | * till the timeframe has passed. If running took longer than 128 | * the timeframe, no throttling will incur. 129 | * 130 | * @return Throttler 131 | */ 132 | public function inBursts(): Throttler { 133 | $this->bucket->setBurstable( true ); 134 | 135 | return $this; 136 | } 137 | 138 | /** 139 | * Disable burst mode. (default) 140 | * 141 | * Will calculate how many seconds every iteration should take, 142 | * and add a slight delay between every iteration if it was faster. 143 | * 144 | * @return Throttler 145 | */ 146 | public function withDelays(): Throttler { 147 | $this->bucket->setBurstable( false ); 148 | 149 | return $this; 150 | } 151 | 152 | /** 153 | * Remove any throttling and let it run infinite iterations. 154 | * 155 | * Useful while testing the throttling instead of having to, 156 | * change the $iterations or $seconds but rather append or 157 | * remove the stopThrottling() call before run(). 158 | * 159 | * @return Throttler 160 | */ 161 | public function stopThrottling(): Throttler { 162 | $this->bucket->makeUnlimited(); 163 | 164 | return $this; 165 | } 166 | 167 | /** 168 | * Run the iterations with throttling. 169 | * 170 | * @param $callback 171 | */ 172 | public function run( $callback ): void { 173 | $this->bucket->reset(); 174 | 175 | // To properly throttle first iteration 176 | $this->lastIterationFinishedAt = microtime( true ); 177 | 178 | $this->iterator->iterate( function ( $value, $key ) use ( $callback ) { 179 | $this->throttleBursts(); 180 | 181 | $this->bucket->pick(); 182 | 183 | $callback( $value, $key ); 184 | 185 | $this->throttle(); 186 | 187 | $this->lastIterationFinishedAt = microtime( true ); 188 | } ); 189 | } 190 | 191 | protected function throttle() { 192 | if ( $this->bucket->isUnlimited() ) { 193 | return false; 194 | } 195 | 196 | if ( ! $this->bucket->shouldBurst() ) { 197 | $this->delay(); 198 | } 199 | } 200 | 201 | protected function throttleBursts() { 202 | if ( $this->bucket->isUnlimited() ) { 203 | return false; 204 | } 205 | 206 | if ( $this->bucket->shouldBurst() && $this->bucket->isExhausted() ) { 207 | $this->pauseForExhaustion(); 208 | $this->bucket->reset(); 209 | } 210 | } 211 | 212 | protected function delay() { 213 | if ( $this->lastIterationFinishedAt 214 | && ( $iterationCompletionTime = ( microtime( true ) - $this->lastIterationFinishedAt ) ) < $this->bucket->expectedTimePerIteration() 215 | ) { 216 | $this->delayFor( $this->bucket->expectedTimePerIteration() - $iterationCompletionTime ); 217 | } 218 | } 219 | 220 | protected function pauseForExhaustion() { 221 | $this->delayFor( $this->bucket->exhaustedForMicroseconds() ); 222 | } 223 | 224 | protected function delayFor( $microseconds ) { 225 | usleep( (int) $microseconds ); 226 | } 227 | } -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | getFunctionName() === 'eval' ) { 21 | return $callback(); 22 | } 23 | 24 | \Rackbeat\Throttler\MethodThrottler::throttle( 25 | $backtrace->getObject(), 26 | $backtrace->getHash( ! $ignoreArguments ), 27 | $maxExecutions, 28 | $seconds 29 | ); 30 | 31 | return $callback( $backtrace->getArguments() ); 32 | 33 | 34 | // todo implement DestroyListener to cleanup! 35 | // todo make sure each class instance is treated as individual items 36 | // todo write test cases! 37 | // consider using this in the main Throttler as well? ? can we..? 38 | } -------------------------------------------------------------------------------- /tests/BucketTest.php: -------------------------------------------------------------------------------- 1 | bucket = new Bucket( 100, 60 ); 15 | } 16 | 17 | // todo test bursts 18 | 19 | /** @test */ 20 | public function it_can_be_picked_from() { 21 | $this->bucket->setSize( 10 ); 22 | 23 | $this->assertEquals( 10, $this->bucket->getRemaining() ); 24 | $this->assertEquals( false, $this->bucket->isExhausted() ); 25 | 26 | $this->bucket->pick(); 27 | 28 | $this->assertEquals( 9, $this->bucket->getRemaining() ); 29 | $this->assertEquals( false, $this->bucket->isExhausted() ); 30 | 31 | $this->bucket->pick(); 32 | $this->bucket->pick(); 33 | 34 | $this->assertEquals( 7, $this->bucket->getRemaining() ); 35 | $this->assertEquals( false, $this->bucket->isExhausted() ); 36 | 37 | // pick till empty 38 | while ( $this->bucket->getRemaining() > 0 ) { 39 | $this->bucket->pick(); 40 | } 41 | 42 | $this->assertEquals( true, $this->bucket->isExhausted() ); 43 | } 44 | 45 | /** @test */ 46 | public function it_can_have_unlimited_picks() { 47 | $this->bucket->makeUnlimited(); 48 | 49 | $this->assertEquals( null, $this->bucket->getRemaining() ); 50 | $this->assertEquals( false, $this->bucket->isExhausted() ); 51 | 52 | $this->bucket->pick(); 53 | 54 | $this->assertEquals( null, $this->bucket->getRemaining() ); 55 | $this->assertEquals( false, $this->bucket->isExhausted() ); 56 | } 57 | 58 | /** @test */ 59 | public function it_can_calculate_expected_time_per_iteration() { 60 | $this->bucket->setSize( 100 )->setSeconds( 10 ); 61 | 62 | $this->assertEquals( 0.1, $this->bucket->expectedSecondsPerIteration() ); 63 | $this->assertEquals( 0.1 * 1000000, $this->bucket->expectedTimePerIteration() ); 64 | 65 | $this->bucket->setSize( 10 )->setSeconds( 10 ); 66 | 67 | $this->assertEquals( 1, $this->bucket->expectedSecondsPerIteration() ); 68 | $this->assertEquals( 1 * 1000000, $this->bucket->expectedTimePerIteration() ); 69 | 70 | $this->bucket->setSize( 10 )->setSeconds( 20 ); 71 | 72 | $this->assertEquals( 2, $this->bucket->expectedSecondsPerIteration() ); 73 | $this->assertEquals( 2 * 1000000, $this->bucket->expectedTimePerIteration() ); 74 | } 75 | 76 | /** @test */ 77 | public function when_unlimited_expected_time_per_iteration_must_be_zero() { 78 | $this->bucket->makeUnlimited(); 79 | 80 | $this->assertEquals( 0, $this->bucket->expectedTimePerIteration() ); 81 | $this->assertEquals( 0, $this->bucket->expectedSecondsPerIteration() ); 82 | } 83 | 84 | /** @test */ 85 | public function it_can_become_exhausted_and_rest_back_to_normal() { 86 | $this->bucket->setSize( 3 ); 87 | 88 | $this->assertEquals( 3, $this->bucket->getRemaining() ); 89 | $this->assertEquals( false, $this->bucket->isExhausted() ); 90 | 91 | $this->bucket->pick(); 92 | 93 | $this->assertEquals( 2, $this->bucket->getRemaining() ); 94 | $this->assertEquals( false, $this->bucket->isExhausted() ); 95 | 96 | $this->bucket->pick(); 97 | $this->bucket->pick(); 98 | 99 | $this->assertEquals( 0, $this->bucket->getRemaining() ); 100 | $this->assertEquals( true, $this->bucket->isExhausted() ); 101 | 102 | $this->bucket->reset(); 103 | 104 | $this->assertEquals( 3, $this->bucket->getRemaining() ); 105 | $this->assertEquals( false, $this->bucket->isExhausted() ); 106 | } 107 | 108 | /** @test */ 109 | public function it_can_calculate_seconds_since_last_reset() { 110 | // last reset ~= last burst 111 | $this->bucket->setSize( 2 ); 112 | $this->bucket->reset(); 113 | 114 | sleep( 2 ); 115 | 116 | $this->assertGreaterThanOrEqual( 2, $this->bucket->secondsSinceLastBurst() ); 117 | 118 | sleep( 3 ); 119 | 120 | $this->assertGreaterThanOrEqual( 3, $this->bucket->secondsSinceLastBurst() ); 121 | 122 | $this->bucket->reset(); 123 | 124 | $this->lessThan( 1, $this->bucket->secondsSinceLastBurst() ); 125 | } 126 | 127 | /** @test */ 128 | public function it_can_calculate_how_long_it_should_remain_exhausted_for() { 129 | $this->bucket->setSize( 2 )->setSeconds( 3 ); 130 | 131 | $this->assertEquals( false, $this->bucket->isExhausted() ); 132 | 133 | $this->bucket->pick(); 134 | $this->bucket->pick(); 135 | $this->bucket->pick(); 136 | 137 | $this->assertEquals( true, $this->bucket->isExhausted() ); 138 | 139 | sleep( 2 ); 140 | 141 | $this->greaterThanOrEqual( 0.8, $this->bucket->exhaustedForSeconds() ); 142 | $this->greaterThanOrEqual( 0.8 * 10000000, $this->bucket->exhaustedForMicroseconds() ); 143 | $this->assertLessThanOrEqual( 1.0, $this->bucket->exhaustedForSeconds() ); 144 | $this->assertLessThanOrEqual( 1.0 * 10000000, $this->bucket->exhaustedForMicroseconds() ); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /tests/IteratorCountingTest.php: -------------------------------------------------------------------------------- 1 | assertEquals( 3, $iterator->count() ); 16 | } 17 | 18 | /** @test */ 19 | public function it_can_count_from_any_iterable_that_implemented_countable() { 20 | $iterator = new Iterator( new IterableCountableClass( [ 1, 2, 3 ] ) ); 21 | 22 | $this->assertEquals( 3, $iterator->count() ); 23 | } 24 | 25 | /** @test */ 26 | public function it_will_count_zero_if_iterable_is_not_countable() { 27 | $iterator = new Iterator( new IterableNotCountableClass( [ 1, 2, 3 ] ) ); 28 | 29 | $this->assertEquals( 0, $iterator->count() ); 30 | } 31 | 32 | /** @test */ 33 | public function it_can_count_from_a_illuminate_query_builder() { 34 | // .. or any class that has a 'count' method! 35 | $iterator = new Iterator( new MockQueryBuilder( [ 1, 2, 3 ] ) ); 36 | 37 | $this->assertEquals( 3, $iterator->count() ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/IteratorTest.php: -------------------------------------------------------------------------------- 1 | iterate( function ( $value ) use ( &$total ) { 17 | $total += $value; 18 | } ); 19 | 20 | $this->assertEquals( 6, $total ); 21 | } 22 | 23 | /** @test */ 24 | public function it_can_iterate_any_iterable() { 25 | $iterator = new Iterator( new IterableCountableClass( [ 1, 2, 3 ] ) ); 26 | 27 | $total = 0; 28 | $iterator->iterate( function ( $value ) use ( &$total ) { 29 | $total += $value; 30 | } ); 31 | 32 | $this->assertEquals( 6, $total ); 33 | } 34 | 35 | /** @test */ 36 | public function it_can_iterate_a_query_builder() { 37 | // .. or any class with an 'each' method! 38 | $iterator = new Iterator( new MockQueryBuilder( [ 1, 2, 3 ] ) ); 39 | 40 | $total = 0; 41 | $iterator->iterate( function ( $value ) use ( &$total ) { 42 | $total += $value; 43 | } ); 44 | 45 | $this->assertEquals( 6, $total ); 46 | } 47 | 48 | /** @test */ 49 | public function it_will_throw_an_exception_if_iterable_is_not_iterable() { 50 | $this->expectException( NotIterableException::class ); 51 | new Iterator( 'hi there?!' ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/MethodThrottlerTest.php: -------------------------------------------------------------------------------- 1 | returnNumberTimesTwo( 1 ); 23 | } 24 | 25 | $this->assertGreaterThanOrEqual( 5, microtime( true ) - $start ); 26 | $this->assertEquals( 10, $total ); 27 | } 28 | 29 | /** @test */ 30 | public function it_can_ignore_arguments_and_always_throttle_same_method_call() { 31 | $testClass = new class() 32 | { 33 | public function addToNumber( &$number ) { 34 | return throttle( function () use ( &$number ) { 35 | return $number++; 36 | }, 1, 2, true ); 37 | } 38 | }; 39 | 40 | $total = 0; 41 | $start = microtime( true ); 42 | 43 | foreach ( range( 1, 3 ) as $i ) { 44 | $testClass->addToNumber( $total ); 45 | } 46 | 47 | $this->assertGreaterThanOrEqual( 6, microtime( true ) - $start ); 48 | $this->assertEquals( 3, $total ); 49 | } 50 | 51 | /** @test */ 52 | public function it_will_limit_nested_method_calls() { 53 | $testClass = new class() 54 | { 55 | public $actions = [ 'buy' => 0, 'return' => 0 ]; 56 | 57 | public function buy() { 58 | return $this->sendApiCall( 'buy' ); 59 | } 60 | 61 | public function return() { 62 | return $this->sendApiCall( 'return' ); 63 | } 64 | 65 | protected function sendApiCall( $method ) { 66 | return throttle( function () use ( $method ) { 67 | $this->actions[ $method ]++; 68 | }, 1, 1, true ); 69 | } 70 | }; 71 | 72 | $start = microtime( true ); 73 | 74 | $testClass->buy(); 75 | $testClass->buy(); 76 | $testClass->return(); 77 | $testClass->buy(); 78 | 79 | $this->assertGreaterThanOrEqual( 4, microtime( true ) - $start ); 80 | $this->assertEquals( 3, $testClass->actions['buy'] ); 81 | $this->assertEquals( 1, $testClass->actions['return'] ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/ThrottlerTest.php: -------------------------------------------------------------------------------- 1 | throttler = new Throttler( [] ); 15 | } 16 | 17 | /** @test */ 18 | public function it_can_count_iterations_to_do() { 19 | $this->throttler->setIterable( [ 1, 2, 3 ] ); 20 | 21 | $this->assertEquals( 3, $this->throttler->getIterator()->count() ); 22 | 23 | $this->throttler->setIterable( [] ); 24 | 25 | $this->assertEquals( 0, $this->throttler->getIterator()->count() ); 26 | 27 | $this->throttler->setIterable( range( 1, 100 ) ); 28 | 29 | $this->assertEquals( 100, $this->throttler->getIterator()->count() ); 30 | } 31 | 32 | /** @test */ 33 | public function it_can_be_instantiated_using_the_static_method() { 34 | $this->throttler = Throttler::make( [ 1, 2, 3 ] ); 35 | 36 | $this->assertEquals( 3, $this->throttler->getIterator()->count() ); 37 | } 38 | 39 | /** @test */ 40 | public function it_can_run_all_iterations_with_a_simple_callback() { 41 | $this->throttler->setIterable( [ 100, 500, 400 ] ); 42 | 43 | $total = 0; 44 | $this->throttler->allow( 3 )->every( 1 )->run( function ( $value ) use ( &$total ) { 45 | $total += $value; 46 | } ); 47 | 48 | $this->assertEquals( 1000, $total ); 49 | } 50 | 51 | /** @test */ 52 | public function it_can_be_throttled_default_without_bursts() { 53 | $this->throttler->setIterable( range( 1, 10 ) ); // 10 items 54 | 55 | $start = microtime( true ); 56 | 57 | $this->throttler->allow( 5 )->every( 3 )->run( function ( $value ) { } ); 58 | 59 | $this->assertGreaterThanOrEqual( 6, microtime( true ) - $start ); 60 | } 61 | 62 | /** @test */ 63 | public function it_can_switch_between_burst_mode() { 64 | $this->assertEquals( false, $this->throttler->getBucket()->shouldBurst() ); 65 | 66 | $this->throttler->inBursts(); 67 | 68 | $this->assertEquals( true, $this->throttler->getBucket()->shouldBurst() ); 69 | 70 | $this->throttler->withDelays(); 71 | 72 | $this->assertEquals( false, $this->throttler->getBucket()->shouldBurst() ); 73 | } 74 | 75 | /** @test */ 76 | public function it_can_be_throttled_in_minutes() { 77 | $this->throttler->setIterable( range( 1, 10 ) ) 78 | ->allow( 120 ) 79 | ->everyMinutes( 1 ); 80 | 81 | $this->assertEquals( 0.5, $this->throttler->getBucket()->expectedSecondsPerIteration() ); 82 | } 83 | 84 | /** @test */ 85 | public function it_can_be_throttled_in_bursts() { 86 | $this->throttler->setIterable( range( 1, 10 ) ); // 10 items 87 | $this->throttler->inBursts(); 88 | 89 | $timeFirstHalf = null; 90 | $timeSecondHalf = null; 91 | $start = microtime( true ); 92 | 93 | $this->throttler->allow( 5 )->every( 3 )->run( function ( $value, $index ) use ( $start, &$timeFirstHalf, &$timeSecondHalf ) { 94 | if ( $index === 4 ) { 95 | $timeFirstHalf = microtime( true ) - $start; 96 | } elseif ( $index === 9 ) { 97 | $timeSecondHalf = microtime( true ) - $start; 98 | } 99 | } ); 100 | 101 | $this->assertLessThan( 3, $timeFirstHalf ); 102 | $this->assertGreaterThanOrEqual( 3, $timeSecondHalf ); 103 | $this->assertGreaterThanOrEqual( 3, microtime( true ) - $start ); 104 | $this->assertLessThan( 4, $timeSecondHalf ); 105 | $this->assertLessThan( 4, microtime( true ) - $start ); 106 | } 107 | 108 | /** @test */ 109 | public function it_wont_be_throttled_in_unlimted_mode() { 110 | $this->throttler->setIterable( $items = range( 1, 1000 ) ); // 1000 items 111 | 112 | $start = microtime( true ); 113 | 114 | $total = 0; 115 | $this->throttler->stopThrottling()->run( function ( $value ) use ( &$total ) { $total += $value; } ); 116 | 117 | $this->assertEquals( array_sum( $items ), $total ); 118 | $this->assertLessThan( 1, microtime( true ) - $start ); 119 | } 120 | 121 | /** @test */ 122 | public function it_can_set_how_many_iterations_per_second_with_helper() { 123 | $this->throttler->allowPerSecond( 10 ); 124 | 125 | $this->assertEquals( 0.1, $this->throttler->getBucket()->expectedSecondsPerIteration() ); 126 | 127 | $this->throttler->allowPerSecond( 1 ); 128 | 129 | $this->assertEquals( 1, $this->throttler->getBucket()->expectedSecondsPerIteration() ); 130 | 131 | $this->throttler->allowPerSecond( 100 ); 132 | 133 | $this->assertEquals( 0.01, $this->throttler->getBucket()->expectedSecondsPerIteration() ); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /tests/helpers/IterableCountableClass.php: -------------------------------------------------------------------------------- 1 | data ); 9 | } 10 | } -------------------------------------------------------------------------------- /tests/helpers/IterableNotCountableClass.php: -------------------------------------------------------------------------------- 1 | data = $data; 12 | } 13 | 14 | public function current() { 15 | return $this->data[ $this->position ]; 16 | } 17 | 18 | public function next() { 19 | $this->position++; 20 | } 21 | 22 | public function key() { 23 | return $this->position; 24 | } 25 | 26 | public function valid() { 27 | return isset( $this->data[ $this->position ] ); 28 | } 29 | 30 | public function rewind() { 31 | $this->position = 0; 32 | } 33 | } -------------------------------------------------------------------------------- /tests/helpers/MockQueryBuilder.php: -------------------------------------------------------------------------------- 1 | data = $data; 9 | } 10 | 11 | public function each( $callback ) { 12 | foreach ( $this->data as $key => $value ) { 13 | $callback( $value, $key ); 14 | } 15 | } 16 | 17 | public function count() { 18 | return \count( $this->data ); 19 | } 20 | } --------------------------------------------------------------------------------