├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── examples └── usage.php ├── phpunit.xml ├── src ├── Beanstalkd.php ├── Client.php ├── Command.php ├── Commander.php ├── Exceptions │ ├── ClientException.php │ ├── CommandException.php │ ├── ResponseException.php │ └── SocketException.php ├── Interfaces │ ├── SerializerInterface.php │ └── SocketInterface.php ├── Serializer │ └── JsonSerializer.php └── Socket │ ├── SocketBase.php │ └── SocketsSocket.php └── tests ├── BeanstalkdTest.php ├── CommandTest.php └── CommanderTest.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: xobotyi 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Do you want to request a _feature_ or report a _bug_?** 2 | 3 | **What is the current behavior?** 4 | 5 | **What behavior do you expect?** 6 | 7 | **If current behavior is a bug, please provide the backtrace and steps to reproduce it:** 8 | 9 | **Versions i'm using:** 10 | - **OS:** 11 | - **PHP:** 12 | - **BeansClient:** 13 | - **beanstalkd:** 14 | 15 | 16 | - [ ] There is no similar issue from other users 17 | - [ ] Issue isn't fixed in `dev` branch 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Before submitting a pull request,** please make sure the following is done: 2 | 3 | 1. Fork [the repository](https://github.com/xobotyi/beansclient) and create your branch from `master`. 4 | 2. If you've fixed a bug or added code that should be tested, add tests! 5 | 3. Ensure the test suite passes. 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "00:00" 8 | timezone: "Etc/UTC" 9 | ignore: 10 | - dependency-name: "php-actions/phpunit" 11 | 12 | - package-ecosystem: composer 13 | directory: / 14 | schedule: 15 | interval: daily 16 | time: "00:00" 17 | timezone: "Etc/UTC" 18 | rebase-strategy: 'auto' 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | workflow_dispatch: 11 | 12 | jobs: 13 | test: 14 | name: "Test" 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: "Checkout" 18 | uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: "Fetch dependencies" 23 | uses: php-actions/composer@v6 24 | with: 25 | php_version: 8.0 26 | php_extensions: mbstring json sockets 27 | version: 2 28 | 29 | - name: "Test" 30 | uses: php-actions/phpunit@v3 31 | with: 32 | configuration: phpunit.xml 33 | php_version: 8.0 34 | php_extensions: mbstring json sockets xdebug 35 | env: 36 | XDEBUG_MODE: coverage 37 | 38 | 39 | - name: "Upload coverage to Codecov" 40 | uses: codecov/codecov-action@v3 41 | with: 42 | token: ${{ secrets.CODECOV_TOKEN }} 43 | files: coverage/clover.xml 44 | fail_ci_if_error: true 45 | 46 | dependabot-merge: 47 | name: "Dependabot automerge" 48 | runs-on: ubuntu-latest 49 | needs: [ "test" ] 50 | if: github.actor == 'dependabot[bot]' && github.event_name == 'pull_request' 51 | steps: 52 | - uses: fastify/github-action-merge-dependabot@v2.7.1 53 | with: 54 | github-token: ${{ secrets.GITHUB_TOKEN }} 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .cache 3 | coverage 4 | vendor 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at xog3@yandex.ru. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Anton Zinovyev 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # beansclient 4 | 5 | [![NPM Version](https://flat.badgen.net/packagist/v/xobotyi/beansclient)](https://packagist.org/packages/xobotyi/beansclient) 6 | [![NPM Downloads](https://flat.badgen.net/packagist/dt/xobotyi/beansclient)](https://packagist.org/packages/xobotyi/beansclient) 7 | [![NPM Dependents](https://flat.badgen.net/packagist/dependents/xobotyi/beansclient)](https://packagist.org/packages/xobotyi/beansclient) 8 | [![Build](https://img.shields.io/github/workflow/status/xobotyi/beansclient/CI?style=flat-square)](https://github.com/xobotyi/beansclient/actions) 9 | [![Coverage](https://flat.badgen.net/codecov/c/github/xobotyi/beansclient)](https://app.codecov.io/gh/xobotyi/beansclient) 10 | [![NPM Dependents](https://flat.badgen.net/packagist/php/xobotyi/beansclient)](https://packagist.org/packages/xobotyi/beansclient) 11 | [![NPM Dependents](https://flat.badgen.net/packagist/license/xobotyi/beansclient)](https://packagist.org/packages/xobotyi/beansclient) 12 | 13 |
14 | 15 | ## About 16 | 17 | BeansClient is a PHP8 client for [beanstalkd work queue](https://github.com/kr/beanstalkd) with thorough unit-testing. 18 | Library uses PSR-4 autoloader standard and always has 100% tests coverage. 19 | Library gives you a simple way to provide your own Socket implementation, in cases when you need to log requests and 20 | responses or to proxy traffic to non-standard transport. 21 | 22 | BeansClient supports whole bunch of commands and responses specified 23 | in [protocol](https://github.com/kr/beanstalkd/blob/master/doc/protocol.txt) for version 1.12 24 |
25 | 26 | ## Why BeansClient? 27 | 28 | 1. Well tested. 29 | 2. Supports UNIX sockets. 30 | 3. Actively maintained. 31 | 4. Predictable (does not throw exception in any situation, hello `pheanstalk`🤪). 32 | 5. PHP8 support. 33 | 34 | ## Contents 35 | 36 | 1. [Requirements](#requirements) 37 | 2. [Installation](#installation) 38 | 3. [Usage](#usage) 39 | 4. [Docs](#docs) 40 | * TBD 41 | 42 | ## Requirements 43 | 44 | - [PHP](//php.net/) 8.0+ 45 | - [beanstalkd](//github.com/kr/beanstalkd/) 1.12+ 46 | 47 | ## Installation 48 | 49 | Install with composer 50 | 51 | ```bash 52 | composer require xobotyi/beansclient 53 | ``` 54 | 55 | ## Usage 56 | 57 | ```php 58 | put("job's payload", delay: 2); 71 | if($job['state'] === Beanstalkd::JOB_STATE_DELAYED) { 72 | echo "Job {$job['id']} is ready to be reserved within 2 seconds\n"; 73 | } 74 | 75 | ## ## 76 | # WORKER # 77 | ## ## 78 | 79 | $client->watchTube('myAwesomeTube2'); 80 | 81 | $job = $client->reserve(); 82 | 83 | if ($job) { 84 | echo "Hey, i received first {$job['payload']} of job with id {$job['id']}\n"; 85 | 86 | $client->delete($job['id']); 87 | 88 | echo "And i've done it!\n"; 89 | } 90 | else { 91 | echo "So sad, i have nothing to do"; 92 | } 93 | 94 | echo "Am I still connected? \n" . ($client->socket()->isConnected() ? 'Yes' : 'No') . "\n"; 95 | ``` 96 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xobotyi/beansclient", 3 | "description": "PHP7.1+ client for beanstalkd work queue with no dependencies", 4 | "type": "library", 5 | "keywords": [ 6 | "php", 7 | "beanstalk", 8 | "beanstalkd", 9 | "queue", 10 | "client" 11 | ], 12 | "license": "MIT", 13 | "homepage": "https://github.com/xobotyi/beansclient", 14 | "support": { 15 | "issues": "https://github.com/xobotyi/beansclient/issues" 16 | }, 17 | "authors": [ 18 | { 19 | "name": "Anton Zinovyev", 20 | "email": "xog3@yandex.ru", 21 | "role": "Developer" 22 | } 23 | ], 24 | "require": { 25 | "php": ">=8", 26 | "ext-mbstring": "*", 27 | "ext-json": "*", 28 | "ext-sockets": "*" 29 | }, 30 | "require-dev": { 31 | "phpunit/phpunit": "^9.5" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "xobotyi\\beansclient\\": "src" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /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": "17341eb1560f963e34fc6520300a75b1", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "doctrine/instantiator", 12 | "version": "1.4.1", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/doctrine/instantiator.git", 16 | "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", 21 | "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": "^7.1 || ^8.0" 26 | }, 27 | "require-dev": { 28 | "doctrine/coding-standard": "^9", 29 | "ext-pdo": "*", 30 | "ext-phar": "*", 31 | "phpbench/phpbench": "^0.16 || ^1", 32 | "phpstan/phpstan": "^1.4", 33 | "phpstan/phpstan-phpunit": "^1", 34 | "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", 35 | "vimeo/psalm": "^4.22" 36 | }, 37 | "type": "library", 38 | "autoload": { 39 | "psr-4": { 40 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 41 | } 42 | }, 43 | "notification-url": "https://packagist.org/downloads/", 44 | "license": [ 45 | "MIT" 46 | ], 47 | "authors": [ 48 | { 49 | "name": "Marco Pivetta", 50 | "email": "ocramius@gmail.com", 51 | "homepage": "https://ocramius.github.io/" 52 | } 53 | ], 54 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 55 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 56 | "keywords": [ 57 | "constructor", 58 | "instantiate" 59 | ], 60 | "support": { 61 | "issues": "https://github.com/doctrine/instantiator/issues", 62 | "source": "https://github.com/doctrine/instantiator/tree/1.4.1" 63 | }, 64 | "funding": [ 65 | { 66 | "url": "https://www.doctrine-project.org/sponsorship.html", 67 | "type": "custom" 68 | }, 69 | { 70 | "url": "https://www.patreon.com/phpdoctrine", 71 | "type": "patreon" 72 | }, 73 | { 74 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", 75 | "type": "tidelift" 76 | } 77 | ], 78 | "time": "2022-03-03T08:28:38+00:00" 79 | }, 80 | { 81 | "name": "myclabs/deep-copy", 82 | "version": "1.11.0", 83 | "source": { 84 | "type": "git", 85 | "url": "https://github.com/myclabs/DeepCopy.git", 86 | "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" 87 | }, 88 | "dist": { 89 | "type": "zip", 90 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", 91 | "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", 92 | "shasum": "" 93 | }, 94 | "require": { 95 | "php": "^7.1 || ^8.0" 96 | }, 97 | "conflict": { 98 | "doctrine/collections": "<1.6.8", 99 | "doctrine/common": "<2.13.3 || >=3,<3.2.2" 100 | }, 101 | "require-dev": { 102 | "doctrine/collections": "^1.6.8", 103 | "doctrine/common": "^2.13.3 || ^3.2.2", 104 | "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" 105 | }, 106 | "type": "library", 107 | "autoload": { 108 | "files": [ 109 | "src/DeepCopy/deep_copy.php" 110 | ], 111 | "psr-4": { 112 | "DeepCopy\\": "src/DeepCopy/" 113 | } 114 | }, 115 | "notification-url": "https://packagist.org/downloads/", 116 | "license": [ 117 | "MIT" 118 | ], 119 | "description": "Create deep copies (clones) of your objects", 120 | "keywords": [ 121 | "clone", 122 | "copy", 123 | "duplicate", 124 | "object", 125 | "object graph" 126 | ], 127 | "support": { 128 | "issues": "https://github.com/myclabs/DeepCopy/issues", 129 | "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" 130 | }, 131 | "funding": [ 132 | { 133 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", 134 | "type": "tidelift" 135 | } 136 | ], 137 | "time": "2022-03-03T13:19:32+00:00" 138 | }, 139 | { 140 | "name": "nikic/php-parser", 141 | "version": "v4.13.2", 142 | "source": { 143 | "type": "git", 144 | "url": "https://github.com/nikic/PHP-Parser.git", 145 | "reference": "210577fe3cf7badcc5814d99455df46564f3c077" 146 | }, 147 | "dist": { 148 | "type": "zip", 149 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077", 150 | "reference": "210577fe3cf7badcc5814d99455df46564f3c077", 151 | "shasum": "" 152 | }, 153 | "require": { 154 | "ext-tokenizer": "*", 155 | "php": ">=7.0" 156 | }, 157 | "require-dev": { 158 | "ircmaxell/php-yacc": "^0.0.7", 159 | "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" 160 | }, 161 | "bin": [ 162 | "bin/php-parse" 163 | ], 164 | "type": "library", 165 | "extra": { 166 | "branch-alias": { 167 | "dev-master": "4.9-dev" 168 | } 169 | }, 170 | "autoload": { 171 | "psr-4": { 172 | "PhpParser\\": "lib/PhpParser" 173 | } 174 | }, 175 | "notification-url": "https://packagist.org/downloads/", 176 | "license": [ 177 | "BSD-3-Clause" 178 | ], 179 | "authors": [ 180 | { 181 | "name": "Nikita Popov" 182 | } 183 | ], 184 | "description": "A PHP parser written in PHP", 185 | "keywords": [ 186 | "parser", 187 | "php" 188 | ], 189 | "support": { 190 | "issues": "https://github.com/nikic/PHP-Parser/issues", 191 | "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2" 192 | }, 193 | "time": "2021-11-30T19:35:32+00:00" 194 | }, 195 | { 196 | "name": "phar-io/manifest", 197 | "version": "2.0.3", 198 | "source": { 199 | "type": "git", 200 | "url": "https://github.com/phar-io/manifest.git", 201 | "reference": "97803eca37d319dfa7826cc2437fc020857acb53" 202 | }, 203 | "dist": { 204 | "type": "zip", 205 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", 206 | "reference": "97803eca37d319dfa7826cc2437fc020857acb53", 207 | "shasum": "" 208 | }, 209 | "require": { 210 | "ext-dom": "*", 211 | "ext-phar": "*", 212 | "ext-xmlwriter": "*", 213 | "phar-io/version": "^3.0.1", 214 | "php": "^7.2 || ^8.0" 215 | }, 216 | "type": "library", 217 | "extra": { 218 | "branch-alias": { 219 | "dev-master": "2.0.x-dev" 220 | } 221 | }, 222 | "autoload": { 223 | "classmap": [ 224 | "src/" 225 | ] 226 | }, 227 | "notification-url": "https://packagist.org/downloads/", 228 | "license": [ 229 | "BSD-3-Clause" 230 | ], 231 | "authors": [ 232 | { 233 | "name": "Arne Blankerts", 234 | "email": "arne@blankerts.de", 235 | "role": "Developer" 236 | }, 237 | { 238 | "name": "Sebastian Heuer", 239 | "email": "sebastian@phpeople.de", 240 | "role": "Developer" 241 | }, 242 | { 243 | "name": "Sebastian Bergmann", 244 | "email": "sebastian@phpunit.de", 245 | "role": "Developer" 246 | } 247 | ], 248 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 249 | "support": { 250 | "issues": "https://github.com/phar-io/manifest/issues", 251 | "source": "https://github.com/phar-io/manifest/tree/2.0.3" 252 | }, 253 | "time": "2021-07-20T11:28:43+00:00" 254 | }, 255 | { 256 | "name": "phar-io/version", 257 | "version": "3.2.1", 258 | "source": { 259 | "type": "git", 260 | "url": "https://github.com/phar-io/version.git", 261 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" 262 | }, 263 | "dist": { 264 | "type": "zip", 265 | "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 266 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 267 | "shasum": "" 268 | }, 269 | "require": { 270 | "php": "^7.2 || ^8.0" 271 | }, 272 | "type": "library", 273 | "autoload": { 274 | "classmap": [ 275 | "src/" 276 | ] 277 | }, 278 | "notification-url": "https://packagist.org/downloads/", 279 | "license": [ 280 | "BSD-3-Clause" 281 | ], 282 | "authors": [ 283 | { 284 | "name": "Arne Blankerts", 285 | "email": "arne@blankerts.de", 286 | "role": "Developer" 287 | }, 288 | { 289 | "name": "Sebastian Heuer", 290 | "email": "sebastian@phpeople.de", 291 | "role": "Developer" 292 | }, 293 | { 294 | "name": "Sebastian Bergmann", 295 | "email": "sebastian@phpunit.de", 296 | "role": "Developer" 297 | } 298 | ], 299 | "description": "Library for handling version information and constraints", 300 | "support": { 301 | "issues": "https://github.com/phar-io/version/issues", 302 | "source": "https://github.com/phar-io/version/tree/3.2.1" 303 | }, 304 | "time": "2022-02-21T01:04:05+00:00" 305 | }, 306 | { 307 | "name": "phpdocumentor/reflection-common", 308 | "version": "2.2.0", 309 | "source": { 310 | "type": "git", 311 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 312 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" 313 | }, 314 | "dist": { 315 | "type": "zip", 316 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", 317 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", 318 | "shasum": "" 319 | }, 320 | "require": { 321 | "php": "^7.2 || ^8.0" 322 | }, 323 | "type": "library", 324 | "extra": { 325 | "branch-alias": { 326 | "dev-2.x": "2.x-dev" 327 | } 328 | }, 329 | "autoload": { 330 | "psr-4": { 331 | "phpDocumentor\\Reflection\\": "src/" 332 | } 333 | }, 334 | "notification-url": "https://packagist.org/downloads/", 335 | "license": [ 336 | "MIT" 337 | ], 338 | "authors": [ 339 | { 340 | "name": "Jaap van Otterdijk", 341 | "email": "opensource@ijaap.nl" 342 | } 343 | ], 344 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 345 | "homepage": "http://www.phpdoc.org", 346 | "keywords": [ 347 | "FQSEN", 348 | "phpDocumentor", 349 | "phpdoc", 350 | "reflection", 351 | "static analysis" 352 | ], 353 | "support": { 354 | "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", 355 | "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" 356 | }, 357 | "time": "2020-06-27T09:03:43+00:00" 358 | }, 359 | { 360 | "name": "phpdocumentor/reflection-docblock", 361 | "version": "5.3.0", 362 | "source": { 363 | "type": "git", 364 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 365 | "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" 366 | }, 367 | "dist": { 368 | "type": "zip", 369 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", 370 | "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", 371 | "shasum": "" 372 | }, 373 | "require": { 374 | "ext-filter": "*", 375 | "php": "^7.2 || ^8.0", 376 | "phpdocumentor/reflection-common": "^2.2", 377 | "phpdocumentor/type-resolver": "^1.3", 378 | "webmozart/assert": "^1.9.1" 379 | }, 380 | "require-dev": { 381 | "mockery/mockery": "~1.3.2", 382 | "psalm/phar": "^4.8" 383 | }, 384 | "type": "library", 385 | "extra": { 386 | "branch-alias": { 387 | "dev-master": "5.x-dev" 388 | } 389 | }, 390 | "autoload": { 391 | "psr-4": { 392 | "phpDocumentor\\Reflection\\": "src" 393 | } 394 | }, 395 | "notification-url": "https://packagist.org/downloads/", 396 | "license": [ 397 | "MIT" 398 | ], 399 | "authors": [ 400 | { 401 | "name": "Mike van Riel", 402 | "email": "me@mikevanriel.com" 403 | }, 404 | { 405 | "name": "Jaap van Otterdijk", 406 | "email": "account@ijaap.nl" 407 | } 408 | ], 409 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 410 | "support": { 411 | "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", 412 | "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" 413 | }, 414 | "time": "2021-10-19T17:43:47+00:00" 415 | }, 416 | { 417 | "name": "phpdocumentor/type-resolver", 418 | "version": "1.6.1", 419 | "source": { 420 | "type": "git", 421 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 422 | "reference": "77a32518733312af16a44300404e945338981de3" 423 | }, 424 | "dist": { 425 | "type": "zip", 426 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", 427 | "reference": "77a32518733312af16a44300404e945338981de3", 428 | "shasum": "" 429 | }, 430 | "require": { 431 | "php": "^7.2 || ^8.0", 432 | "phpdocumentor/reflection-common": "^2.0" 433 | }, 434 | "require-dev": { 435 | "ext-tokenizer": "*", 436 | "psalm/phar": "^4.8" 437 | }, 438 | "type": "library", 439 | "extra": { 440 | "branch-alias": { 441 | "dev-1.x": "1.x-dev" 442 | } 443 | }, 444 | "autoload": { 445 | "psr-4": { 446 | "phpDocumentor\\Reflection\\": "src" 447 | } 448 | }, 449 | "notification-url": "https://packagist.org/downloads/", 450 | "license": [ 451 | "MIT" 452 | ], 453 | "authors": [ 454 | { 455 | "name": "Mike van Riel", 456 | "email": "me@mikevanriel.com" 457 | } 458 | ], 459 | "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", 460 | "support": { 461 | "issues": "https://github.com/phpDocumentor/TypeResolver/issues", 462 | "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" 463 | }, 464 | "time": "2022-03-15T21:29:03+00:00" 465 | }, 466 | { 467 | "name": "phpspec/prophecy", 468 | "version": "v1.15.0", 469 | "source": { 470 | "type": "git", 471 | "url": "https://github.com/phpspec/prophecy.git", 472 | "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" 473 | }, 474 | "dist": { 475 | "type": "zip", 476 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", 477 | "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", 478 | "shasum": "" 479 | }, 480 | "require": { 481 | "doctrine/instantiator": "^1.2", 482 | "php": "^7.2 || ~8.0, <8.2", 483 | "phpdocumentor/reflection-docblock": "^5.2", 484 | "sebastian/comparator": "^3.0 || ^4.0", 485 | "sebastian/recursion-context": "^3.0 || ^4.0" 486 | }, 487 | "require-dev": { 488 | "phpspec/phpspec": "^6.0 || ^7.0", 489 | "phpunit/phpunit": "^8.0 || ^9.0" 490 | }, 491 | "type": "library", 492 | "extra": { 493 | "branch-alias": { 494 | "dev-master": "1.x-dev" 495 | } 496 | }, 497 | "autoload": { 498 | "psr-4": { 499 | "Prophecy\\": "src/Prophecy" 500 | } 501 | }, 502 | "notification-url": "https://packagist.org/downloads/", 503 | "license": [ 504 | "MIT" 505 | ], 506 | "authors": [ 507 | { 508 | "name": "Konstantin Kudryashov", 509 | "email": "ever.zet@gmail.com", 510 | "homepage": "http://everzet.com" 511 | }, 512 | { 513 | "name": "Marcello Duarte", 514 | "email": "marcello.duarte@gmail.com" 515 | } 516 | ], 517 | "description": "Highly opinionated mocking framework for PHP 5.3+", 518 | "homepage": "https://github.com/phpspec/prophecy", 519 | "keywords": [ 520 | "Double", 521 | "Dummy", 522 | "fake", 523 | "mock", 524 | "spy", 525 | "stub" 526 | ], 527 | "support": { 528 | "issues": "https://github.com/phpspec/prophecy/issues", 529 | "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" 530 | }, 531 | "time": "2021-12-08T12:19:24+00:00" 532 | }, 533 | { 534 | "name": "phpunit/php-code-coverage", 535 | "version": "9.2.15", 536 | "source": { 537 | "type": "git", 538 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 539 | "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" 540 | }, 541 | "dist": { 542 | "type": "zip", 543 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", 544 | "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", 545 | "shasum": "" 546 | }, 547 | "require": { 548 | "ext-dom": "*", 549 | "ext-libxml": "*", 550 | "ext-xmlwriter": "*", 551 | "nikic/php-parser": "^4.13.0", 552 | "php": ">=7.3", 553 | "phpunit/php-file-iterator": "^3.0.3", 554 | "phpunit/php-text-template": "^2.0.2", 555 | "sebastian/code-unit-reverse-lookup": "^2.0.2", 556 | "sebastian/complexity": "^2.0", 557 | "sebastian/environment": "^5.1.2", 558 | "sebastian/lines-of-code": "^1.0.3", 559 | "sebastian/version": "^3.0.1", 560 | "theseer/tokenizer": "^1.2.0" 561 | }, 562 | "require-dev": { 563 | "phpunit/phpunit": "^9.3" 564 | }, 565 | "suggest": { 566 | "ext-pcov": "*", 567 | "ext-xdebug": "*" 568 | }, 569 | "type": "library", 570 | "extra": { 571 | "branch-alias": { 572 | "dev-master": "9.2-dev" 573 | } 574 | }, 575 | "autoload": { 576 | "classmap": [ 577 | "src/" 578 | ] 579 | }, 580 | "notification-url": "https://packagist.org/downloads/", 581 | "license": [ 582 | "BSD-3-Clause" 583 | ], 584 | "authors": [ 585 | { 586 | "name": "Sebastian Bergmann", 587 | "email": "sebastian@phpunit.de", 588 | "role": "lead" 589 | } 590 | ], 591 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 592 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 593 | "keywords": [ 594 | "coverage", 595 | "testing", 596 | "xunit" 597 | ], 598 | "support": { 599 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", 600 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15" 601 | }, 602 | "funding": [ 603 | { 604 | "url": "https://github.com/sebastianbergmann", 605 | "type": "github" 606 | } 607 | ], 608 | "time": "2022-03-07T09:28:20+00:00" 609 | }, 610 | { 611 | "name": "phpunit/php-file-iterator", 612 | "version": "3.0.6", 613 | "source": { 614 | "type": "git", 615 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 616 | "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" 617 | }, 618 | "dist": { 619 | "type": "zip", 620 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", 621 | "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", 622 | "shasum": "" 623 | }, 624 | "require": { 625 | "php": ">=7.3" 626 | }, 627 | "require-dev": { 628 | "phpunit/phpunit": "^9.3" 629 | }, 630 | "type": "library", 631 | "extra": { 632 | "branch-alias": { 633 | "dev-master": "3.0-dev" 634 | } 635 | }, 636 | "autoload": { 637 | "classmap": [ 638 | "src/" 639 | ] 640 | }, 641 | "notification-url": "https://packagist.org/downloads/", 642 | "license": [ 643 | "BSD-3-Clause" 644 | ], 645 | "authors": [ 646 | { 647 | "name": "Sebastian Bergmann", 648 | "email": "sebastian@phpunit.de", 649 | "role": "lead" 650 | } 651 | ], 652 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 653 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 654 | "keywords": [ 655 | "filesystem", 656 | "iterator" 657 | ], 658 | "support": { 659 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", 660 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" 661 | }, 662 | "funding": [ 663 | { 664 | "url": "https://github.com/sebastianbergmann", 665 | "type": "github" 666 | } 667 | ], 668 | "time": "2021-12-02T12:48:52+00:00" 669 | }, 670 | { 671 | "name": "phpunit/php-invoker", 672 | "version": "3.1.1", 673 | "source": { 674 | "type": "git", 675 | "url": "https://github.com/sebastianbergmann/php-invoker.git", 676 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" 677 | }, 678 | "dist": { 679 | "type": "zip", 680 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", 681 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", 682 | "shasum": "" 683 | }, 684 | "require": { 685 | "php": ">=7.3" 686 | }, 687 | "require-dev": { 688 | "ext-pcntl": "*", 689 | "phpunit/phpunit": "^9.3" 690 | }, 691 | "suggest": { 692 | "ext-pcntl": "*" 693 | }, 694 | "type": "library", 695 | "extra": { 696 | "branch-alias": { 697 | "dev-master": "3.1-dev" 698 | } 699 | }, 700 | "autoload": { 701 | "classmap": [ 702 | "src/" 703 | ] 704 | }, 705 | "notification-url": "https://packagist.org/downloads/", 706 | "license": [ 707 | "BSD-3-Clause" 708 | ], 709 | "authors": [ 710 | { 711 | "name": "Sebastian Bergmann", 712 | "email": "sebastian@phpunit.de", 713 | "role": "lead" 714 | } 715 | ], 716 | "description": "Invoke callables with a timeout", 717 | "homepage": "https://github.com/sebastianbergmann/php-invoker/", 718 | "keywords": [ 719 | "process" 720 | ], 721 | "support": { 722 | "issues": "https://github.com/sebastianbergmann/php-invoker/issues", 723 | "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" 724 | }, 725 | "funding": [ 726 | { 727 | "url": "https://github.com/sebastianbergmann", 728 | "type": "github" 729 | } 730 | ], 731 | "time": "2020-09-28T05:58:55+00:00" 732 | }, 733 | { 734 | "name": "phpunit/php-text-template", 735 | "version": "2.0.4", 736 | "source": { 737 | "type": "git", 738 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 739 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" 740 | }, 741 | "dist": { 742 | "type": "zip", 743 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", 744 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", 745 | "shasum": "" 746 | }, 747 | "require": { 748 | "php": ">=7.3" 749 | }, 750 | "require-dev": { 751 | "phpunit/phpunit": "^9.3" 752 | }, 753 | "type": "library", 754 | "extra": { 755 | "branch-alias": { 756 | "dev-master": "2.0-dev" 757 | } 758 | }, 759 | "autoload": { 760 | "classmap": [ 761 | "src/" 762 | ] 763 | }, 764 | "notification-url": "https://packagist.org/downloads/", 765 | "license": [ 766 | "BSD-3-Clause" 767 | ], 768 | "authors": [ 769 | { 770 | "name": "Sebastian Bergmann", 771 | "email": "sebastian@phpunit.de", 772 | "role": "lead" 773 | } 774 | ], 775 | "description": "Simple template engine.", 776 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 777 | "keywords": [ 778 | "template" 779 | ], 780 | "support": { 781 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues", 782 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" 783 | }, 784 | "funding": [ 785 | { 786 | "url": "https://github.com/sebastianbergmann", 787 | "type": "github" 788 | } 789 | ], 790 | "time": "2020-10-26T05:33:50+00:00" 791 | }, 792 | { 793 | "name": "phpunit/php-timer", 794 | "version": "5.0.3", 795 | "source": { 796 | "type": "git", 797 | "url": "https://github.com/sebastianbergmann/php-timer.git", 798 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" 799 | }, 800 | "dist": { 801 | "type": "zip", 802 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", 803 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", 804 | "shasum": "" 805 | }, 806 | "require": { 807 | "php": ">=7.3" 808 | }, 809 | "require-dev": { 810 | "phpunit/phpunit": "^9.3" 811 | }, 812 | "type": "library", 813 | "extra": { 814 | "branch-alias": { 815 | "dev-master": "5.0-dev" 816 | } 817 | }, 818 | "autoload": { 819 | "classmap": [ 820 | "src/" 821 | ] 822 | }, 823 | "notification-url": "https://packagist.org/downloads/", 824 | "license": [ 825 | "BSD-3-Clause" 826 | ], 827 | "authors": [ 828 | { 829 | "name": "Sebastian Bergmann", 830 | "email": "sebastian@phpunit.de", 831 | "role": "lead" 832 | } 833 | ], 834 | "description": "Utility class for timing", 835 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 836 | "keywords": [ 837 | "timer" 838 | ], 839 | "support": { 840 | "issues": "https://github.com/sebastianbergmann/php-timer/issues", 841 | "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" 842 | }, 843 | "funding": [ 844 | { 845 | "url": "https://github.com/sebastianbergmann", 846 | "type": "github" 847 | } 848 | ], 849 | "time": "2020-10-26T13:16:10+00:00" 850 | }, 851 | { 852 | "name": "phpunit/phpunit", 853 | "version": "9.5.20", 854 | "source": { 855 | "type": "git", 856 | "url": "https://github.com/sebastianbergmann/phpunit.git", 857 | "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba" 858 | }, 859 | "dist": { 860 | "type": "zip", 861 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/12bc8879fb65aef2138b26fc633cb1e3620cffba", 862 | "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba", 863 | "shasum": "" 864 | }, 865 | "require": { 866 | "doctrine/instantiator": "^1.3.1", 867 | "ext-dom": "*", 868 | "ext-json": "*", 869 | "ext-libxml": "*", 870 | "ext-mbstring": "*", 871 | "ext-xml": "*", 872 | "ext-xmlwriter": "*", 873 | "myclabs/deep-copy": "^1.10.1", 874 | "phar-io/manifest": "^2.0.3", 875 | "phar-io/version": "^3.0.2", 876 | "php": ">=7.3", 877 | "phpspec/prophecy": "^1.12.1", 878 | "phpunit/php-code-coverage": "^9.2.13", 879 | "phpunit/php-file-iterator": "^3.0.5", 880 | "phpunit/php-invoker": "^3.1.1", 881 | "phpunit/php-text-template": "^2.0.3", 882 | "phpunit/php-timer": "^5.0.2", 883 | "sebastian/cli-parser": "^1.0.1", 884 | "sebastian/code-unit": "^1.0.6", 885 | "sebastian/comparator": "^4.0.5", 886 | "sebastian/diff": "^4.0.3", 887 | "sebastian/environment": "^5.1.3", 888 | "sebastian/exporter": "^4.0.3", 889 | "sebastian/global-state": "^5.0.1", 890 | "sebastian/object-enumerator": "^4.0.3", 891 | "sebastian/resource-operations": "^3.0.3", 892 | "sebastian/type": "^3.0", 893 | "sebastian/version": "^3.0.2" 894 | }, 895 | "require-dev": { 896 | "ext-pdo": "*", 897 | "phpspec/prophecy-phpunit": "^2.0.1" 898 | }, 899 | "suggest": { 900 | "ext-soap": "*", 901 | "ext-xdebug": "*" 902 | }, 903 | "bin": [ 904 | "phpunit" 905 | ], 906 | "type": "library", 907 | "extra": { 908 | "branch-alias": { 909 | "dev-master": "9.5-dev" 910 | } 911 | }, 912 | "autoload": { 913 | "files": [ 914 | "src/Framework/Assert/Functions.php" 915 | ], 916 | "classmap": [ 917 | "src/" 918 | ] 919 | }, 920 | "notification-url": "https://packagist.org/downloads/", 921 | "license": [ 922 | "BSD-3-Clause" 923 | ], 924 | "authors": [ 925 | { 926 | "name": "Sebastian Bergmann", 927 | "email": "sebastian@phpunit.de", 928 | "role": "lead" 929 | } 930 | ], 931 | "description": "The PHP Unit Testing framework.", 932 | "homepage": "https://phpunit.de/", 933 | "keywords": [ 934 | "phpunit", 935 | "testing", 936 | "xunit" 937 | ], 938 | "support": { 939 | "issues": "https://github.com/sebastianbergmann/phpunit/issues", 940 | "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.20" 941 | }, 942 | "funding": [ 943 | { 944 | "url": "https://phpunit.de/sponsors.html", 945 | "type": "custom" 946 | }, 947 | { 948 | "url": "https://github.com/sebastianbergmann", 949 | "type": "github" 950 | } 951 | ], 952 | "time": "2022-04-01T12:37:26+00:00" 953 | }, 954 | { 955 | "name": "sebastian/cli-parser", 956 | "version": "1.0.1", 957 | "source": { 958 | "type": "git", 959 | "url": "https://github.com/sebastianbergmann/cli-parser.git", 960 | "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" 961 | }, 962 | "dist": { 963 | "type": "zip", 964 | "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", 965 | "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", 966 | "shasum": "" 967 | }, 968 | "require": { 969 | "php": ">=7.3" 970 | }, 971 | "require-dev": { 972 | "phpunit/phpunit": "^9.3" 973 | }, 974 | "type": "library", 975 | "extra": { 976 | "branch-alias": { 977 | "dev-master": "1.0-dev" 978 | } 979 | }, 980 | "autoload": { 981 | "classmap": [ 982 | "src/" 983 | ] 984 | }, 985 | "notification-url": "https://packagist.org/downloads/", 986 | "license": [ 987 | "BSD-3-Clause" 988 | ], 989 | "authors": [ 990 | { 991 | "name": "Sebastian Bergmann", 992 | "email": "sebastian@phpunit.de", 993 | "role": "lead" 994 | } 995 | ], 996 | "description": "Library for parsing CLI options", 997 | "homepage": "https://github.com/sebastianbergmann/cli-parser", 998 | "support": { 999 | "issues": "https://github.com/sebastianbergmann/cli-parser/issues", 1000 | "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" 1001 | }, 1002 | "funding": [ 1003 | { 1004 | "url": "https://github.com/sebastianbergmann", 1005 | "type": "github" 1006 | } 1007 | ], 1008 | "time": "2020-09-28T06:08:49+00:00" 1009 | }, 1010 | { 1011 | "name": "sebastian/code-unit", 1012 | "version": "1.0.8", 1013 | "source": { 1014 | "type": "git", 1015 | "url": "https://github.com/sebastianbergmann/code-unit.git", 1016 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" 1017 | }, 1018 | "dist": { 1019 | "type": "zip", 1020 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", 1021 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", 1022 | "shasum": "" 1023 | }, 1024 | "require": { 1025 | "php": ">=7.3" 1026 | }, 1027 | "require-dev": { 1028 | "phpunit/phpunit": "^9.3" 1029 | }, 1030 | "type": "library", 1031 | "extra": { 1032 | "branch-alias": { 1033 | "dev-master": "1.0-dev" 1034 | } 1035 | }, 1036 | "autoload": { 1037 | "classmap": [ 1038 | "src/" 1039 | ] 1040 | }, 1041 | "notification-url": "https://packagist.org/downloads/", 1042 | "license": [ 1043 | "BSD-3-Clause" 1044 | ], 1045 | "authors": [ 1046 | { 1047 | "name": "Sebastian Bergmann", 1048 | "email": "sebastian@phpunit.de", 1049 | "role": "lead" 1050 | } 1051 | ], 1052 | "description": "Collection of value objects that represent the PHP code units", 1053 | "homepage": "https://github.com/sebastianbergmann/code-unit", 1054 | "support": { 1055 | "issues": "https://github.com/sebastianbergmann/code-unit/issues", 1056 | "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" 1057 | }, 1058 | "funding": [ 1059 | { 1060 | "url": "https://github.com/sebastianbergmann", 1061 | "type": "github" 1062 | } 1063 | ], 1064 | "time": "2020-10-26T13:08:54+00:00" 1065 | }, 1066 | { 1067 | "name": "sebastian/code-unit-reverse-lookup", 1068 | "version": "2.0.3", 1069 | "source": { 1070 | "type": "git", 1071 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 1072 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" 1073 | }, 1074 | "dist": { 1075 | "type": "zip", 1076 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", 1077 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", 1078 | "shasum": "" 1079 | }, 1080 | "require": { 1081 | "php": ">=7.3" 1082 | }, 1083 | "require-dev": { 1084 | "phpunit/phpunit": "^9.3" 1085 | }, 1086 | "type": "library", 1087 | "extra": { 1088 | "branch-alias": { 1089 | "dev-master": "2.0-dev" 1090 | } 1091 | }, 1092 | "autoload": { 1093 | "classmap": [ 1094 | "src/" 1095 | ] 1096 | }, 1097 | "notification-url": "https://packagist.org/downloads/", 1098 | "license": [ 1099 | "BSD-3-Clause" 1100 | ], 1101 | "authors": [ 1102 | { 1103 | "name": "Sebastian Bergmann", 1104 | "email": "sebastian@phpunit.de" 1105 | } 1106 | ], 1107 | "description": "Looks up which function or method a line of code belongs to", 1108 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 1109 | "support": { 1110 | "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", 1111 | "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" 1112 | }, 1113 | "funding": [ 1114 | { 1115 | "url": "https://github.com/sebastianbergmann", 1116 | "type": "github" 1117 | } 1118 | ], 1119 | "time": "2020-09-28T05:30:19+00:00" 1120 | }, 1121 | { 1122 | "name": "sebastian/comparator", 1123 | "version": "4.0.6", 1124 | "source": { 1125 | "type": "git", 1126 | "url": "https://github.com/sebastianbergmann/comparator.git", 1127 | "reference": "55f4261989e546dc112258c7a75935a81a7ce382" 1128 | }, 1129 | "dist": { 1130 | "type": "zip", 1131 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", 1132 | "reference": "55f4261989e546dc112258c7a75935a81a7ce382", 1133 | "shasum": "" 1134 | }, 1135 | "require": { 1136 | "php": ">=7.3", 1137 | "sebastian/diff": "^4.0", 1138 | "sebastian/exporter": "^4.0" 1139 | }, 1140 | "require-dev": { 1141 | "phpunit/phpunit": "^9.3" 1142 | }, 1143 | "type": "library", 1144 | "extra": { 1145 | "branch-alias": { 1146 | "dev-master": "4.0-dev" 1147 | } 1148 | }, 1149 | "autoload": { 1150 | "classmap": [ 1151 | "src/" 1152 | ] 1153 | }, 1154 | "notification-url": "https://packagist.org/downloads/", 1155 | "license": [ 1156 | "BSD-3-Clause" 1157 | ], 1158 | "authors": [ 1159 | { 1160 | "name": "Sebastian Bergmann", 1161 | "email": "sebastian@phpunit.de" 1162 | }, 1163 | { 1164 | "name": "Jeff Welch", 1165 | "email": "whatthejeff@gmail.com" 1166 | }, 1167 | { 1168 | "name": "Volker Dusch", 1169 | "email": "github@wallbash.com" 1170 | }, 1171 | { 1172 | "name": "Bernhard Schussek", 1173 | "email": "bschussek@2bepublished.at" 1174 | } 1175 | ], 1176 | "description": "Provides the functionality to compare PHP values for equality", 1177 | "homepage": "https://github.com/sebastianbergmann/comparator", 1178 | "keywords": [ 1179 | "comparator", 1180 | "compare", 1181 | "equality" 1182 | ], 1183 | "support": { 1184 | "issues": "https://github.com/sebastianbergmann/comparator/issues", 1185 | "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" 1186 | }, 1187 | "funding": [ 1188 | { 1189 | "url": "https://github.com/sebastianbergmann", 1190 | "type": "github" 1191 | } 1192 | ], 1193 | "time": "2020-10-26T15:49:45+00:00" 1194 | }, 1195 | { 1196 | "name": "sebastian/complexity", 1197 | "version": "2.0.2", 1198 | "source": { 1199 | "type": "git", 1200 | "url": "https://github.com/sebastianbergmann/complexity.git", 1201 | "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" 1202 | }, 1203 | "dist": { 1204 | "type": "zip", 1205 | "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", 1206 | "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", 1207 | "shasum": "" 1208 | }, 1209 | "require": { 1210 | "nikic/php-parser": "^4.7", 1211 | "php": ">=7.3" 1212 | }, 1213 | "require-dev": { 1214 | "phpunit/phpunit": "^9.3" 1215 | }, 1216 | "type": "library", 1217 | "extra": { 1218 | "branch-alias": { 1219 | "dev-master": "2.0-dev" 1220 | } 1221 | }, 1222 | "autoload": { 1223 | "classmap": [ 1224 | "src/" 1225 | ] 1226 | }, 1227 | "notification-url": "https://packagist.org/downloads/", 1228 | "license": [ 1229 | "BSD-3-Clause" 1230 | ], 1231 | "authors": [ 1232 | { 1233 | "name": "Sebastian Bergmann", 1234 | "email": "sebastian@phpunit.de", 1235 | "role": "lead" 1236 | } 1237 | ], 1238 | "description": "Library for calculating the complexity of PHP code units", 1239 | "homepage": "https://github.com/sebastianbergmann/complexity", 1240 | "support": { 1241 | "issues": "https://github.com/sebastianbergmann/complexity/issues", 1242 | "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" 1243 | }, 1244 | "funding": [ 1245 | { 1246 | "url": "https://github.com/sebastianbergmann", 1247 | "type": "github" 1248 | } 1249 | ], 1250 | "time": "2020-10-26T15:52:27+00:00" 1251 | }, 1252 | { 1253 | "name": "sebastian/diff", 1254 | "version": "4.0.4", 1255 | "source": { 1256 | "type": "git", 1257 | "url": "https://github.com/sebastianbergmann/diff.git", 1258 | "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" 1259 | }, 1260 | "dist": { 1261 | "type": "zip", 1262 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", 1263 | "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", 1264 | "shasum": "" 1265 | }, 1266 | "require": { 1267 | "php": ">=7.3" 1268 | }, 1269 | "require-dev": { 1270 | "phpunit/phpunit": "^9.3", 1271 | "symfony/process": "^4.2 || ^5" 1272 | }, 1273 | "type": "library", 1274 | "extra": { 1275 | "branch-alias": { 1276 | "dev-master": "4.0-dev" 1277 | } 1278 | }, 1279 | "autoload": { 1280 | "classmap": [ 1281 | "src/" 1282 | ] 1283 | }, 1284 | "notification-url": "https://packagist.org/downloads/", 1285 | "license": [ 1286 | "BSD-3-Clause" 1287 | ], 1288 | "authors": [ 1289 | { 1290 | "name": "Sebastian Bergmann", 1291 | "email": "sebastian@phpunit.de" 1292 | }, 1293 | { 1294 | "name": "Kore Nordmann", 1295 | "email": "mail@kore-nordmann.de" 1296 | } 1297 | ], 1298 | "description": "Diff implementation", 1299 | "homepage": "https://github.com/sebastianbergmann/diff", 1300 | "keywords": [ 1301 | "diff", 1302 | "udiff", 1303 | "unidiff", 1304 | "unified diff" 1305 | ], 1306 | "support": { 1307 | "issues": "https://github.com/sebastianbergmann/diff/issues", 1308 | "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" 1309 | }, 1310 | "funding": [ 1311 | { 1312 | "url": "https://github.com/sebastianbergmann", 1313 | "type": "github" 1314 | } 1315 | ], 1316 | "time": "2020-10-26T13:10:38+00:00" 1317 | }, 1318 | { 1319 | "name": "sebastian/environment", 1320 | "version": "5.1.4", 1321 | "source": { 1322 | "type": "git", 1323 | "url": "https://github.com/sebastianbergmann/environment.git", 1324 | "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" 1325 | }, 1326 | "dist": { 1327 | "type": "zip", 1328 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", 1329 | "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", 1330 | "shasum": "" 1331 | }, 1332 | "require": { 1333 | "php": ">=7.3" 1334 | }, 1335 | "require-dev": { 1336 | "phpunit/phpunit": "^9.3" 1337 | }, 1338 | "suggest": { 1339 | "ext-posix": "*" 1340 | }, 1341 | "type": "library", 1342 | "extra": { 1343 | "branch-alias": { 1344 | "dev-master": "5.1-dev" 1345 | } 1346 | }, 1347 | "autoload": { 1348 | "classmap": [ 1349 | "src/" 1350 | ] 1351 | }, 1352 | "notification-url": "https://packagist.org/downloads/", 1353 | "license": [ 1354 | "BSD-3-Clause" 1355 | ], 1356 | "authors": [ 1357 | { 1358 | "name": "Sebastian Bergmann", 1359 | "email": "sebastian@phpunit.de" 1360 | } 1361 | ], 1362 | "description": "Provides functionality to handle HHVM/PHP environments", 1363 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1364 | "keywords": [ 1365 | "Xdebug", 1366 | "environment", 1367 | "hhvm" 1368 | ], 1369 | "support": { 1370 | "issues": "https://github.com/sebastianbergmann/environment/issues", 1371 | "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" 1372 | }, 1373 | "funding": [ 1374 | { 1375 | "url": "https://github.com/sebastianbergmann", 1376 | "type": "github" 1377 | } 1378 | ], 1379 | "time": "2022-04-03T09:37:03+00:00" 1380 | }, 1381 | { 1382 | "name": "sebastian/exporter", 1383 | "version": "4.0.4", 1384 | "source": { 1385 | "type": "git", 1386 | "url": "https://github.com/sebastianbergmann/exporter.git", 1387 | "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" 1388 | }, 1389 | "dist": { 1390 | "type": "zip", 1391 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", 1392 | "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", 1393 | "shasum": "" 1394 | }, 1395 | "require": { 1396 | "php": ">=7.3", 1397 | "sebastian/recursion-context": "^4.0" 1398 | }, 1399 | "require-dev": { 1400 | "ext-mbstring": "*", 1401 | "phpunit/phpunit": "^9.3" 1402 | }, 1403 | "type": "library", 1404 | "extra": { 1405 | "branch-alias": { 1406 | "dev-master": "4.0-dev" 1407 | } 1408 | }, 1409 | "autoload": { 1410 | "classmap": [ 1411 | "src/" 1412 | ] 1413 | }, 1414 | "notification-url": "https://packagist.org/downloads/", 1415 | "license": [ 1416 | "BSD-3-Clause" 1417 | ], 1418 | "authors": [ 1419 | { 1420 | "name": "Sebastian Bergmann", 1421 | "email": "sebastian@phpunit.de" 1422 | }, 1423 | { 1424 | "name": "Jeff Welch", 1425 | "email": "whatthejeff@gmail.com" 1426 | }, 1427 | { 1428 | "name": "Volker Dusch", 1429 | "email": "github@wallbash.com" 1430 | }, 1431 | { 1432 | "name": "Adam Harvey", 1433 | "email": "aharvey@php.net" 1434 | }, 1435 | { 1436 | "name": "Bernhard Schussek", 1437 | "email": "bschussek@gmail.com" 1438 | } 1439 | ], 1440 | "description": "Provides the functionality to export PHP variables for visualization", 1441 | "homepage": "https://www.github.com/sebastianbergmann/exporter", 1442 | "keywords": [ 1443 | "export", 1444 | "exporter" 1445 | ], 1446 | "support": { 1447 | "issues": "https://github.com/sebastianbergmann/exporter/issues", 1448 | "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4" 1449 | }, 1450 | "funding": [ 1451 | { 1452 | "url": "https://github.com/sebastianbergmann", 1453 | "type": "github" 1454 | } 1455 | ], 1456 | "time": "2021-11-11T14:18:36+00:00" 1457 | }, 1458 | { 1459 | "name": "sebastian/global-state", 1460 | "version": "5.0.5", 1461 | "source": { 1462 | "type": "git", 1463 | "url": "https://github.com/sebastianbergmann/global-state.git", 1464 | "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" 1465 | }, 1466 | "dist": { 1467 | "type": "zip", 1468 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", 1469 | "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", 1470 | "shasum": "" 1471 | }, 1472 | "require": { 1473 | "php": ">=7.3", 1474 | "sebastian/object-reflector": "^2.0", 1475 | "sebastian/recursion-context": "^4.0" 1476 | }, 1477 | "require-dev": { 1478 | "ext-dom": "*", 1479 | "phpunit/phpunit": "^9.3" 1480 | }, 1481 | "suggest": { 1482 | "ext-uopz": "*" 1483 | }, 1484 | "type": "library", 1485 | "extra": { 1486 | "branch-alias": { 1487 | "dev-master": "5.0-dev" 1488 | } 1489 | }, 1490 | "autoload": { 1491 | "classmap": [ 1492 | "src/" 1493 | ] 1494 | }, 1495 | "notification-url": "https://packagist.org/downloads/", 1496 | "license": [ 1497 | "BSD-3-Clause" 1498 | ], 1499 | "authors": [ 1500 | { 1501 | "name": "Sebastian Bergmann", 1502 | "email": "sebastian@phpunit.de" 1503 | } 1504 | ], 1505 | "description": "Snapshotting of global state", 1506 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1507 | "keywords": [ 1508 | "global state" 1509 | ], 1510 | "support": { 1511 | "issues": "https://github.com/sebastianbergmann/global-state/issues", 1512 | "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" 1513 | }, 1514 | "funding": [ 1515 | { 1516 | "url": "https://github.com/sebastianbergmann", 1517 | "type": "github" 1518 | } 1519 | ], 1520 | "time": "2022-02-14T08:28:10+00:00" 1521 | }, 1522 | { 1523 | "name": "sebastian/lines-of-code", 1524 | "version": "1.0.3", 1525 | "source": { 1526 | "type": "git", 1527 | "url": "https://github.com/sebastianbergmann/lines-of-code.git", 1528 | "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" 1529 | }, 1530 | "dist": { 1531 | "type": "zip", 1532 | "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", 1533 | "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", 1534 | "shasum": "" 1535 | }, 1536 | "require": { 1537 | "nikic/php-parser": "^4.6", 1538 | "php": ">=7.3" 1539 | }, 1540 | "require-dev": { 1541 | "phpunit/phpunit": "^9.3" 1542 | }, 1543 | "type": "library", 1544 | "extra": { 1545 | "branch-alias": { 1546 | "dev-master": "1.0-dev" 1547 | } 1548 | }, 1549 | "autoload": { 1550 | "classmap": [ 1551 | "src/" 1552 | ] 1553 | }, 1554 | "notification-url": "https://packagist.org/downloads/", 1555 | "license": [ 1556 | "BSD-3-Clause" 1557 | ], 1558 | "authors": [ 1559 | { 1560 | "name": "Sebastian Bergmann", 1561 | "email": "sebastian@phpunit.de", 1562 | "role": "lead" 1563 | } 1564 | ], 1565 | "description": "Library for counting the lines of code in PHP source code", 1566 | "homepage": "https://github.com/sebastianbergmann/lines-of-code", 1567 | "support": { 1568 | "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", 1569 | "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" 1570 | }, 1571 | "funding": [ 1572 | { 1573 | "url": "https://github.com/sebastianbergmann", 1574 | "type": "github" 1575 | } 1576 | ], 1577 | "time": "2020-11-28T06:42:11+00:00" 1578 | }, 1579 | { 1580 | "name": "sebastian/object-enumerator", 1581 | "version": "4.0.4", 1582 | "source": { 1583 | "type": "git", 1584 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1585 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" 1586 | }, 1587 | "dist": { 1588 | "type": "zip", 1589 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", 1590 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", 1591 | "shasum": "" 1592 | }, 1593 | "require": { 1594 | "php": ">=7.3", 1595 | "sebastian/object-reflector": "^2.0", 1596 | "sebastian/recursion-context": "^4.0" 1597 | }, 1598 | "require-dev": { 1599 | "phpunit/phpunit": "^9.3" 1600 | }, 1601 | "type": "library", 1602 | "extra": { 1603 | "branch-alias": { 1604 | "dev-master": "4.0-dev" 1605 | } 1606 | }, 1607 | "autoload": { 1608 | "classmap": [ 1609 | "src/" 1610 | ] 1611 | }, 1612 | "notification-url": "https://packagist.org/downloads/", 1613 | "license": [ 1614 | "BSD-3-Clause" 1615 | ], 1616 | "authors": [ 1617 | { 1618 | "name": "Sebastian Bergmann", 1619 | "email": "sebastian@phpunit.de" 1620 | } 1621 | ], 1622 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1623 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1624 | "support": { 1625 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", 1626 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" 1627 | }, 1628 | "funding": [ 1629 | { 1630 | "url": "https://github.com/sebastianbergmann", 1631 | "type": "github" 1632 | } 1633 | ], 1634 | "time": "2020-10-26T13:12:34+00:00" 1635 | }, 1636 | { 1637 | "name": "sebastian/object-reflector", 1638 | "version": "2.0.4", 1639 | "source": { 1640 | "type": "git", 1641 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1642 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" 1643 | }, 1644 | "dist": { 1645 | "type": "zip", 1646 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", 1647 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", 1648 | "shasum": "" 1649 | }, 1650 | "require": { 1651 | "php": ">=7.3" 1652 | }, 1653 | "require-dev": { 1654 | "phpunit/phpunit": "^9.3" 1655 | }, 1656 | "type": "library", 1657 | "extra": { 1658 | "branch-alias": { 1659 | "dev-master": "2.0-dev" 1660 | } 1661 | }, 1662 | "autoload": { 1663 | "classmap": [ 1664 | "src/" 1665 | ] 1666 | }, 1667 | "notification-url": "https://packagist.org/downloads/", 1668 | "license": [ 1669 | "BSD-3-Clause" 1670 | ], 1671 | "authors": [ 1672 | { 1673 | "name": "Sebastian Bergmann", 1674 | "email": "sebastian@phpunit.de" 1675 | } 1676 | ], 1677 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1678 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1679 | "support": { 1680 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues", 1681 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" 1682 | }, 1683 | "funding": [ 1684 | { 1685 | "url": "https://github.com/sebastianbergmann", 1686 | "type": "github" 1687 | } 1688 | ], 1689 | "time": "2020-10-26T13:14:26+00:00" 1690 | }, 1691 | { 1692 | "name": "sebastian/recursion-context", 1693 | "version": "4.0.4", 1694 | "source": { 1695 | "type": "git", 1696 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1697 | "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" 1698 | }, 1699 | "dist": { 1700 | "type": "zip", 1701 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", 1702 | "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", 1703 | "shasum": "" 1704 | }, 1705 | "require": { 1706 | "php": ">=7.3" 1707 | }, 1708 | "require-dev": { 1709 | "phpunit/phpunit": "^9.3" 1710 | }, 1711 | "type": "library", 1712 | "extra": { 1713 | "branch-alias": { 1714 | "dev-master": "4.0-dev" 1715 | } 1716 | }, 1717 | "autoload": { 1718 | "classmap": [ 1719 | "src/" 1720 | ] 1721 | }, 1722 | "notification-url": "https://packagist.org/downloads/", 1723 | "license": [ 1724 | "BSD-3-Clause" 1725 | ], 1726 | "authors": [ 1727 | { 1728 | "name": "Sebastian Bergmann", 1729 | "email": "sebastian@phpunit.de" 1730 | }, 1731 | { 1732 | "name": "Jeff Welch", 1733 | "email": "whatthejeff@gmail.com" 1734 | }, 1735 | { 1736 | "name": "Adam Harvey", 1737 | "email": "aharvey@php.net" 1738 | } 1739 | ], 1740 | "description": "Provides functionality to recursively process PHP variables", 1741 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1742 | "support": { 1743 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues", 1744 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" 1745 | }, 1746 | "funding": [ 1747 | { 1748 | "url": "https://github.com/sebastianbergmann", 1749 | "type": "github" 1750 | } 1751 | ], 1752 | "time": "2020-10-26T13:17:30+00:00" 1753 | }, 1754 | { 1755 | "name": "sebastian/resource-operations", 1756 | "version": "3.0.3", 1757 | "source": { 1758 | "type": "git", 1759 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1760 | "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" 1761 | }, 1762 | "dist": { 1763 | "type": "zip", 1764 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", 1765 | "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", 1766 | "shasum": "" 1767 | }, 1768 | "require": { 1769 | "php": ">=7.3" 1770 | }, 1771 | "require-dev": { 1772 | "phpunit/phpunit": "^9.0" 1773 | }, 1774 | "type": "library", 1775 | "extra": { 1776 | "branch-alias": { 1777 | "dev-master": "3.0-dev" 1778 | } 1779 | }, 1780 | "autoload": { 1781 | "classmap": [ 1782 | "src/" 1783 | ] 1784 | }, 1785 | "notification-url": "https://packagist.org/downloads/", 1786 | "license": [ 1787 | "BSD-3-Clause" 1788 | ], 1789 | "authors": [ 1790 | { 1791 | "name": "Sebastian Bergmann", 1792 | "email": "sebastian@phpunit.de" 1793 | } 1794 | ], 1795 | "description": "Provides a list of PHP built-in functions that operate on resources", 1796 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1797 | "support": { 1798 | "issues": "https://github.com/sebastianbergmann/resource-operations/issues", 1799 | "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" 1800 | }, 1801 | "funding": [ 1802 | { 1803 | "url": "https://github.com/sebastianbergmann", 1804 | "type": "github" 1805 | } 1806 | ], 1807 | "time": "2020-09-28T06:45:17+00:00" 1808 | }, 1809 | { 1810 | "name": "sebastian/type", 1811 | "version": "3.0.0", 1812 | "source": { 1813 | "type": "git", 1814 | "url": "https://github.com/sebastianbergmann/type.git", 1815 | "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" 1816 | }, 1817 | "dist": { 1818 | "type": "zip", 1819 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", 1820 | "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", 1821 | "shasum": "" 1822 | }, 1823 | "require": { 1824 | "php": ">=7.3" 1825 | }, 1826 | "require-dev": { 1827 | "phpunit/phpunit": "^9.5" 1828 | }, 1829 | "type": "library", 1830 | "extra": { 1831 | "branch-alias": { 1832 | "dev-master": "3.0-dev" 1833 | } 1834 | }, 1835 | "autoload": { 1836 | "classmap": [ 1837 | "src/" 1838 | ] 1839 | }, 1840 | "notification-url": "https://packagist.org/downloads/", 1841 | "license": [ 1842 | "BSD-3-Clause" 1843 | ], 1844 | "authors": [ 1845 | { 1846 | "name": "Sebastian Bergmann", 1847 | "email": "sebastian@phpunit.de", 1848 | "role": "lead" 1849 | } 1850 | ], 1851 | "description": "Collection of value objects that represent the types of the PHP type system", 1852 | "homepage": "https://github.com/sebastianbergmann/type", 1853 | "support": { 1854 | "issues": "https://github.com/sebastianbergmann/type/issues", 1855 | "source": "https://github.com/sebastianbergmann/type/tree/3.0.0" 1856 | }, 1857 | "funding": [ 1858 | { 1859 | "url": "https://github.com/sebastianbergmann", 1860 | "type": "github" 1861 | } 1862 | ], 1863 | "time": "2022-03-15T09:54:48+00:00" 1864 | }, 1865 | { 1866 | "name": "sebastian/version", 1867 | "version": "3.0.2", 1868 | "source": { 1869 | "type": "git", 1870 | "url": "https://github.com/sebastianbergmann/version.git", 1871 | "reference": "c6c1022351a901512170118436c764e473f6de8c" 1872 | }, 1873 | "dist": { 1874 | "type": "zip", 1875 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", 1876 | "reference": "c6c1022351a901512170118436c764e473f6de8c", 1877 | "shasum": "" 1878 | }, 1879 | "require": { 1880 | "php": ">=7.3" 1881 | }, 1882 | "type": "library", 1883 | "extra": { 1884 | "branch-alias": { 1885 | "dev-master": "3.0-dev" 1886 | } 1887 | }, 1888 | "autoload": { 1889 | "classmap": [ 1890 | "src/" 1891 | ] 1892 | }, 1893 | "notification-url": "https://packagist.org/downloads/", 1894 | "license": [ 1895 | "BSD-3-Clause" 1896 | ], 1897 | "authors": [ 1898 | { 1899 | "name": "Sebastian Bergmann", 1900 | "email": "sebastian@phpunit.de", 1901 | "role": "lead" 1902 | } 1903 | ], 1904 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1905 | "homepage": "https://github.com/sebastianbergmann/version", 1906 | "support": { 1907 | "issues": "https://github.com/sebastianbergmann/version/issues", 1908 | "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" 1909 | }, 1910 | "funding": [ 1911 | { 1912 | "url": "https://github.com/sebastianbergmann", 1913 | "type": "github" 1914 | } 1915 | ], 1916 | "time": "2020-09-28T06:39:44+00:00" 1917 | }, 1918 | { 1919 | "name": "symfony/polyfill-ctype", 1920 | "version": "v1.25.0", 1921 | "source": { 1922 | "type": "git", 1923 | "url": "https://github.com/symfony/polyfill-ctype.git", 1924 | "reference": "30885182c981ab175d4d034db0f6f469898070ab" 1925 | }, 1926 | "dist": { 1927 | "type": "zip", 1928 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", 1929 | "reference": "30885182c981ab175d4d034db0f6f469898070ab", 1930 | "shasum": "" 1931 | }, 1932 | "require": { 1933 | "php": ">=7.1" 1934 | }, 1935 | "provide": { 1936 | "ext-ctype": "*" 1937 | }, 1938 | "suggest": { 1939 | "ext-ctype": "For best performance" 1940 | }, 1941 | "type": "library", 1942 | "extra": { 1943 | "branch-alias": { 1944 | "dev-main": "1.23-dev" 1945 | }, 1946 | "thanks": { 1947 | "name": "symfony/polyfill", 1948 | "url": "https://github.com/symfony/polyfill" 1949 | } 1950 | }, 1951 | "autoload": { 1952 | "files": [ 1953 | "bootstrap.php" 1954 | ], 1955 | "psr-4": { 1956 | "Symfony\\Polyfill\\Ctype\\": "" 1957 | } 1958 | }, 1959 | "notification-url": "https://packagist.org/downloads/", 1960 | "license": [ 1961 | "MIT" 1962 | ], 1963 | "authors": [ 1964 | { 1965 | "name": "Gert de Pagter", 1966 | "email": "BackEndTea@gmail.com" 1967 | }, 1968 | { 1969 | "name": "Symfony Community", 1970 | "homepage": "https://symfony.com/contributors" 1971 | } 1972 | ], 1973 | "description": "Symfony polyfill for ctype functions", 1974 | "homepage": "https://symfony.com", 1975 | "keywords": [ 1976 | "compatibility", 1977 | "ctype", 1978 | "polyfill", 1979 | "portable" 1980 | ], 1981 | "support": { 1982 | "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" 1983 | }, 1984 | "funding": [ 1985 | { 1986 | "url": "https://symfony.com/sponsor", 1987 | "type": "custom" 1988 | }, 1989 | { 1990 | "url": "https://github.com/fabpot", 1991 | "type": "github" 1992 | }, 1993 | { 1994 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1995 | "type": "tidelift" 1996 | } 1997 | ], 1998 | "time": "2021-10-20T20:35:02+00:00" 1999 | }, 2000 | { 2001 | "name": "theseer/tokenizer", 2002 | "version": "1.2.1", 2003 | "source": { 2004 | "type": "git", 2005 | "url": "https://github.com/theseer/tokenizer.git", 2006 | "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" 2007 | }, 2008 | "dist": { 2009 | "type": "zip", 2010 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", 2011 | "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", 2012 | "shasum": "" 2013 | }, 2014 | "require": { 2015 | "ext-dom": "*", 2016 | "ext-tokenizer": "*", 2017 | "ext-xmlwriter": "*", 2018 | "php": "^7.2 || ^8.0" 2019 | }, 2020 | "type": "library", 2021 | "autoload": { 2022 | "classmap": [ 2023 | "src/" 2024 | ] 2025 | }, 2026 | "notification-url": "https://packagist.org/downloads/", 2027 | "license": [ 2028 | "BSD-3-Clause" 2029 | ], 2030 | "authors": [ 2031 | { 2032 | "name": "Arne Blankerts", 2033 | "email": "arne@blankerts.de", 2034 | "role": "Developer" 2035 | } 2036 | ], 2037 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 2038 | "support": { 2039 | "issues": "https://github.com/theseer/tokenizer/issues", 2040 | "source": "https://github.com/theseer/tokenizer/tree/1.2.1" 2041 | }, 2042 | "funding": [ 2043 | { 2044 | "url": "https://github.com/theseer", 2045 | "type": "github" 2046 | } 2047 | ], 2048 | "time": "2021-07-28T10:34:58+00:00" 2049 | }, 2050 | { 2051 | "name": "webmozart/assert", 2052 | "version": "1.10.0", 2053 | "source": { 2054 | "type": "git", 2055 | "url": "https://github.com/webmozarts/assert.git", 2056 | "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" 2057 | }, 2058 | "dist": { 2059 | "type": "zip", 2060 | "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", 2061 | "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", 2062 | "shasum": "" 2063 | }, 2064 | "require": { 2065 | "php": "^7.2 || ^8.0", 2066 | "symfony/polyfill-ctype": "^1.8" 2067 | }, 2068 | "conflict": { 2069 | "phpstan/phpstan": "<0.12.20", 2070 | "vimeo/psalm": "<4.6.1 || 4.6.2" 2071 | }, 2072 | "require-dev": { 2073 | "phpunit/phpunit": "^8.5.13" 2074 | }, 2075 | "type": "library", 2076 | "extra": { 2077 | "branch-alias": { 2078 | "dev-master": "1.10-dev" 2079 | } 2080 | }, 2081 | "autoload": { 2082 | "psr-4": { 2083 | "Webmozart\\Assert\\": "src/" 2084 | } 2085 | }, 2086 | "notification-url": "https://packagist.org/downloads/", 2087 | "license": [ 2088 | "MIT" 2089 | ], 2090 | "authors": [ 2091 | { 2092 | "name": "Bernhard Schussek", 2093 | "email": "bschussek@gmail.com" 2094 | } 2095 | ], 2096 | "description": "Assertions to validate method input/output with nice error messages.", 2097 | "keywords": [ 2098 | "assert", 2099 | "check", 2100 | "validate" 2101 | ], 2102 | "support": { 2103 | "issues": "https://github.com/webmozarts/assert/issues", 2104 | "source": "https://github.com/webmozarts/assert/tree/1.10.0" 2105 | }, 2106 | "time": "2021-03-09T10:59:23+00:00" 2107 | } 2108 | ], 2109 | "aliases": [], 2110 | "minimum-stability": "stable", 2111 | "stability-flags": [], 2112 | "prefer-stable": false, 2113 | "prefer-lowest": false, 2114 | "platform": { 2115 | "php": ">=8", 2116 | "ext-mbstring": "*", 2117 | "ext-json": "*", 2118 | "ext-sockets": "*" 2119 | }, 2120 | "platform-dev": [], 2121 | "plugin-api-version": "2.2.0" 2122 | } 2123 | -------------------------------------------------------------------------------- /examples/usage.php: -------------------------------------------------------------------------------- 1 | put("job's payload", delay: 2); 19 | if($job['state'] === Beanstalkd::JOB_STATE_DELAYED) { 20 | echo "Job {$job['id']} is ready to be reserved within 2 seconds\n"; 21 | } 22 | 23 | ## ## 24 | # WORKER # 25 | ## ## 26 | 27 | $client->watchTube('myAwesomeTube2'); 28 | 29 | $job = $client->reserve(); 30 | 31 | if ($job) { 32 | echo "Hey, i received first {$job['payload']} of job with id {$job['id']}\n"; 33 | 34 | $jobStats = $client->statsJob($job['id']); 35 | echo sprintf( 36 | 'It will be released by server in %d seconds, , it\'s ttr is %d', 37 | $jobStats['time-left'], 38 | $jobStats['ttr'] 39 | ); 40 | echo "\n*sleeping for 2 seconds*\n\n"; 41 | sleep(2); 42 | 43 | $jobStats = $client->statsJob($job['id']); 44 | echo "And now job will be released in {$jobStats['time-left']} seconds\n"; 45 | 46 | $client->release($job['id'], delay: 5); 47 | $jobStats = $client->statsJob($job['id']); 48 | echo "Job is released and delayed for {$jobStats['delay']} seconds\n"; 49 | echo "\n*sleeping for {$jobStats['delay']}+1 seconds*\n\n"; 50 | sleep($jobStats['delay'] + 1); 51 | 52 | $jobStats = $client->statsJob($job['id']); 53 | echo "Current job state is {$jobStats['state']}\n"; 54 | 55 | echo "Job is done, deleting it\n"; 56 | $client->delete($job['id']); 57 | } else { 58 | echo "So sad, i have nothing to do\n"; 59 | } 60 | 61 | echo "\nAm I still connected? " . ($client->socket()->isConnected() ? 'Yes' : 'No') . "\n"; 62 | echo "\nHave i anything to do? "; 63 | echo ($client->reserveWithTimeout(5) ? 'Yes' : 'No') . "\n"; 64 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | ./tests 11 | 12 | 13 | 14 | 15 | 16 | ./src 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Beanstalkd.php: -------------------------------------------------------------------------------- 1 | = " . Beanstalkd::PRIORITY_MIN); 33 | } 34 | if ($priority > Beanstalkd::PRIORITY_MAX) { 35 | throw new \InvalidArgumentException("priority should be <= " . Beanstalkd::PRIORITY_MAX); 36 | } 37 | } 38 | 39 | public static function validateDelay(int $delay) 40 | { 41 | if ($delay < Beanstalkd::DELAY_MIN) { 42 | throw new \InvalidArgumentException("delay should be >= " . Beanstalkd::DELAY_MIN); 43 | } 44 | if ($delay > Beanstalkd::DELAY_MAX) { 45 | throw new \InvalidArgumentException("delay should be <= " . Beanstalkd::DELAY_MAX); 46 | } 47 | } 48 | 49 | public static function validateTTR(int $ttr) 50 | { 51 | if ($ttr < Beanstalkd::TTR_MIN) { 52 | throw new \InvalidArgumentException("ttr should be >= " . Beanstalkd::TTR_MIN); 53 | } 54 | if ($ttr > Beanstalkd::TTR_MAX) { 55 | throw new \InvalidArgumentException("ttr should be <= " . Beanstalkd::TTR_MAX); 56 | } 57 | } 58 | 59 | public static function validateTimeout(int $timeout) 60 | { 61 | if ($timeout < Beanstalkd::TIMEOUT_MIN) { 62 | throw new \InvalidArgumentException("timeout should be >= " . Beanstalkd::TIMEOUT_MIN); 63 | } 64 | } 65 | 66 | public static function validateJobID(int $jobId) 67 | { 68 | if ($jobId < Beanstalkd::JOB_ID_MIN) { 69 | throw new \InvalidArgumentException("job id should be >= " . Beanstalkd::JOB_ID_MIN); 70 | } 71 | } 72 | 73 | public static function validateTubeName(string $name) 74 | { 75 | if (!preg_match('~^[A-Za-z0-9\-+/;.$_()]{1,200}$~', $name)) { 76 | throw new \InvalidArgumentException('tube name should satisfy regexp: /^[A-Za-z0-9-+/;.$_()]{1,200}$/'); 77 | } 78 | } 79 | 80 | public const CMD_PUT = 'put'; 81 | public const CMD_USE = 'use'; 82 | public const CMD_RESERVE = 'reserve'; 83 | public const CMD_RESERVE_WITH_TIMEOUT = 'reserve-with-timeout'; 84 | public const CMD_RESERVE_JOB = 'reserve-job'; 85 | public const CMD_DELETE = 'delete'; 86 | public const CMD_RELEASE = 'release'; 87 | public const CMD_BURY = 'bury'; 88 | public const CMD_TOUCH = 'touch'; 89 | public const CMD_WATCH = 'watch'; 90 | public const CMD_IGNORE = 'ignore'; 91 | public const CMD_PEEK = 'peek'; 92 | public const CMD_PEEK_READY = 'peek-ready'; 93 | public const CMD_PEEK_DELAYED = 'peek-delayed'; 94 | public const CMD_PEEK_BURIED = 'peek-buried'; 95 | public const CMD_KICK = 'kick'; 96 | public const CMD_KICK_JOB = 'kick-job'; 97 | public const CMD_STATS = 'stats'; 98 | public const CMD_STATS_JOB = 'stats-job'; 99 | public const CMD_STATS_TUBE = 'stats-tube'; 100 | public const CMD_LIST_TUBES = 'list-tubes'; 101 | public const CMD_LIST_TUBE_USED = 'list-tube-used'; 102 | public const CMD_LIST_TUBES_WATCHED = 'list-tubes-watched'; 103 | public const CMD_PAUSE_TUBE = 'pause-tube'; 104 | public const CMD_QUIT = 'quit'; 105 | 106 | const CMDS_LIST = [ 107 | self::CMD_PUT => true, 108 | self::CMD_USE => true, 109 | self::CMD_RESERVE => true, 110 | self::CMD_RESERVE_WITH_TIMEOUT => true, 111 | self::CMD_RESERVE_JOB => true, 112 | self::CMD_DELETE => true, 113 | self::CMD_RELEASE => true, 114 | self::CMD_BURY => true, 115 | self::CMD_TOUCH => true, 116 | self::CMD_WATCH => true, 117 | self::CMD_IGNORE => true, 118 | self::CMD_PEEK => true, 119 | self::CMD_PEEK_READY => true, 120 | self::CMD_PEEK_DELAYED => true, 121 | self::CMD_PEEK_BURIED => true, 122 | self::CMD_KICK => true, 123 | self::CMD_KICK_JOB => true, 124 | self::CMD_STATS => true, 125 | self::CMD_STATS_JOB => true, 126 | self::CMD_STATS_TUBE => true, 127 | self::CMD_LIST_TUBES => true, 128 | self::CMD_LIST_TUBE_USED => true, 129 | self::CMD_LIST_TUBES_WATCHED => true, 130 | self::CMD_PAUSE_TUBE => true, 131 | self::CMD_QUIT => true, 132 | ]; 133 | 134 | public static function supportsCommand(string $command): bool 135 | { 136 | return self::CMDS_LIST[$command] ?? false; 137 | } 138 | 139 | public const STATUS_BAD_FORMAT = 'BAD_FORMAT'; 140 | public const STATUS_BURIED = 'BURIED'; 141 | public const STATUS_DEADLINE_SOON = 'DEADLINE_SOON'; 142 | public const STATUS_DELETED = 'DELETED'; 143 | public const STATUS_DRAINING = 'DRAINING'; 144 | public const STATUS_EXPECTED_CRLF = 'EXPECTED_CRLF'; 145 | public const STATUS_FOUND = 'FOUND'; 146 | public const STATUS_INSERTED = 'INSERTED'; 147 | public const STATUS_INTERNAL_ERROR = 'INTERNAL_ERROR'; 148 | public const STATUS_JOB_TOO_BIG = 'JOB_TOO_BIG'; 149 | public const STATUS_KICKED = 'KICKED'; 150 | public const STATUS_NOT_FOUND = 'NOT_FOUND'; 151 | public const STATUS_NOT_IGNORED = 'NOT_IGNORED'; 152 | public const STATUS_OK = 'OK'; 153 | public const STATUS_OUT_OF_MEMORY = 'OUT_OF_MEMORY'; 154 | public const STATUS_PAUSED = 'PAUSED'; 155 | public const STATUS_RELEASED = 'RELEASED'; 156 | public const STATUS_RESERVED = 'RESERVED'; 157 | public const STATUS_TIMED_OUT = 'TIMED_OUT'; 158 | public const STATUS_TOUCHED = 'TOUCHED'; 159 | public const STATUS_UNKNOWN_COMMAND = 'UNKNOWN_COMMAND'; 160 | public const STATUS_USING = 'USING'; 161 | public const STATUS_WATCHING = 'WATCHING'; 162 | 163 | const STATUSES_LIST = [ 164 | self::STATUS_BAD_FORMAT => true, 165 | self::STATUS_BURIED => true, 166 | self::STATUS_DEADLINE_SOON => true, 167 | self::STATUS_DELETED => true, 168 | self::STATUS_DRAINING => true, 169 | self::STATUS_EXPECTED_CRLF => true, 170 | self::STATUS_FOUND => true, 171 | self::STATUS_INSERTED => true, 172 | self::STATUS_INTERNAL_ERROR => true, 173 | self::STATUS_JOB_TOO_BIG => true, 174 | self::STATUS_KICKED => true, 175 | self::STATUS_NOT_FOUND => true, 176 | self::STATUS_NOT_IGNORED => true, 177 | self::STATUS_OK => true, 178 | self::STATUS_OUT_OF_MEMORY => true, 179 | self::STATUS_PAUSED => true, 180 | self::STATUS_RELEASED => true, 181 | self::STATUS_RESERVED => true, 182 | self::STATUS_TIMED_OUT => true, 183 | self::STATUS_TOUCHED => true, 184 | self::STATUS_UNKNOWN_COMMAND => true, 185 | self::STATUS_USING => true, 186 | self::STATUS_WATCHING => true, 187 | ]; 188 | 189 | public static function supportsResponseStatus(string $status): bool 190 | { 191 | return self::STATUSES_LIST[$status] ?? false; 192 | } 193 | 194 | const DATA_STATUSES_LIST = [ 195 | self::STATUS_OK => true, 196 | self::STATUS_RESERVED => true, 197 | self::STATUS_FOUND => true, 198 | ]; 199 | 200 | public static function isDataResponseStatus(string $status): bool 201 | { 202 | return self::DATA_STATUSES_LIST[$status] ?? false; 203 | } 204 | 205 | const ERROR_STATUSES_LIST = [ 206 | self::STATUS_OUT_OF_MEMORY => true, 207 | self::STATUS_INTERNAL_ERROR => true, 208 | self::STATUS_BAD_FORMAT => true, 209 | self::STATUS_DRAINING => true, 210 | self::STATUS_UNKNOWN_COMMAND => true, 211 | ]; 212 | 213 | public static function isErrorStatus(string $status): bool 214 | { 215 | return self::ERROR_STATUSES_LIST[$status] ?? false; 216 | } 217 | 218 | public const JOB_STATE_READY = 'ready'; 219 | public const JOB_STATE_DELAYED = 'delayed'; 220 | public const JOB_STATE_RESERVED = 'reserved'; 221 | public const JOB_STATE_BURIED = 'buried'; 222 | 223 | 224 | /** 225 | * @throws ResponseException 226 | */ 227 | #[ArrayShape(["status" => "string", "headers" => "string[]", "hasData" => "bool", "dataLength" => "int"])] 228 | public static function parseResponseHeaders(string $headers): array 229 | { 230 | $headersArray = explode(" ", $headers); 231 | 232 | $status = (string)array_shift($headersArray); 233 | 234 | $hasData = self::isDataResponseStatus($status); 235 | $dataLength = 0; 236 | 237 | if ($hasData) { 238 | $dataLength = array_pop($headersArray); 239 | 240 | if (!is_numeric($dataLength)) { 241 | throw new ResponseException( 242 | sprintf('Received invalid data length for `%s` response, expected number, got `%s`', $status, $dataLength) 243 | ); 244 | } 245 | } 246 | 247 | return [ 248 | "status" => $status, 249 | "headers" => $headersArray, 250 | "hasData" => $hasData, 251 | "dataLength" => $dataLength, 252 | ]; 253 | } 254 | 255 | /** 256 | * @throws ResponseException 257 | */ 258 | public static function simpleYamlParse(string $str): ?array 259 | { 260 | $str = rtrim($str); 261 | if (empty($str)) { 262 | return null; 263 | } 264 | 265 | $result = []; 266 | $lines = explode("\n", $str); 267 | 268 | # 1st line is a separator - don't need it; 269 | array_shift($lines); 270 | 271 | # list array 272 | if (mb_substr($lines[0], 0, 2, encoding: '8BIT') === '- ') { 273 | foreach ($lines as $line) { 274 | $result[] = mb_substr($line, 2, encoding: '8BIT'); 275 | } 276 | } else { 277 | foreach ($lines as $line) { 278 | if (preg_match('/([\S]+): (.+)/', $line, $res) !== 1) { 279 | throw new ResponseException('Failed to parse YAML string [' . $line . ']'); 280 | } 281 | 282 | $result[$res[1]] = $res[2]; 283 | } 284 | } 285 | 286 | return $result; 287 | } 288 | 289 | #[Pure] 290 | public static function processStats(array $arr): array 291 | { 292 | return [ 293 | 'current-jobs-urgent' => (int)$arr['current-jobs-urgent'], 294 | 'current-jobs-ready' => (int)$arr['current-jobs-ready'], 295 | 'current-jobs-reserved' => (int)$arr['current-jobs-reserved'], 296 | 'current-jobs-delayed' => (int)$arr['current-jobs-delayed'], 297 | 'current-jobs-buried' => (int)$arr['current-jobs-buried'], 298 | 'cmd-put' => (int)$arr['cmd-put'], 299 | 'cmd-peek' => (int)$arr['cmd-peek'], 300 | 'cmd-peek-ready' => (int)$arr['cmd-peek-ready'], 301 | 'cmd-peek-delayed' => (int)$arr['cmd-peek-delayed'], 302 | 'cmd-peek-buried' => (int)$arr['cmd-peek-buried'], 303 | 'cmd-reserve' => (int)$arr['cmd-reserve'], 304 | 'cmd-reserve-with-timeout' => (int)$arr['cmd-reserve-with-timeout'], 305 | 'cmd-use' => (int)$arr['cmd-use'], 306 | 'cmd-watch' => (int)$arr['cmd-watch'], 307 | 'cmd-ignore' => (int)$arr['cmd-ignore'], 308 | 'cmd-delete' => (int)$arr['cmd-delete'], 309 | 'cmd-release' => (int)$arr['cmd-release'], 310 | 'cmd-bury' => (int)$arr['cmd-bury'], 311 | 'cmd-kick' => (int)$arr['cmd-kick'], 312 | 'cmd-stats' => (int)$arr['cmd-stats'], 313 | 'cmd-stats-job' => (int)$arr['cmd-stats-job'], 314 | 'cmd-stats-tube' => (int)$arr['cmd-stats-tube'], 315 | 'cmd-list-tubes' => (int)$arr['cmd-list-tubes'], 316 | 'cmd-list-tube-used' => (int)$arr['cmd-list-tube-used'], 317 | 'cmd-list-tubes-watched' => (int)$arr['cmd-list-tubes-watched'], 318 | 'cmd-pause-tube' => (int)$arr['cmd-pause-tube'], 319 | 'job-timeouts' => (int)$arr['job-timeouts'], 320 | 'cmd-touch' => (int)$arr['cmd-touch'], 321 | 'total-jobs' => (int)$arr['total-jobs'], 322 | 'max-job-size' => (int)$arr['max-job-size'], 323 | 'current-tubes' => (int)$arr['current-tubes'], 324 | 'current-connections' => (int)$arr['current-connections'], 325 | 'current-producers' => (int)$arr['current-producers'], 326 | 'current-workers' => (int)$arr['current-workers'], 327 | 'current-waiting' => (int)$arr['current-waiting'], 328 | 'total-connections' => (int)$arr['total-connections'], 329 | 'pid' => (int)$arr['pid'], 330 | 'version' => (string)$arr['version'], 331 | 'rusage-utime' => (float)$arr['rusage-utime'], 332 | 'rusage-stime' => (float)$arr['rusage-stime'], 333 | 'uptime' => (int)$arr['uptime'], 334 | 'binlog-oldest-index' => (int)$arr['binlog-oldest-index'], 335 | 'binlog-current-index' => (int)$arr['binlog-current-index'], 336 | 'binlog-max-size' => (int)$arr['binlog-max-size'], 337 | 'binlog-records-written' => (int)$arr['binlog-records-written'], 338 | 'binlog-records-migrated' => (int)$arr['binlog-records-migrated'], 339 | 'draining' => (string)$arr['draining'] === 'true', 340 | 'id' => (string)$arr['id'], 341 | 'hostname' => (string)$arr['hostname'], 342 | 'os' => (string)$arr['os'] ?? null, 343 | 'platform' => (string)$arr['platform'], 344 | ]; 345 | } 346 | 347 | #[Pure] 348 | public static function processTubeStats(array $arr): array 349 | { 350 | return [ 351 | 'name' => (string)$arr['name'], 352 | 'current-jobs-urgent' => (int)$arr['current-jobs-urgent'], 353 | 'current-jobs-ready' => (int)$arr['current-jobs-ready'], 354 | 'current-jobs-reserved' => (int)$arr['current-jobs-reserved'], 355 | 'current-jobs-delayed' => (int)$arr['current-jobs-delayed'], 356 | 'current-jobs-buried' => (int)$arr['current-jobs-buried'], 357 | 'total-jobs' => (int)$arr['total-jobs'], 358 | 'current-using' => (int)$arr['current-using'], 359 | 'current-waiting' => (int)$arr['current-waiting'], 360 | 'current-watching' => (int)$arr['current-watching'], 361 | 'pause' => (int)$arr['pause'], 362 | 'cmd-delete' => (int)$arr['cmd-delete'], 363 | 'cmd-pause-tube' => (int)$arr['cmd-pause-tube'], 364 | 'pause-time-left' => (int)$arr['pause-time-left'], 365 | ]; 366 | } 367 | 368 | #[Pure] 369 | public static function processJobStats(array $arr): array 370 | { 371 | return [ 372 | 'id' => (int)$arr['id'], 373 | 'tube' => (string)$arr['tube'], 374 | 'state' => (string)$arr['state'], 375 | 'pri' => (int)$arr['pri'], 376 | 'age' => (int)$arr['age'], 377 | 'delay' => (int)$arr['delay'], 378 | 'ttr' => (int)$arr['ttr'], 379 | 'time-left' => (int)$arr['time-left'], 380 | 'file' => (int)$arr['file'], 381 | 'reserves' => (int)$arr['reserves'], 382 | 'timeouts' => (int)$arr['timeouts'], 383 | 'releases' => (int)$arr['releases'], 384 | 'buries' => (int)$arr['buries'], 385 | 'kicks' => (int)$arr['kicks'], 386 | ]; 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | setSocket($socket) 28 | ->setSerializer($serializer) 29 | ->setDefaultPriority($defaultPriority) 30 | ->setDefaultDelay($defaultDelay) 31 | ->setDefaultTTR($defaultTTR); 32 | 33 | if ($defaultTube) { 34 | $this->useTube($defaultTube); 35 | $this->watchTube($defaultTube); 36 | } 37 | } 38 | 39 | public function socket(): SocketInterface 40 | { 41 | return $this->socket; 42 | } 43 | 44 | /** 45 | * @throws ClientException 46 | */ 47 | public function setSocket(SocketInterface $connection): self 48 | { 49 | if (!$connection->isConnected()) { 50 | throw new ClientException("Unable to use closed socket"); 51 | } 52 | 53 | $this->socket = $connection; 54 | 55 | return $this; 56 | } 57 | 58 | public function serializer(): ?SerializerInterface 59 | { 60 | return $this->serializer; 61 | } 62 | 63 | public function setSerializer(?SerializerInterface $serializer): self 64 | { 65 | $this->serializer = $serializer; 66 | 67 | return $this; 68 | } 69 | 70 | public function defaultPriority(): int 71 | { 72 | return $this->defaultPriority; 73 | } 74 | 75 | public function setDefaultPriority(int $priority): self 76 | { 77 | Beanstalkd::validatePriority($priority); 78 | 79 | $this->defaultPriority = $priority; 80 | 81 | return $this; 82 | } 83 | 84 | public function defaultTTR(): int 85 | { 86 | return $this->defaultTTR; 87 | } 88 | 89 | public function setDefaultTTR(int $ttr): self 90 | { 91 | Beanstalkd::validateTTR($ttr); 92 | 93 | $this->defaultTTR = $ttr; 94 | 95 | return $this; 96 | } 97 | 98 | public function defaultDelay(): int 99 | { 100 | return $this->defaultDelay; 101 | } 102 | 103 | public function setDefaultDelay(int $delay): self 104 | { 105 | Beanstalkd::validateDelay($delay); 106 | 107 | $this->defaultDelay = $delay; 108 | 109 | return $this; 110 | } 111 | 112 | private function serializePayload(mixed $data): ?string 113 | { 114 | if ($data === null) { 115 | return null; 116 | } 117 | 118 | if ($this->serializer) { 119 | $data = $this->serializer->serialize($data); 120 | } else if (!is_string($data)) { 121 | throw new \InvalidArgumentException( 122 | sprintf( 123 | 'Serializer not defined, payload has to be string, got `%s`. Configure serializer or serialize payload manually.', 124 | gettype($data) 125 | ) 126 | ); 127 | } 128 | 129 | $payloadSize = mb_strlen($data, '8BIT'); 130 | if ($payloadSize > $this->maxPayloadSize) { 131 | throw new \InvalidArgumentException( 132 | sprintf( 133 | '%s is too big, maximum size is %d bytes, got %d', 134 | $this->serializer ? 'Serialized payload' : 'Payload', 135 | $this->maxPayloadSize, 136 | $payloadSize 137 | ) 138 | ); 139 | } 140 | 141 | return $data; 142 | } 143 | 144 | /** 145 | * @throws Exceptions\CommandException 146 | * @throws Exceptions\ResponseException 147 | */ 148 | #[ArrayShape(["status" => "string", "headers" => "string[]", 'data' => "null|string",])] 149 | private function dispatchCommand(Command $cmd, array $args = [], mixed $payload = null): array 150 | { 151 | # write command to a socket 152 | $this->socket->write($cmd->buildCommand($args, $this->serializePayload($payload))); 153 | 154 | # read first line that contains most of the infos 155 | $headers = Beanstalkd::parseResponseHeaders($this->socket->readline()); 156 | 157 | $response = [ 158 | "status" => $headers['status'], 159 | "headers" => $headers['headers'], 160 | "data" => null, 161 | ]; 162 | 163 | if ($headers['hasData']) { 164 | $response['data'] = $this->socket->read($headers['dataLength'] + Beanstalkd::CRLF_LEN); 165 | } 166 | 167 | return $cmd->handleResponse($response, $this->serializer); 168 | } 169 | 170 | ## COMMANDS 171 | 172 | /** 173 | * Subsequent put commands will put jobs into the tube specified by this command. If no use 174 | * command has been issued, jobs will be put into the tube named "default". 175 | * 176 | * @throws Exceptions\CommandException 177 | * @throws Exceptions\ResponseException 178 | */ 179 | public function useTube(string $tubeName): string 180 | { 181 | Beanstalkd::validateTubeName($tubeName); 182 | 183 | $cmd = Commander::getCommand(Beanstalkd::CMD_USE); 184 | 185 | $result = $this->dispatchCommand($cmd, [$tubeName]); 186 | 187 | return $result['headers'][0]; 188 | } 189 | 190 | /** 191 | * This command for any process that wants to insert a job into the queue. 192 | * 193 | * @param $payload - Payload of the job. Non string or integer values will be serialized with 194 | * [[IClientCtorOptions.serializer]]. Byte size of payload should be less than less than server's 195 | * max-job-size (default: 2**16) and client's [[IClientCtorOptions.maxPayloadSize]]. 196 | * 197 | * @param $ttr - Time to run -- is an integer number of seconds to allow a worker 198 | * to run this job. This time is counted from the moment a worker reserves 199 | * this job. If the worker does not delete, release, or bury the job within 200 | * seconds, the job will time out and the server will release the job. 201 | * The minimum ttr is 1. Maximum ttr is 2**32-1. 202 | * 203 | * @param $priority - Integer < 2**32. Jobs with smaller priority values will be 204 | * scheduled before jobs with larger priorities. The most urgent priority is 0; 205 | * the least urgent priority is 4,294,967,295. 206 | * 207 | * @param $delay - Integer number of seconds to wait before putting the job in 208 | * the ready queue. The job will be in the "delayed" state during this time. 209 | * Maximum delay is 2**32-1. 210 | * 211 | * @throws Exceptions\CommandException 212 | * @throws Exceptions\ResponseException 213 | * @throws ClientException 214 | */ 215 | #[ArrayShape(["id" => "int", "state" => "string"])] 216 | public function put(mixed $payload, int $ttr = null, int $priority = null, int $delay = null): array 217 | { 218 | $ttr ??= $this->defaultTTR; 219 | $priority ??= $this->defaultPriority; 220 | $delay ??= $this->defaultDelay; 221 | 222 | Beanstalkd::validateTTR($ttr); 223 | Beanstalkd::validatePriority($priority); 224 | Beanstalkd::validateDelay($delay); 225 | 226 | $cmd = Commander::getCommand(Beanstalkd::CMD_PUT); 227 | 228 | $result = $this->dispatchCommand($cmd, [$priority, $delay, $ttr], $payload); 229 | 230 | if ($result['status'] === Beanstalkd::STATUS_JOB_TOO_BIG) { 231 | throw new ClientException("Provided job payload exceeds maximal server's `max-job-size` config"); 232 | } 233 | 234 | if ($result['status'] === Beanstalkd::STATUS_EXPECTED_CRLF) { 235 | throw new ClientException('Missing trailing CRLF'); 236 | } 237 | 238 | if ($result['status'] === Beanstalkd::STATUS_DRAINING) { 239 | throw new ClientException('Server is in `drain mode` and no longer accepting new jobs.',); 240 | } 241 | 242 | $state = $delay !== 0 ? Beanstalkd::JOB_STATE_DELAYED : Beanstalkd::JOB_STATE_READY; 243 | 244 | return [ 245 | 'id' => (int)$result['headers'][0], 246 | 'state' => $result['status'] === Beanstalkd::STATUS_BURIED ? Beanstalkd::JOB_STATE_BURIED : $state, 247 | ]; 248 | } 249 | 250 | /** 251 | * This will return a newly-reserved job. If no job is available to be reserved, 252 | * beanstalkd will wait to send a response until one becomes available. Once a 253 | * job is reserved for the client, the client has limited time to run (TTR) the 254 | * job before the job times out. When the job times out, the server will put the 255 | * job back into the ready queue. Both the TTR and the actual time left can be 256 | * found in response to the [[Client.statsJob]] command. 257 | * 258 | * If more than one job is ready, beanstalkd will choose the one with the 259 | * smallest priority value. Within each priority, it will choose the one that 260 | * was received first. 261 | * 262 | * During the TTR of a reserved job, the last second is kept by the server as a 263 | * safety margin, during which the client will not be made to wait for another 264 | * job. If the client issues a reserve command during the safety margin, or if 265 | * the safety margin arrives while the client is waiting on a reserve command, 266 | * the server will respond with: DEADLINE_SOON 267 | * 268 | * This gives the client a chance to delete or release its reserved job before 269 | * the server automatically releases it. 270 | * 271 | * @throws Exceptions\CommandException 272 | * @throws Exceptions\ResponseException 273 | * @throws Exceptions\ClientException 274 | */ 275 | #[ArrayShape(["id" => "int", "payload" => "mixed"])] 276 | public function reserve(): ?array 277 | { 278 | $cmd = Commander::getCommand(Beanstalkd::CMD_RESERVE); 279 | 280 | $result = $this->dispatchCommand($cmd); 281 | 282 | if ($result['status'] === Beanstalkd::STATUS_TIMED_OUT) { 283 | return null; 284 | } 285 | 286 | if ($result['status'] === Beanstalkd::STATUS_DEADLINE_SOON) { 287 | throw new ClientException("One of jobs reserved by this client will reach deadline soon, release it first."); 288 | } 289 | 290 | return [ 291 | 'id' => (int)$result['headers'][0], 292 | 'payload' => $result['data'], 293 | ]; 294 | } 295 | 296 | /** 297 | * Same as [[Client.reserve]] but with limited amount of time to wait for the job. 298 | * 299 | * A timeout value of 0 will cause the server to immediately return either a 300 | * response or TIMED_OUT. A positive value of timeout will limit the amount of 301 | * time the client will block on the reserve request until a job becomes 302 | * available. 303 | * 304 | * @throws Exceptions\CommandException 305 | * @throws Exceptions\ResponseException 306 | * @throws ClientException 307 | */ 308 | #[ArrayShape(["id" => "int", "payload" => "mixed"])] 309 | public function reserveWithTimeout(int $timeout): ?array 310 | { 311 | Beanstalkd::validateTimeout($timeout); 312 | 313 | $cmd = Commander::getCommand(Beanstalkd::CMD_RESERVE_WITH_TIMEOUT); 314 | 315 | $result = $this->dispatchCommand($cmd, [$timeout]); 316 | 317 | if ($result['status'] === Beanstalkd::STATUS_TIMED_OUT) { 318 | return null; 319 | } 320 | 321 | if ($result['status'] === Beanstalkd::STATUS_DEADLINE_SOON) { 322 | throw new ClientException("One of jobs reserved by this client will reach deadline soon, release it first."); 323 | } 324 | 325 | return [ 326 | 'id' => (int)$result['headers'][0], 327 | 'payload' => $result['data'], 328 | ]; 329 | } 330 | 331 | /** 332 | * A job can be reserved by its id. Once a job is reserved for the client, 333 | * the client has limited time to run (TTR) the job before the job times out. 334 | * When the job times out, the server will put the job back into the ready queue. 335 | * 336 | * @throws Exceptions\CommandException 337 | * @throws Exceptions\ResponseException 338 | * @throws ClientException 339 | */ 340 | #[ArrayShape(["id" => "int", "payload" => "mixed"])] 341 | public function reserveJob(int $jobId): ?array 342 | { 343 | Beanstalkd::validateJobID($jobId); 344 | 345 | $cmd = Commander::getCommand(Beanstalkd::CMD_RESERVE_JOB); 346 | 347 | $result = $this->dispatchCommand($cmd, [$jobId]); 348 | 349 | if ($result['status'] === Beanstalkd::STATUS_NOT_FOUND) { 350 | return null; 351 | } 352 | 353 | if ($result['status'] === Beanstalkd::STATUS_DEADLINE_SOON) { 354 | throw new ClientException("One of jobs reserved by this client will reach deadline soon, release it first."); 355 | } 356 | 357 | return [ 358 | 'id' => (int)$result['headers'][0], 359 | 'payload' => $result['data'], 360 | ]; 361 | } 362 | 363 | /** 364 | * The delete command removes a job from the server entirely. It is normally used 365 | * by the client when the job has successfully run to completion. A client can 366 | * delete jobs that it has reserved, ready jobs, delayed jobs, and jobs that are 367 | * buried. 368 | * 369 | * @throws Exceptions\CommandException 370 | * @throws Exceptions\ResponseException 371 | */ 372 | public function delete(int $jobId): bool 373 | { 374 | Beanstalkd::validateJobID($jobId); 375 | 376 | $cmd = Commander::getCommand(Beanstalkd::CMD_DELETE); 377 | 378 | $result = $this->dispatchCommand($cmd, [$jobId]); 379 | 380 | return $result['status'] === Beanstalkd::STATUS_DELETED; 381 | } 382 | 383 | /** 384 | * The release command puts a reserved job back into the ready queue (and marks 385 | * its state as "ready") to be run by any client. It is normally used when the job 386 | * fails because of a transitory error. 387 | * 388 | * @param $jobId - job id to release. 389 | * @param $priority - a new priority to assign to the job. 390 | * @param $delay - integer number of seconds to wait before putting the job in 391 | * the ready queue. The job will be in the "delayed" state during this time. 392 | * 393 | * @throws Exceptions\CommandException 394 | * @throws Exceptions\ResponseException 395 | */ 396 | public 397 | function release(int $jobId, int $priority = null, int $delay = null): ?string 398 | { 399 | $priority ??= $this->defaultPriority; 400 | $delay ??= $this->defaultDelay; 401 | 402 | Beanstalkd::validateJobID($jobId); 403 | Beanstalkd::validatePriority($priority); 404 | Beanstalkd::validateDelay($delay); 405 | 406 | $cmd = Commander::getCommand(Beanstalkd::CMD_RELEASE); 407 | 408 | $result = $this->dispatchCommand($cmd, [$jobId, $priority, $delay]); 409 | 410 | if ($result['status'] === Beanstalkd::STATUS_NOT_FOUND) { 411 | return null; 412 | } 413 | 414 | if ($result['status'] === Beanstalkd::STATUS_BURIED) { 415 | return Beanstalkd::JOB_STATE_BURIED; 416 | } 417 | 418 | return $delay !== 0 ? Beanstalkd::JOB_STATE_DELAYED : Beanstalkd::JOB_STATE_READY; 419 | } 420 | 421 | /** 422 | * The bury command puts a job into the "buried" state. Buried jobs are put into a 423 | * FIFO linked list and will not be touched by the server again until a client 424 | * kicks them with the [[Client.kick]] command 425 | * 426 | * @param $jobId - job id to bury. 427 | * @param $priority - a new priority to assign to the job. 428 | * 429 | * @throws Exceptions\CommandException 430 | * @throws Exceptions\ResponseException 431 | */ 432 | public function bury(int $jobId, int $priority = null): bool 433 | { 434 | $priority ??= $this->defaultPriority; 435 | 436 | Beanstalkd::validateJobID($jobId); 437 | Beanstalkd::validatePriority($priority); 438 | 439 | $cmd = Commander::getCommand(Beanstalkd::CMD_BURY); 440 | 441 | $result = $this->dispatchCommand($cmd, [$jobId, $priority]); 442 | 443 | return $result['status'] === Beanstalkd::STATUS_BURIED; 444 | } 445 | 446 | /** 447 | * The "touch" command allows a worker to request more time to work on a job. 448 | * This is useful for jobs that potentially take a long time, but you still want 449 | * the benefits of a TTR pulling a job away from an unresponsive worker. A worker 450 | * may periodically tell the server that it's still alive and processing a job 451 | * (e.g. it may do this on DEADLINE_SOON). The command postpones the auto 452 | * release of a reserved job until TTR seconds from when the command is issued 453 | * 454 | * @throws Exceptions\CommandException 455 | * @throws Exceptions\ResponseException 456 | */ 457 | public function touch(int $jobId): bool 458 | { 459 | Beanstalkd::validateJobID($jobId); 460 | 461 | $cmd = Commander::getCommand(Beanstalkd::CMD_TOUCH); 462 | 463 | $result = $this->dispatchCommand($cmd, [$jobId]); 464 | 465 | return $result['status'] === Beanstalkd::STATUS_TOUCHED; 466 | } 467 | 468 | /** 469 | * The "watch" command adds the named tube to the watch list for the current 470 | * connection. A reserve command will take a job from any of the tubes in the 471 | * watch list. For each new connection, the watch list initially consists of one 472 | * tube, named "default". 473 | * 474 | * @throws Exceptions\CommandException 475 | * @throws Exceptions\ResponseException 476 | */ 477 | public 478 | function watchTube(string $tubeName): int 479 | { 480 | Beanstalkd::validateTubeName($tubeName); 481 | 482 | $cmd = Commander::getCommand(Beanstalkd::CMD_WATCH); 483 | 484 | $result = $this->dispatchCommand($cmd, [$tubeName]); 485 | 486 | return (int)$result['headers'][0]; 487 | } 488 | 489 | /** 490 | * Removes the named tube from the watch list for the current connection. 491 | * 492 | * False returned in case of attempt to ignore last tube watched 493 | * (`NOT_IGNORED` returned from server). 494 | * 495 | * @throws Exceptions\CommandException 496 | * @throws Exceptions\ResponseException 497 | */ 498 | public 499 | function ignore(string $tubeName): bool 500 | { 501 | Beanstalkd::validateTubeName($tubeName); 502 | 503 | $cmd = Commander::getCommand(Beanstalkd::CMD_IGNORE); 504 | 505 | $result = $this->dispatchCommand($cmd, [$tubeName]); 506 | 507 | if ($result['status'] === Beanstalkd::STATUS_WATCHING) { 508 | return true; 509 | } 510 | 511 | return false; 512 | } 513 | 514 | /** 515 | * Inspect a job with given ID without reserving it. 516 | * 517 | * @throws Exceptions\CommandException 518 | * @throws Exceptions\ResponseException 519 | */ 520 | #[ArrayShape(["id" => "int", "payload" => "mixed"])] 521 | public function peek(int $jobId): ?array 522 | { 523 | Beanstalkd::validateJobID($jobId); 524 | 525 | $cmd = Commander::getCommand(Beanstalkd::CMD_PEEK); 526 | 527 | $result = $this->dispatchCommand($cmd, [$jobId]); 528 | 529 | if ($result['status'] === Beanstalkd::STATUS_NOT_FOUND) { 530 | return null; 531 | } 532 | 533 | return [ 534 | 'id' => (int)$result['headers'][0], 535 | 'payload' => $result['data'], 536 | ]; 537 | } 538 | 539 | /** 540 | * Inspect the next ready job. Operates only on the currently used tube. 541 | * 542 | * @throws Exceptions\CommandException 543 | * @throws Exceptions\ResponseException 544 | */ 545 | #[ArrayShape(["id" => "int", "payload" => "mixed"])] 546 | public function peekReady(): ?array 547 | { 548 | $cmd = Commander::getCommand(Beanstalkd::CMD_PEEK_READY); 549 | 550 | $result = $this->dispatchCommand($cmd); 551 | 552 | if ($result['status'] === Beanstalkd::STATUS_NOT_FOUND) { 553 | return null; 554 | } 555 | 556 | return [ 557 | 'id' => (int)$result['headers'][0], 558 | 'payload' => $result['data'], 559 | ]; 560 | } 561 | 562 | /** 563 | * Inspect the next delayed job. Operates only on the currently used tube. 564 | * 565 | * @throws Exceptions\CommandException 566 | * @throws Exceptions\ResponseException 567 | */ 568 | #[ArrayShape(["id" => "int", "payload" => "mixed"])] 569 | public function peekDelayed(): ?array 570 | { 571 | $cmd = Commander::getCommand(Beanstalkd::CMD_PEEK_DELAYED); 572 | 573 | $result = $this->dispatchCommand($cmd); 574 | 575 | if ($result['status'] === Beanstalkd::STATUS_NOT_FOUND) { 576 | return null; 577 | } 578 | 579 | return [ 580 | 'id' => (int)$result['headers'][0], 581 | 'payload' => $result['data'], 582 | ]; 583 | } 584 | 585 | /** 586 | * Inspect the next buried job. Operates only on the currently used tube. 587 | * 588 | * @throws Exceptions\CommandException 589 | * @throws Exceptions\ResponseException 590 | */ 591 | #[ArrayShape(["id" => "int", "payload" => "mixed"])] 592 | public function peekBuried(): ?array 593 | { 594 | $cmd = Commander::getCommand(Beanstalkd::CMD_PEEK_BURIED); 595 | 596 | $result = $this->dispatchCommand($cmd); 597 | 598 | if ($result['status'] === Beanstalkd::STATUS_NOT_FOUND) { 599 | return null; 600 | } 601 | 602 | return [ 603 | 'id' => (int)$result['headers'][0], 604 | 'payload' => $result['data'], 605 | ]; 606 | } 607 | 608 | /** 609 | * The kick command applies only to the currently used tube. It moves jobs into 610 | * the ready queue. If there are any buried jobs, it will only kick buried jobs. 611 | * Otherwise it will kick delayed jobs. 612 | * 613 | * @param $bound - integer upper bound on the number of jobs to kick. The server 614 | * will kick no more than jobs. 615 | * 616 | * @throws Exceptions\CommandException 617 | * @throws Exceptions\ResponseException 618 | */ 619 | public function kick(int $bound): int 620 | { 621 | $cmd = Commander::getCommand(Beanstalkd::CMD_KICK); 622 | 623 | $result = $this->dispatchCommand($cmd, [$bound]); 624 | 625 | return (int)$result['headers'][0]; 626 | } 627 | 628 | /** 629 | * The kick-job command is a variant of kick that operates with a single job 630 | * identified by its job id. If the given job id exists and is in a buried or 631 | * delayed state, it will be moved to the ready queue of the the same tube where it 632 | * currently belongs. 633 | * 634 | * @throws Exceptions\CommandException 635 | * @throws Exceptions\ResponseException 636 | */ 637 | public function kickJob(int $jobId): bool 638 | { 639 | Beanstalkd::validateJobID($jobId); 640 | 641 | $cmd = Commander::getCommand(Beanstalkd::CMD_KICK_JOB); 642 | 643 | $result = $this->dispatchCommand($cmd, [$jobId]); 644 | 645 | return $result['status'] === Beanstalkd::STATUS_KICKED; 646 | } 647 | 648 | /** 649 | * The stats command gives statistical information about the system as a whole. 650 | * 651 | * @throws Exceptions\CommandException 652 | * @throws Exceptions\ResponseException 653 | */ 654 | public function stats(): array 655 | { 656 | $cmd = Commander::getCommand(Beanstalkd::CMD_STATS); 657 | 658 | $result = $this->dispatchCommand($cmd); 659 | 660 | return Beanstalkd::processStats($result['data']); 661 | } 662 | 663 | /** 664 | * The stats-tube command gives statistical information about the specified tube 665 | * if it exists. 666 | * 667 | * @throws Exceptions\CommandException 668 | * @throws Exceptions\ResponseException 669 | */ 670 | public function statsTube(string $tubeName): ?array 671 | { 672 | Beanstalkd::validateTubeName($tubeName); 673 | 674 | $cmd = Commander::getCommand(Beanstalkd::CMD_STATS_TUBE); 675 | 676 | $result = $this->dispatchCommand($cmd, [$tubeName]); 677 | 678 | if ($result['status'] === Beanstalkd::STATUS_NOT_FOUND) { 679 | return null; 680 | } 681 | 682 | return Beanstalkd::processTubeStats($result['data']); 683 | } 684 | 685 | /** 686 | * The stats-job command gives statistical information about the specified job if 687 | * it exists. 688 | * 689 | * @throws Exceptions\CommandException 690 | * @throws Exceptions\ResponseException 691 | */ 692 | public function statsJob(int $jobId): ?array 693 | { 694 | Beanstalkd::validateJobID($jobId); 695 | 696 | $cmd = Commander::getCommand(Beanstalkd::CMD_STATS_JOB); 697 | 698 | $result = $this->dispatchCommand($cmd, [$jobId]); 699 | 700 | if ($result['status'] === Beanstalkd::STATUS_NOT_FOUND) { 701 | return null; 702 | } 703 | 704 | return Beanstalkd::processJobStats($result['data']); 705 | } 706 | 707 | /** 708 | * The list-tubes command returns a list of all existing tubes. 709 | * 710 | * @throws Exceptions\CommandException 711 | * @throws Exceptions\ResponseException 712 | */ 713 | public function listTubes(): array 714 | { 715 | $cmd = Commander::getCommand(Beanstalkd::CMD_LIST_TUBES); 716 | 717 | $result = $this->dispatchCommand($cmd); 718 | 719 | return $result['data']; 720 | } 721 | 722 | /** 723 | * The list-tube-used command returns the tube currently being used by the 724 | * client. 725 | * 726 | * @throws Exceptions\CommandException 727 | * @throws Exceptions\ResponseException 728 | */ 729 | public function listTubeUsed(): string 730 | { 731 | $cmd = Commander::getCommand(Beanstalkd::CMD_LIST_TUBE_USED); 732 | 733 | $result = $this->dispatchCommand($cmd); 734 | 735 | return $result['headers'][0]; 736 | } 737 | 738 | /** 739 | * The list-tubes-watched command returns a list tubes currently being watched by 740 | * the client. 741 | * 742 | * @throws Exceptions\CommandException 743 | * @throws Exceptions\ResponseException 744 | */ 745 | public function listTubesWatched(): array 746 | { 747 | $cmd = Commander::getCommand(Beanstalkd::CMD_LIST_TUBES_WATCHED); 748 | 749 | $result = $this->dispatchCommand($cmd); 750 | 751 | return $result['data']; 752 | } 753 | 754 | /** 755 | * The pause-tube command can delay any new job being reserved for a given time. 756 | * 757 | * @param $tubeName - tube to pause 758 | * @param $delay - integer number of seconds < 2**32 to wait before reserving any more 759 | * jobs from the queue 760 | * 761 | * @throws Exceptions\CommandException 762 | * @throws Exceptions\ResponseException 763 | */ 764 | public function pauseTube(string $tubeName, int $delay): bool 765 | { 766 | Beanstalkd::validateTubeName($tubeName); 767 | Beanstalkd::validateDelay($delay); 768 | 769 | $cmd = Commander::getCommand(Beanstalkd::CMD_PAUSE_TUBE); 770 | 771 | $result = $this->dispatchCommand($cmd, [$tubeName, $delay]); 772 | 773 | return $result['status'] === Beanstalkd::STATUS_PAUSED; 774 | } 775 | } 776 | -------------------------------------------------------------------------------- /src/Command.php: -------------------------------------------------------------------------------- 1 | command)) { 25 | throw new CommandException(sprintf('Unknown beanstalkd command `%s`', $this->command)); 26 | } 27 | 28 | $expected = []; 29 | 30 | foreach ($this->expectedStatuses as $status) { 31 | if (!Beanstalkd::supportsResponseStatus($status)) { 32 | throw new CommandException(sprintf('Unknown beanstalkd response status `%s`', $status)); 33 | } 34 | 35 | $expected[$status] = true; 36 | } 37 | 38 | $this->expectedStatuses = $expected; 39 | } 40 | 41 | /** 42 | * Build command string 43 | * 44 | * @param array $args 45 | * @param mixed|null $payload 46 | * @return string 47 | */ 48 | public function buildCommand(array $args = [], mixed $payload = null): string 49 | { 50 | $chunks = [$this->command, ...$args]; 51 | 52 | if ($payload === null) { 53 | return join(" ", $chunks) . Beanstalkd::CRLF; 54 | } 55 | 56 | $chunks[] = mb_strlen($payload, "8BIT"); 57 | 58 | return join(" ", $chunks) . Beanstalkd::CRLF . $payload . Beanstalkd::CRLF; 59 | } 60 | 61 | /** 62 | * @throws CommandException|ResponseException 63 | */ 64 | #[ArrayShape(["status" => "string", "headers" => "string[]", 'data' => "null|string",])] 65 | public function handleResponse(array $response, ?SerializerInterface $serializer): array 66 | { 67 | if (Beanstalkd::isErrorStatus($response['status'])) { 68 | throw new CommandException( 69 | sprintf('Error status `%s` received in response to `%s` command', $response['status'], $this->command) 70 | ); 71 | } 72 | 73 | if (empty($this->expectedStatuses[$response['status']])) { 74 | throw new CommandException( 75 | sprintf('Unexpected status `%s` received in response to `%s` command', $response['status'], $this->command) 76 | ); 77 | } 78 | 79 | $result = [ 80 | "status" => $response['status'], 81 | "headers" => $response['headers'], 82 | "data" => null, 83 | ]; 84 | 85 | if (!empty($response['data']) && ($this->payloadBody || $this->yamlBody)) { 86 | $result['data'] = mb_substr($response['data'], 0, -Beanstalkd::CRLF_LEN, '8BIT'); 87 | 88 | if ($this->payloadBody) { 89 | if ($serializer) { 90 | $result['data'] = $serializer->deserialize($result['data']); 91 | } 92 | } else if ($this->yamlBody) { 93 | $result['data'] = Beanstalkd::simpleYamlParse($result['data']); 94 | } 95 | } 96 | 97 | return $result; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Commander.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'expectedStatuses' => [ 14 | Beanstalkd::STATUS_INSERTED, 15 | Beanstalkd::STATUS_BURIED, 16 | Beanstalkd::STATUS_EXPECTED_CRLF, 17 | Beanstalkd::STATUS_JOB_TOO_BIG, 18 | Beanstalkd::STATUS_DRAINING, 19 | ], 20 | ], 21 | 22 | Beanstalkd::CMD_USE => [ 23 | 'expectedStatuses' => [Beanstalkd::STATUS_USING], 24 | ], 25 | 26 | Beanstalkd::CMD_RESERVE => [ 27 | 'expectedStatuses' => [ 28 | Beanstalkd::STATUS_TIMED_OUT, 29 | Beanstalkd::STATUS_DEADLINE_SOON, 30 | Beanstalkd::STATUS_RESERVED, 31 | ], 32 | 'payloadBody' => true, 33 | ], 34 | Beanstalkd::CMD_RESERVE_WITH_TIMEOUT => [ 35 | 'expectedStatuses' => [ 36 | Beanstalkd::STATUS_TIMED_OUT, 37 | Beanstalkd::STATUS_DEADLINE_SOON, 38 | Beanstalkd::STATUS_RESERVED, 39 | ], 40 | 'payloadBody' => true, 41 | ], 42 | Beanstalkd::CMD_RESERVE_JOB => [ 43 | 'expectedStatuses' => [Beanstalkd::STATUS_NOT_FOUND, Beanstalkd::STATUS_RESERVED], 44 | 'payloadBody' => true, 45 | ], 46 | Beanstalkd::CMD_DELETE => [ 47 | 'expectedStatuses' => [Beanstalkd::STATUS_NOT_FOUND, Beanstalkd::STATUS_DELETED], 48 | ], 49 | Beanstalkd::CMD_RELEASE => [ 50 | 'expectedStatuses' => [ 51 | Beanstalkd::STATUS_RELEASED, 52 | Beanstalkd::STATUS_BURIED, 53 | Beanstalkd::STATUS_NOT_FOUND, 54 | ], 55 | ], 56 | Beanstalkd::CMD_BURY => [ 57 | 'expectedStatuses' => [Beanstalkd::STATUS_BURIED, Beanstalkd::STATUS_NOT_FOUND], 58 | ], 59 | Beanstalkd::CMD_TOUCH => [ 60 | 'expectedStatuses' => [Beanstalkd::STATUS_TOUCHED, Beanstalkd::STATUS_NOT_FOUND], 61 | ], 62 | 63 | Beanstalkd::CMD_WATCH => [ 64 | 'expectedStatuses' => [Beanstalkd::STATUS_WATCHING], 65 | ], 66 | Beanstalkd::CMD_IGNORE => [ 67 | 'expectedStatuses' => [Beanstalkd::STATUS_WATCHING, Beanstalkd::STATUS_NOT_IGNORED], 68 | ], 69 | 70 | Beanstalkd::CMD_PEEK => [ 71 | 'expectedStatuses' => [Beanstalkd::STATUS_FOUND, Beanstalkd::STATUS_NOT_FOUND], 72 | 'payloadBody' => true, 73 | ], 74 | Beanstalkd::CMD_PEEK_READY => [ 75 | 'expectedStatuses' => [Beanstalkd::STATUS_FOUND, Beanstalkd::STATUS_NOT_FOUND], 76 | 'payloadBody' => true, 77 | ], 78 | Beanstalkd::CMD_PEEK_BURIED => [ 79 | 'expectedStatuses' => [Beanstalkd::STATUS_FOUND, Beanstalkd::STATUS_NOT_FOUND], 80 | 'payloadBody' => true, 81 | ], 82 | Beanstalkd::CMD_PEEK_DELAYED => [ 83 | 'expectedStatuses' => [Beanstalkd::STATUS_FOUND, Beanstalkd::STATUS_NOT_FOUND], 84 | 'payloadBody' => true, 85 | ], 86 | 87 | Beanstalkd::CMD_KICK => [ 88 | 'expectedStatuses' => [Beanstalkd::STATUS_KICKED], 89 | ], 90 | Beanstalkd::CMD_KICK_JOB => [ 91 | 'expectedStatuses' => [Beanstalkd::STATUS_KICKED, Beanstalkd::STATUS_NOT_FOUND], 92 | ], 93 | 94 | Beanstalkd::CMD_STATS => [ 95 | 'expectedStatuses' => [Beanstalkd::STATUS_OK], 96 | 'yamlBody' => true, 97 | ], 98 | Beanstalkd::CMD_STATS_JOB => [ 99 | 'expectedStatuses' => [Beanstalkd::STATUS_OK, Beanstalkd::STATUS_NOT_FOUND], 100 | 'yamlBody' => true, 101 | ], 102 | Beanstalkd::CMD_STATS_TUBE => [ 103 | 'expectedStatuses' => [Beanstalkd::STATUS_OK, Beanstalkd::STATUS_NOT_FOUND], 104 | 'yamlBody' => true, 105 | ], 106 | 107 | Beanstalkd::CMD_LIST_TUBES => [ 108 | 'expectedStatuses' => [Beanstalkd::STATUS_OK], 109 | 'yamlBody' => true, 110 | ], 111 | Beanstalkd::CMD_LIST_TUBE_USED => [ 112 | 'expectedStatuses' => [Beanstalkd::STATUS_USING], 113 | ], 114 | Beanstalkd::CMD_LIST_TUBES_WATCHED => [ 115 | 'yamlBody' => true, 116 | 'expectedStatuses' => [Beanstalkd::STATUS_OK], 117 | ], 118 | 119 | Beanstalkd::CMD_PAUSE_TUBE => [ 120 | 'expectedStatuses' => [Beanstalkd::STATUS_PAUSED, Beanstalkd::STATUS_NOT_FOUND], 121 | ], 122 | 123 | Beanstalkd::CMD_QUIT => [ 124 | 'expectedStatuses' => [], 125 | ], 126 | ]; 127 | 128 | /** 129 | * @var Command[] 130 | */ 131 | private static array $commands = []; 132 | 133 | /** 134 | * @throws CommandException 135 | */ 136 | public static function getCommand(string $cmd): Command 137 | { 138 | $command = self::$commands[$cmd] ?? null; 139 | 140 | if ($command !== null) { 141 | return $command; 142 | } 143 | 144 | $cfg = self::COMMAND_CONFIG[$cmd] ?? null; 145 | 146 | if (empty($cfg)) { 147 | throw new CommandException(sprintf('Unknown beanstalkd command `%s`', $cmd)); 148 | } 149 | 150 | $command = new Command( 151 | command: $cmd, 152 | expectedStatuses: $cfg['expectedStatuses'] ?? [], 153 | payloadBody: $cfg['payloadBody'] ?? false, 154 | yamlBody: $cfg['yamlBody'] ?? false, 155 | ); 156 | 157 | self::$commands[$cmd] = $command; 158 | 159 | return $command; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/Exceptions/ClientException.php: -------------------------------------------------------------------------------- 1 | host; 19 | } 20 | 21 | public function port(): int 22 | { 23 | return $this->port; 24 | } 25 | 26 | public function connectionTimeout(): int 27 | { 28 | return $this->connectionTimeout; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Socket/SocketsSocket.php: -------------------------------------------------------------------------------- 1 | connect(); 22 | } 23 | 24 | /** 25 | * @throws SocketException 26 | */ 27 | public function __destruct() 28 | { 29 | $this->disconnect(); 30 | } 31 | 32 | /** 33 | * Throws last error occurred on socket if there is any. 34 | * 35 | * @throws SocketException 36 | */ 37 | public function throwLastError() 38 | { 39 | if (!$this->socket) { 40 | return; 41 | } 42 | 43 | $errno = socket_last_error($this->socket); 44 | socket_clear_error(); 45 | 46 | if (!$errno) { 47 | return; 48 | } 49 | 50 | throw new SocketException(sprintf('Socket error: [%s] %s', $errno, socket_strerror($errno))); 51 | } 52 | 53 | /** 54 | * @inheritdoc 55 | */ 56 | public function isConnected(): bool 57 | { 58 | return $this->socket !== null; 59 | } 60 | 61 | /** 62 | * @inheritdoc 63 | * 64 | * @throws SocketException in case of calling on already opened connection. 65 | */ 66 | public function connect(): self 67 | { 68 | if ($this->isConnected()) { 69 | throw new SocketException('Multiple connection attempts, socket connection already established'); 70 | } 71 | 72 | $hostname = $this->host; 73 | $domain = AF_UNIX; 74 | 75 | # port less than 0 means that unix sockets should be used, otherwise - IPv4 76 | if ($this->port >= 0) { 77 | $ips = gethostbynamel($hostname); 78 | if ($ips === false) { 79 | throw new SocketException(sprintf("Could not resolve hostname `%s`", $hostname)); 80 | } 81 | 82 | $hostname = $ips[0]; 83 | $domain = AF_INET; 84 | } 85 | 86 | $this->socket = socket_create($domain, SOCK_STREAM, SOL_TCP); 87 | if ($this->socket === false) { 88 | $this->throwLastError(); 89 | throw new SocketException('Unknown socket error occurred during `connect`'); 90 | } 91 | 92 | $SNDTIMEO = socket_get_option($this->socket, SOL_SOCKET, SO_SNDTIMEO); 93 | $RCVTIMEO = socket_get_option($this->socket, SOL_SOCKET, SO_RCVTIMEO); 94 | 95 | socket_set_option($this->socket, SOL_SOCKET, SO_KEEPALIVE, 1); 96 | # set write timeout 97 | socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => $this->connectionTimeout, 'usec' => 0]); 98 | # set read timeout 99 | socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => $this->connectionTimeout, 'usec' => 0]); 100 | 101 | if (socket_set_block($this->socket) === false) { 102 | $this->throwLastError(); 103 | throw new SocketException('Failed to set blocking mode on a socket'); 104 | } 105 | 106 | if (@socket_connect($this->socket, $hostname, $this->port) === false) { 107 | $this->throwLastError(); 108 | throw new SocketException('Unknown socket error occurred during `socket_connect`'); 109 | } 110 | 111 | # reset write timeout back to default 112 | socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, $SNDTIMEO); 113 | # reset read timeout back to default 114 | socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, $RCVTIMEO); 115 | 116 | return $this; 117 | } 118 | 119 | /** 120 | * @inheritdoc 121 | */ 122 | public function disconnect(): bool 123 | { 124 | if (!$this->isConnected()) { 125 | return false; 126 | } 127 | 128 | socket_close($this->socket); 129 | 130 | $this->socket = null; 131 | 132 | return true; 133 | } 134 | 135 | /** 136 | * @inheritdoc 137 | * 138 | * @throws SocketException 139 | */ 140 | public function read(int $bytes): string 141 | { 142 | if (!$this->isConnected()) { 143 | throw new SocketException('Unable to `read` on closed socket connection, perform `connect` first.'); 144 | } 145 | 146 | $result = ""; 147 | $bytesRead = 0; 148 | 149 | while ($bytesRead < $bytes) { 150 | $chunk = socket_read($this->socket, $bytes - $bytesRead); 151 | if ($chunk === false) { 152 | $this->throwLastError(); 153 | throw new SocketException('Unknown socket error occurred during `read`'); 154 | } 155 | 156 | $result .= $chunk; 157 | $bytesRead = mb_strlen($result, '8BIT'); 158 | } 159 | 160 | return $result; 161 | } 162 | 163 | /** 164 | * @inheritdoc 165 | * 166 | * @throws SocketException 167 | */ 168 | public function readline(): string 169 | { 170 | if (!$this->isConnected()) { 171 | throw new SocketException('Unable to `readline` on closed socket connection, perform `connect` first.'); 172 | } 173 | 174 | $result = ""; 175 | 176 | while (true) { 177 | $chunk = socket_read($this->socket, 1); 178 | if ($chunk === false) { 179 | $this->throwLastError(); 180 | throw new SocketException('Unknown socket error occurred during `readline`'); 181 | } 182 | 183 | $result .= $chunk; 184 | if ($chunk === "\n") { 185 | break; 186 | } 187 | } 188 | 189 | return rtrim($result); 190 | } 191 | 192 | /** 193 | * @inheritdoc 194 | * 195 | * @throws SocketException 196 | */ 197 | public function write(string $data): int 198 | { 199 | if (!$this->isConnected()) { 200 | throw new SocketException('Unable to `write` on closed socket connection, perform `connect` first.'); 201 | } 202 | 203 | $bytesToWrite = mb_strlen($data, '8BIT'); 204 | $bytesWritten = 0; 205 | 206 | $attempts = 0; 207 | 208 | # write until we write all data or reach attempts limit 209 | while ($bytesToWrite !== $bytesWritten && $attempts < 10) { 210 | $bytes = socket_write($this->socket, $data); 211 | 212 | if ($bytes === false) { 213 | $this->throwLastError(); 214 | throw new SocketException('Unknown socket error occurred during `write`'); 215 | } else if ($bytes === 0) { 216 | $attempts++; 217 | continue; 218 | } 219 | 220 | $attempts = 0; 221 | $bytesWritten += $bytes; 222 | $data = mb_substr($data, $bytes, encoding: '8BIT'); 223 | } 224 | 225 | if ($bytesToWrite !== $bytesWritten) { 226 | throw new SocketException(sprintf('Failed to write data to a socket after %d attempts', $attempts)); 227 | } 228 | 229 | return $bytesWritten; 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /tests/BeanstalkdTest.php: -------------------------------------------------------------------------------- 1 | addToAssertionCount(1); 14 | 15 | Beanstalkd::validateDelay(Beanstalkd::DELAY_MIN + 1); 16 | $this->addToAssertionCount(1); 17 | 18 | Beanstalkd::validateDelay(Beanstalkd::DELAY_MAX); 19 | $this->addToAssertionCount(1); 20 | 21 | Beanstalkd::validateDelay(Beanstalkd::DELAY_MAX - 1); 22 | $this->addToAssertionCount(1); 23 | 24 | Beanstalkd::validateDelay(100500); 25 | $this->addToAssertionCount(1); 26 | } 27 | 28 | public function testValidateDelayException1() 29 | { 30 | $this->expectException(InvalidArgumentException::class); 31 | $this->expectExceptionMessage("delay should be >= " . Beanstalkd::DELAY_MIN); 32 | Beanstalkd::validateDelay(Beanstalkd::DELAY_MIN - 1); 33 | } 34 | 35 | public function testValidateDelayException2() 36 | { 37 | $this->expectException(InvalidArgumentException::class); 38 | $this->expectExceptionMessage("delay should be <= " . Beanstalkd::DELAY_MAX); 39 | Beanstalkd::validateDelay(Beanstalkd::DELAY_MAX + 1); 40 | } 41 | 42 | public function testValidateTTR() 43 | { 44 | Beanstalkd::validateTTR(Beanstalkd::TTR_MIN); 45 | $this->addToAssertionCount(1); 46 | 47 | Beanstalkd::validateTTR(Beanstalkd::TTR_MIN + 1); 48 | $this->addToAssertionCount(1); 49 | 50 | Beanstalkd::validateTTR(Beanstalkd::TTR_MAX); 51 | $this->addToAssertionCount(1); 52 | 53 | Beanstalkd::validateTTR(Beanstalkd::TTR_MAX - 1); 54 | $this->addToAssertionCount(1); 55 | 56 | Beanstalkd::validateTTR(100500); 57 | $this->addToAssertionCount(1); 58 | } 59 | 60 | public function testValidateTTRException1() 61 | { 62 | $this->expectException(InvalidArgumentException::class); 63 | $this->expectExceptionMessage("ttr should be >= " . Beanstalkd::TTR_MIN); 64 | Beanstalkd::validateTTR(Beanstalkd::TTR_MIN - 1); 65 | } 66 | 67 | public function testValidateTTRException2() 68 | { 69 | $this->expectException(InvalidArgumentException::class); 70 | $this->expectExceptionMessage("ttr should be <= " . Beanstalkd::TTR_MAX); 71 | Beanstalkd::validateTTR(Beanstalkd::TTR_MAX + 1); 72 | } 73 | 74 | public function testValidatePriority() 75 | { 76 | Beanstalkd::validatePriority(Beanstalkd::PRIORITY_MIN); 77 | $this->addToAssertionCount(1); 78 | 79 | Beanstalkd::validatePriority(Beanstalkd::PRIORITY_MIN + 1); 80 | $this->addToAssertionCount(1); 81 | 82 | Beanstalkd::validatePriority(Beanstalkd::PRIORITY_MAX); 83 | $this->addToAssertionCount(1); 84 | 85 | Beanstalkd::validatePriority(Beanstalkd::PRIORITY_MAX - 1); 86 | $this->addToAssertionCount(1); 87 | 88 | Beanstalkd::validatePriority(100500); 89 | $this->addToAssertionCount(1); 90 | } 91 | 92 | public function testValidatePriorityException1() 93 | { 94 | $this->expectException(InvalidArgumentException::class); 95 | $this->expectExceptionMessage("priority should be >= " . Beanstalkd::PRIORITY_MIN); 96 | Beanstalkd::validatePriority(Beanstalkd::PRIORITY_MIN - 1); 97 | } 98 | 99 | public function testValidatePriorityException2() 100 | { 101 | $this->expectException(InvalidArgumentException::class); 102 | $this->expectExceptionMessage("priority should be <= " . Beanstalkd::PRIORITY_MAX); 103 | Beanstalkd::validatePriority(Beanstalkd::PRIORITY_MAX + 1); 104 | } 105 | 106 | public function testValidateTimeout() 107 | { 108 | Beanstalkd::validateTimeout(Beanstalkd::TIMEOUT_MIN); 109 | $this->addToAssertionCount(1); 110 | 111 | Beanstalkd::validateTimeout(Beanstalkd::TIMEOUT_MIN + 1); 112 | $this->addToAssertionCount(1); 113 | 114 | Beanstalkd::validateTimeout(100500); 115 | $this->addToAssertionCount(1); 116 | } 117 | 118 | public function testValidateTimeoutException1() 119 | { 120 | $this->expectException(InvalidArgumentException::class); 121 | $this->expectExceptionMessage("timeout should be >= " . Beanstalkd::TIMEOUT_MIN); 122 | Beanstalkd::validateTimeout(Beanstalkd::TIMEOUT_MIN - 1); 123 | } 124 | 125 | public function testValidateJobID() 126 | { 127 | Beanstalkd::validateJobID(Beanstalkd::JOB_ID_MIN); 128 | $this->addToAssertionCount(1); 129 | 130 | Beanstalkd::validateJobID(Beanstalkd::JOB_ID_MIN + 1); 131 | $this->addToAssertionCount(1); 132 | 133 | Beanstalkd::validateJobID(100500); 134 | $this->addToAssertionCount(1); 135 | } 136 | 137 | public function testValidateJobIDException1() 138 | { 139 | $this->expectException(InvalidArgumentException::class); 140 | $this->expectExceptionMessage("job id should be >= " . Beanstalkd::JOB_ID_MIN); 141 | Beanstalkd::validateJobID(Beanstalkd::JOB_ID_MIN - 1); 142 | } 143 | 144 | public function testValidateTubeName() 145 | { 146 | Beanstalkd::validateTubeName('some-tune-name'); 147 | $this->addToAssertionCount(1); 148 | 149 | Beanstalkd::validateTubeName('A-Za-z0-9-+/;.$_()'); 150 | $this->addToAssertionCount(1); 151 | } 152 | 153 | public function testValidateTubeNameException1() 154 | { 155 | $this->expectException(InvalidArgumentException::class); 156 | $this->expectExceptionMessage('tube name should satisfy regexp: /^[A-Za-z0-9-+/;.$_()]{1,200}$/'); 157 | Beanstalkd::validateTubeName(''); 158 | } 159 | 160 | public function testValidateTubeNameException2() 161 | { 162 | $this->expectException(InvalidArgumentException::class); 163 | $this->expectExceptionMessage('tube name should satisfy regexp: /^[A-Za-z0-9-+/;.$_()]{1,200}$/'); 164 | Beanstalkd::validateTubeName(bin2hex(random_bytes(201))); 165 | } 166 | 167 | public function testSupportsCommand() 168 | { 169 | foreach (Beanstalkd::CMDS_LIST as $cmd => $_) { 170 | $this->assertTrue(Beanstalkd::supportsCommand($cmd)); 171 | $this->addToAssertionCount(1); 172 | } 173 | 174 | $this->assertFalse(Beanstalkd::supportsCommand('random stuff')); 175 | } 176 | 177 | public function testSupportsResponseStatus() 178 | { 179 | foreach (Beanstalkd::STATUSES_LIST as $status => $_) { 180 | $this->assertTrue(Beanstalkd::supportsResponseStatus($status)); 181 | $this->addToAssertionCount(1); 182 | } 183 | 184 | $this->assertFalse(Beanstalkd::supportsResponseStatus('random stuff')); 185 | } 186 | 187 | public function testIsErrorStatus() 188 | { 189 | foreach (Beanstalkd::ERROR_STATUSES_LIST as $status => $_) { 190 | $this->assertTrue(Beanstalkd::isErrorStatus($status)); 191 | $this->addToAssertionCount(1); 192 | } 193 | 194 | $this->assertFalse(Beanstalkd::isErrorStatus(Beanstalkd::STATUS_OK)); 195 | } 196 | 197 | public function testIsDataResponseStatus() 198 | { 199 | foreach (Beanstalkd::DATA_STATUSES_LIST as $status => $_) { 200 | $this->assertTrue(Beanstalkd::isDataResponseStatus($status)); 201 | $this->addToAssertionCount(1); 202 | } 203 | 204 | $this->assertFalse(Beanstalkd::isDataResponseStatus(Beanstalkd::STATUS_BAD_FORMAT)); 205 | } 206 | 207 | public function testParseResponseHeaders() 208 | { 209 | $this->assertEquals( 210 | [ 211 | "status" => "OK", 212 | "headers" => ["arg1", "arg2"], 213 | "hasData" => true, 214 | "dataLength" => 123, 215 | ], 216 | Beanstalkd::parseResponseHeaders("OK arg1 arg2 123") 217 | ); 218 | $this->assertEquals( 219 | [ 220 | "status" => "KICKED", 221 | "headers" => ["arg1", "arg2", "123"], 222 | "hasData" => false, 223 | "dataLength" => 0, 224 | ], 225 | Beanstalkd::parseResponseHeaders("KICKED arg1 arg2 123") 226 | ); 227 | } 228 | 229 | public function testParseResponseHeadersException1() 230 | { 231 | $this->expectException(ResponseException::class); 232 | $this->expectExceptionMessage( 233 | sprintf('Received invalid data length for `%s` response, expected number, got `%s`', 'OK', 'arg2') 234 | ); 235 | Beanstalkd::parseResponseHeaders("OK arg1 arg2"); 236 | } 237 | 238 | public function testSimpleYamlParse() 239 | { 240 | $this->assertNull(Beanstalkd::simpleYamlParse(" ")); 241 | $this->assertEquals( 242 | ["default", "myAwesomeTube", "myAwesomeTube2"], 243 | Beanstalkd::simpleYamlParse("---\n- default\n- myAwesomeTube\n- myAwesomeTube2\n") 244 | ); 245 | $this->assertEquals( 246 | [ 247 | "id" => "5", 248 | "tube" => "myAwesomeTube", 249 | "state" => "delayed", 250 | "pri" => "1024", 251 | "age" => "224", 252 | "delay" => "5", 253 | "ttr" => "30", 254 | "time-left" => "4", 255 | "file" => "0", 256 | "reserves" => "1", 257 | "timeouts" => "0", 258 | "releases" => "1", 259 | "buries" => "0", 260 | "kicks" => "0", 261 | ], 262 | Beanstalkd::simpleYamlParse( 263 | "--- 264 | id: 5 265 | tube: myAwesomeTube 266 | state: delayed 267 | pri: 1024 268 | age: 224 269 | delay: 5 270 | ttr: 30 271 | time-left: 4 272 | file: 0 273 | reserves: 1 274 | timeouts: 0 275 | releases: 1 276 | buries: 0 277 | kicks: 0 278 | " 279 | ) 280 | ); 281 | } 282 | 283 | public function testSimpleYamlParseException1() 284 | { 285 | $this->expectException(ResponseException::class); 286 | $this->expectExceptionMessage('Failed to parse YAML string [id:]'); 287 | Beanstalkd::simpleYamlParse("---\nid: \n"); 288 | } 289 | 290 | public function testProcessStats() 291 | { 292 | $this->assertEquals( 293 | [ 294 | 'current-jobs-urgent' => 0, 295 | 'current-jobs-ready' => 5, 296 | 'current-jobs-reserved' => 0, 297 | 'current-jobs-delayed' => 1, 298 | 'current-jobs-buried' => 0, 299 | 'cmd-put' => 7, 300 | 'cmd-peek' => 0, 301 | 'cmd-peek-ready' => 0, 302 | 'cmd-peek-delayed' => 0, 303 | 'cmd-peek-buried' => 0, 304 | 'cmd-reserve' => 3, 305 | 'cmd-reserve-with-timeout' => 1, 306 | 'cmd-delete' => 1, 307 | 'cmd-release' => 2, 308 | 'cmd-use' => 7, 309 | 'cmd-watch' => 8, 310 | 'cmd-ignore' => 0, 311 | 'cmd-bury' => 0, 312 | 'cmd-kick' => 0, 313 | 'cmd-touch' => 0, 314 | 'cmd-stats' => 1, 315 | 'cmd-stats-job' => 7, 316 | 'cmd-stats-tube' => 0, 317 | 'cmd-list-tubes' => 0, 318 | 'cmd-list-tube-used' => 0, 319 | 'cmd-list-tubes-watched' => 3, 320 | 'cmd-pause-tube' => 0, 321 | 'job-timeouts' => 0, 322 | 'total-jobs' => 7, 323 | 'max-job-size' => 65535, 324 | 'current-tubes' => 4, 325 | 'current-connections' => 1, 326 | 'current-producers' => 1, 327 | 'current-workers' => 0, 328 | 'current-waiting' => 0, 329 | 'total-connections' => 7, 330 | 'pid' => 1, 331 | 'version' => "1.12", 332 | 'rusage-utime' => 0.012908, 333 | 'rusage-stime' => 0.013901, 334 | 'uptime' => 192868, 335 | 'binlog-oldest-index' => 0, 336 | 'binlog-current-index' => 0, 337 | 'binlog-records-migrated' => 0, 338 | 'binlog-records-written' => 0, 339 | 'binlog-max-size' => 10485760, 340 | 'draining' => false, 341 | 'id' => "d29afae409ec7a2c", 342 | 'hostname' => "24bb68330c4c", 343 | 'os' => "#1 SMP Tue Mar 23 09:27:39 UTC 2021", 344 | 'platform' => "x86_64", 345 | ], 346 | Beanstalkd::processStats([ 347 | 'current-jobs-urgent' => "0", 348 | 'current-jobs-ready' => "5", 349 | 'current-jobs-reserved' => "0", 350 | 'current-jobs-delayed' => "1", 351 | 'current-jobs-buried' => "0", 352 | 'cmd-put' => "7", 353 | 'cmd-peek' => "0", 354 | 'cmd-peek-ready' => "0", 355 | 'cmd-peek-delayed' => "0", 356 | 'cmd-peek-buried' => "0", 357 | 'cmd-reserve' => "3", 358 | 'cmd-reserve-with-timeout' => "1", 359 | 'cmd-delete' => "1", 360 | 'cmd-release' => "2", 361 | 'cmd-use' => "7", 362 | 'cmd-watch' => "8", 363 | 'cmd-ignore' => "0", 364 | 'cmd-bury' => "0", 365 | 'cmd-kick' => "0", 366 | 'cmd-touch' => "0", 367 | 'cmd-stats' => "1", 368 | 'cmd-stats-job' => "7", 369 | 'cmd-stats-tube' => "0", 370 | 'cmd-list-tubes' => "0", 371 | 'cmd-list-tube-used' => "0", 372 | 'cmd-list-tubes-watched' => "3", 373 | 'cmd-pause-tube' => "0", 374 | 'job-timeouts' => "0", 375 | 'total-jobs' => "7", 376 | 'max-job-size' => "65535", 377 | 'current-tubes' => "4", 378 | 'current-connections' => "1", 379 | 'current-producers' => "1", 380 | 'current-workers' => "0", 381 | 'current-waiting' => "0", 382 | 'total-connections' => "7", 383 | 'pid' => "1", 384 | 'version' => "1.12", 385 | 'rusage-utime' => "0.012908", 386 | 'rusage-stime' => "0.013901", 387 | 'uptime' => "192868", 388 | 'binlog-oldest-index' => "0", 389 | 'binlog-current-index' => "0", 390 | 'binlog-records-migrated' => "0", 391 | 'binlog-records-written' => "0", 392 | 'binlog-max-size' => "10485760", 393 | 'draining' => "false", 394 | 'id' => "d29afae409ec7a2c", 395 | 'hostname' => "24bb68330c4c", 396 | 'os' => "#1 SMP Tue Mar 23 09:27:39 UTC 2021", 397 | 'platform' => "x86_64", 398 | ]) 399 | ); 400 | } 401 | 402 | public function testProcessTubeStats() 403 | { 404 | $this->assertEquals( 405 | [ 406 | 'name' => "default", 407 | 'current-jobs-urgent' => 0, 408 | 'current-jobs-ready' => 0, 409 | 'current-jobs-reserved' => 0, 410 | 'current-jobs-delayed' => 0, 411 | 'current-jobs-buried' => 0, 412 | 'total-jobs' => 0, 413 | 'current-using' => 0, 414 | 'current-watching' => 1, 415 | 'current-waiting' => 0, 416 | 'cmd-delete' => 0, 417 | 'cmd-pause-tube' => 0, 418 | 'pause' => 0, 419 | 'pause-time-left' => 0, 420 | ], 421 | Beanstalkd::processTubeStats([ 422 | 'name' => "default", 423 | 'current-jobs-urgent' => "0", 424 | 'current-jobs-ready' => "0", 425 | 'current-jobs-reserved' => "0", 426 | 'current-jobs-delayed' => "0", 427 | 'current-jobs-buried' => "0", 428 | 'total-jobs' => "0", 429 | 'current-using' => "0", 430 | 'current-watching' => "1", 431 | 'current-waiting' => "0", 432 | 'cmd-delete' => "0", 433 | 'cmd-pause-tube' => "0", 434 | 'pause' => "0", 435 | 'pause-time-left' => "0", 436 | ]) 437 | ); 438 | } 439 | 440 | public function testProcessJobStats() 441 | { 442 | $this->assertEquals( 443 | [ 444 | "id" => 5, 445 | "tube" => "myAwesomeTube", 446 | "state" => "delayed", 447 | "pri" => 1024, 448 | "age" => 224, 449 | "delay" => 5, 450 | "ttr" => 30, 451 | "time-left" => 4, 452 | "file" => 0, 453 | "reserves" => 1, 454 | "timeouts" => 0, 455 | "releases" => 1, 456 | "buries" => 0, 457 | "kicks" => 0, 458 | ], 459 | Beanstalkd::processJobStats([ 460 | "id" => "5", 461 | "tube" => "myAwesomeTube", 462 | "state" => "delayed", 463 | "pri" => "1024", 464 | "age" => "224", 465 | "delay" => "5", 466 | "ttr" => "30", 467 | "time-left" => "4", 468 | "file" => "0", 469 | "reserves" => "1", 470 | "timeouts" => "0", 471 | "releases" => "1", 472 | "buries" => "0", 473 | "kicks" => "0", 474 | ]) 475 | ); 476 | } 477 | } 478 | -------------------------------------------------------------------------------- /tests/CommandTest.php: -------------------------------------------------------------------------------- 1 | addToAssertionCount(1); 17 | 18 | new Command(Beanstalkd::CMD_WATCH, [Beanstalkd::STATUS_OK], true, true); 19 | $this->addToAssertionCount(1); 20 | } 21 | 22 | public function test__constructException1() 23 | { 24 | $this->expectException(CommandException::class); 25 | $this->expectExceptionMessage(sprintf('Unknown beanstalkd command `%s`', 'non-existent-command')); 26 | new Command('non-existent-command', [Beanstalkd::STATUS_OK], false, false); 27 | } 28 | 29 | public function test__constructException2() 30 | { 31 | $this->expectException(CommandException::class); 32 | $this->expectExceptionMessage(sprintf('Unknown beanstalkd response status `%s`', 'unknown-status')); 33 | new Command(Beanstalkd::CMD_WATCH, ['unknown-status'], false, false); 34 | } 35 | 36 | public function testBuildCommand() 37 | { 38 | $cmd = new Command(Beanstalkd::CMD_WATCH, [Beanstalkd::STATUS_OK], false, false); 39 | 40 | $this->assertEquals("watch 123 321\r\n", $cmd->buildCommand(['123', '321'])); 41 | 42 | $this->assertEquals( 43 | "watch 123 321 16\r\nsome job payload\r\n", 44 | $cmd->buildCommand(['123', '321'], "some job payload") 45 | ); 46 | } 47 | 48 | public function testHandleResponse() 49 | { 50 | $cmd = new Command(Beanstalkd::CMD_WATCH, [Beanstalkd::STATUS_BURIED], false, false); 51 | 52 | $this->assertEquals( 53 | [ 54 | 'status' => Beanstalkd::STATUS_BURIED, 55 | 'headers' => [ 56 | '123', 57 | '321', 58 | ], 59 | 'data' => null, 60 | ], 61 | $cmd->handleResponse(['status' => Beanstalkd::STATUS_BURIED, 'headers' => ['123', '321']], null), 62 | ); 63 | 64 | $serializer = new JsonSerializer(); 65 | $cmd = new Command(Beanstalkd::CMD_WATCH, [Beanstalkd::STATUS_BURIED], true, false); 66 | $this->assertEquals( 67 | [ 68 | 'status' => Beanstalkd::STATUS_BURIED, 69 | 'headers' => [ 70 | '123', 71 | '321', 72 | ], 73 | 'data' => "job payload", 74 | ], 75 | $cmd->handleResponse( 76 | ['status' => Beanstalkd::STATUS_BURIED, 'headers' => ['123', '321'], 'data' => "\"job payload\"\r\n"], 77 | $serializer 78 | ), 79 | ); 80 | 81 | $cmd = new Command(Beanstalkd::CMD_WATCH, [Beanstalkd::STATUS_BURIED], false, true); 82 | $this->assertEquals( 83 | [ 84 | 'status' => Beanstalkd::STATUS_BURIED, 85 | 'headers' => [], 86 | 'data' => [ 87 | 'tube1', 88 | 'tube2', 89 | ], 90 | ], 91 | $cmd->handleResponse( 92 | ['status' => Beanstalkd::STATUS_BURIED, 'headers' => [], 'data' => "----\n- tube1\n- tube2\r\n"], 93 | $serializer 94 | ), 95 | ); 96 | } 97 | 98 | public function testHandleResponseException1() 99 | { 100 | $cmd = new Command(Beanstalkd::CMD_WATCH, [Beanstalkd::STATUS_OK], false, false); 101 | 102 | $this->expectException(CommandException::class); 103 | $this->expectExceptionMessage( 104 | sprintf( 105 | 'Error status `%s` received in response to `%s` command', 106 | Beanstalkd::STATUS_OUT_OF_MEMORY, 107 | Beanstalkd::CMD_WATCH 108 | ) 109 | ); 110 | $cmd->handleResponse(['status' => Beanstalkd::STATUS_OUT_OF_MEMORY], null); 111 | } 112 | 113 | public function testHandleResponseException2() 114 | { 115 | $cmd = new Command(Beanstalkd::CMD_WATCH, [Beanstalkd::STATUS_OK], false, false); 116 | 117 | $this->expectException(CommandException::class); 118 | $this->expectExceptionMessage( 119 | sprintf( 120 | 'Unexpected status `%s` received in response to `%s` command', 121 | Beanstalkd::STATUS_DEADLINE_SOON, 122 | Beanstalkd::CMD_WATCH 123 | ) 124 | ); 125 | $cmd->handleResponse(['status' => Beanstalkd::STATUS_DEADLINE_SOON], null); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /tests/CommanderTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($cmd, Commander::getCommand(Beanstalkd::CMD_WATCH)); 15 | } 16 | 17 | public function testGetCommandException1() 18 | { 19 | $this->expectException(CommandException::class); 20 | $this->expectExceptionMessage( 21 | sprintf('Unknown beanstalkd command `%s`', 'unknown command') 22 | ); 23 | 24 | $cmd = Commander::getCommand('unknown command'); 25 | } 26 | } 27 | --------------------------------------------------------------------------------