├── .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 | [](https://packagist.org/packages/xobotyi/beansclient)
6 | [](https://packagist.org/packages/xobotyi/beansclient)
7 | [](https://packagist.org/packages/xobotyi/beansclient)
8 | [](https://github.com/xobotyi/beansclient/actions)
9 | [](https://app.codecov.io/gh/xobotyi/beansclient)
10 | [](https://packagist.org/packages/xobotyi/beansclient)
11 | [](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 |
--------------------------------------------------------------------------------