├── .gitignore ├── .travis.yml ├── LICENSE ├── README ├── composer.json ├── src └── Client.php └── tests ├── ConnectTest.php ├── ProducerTest.php └── PutBench.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # 2 | # beanstalk: A minimalistic PHP beanstalk client. 3 | # 4 | # Copyright (c) 2009-2014 David Persson 5 | # 6 | # Distributed under the terms of the MIT License. 7 | # Redistributions of files must retain the above copyright notice. 8 | # 9 | 10 | language: php 11 | 12 | php: 13 | - 5.4 14 | - 5.5 15 | - 5.6 16 | 17 | before_install: 18 | - sudo apt-get install -qq beanstalkd 19 | - sudo beanstalkd -d -l 127.0.0.1 -p 11301 20 | 21 | before_script: 22 | - composer install --no-interaction 23 | 24 | script: 25 | - TEST_BEANSTALKD_HOST=127.0.0.1 TEST_BEANSTALKD_PORT=11301 vendor/bin/phpunit tests -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009-2015 David Persson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | BEANSTALK 3 | ---- Minimalistic PHP client for beanstalkd. 4 | 5 | Synopsis 6 | -------- 7 | This library allows you to interface with the beanstalkd[1] work queue and is 8 | built with a minimal featureset still supporting the complete protocol. The 9 | main (and currently only) class can be found at `src/Client.php`. 10 | 11 | The class was formerly part of the queue plugin for CakePHP[2]. It has been 12 | extracted for higher reusability in other places and frameworks. 13 | 14 | Cute[3] is such a project. It uses this library to provide convenient 15 | access and tooling around the work queue for PHP. 16 | 17 | [1] http://kr.github.com/beanstalkd 18 | [2] https://github.com/davidpersson/queue 19 | [3] https://github.com/atelierdisko/cute_php 20 | 21 | Copyright & License 22 | ------------------- 23 | Beanstalk, a beanstalk PHP client library for the beanstalkd work queue is 24 | Copyright (c) 2009-2015 David Persson if not otherwise stated. The code 25 | is distributed under the terms of the MIT License. For the full license 26 | text see the LICENSE file. 27 | 28 | Versions & Requirements 29 | ----------------------- 30 | 1.0.0, PHP >=5.2.1 31 | 1.1.0, PHP >=5.2.1 32 | 2.0.0, PHP >=5.4.1 33 | 34 | Note: In PHP Versions 5.3.9, 5.3.10 and 5.4.0 the `stream_get_line()` 35 | function exhibits buggy behavior (PHP bug #60817), thus 36 | these versions cannot be used with this library. 37 | 38 | Installation 39 | ------------ 40 | The preferred installation method is via composer. You can add the library 41 | as a dependency via: 42 | 43 | $ composer require davidpersson/beanstalk 44 | 45 | Usage 46 | ----- 47 | connect(); 58 | $beanstalk->useTube('flux'); // Begin to use tube `'flux'`. 59 | $beanstalk->put( 60 | 23, // Give the job a priority of 23. 61 | 0, // Do not wait to put job into the ready queue. 62 | 60, // Give the job 1 minute to run. 63 | '/path/to/cat-image.png' // The job's body. 64 | ); 65 | $beanstalk->disconnect(); 66 | 67 | // 68 | // A sample consumer. 69 | // 70 | $beanstalk = new Client(); 71 | 72 | $beanstalk->connect(); 73 | $beanstalk->watch('flux'); 74 | 75 | while (true) { 76 | $job = $beanstalk->reserve(); // Block until job is available. 77 | // Now $job is an array which contains its ID and body: 78 | // ['id' => 123, 'body' => '/path/to/cat-image.png'] 79 | 80 | // Processing of the job... 81 | $result = touch($job['body']); 82 | 83 | if ($result) { 84 | $beanstalk->delete($job['id']); 85 | } else { 86 | $beanstalk->bury($job['id']); 87 | } 88 | } 89 | // When exiting i.e. on critical error conditions 90 | // you may also want to disconnect the consumer. 91 | // $beanstalk->disconnect(); 92 | 93 | ?> 94 | 95 | Running the Tests 96 | ----------------- 97 | The integration tests contained in this library require a running beanstalkd 98 | instance. You'll need to start a dedicated instance on port 11301 as follows. 99 | 100 | ``` 101 | beanstalkd -VV -l 127.0.0.1 -p 11301 102 | ``` 103 | 104 | Tests for this library are PHPUnit based. To run the tests you'll need 105 | to have PHPUnit installed[1]. Following commands will run all the tests. 106 | 107 | ``` 108 | cd /path/to/beanstalk 109 | composer install 110 | 111 | export TEST_BEANSTALKD_HOST=127.0.0.1 112 | export TEST_BEANSTALKD_PORT=11301 113 | vendor/bin/phpunit tests 114 | ``` 115 | 116 | [1] http://www.phpunit.de/manual/current/en/installation.html 117 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "davidpersson/beanstalk", 3 | "description": "Minimalistic PHP client for beanstalkd.", 4 | "keywords": ["queue", "socket", "beanstalk", "beanstalkd"], 5 | "homepage": "https://github.com/davidpersson/beanstalk", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "David Persson", 10 | "email": "davidpersson@gmx.de" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.4.1" 15 | }, 16 | "require-dev": { 17 | "phpunit/phpunit": ">=4.0.0" 18 | }, 19 | "suggests": { 20 | "monolog/monolog": "This or any other PSR-3 logger can be used to enable logging." 21 | }, 22 | "autoload": { 23 | "psr-4": { "Beanstalk\\": "src/" } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | true, 83 | 'host' => '127.0.0.1', 84 | 'port' => 11300, 85 | 'timeout' => 1, 86 | 'logger' => null 87 | ]; 88 | $this->_config = $config + $defaults; 89 | } 90 | 91 | /** 92 | * Destructor, disconnects from the server. 93 | * 94 | * @return void 95 | */ 96 | public function __destruct() { 97 | $this->disconnect(); 98 | } 99 | 100 | /** 101 | * Initiates a socket connection to the beanstalk server. The resulting 102 | * stream will not have any timeout set on it. Which means it can wait 103 | * an unlimited amount of time until a packet becomes available. This 104 | * is required for doing blocking reads. 105 | * 106 | * @see \Beanstalk\Client::$_connection 107 | * @see \Beanstalk\Client::reserve() 108 | * @return boolean `true` if the connection was established, `false` otherwise. 109 | */ 110 | public function connect() { 111 | if (isset($this->_connection)) { 112 | $this->disconnect(); 113 | } 114 | 115 | $function = $this->_config['persistent'] ? 'pfsockopen' : 'fsockopen'; 116 | $params = [$this->_config['host'], $this->_config['port'], &$errNum, &$errStr]; 117 | 118 | if ($this->_config['timeout']) { 119 | $params[] = $this->_config['timeout']; 120 | } 121 | $this->_connection = @call_user_func_array($function, $params); 122 | 123 | if (!empty($errNum) || !empty($errStr)) { 124 | $this->_error("{$errNum}: {$errStr}"); 125 | } 126 | 127 | $this->connected = is_resource($this->_connection); 128 | 129 | if ($this->connected) { 130 | stream_set_timeout($this->_connection, -1); 131 | } 132 | return $this->connected; 133 | } 134 | 135 | /** 136 | * Closes the connection to the beanstalk server by first signaling 137 | * that we want to quit then actually closing the socket connection. 138 | * 139 | * @return boolean `true` if diconnecting was successful. 140 | */ 141 | public function disconnect() { 142 | if (!is_resource($this->_connection)) { 143 | $this->connected = false; 144 | } else { 145 | $this->_write('quit'); 146 | $this->connected = !fclose($this->_connection); 147 | 148 | if (!$this->connected) { 149 | $this->_connection = null; 150 | } 151 | } 152 | return !$this->connected; 153 | } 154 | 155 | /** 156 | * Pushes an error message to the logger, when one is configured. 157 | * 158 | * @param string $message The error message. 159 | * @return void 160 | */ 161 | protected function _error($message) { 162 | if ($this->_config['logger']) { 163 | $this->_config['logger']->error($message); 164 | } 165 | } 166 | 167 | /** 168 | * Writes a packet to the socket. Prior to writing to the socket will 169 | * check for availability of the connection. 170 | * 171 | * @param string $data 172 | * @return integer|boolean number of written bytes or `false` on error. 173 | */ 174 | protected function _write($data) { 175 | if (!$this->connected) { 176 | $message = 'No connecting found while writing data to socket.'; 177 | throw new RuntimeException($message); 178 | } 179 | 180 | $data .= "\r\n"; 181 | return fwrite($this->_connection, $data, strlen($data)); 182 | } 183 | 184 | /** 185 | * Reads a packet from the socket. Prior to reading from the socket 186 | * will check for availability of the connection. 187 | * 188 | * @param integer $length Number of bytes to read. 189 | * @return string|boolean Data or `false` on error. 190 | */ 191 | protected function _read($length = null) { 192 | if (!$this->connected) { 193 | $message = 'No connection found while reading data from socket.'; 194 | throw new RuntimeException($message); 195 | } 196 | if ($length) { 197 | if (feof($this->_connection)) { 198 | return false; 199 | } 200 | $data = stream_get_contents($this->_connection, $length + 2); 201 | $meta = stream_get_meta_data($this->_connection); 202 | 203 | if ($meta['timed_out']) { 204 | $message = 'Connection timed out while reading data from socket.'; 205 | throw new RuntimeException($message); 206 | } 207 | $packet = rtrim($data, "\r\n"); 208 | } else { 209 | $packet = stream_get_line($this->_connection, 16384, "\r\n"); 210 | } 211 | return $packet; 212 | } 213 | 214 | /* Producer Commands */ 215 | 216 | /** 217 | * The `put` command is for any process that wants to insert a job into the queue. 218 | * 219 | * @param integer $pri Jobs with smaller priority values will be scheduled 220 | * before jobs with larger priorities. The most urgent priority is 221 | * 0; the least urgent priority is 4294967295. 222 | * @param integer $delay Seconds to wait before putting the job in the 223 | * ready queue. The job will be in the "delayed" state during this time. 224 | * @param integer $ttr Time to run - Number of seconds to allow a worker to 225 | * run this job. The minimum ttr is 1. 226 | * @param string $data The job body. 227 | * @return integer|boolean `false` on error otherwise an integer indicating 228 | * the job id. 229 | */ 230 | public function put($pri, $delay, $ttr, $data) { 231 | $this->_write(sprintf("put %d %d %d %d\r\n%s", $pri, $delay, $ttr, strlen($data), $data)); 232 | $status = strtok($this->_read(), ' '); 233 | 234 | switch ($status) { 235 | case 'INSERTED': 236 | case 'BURIED': 237 | return (integer) strtok(' '); // job id 238 | case 'EXPECTED_CRLF': 239 | case 'JOB_TOO_BIG': 240 | default: 241 | $this->_error($status); 242 | return false; 243 | } 244 | } 245 | 246 | /** 247 | * The `use` command is for producers. Subsequent put commands will put 248 | * jobs into the tube specified by this command. If no use command has 249 | * been issued, jobs will be put into the tube named `default`. 250 | * 251 | * @param string $tube A name at most 200 bytes. It specifies the tube to 252 | * use. If the tube does not exist, it will be created. 253 | * @return string|boolean `false` on error otherwise the name of the tube. 254 | */ 255 | public function useTube($tube) { 256 | $this->_write(sprintf('use %s', $tube)); 257 | $status = strtok($this->_read(), ' '); 258 | 259 | switch ($status) { 260 | case 'USING': 261 | return strtok(' '); 262 | default: 263 | $this->_error($status); 264 | return false; 265 | } 266 | } 267 | 268 | /** 269 | * Pause a tube delaying any new job in it being reserved for a given time. 270 | * 271 | * @param string $tube The name of the tube to pause. 272 | * @param integer $delay Number of seconds to wait before reserving any more 273 | * jobs from the queue. 274 | * @return boolean `false` on error otherwise `true`. 275 | */ 276 | public function pauseTube($tube, $delay) { 277 | $this->_write(sprintf('pause-tube %s %d', $tube, $delay)); 278 | $status = strtok($this->_read(), ' '); 279 | 280 | switch ($status) { 281 | case 'PAUSED': 282 | return true; 283 | case 'NOT_FOUND': 284 | default: 285 | $this->_error($status); 286 | return false; 287 | } 288 | } 289 | 290 | /* Worker Commands */ 291 | 292 | /** 293 | * Reserve a job (with a timeout). 294 | * 295 | * @param integer $timeout If given specifies number of seconds to wait for 296 | * a job. `0` returns immediately. 297 | * @return array|false `false` on error otherwise an array holding job id 298 | * and body. 299 | */ 300 | public function reserve($timeout = null) { 301 | if (isset($timeout)) { 302 | $this->_write(sprintf('reserve-with-timeout %d', $timeout)); 303 | } else { 304 | $this->_write('reserve'); 305 | } 306 | $status = strtok($this->_read(), ' '); 307 | 308 | switch ($status) { 309 | case 'RESERVED': 310 | return [ 311 | 'id' => (integer) strtok(' '), 312 | 'body' => $this->_read((integer) strtok(' ')) 313 | ]; 314 | case 'DEADLINE_SOON': 315 | case 'TIMED_OUT': 316 | default: 317 | $this->_error($status); 318 | return false; 319 | } 320 | } 321 | 322 | /** 323 | * Removes a job from the server entirely. 324 | * 325 | * @param integer $id The id of the job. 326 | * @return boolean `false` on error, `true` on success. 327 | */ 328 | public function delete($id) { 329 | $this->_write(sprintf('delete %d', $id)); 330 | $status = $this->_read(); 331 | 332 | switch ($status) { 333 | case 'DELETED': 334 | return true; 335 | case 'NOT_FOUND': 336 | default: 337 | $this->_error($status); 338 | return false; 339 | } 340 | } 341 | 342 | /** 343 | * Puts a reserved job back into the ready queue. 344 | * 345 | * @param integer $id The id of the job. 346 | * @param integer $pri Priority to assign to the job. 347 | * @param integer $delay Number of seconds to wait before putting the job in the ready queue. 348 | * @return boolean `false` on error, `true` on success. 349 | */ 350 | public function release($id, $pri, $delay) { 351 | $this->_write(sprintf('release %d %d %d', $id, $pri, $delay)); 352 | $status = $this->_read(); 353 | 354 | switch ($status) { 355 | case 'RELEASED': 356 | case 'BURIED': 357 | return true; 358 | case 'NOT_FOUND': 359 | default: 360 | $this->_error($status); 361 | return false; 362 | } 363 | } 364 | 365 | /** 366 | * Puts a job into the `buried` state Buried jobs are put into a FIFO 367 | * linked list and will not be touched until a client kicks them. 368 | * 369 | * @param integer $id The id of the job. 370 | * @param integer $pri *New* priority to assign to the job. 371 | * @return boolean `false` on error, `true` on success. 372 | */ 373 | public function bury($id, $pri) { 374 | $this->_write(sprintf('bury %d %d', $id, $pri)); 375 | $status = $this->_read(); 376 | 377 | switch ($status) { 378 | case 'BURIED': 379 | return true; 380 | case 'NOT_FOUND': 381 | default: 382 | $this->_error($status); 383 | return false; 384 | } 385 | } 386 | 387 | /** 388 | * Allows a worker to request more time to work on a job. 389 | * 390 | * @param integer $id The id of the job. 391 | * @return boolean `false` on error, `true` on success. 392 | */ 393 | public function touch($id) { 394 | $this->_write(sprintf('touch %d', $id)); 395 | $status = $this->_read(); 396 | 397 | switch ($status) { 398 | case 'TOUCHED': 399 | return true; 400 | case 'NOT_TOUCHED': 401 | default: 402 | $this->_error($status); 403 | return false; 404 | } 405 | } 406 | 407 | /** 408 | * Adds the named tube to the watch list for the current connection. 409 | * 410 | * @param string $tube Name of tube to watch. 411 | * @return integer|boolean `false` on error otherwise number of tubes in watch list. 412 | */ 413 | public function watch($tube) { 414 | $this->_write(sprintf('watch %s', $tube)); 415 | $status = strtok($this->_read(), ' '); 416 | 417 | switch ($status) { 418 | case 'WATCHING': 419 | return (integer) strtok(' '); 420 | default: 421 | $this->_error($status); 422 | return false; 423 | } 424 | } 425 | 426 | /** 427 | * Remove the named tube from the watch list. 428 | * 429 | * @param string $tube Name of tube to ignore. 430 | * @return integer|boolean `false` on error otherwise number of tubes in watch list. 431 | */ 432 | public function ignore($tube) { 433 | $this->_write(sprintf('ignore %s', $tube)); 434 | $status = strtok($this->_read(), ' '); 435 | 436 | switch ($status) { 437 | case 'WATCHING': 438 | return (integer) strtok(' '); 439 | case 'NOT_IGNORED': 440 | default: 441 | $this->_error($status); 442 | return false; 443 | } 444 | } 445 | 446 | /* Other Commands */ 447 | 448 | /** 449 | * Inspect a job by its id. 450 | * 451 | * @param integer $id The id of the job. 452 | * @return string|boolean `false` on error otherwise the body of the job. 453 | */ 454 | public function peek($id) { 455 | $this->_write(sprintf('peek %d', $id)); 456 | return $this->_peekRead(); 457 | } 458 | 459 | /** 460 | * Inspect the next ready job. 461 | * 462 | * @return string|boolean `false` on error otherwise the body of the job. 463 | */ 464 | public function peekReady() { 465 | $this->_write('peek-ready'); 466 | return $this->_peekRead(); 467 | } 468 | 469 | /** 470 | * Inspect the job with the shortest delay left. 471 | * 472 | * @return string|boolean `false` on error otherwise the body of the job. 473 | */ 474 | public function peekDelayed() { 475 | $this->_write('peek-delayed'); 476 | return $this->_peekRead(); 477 | } 478 | 479 | /** 480 | * Inspect the next job in the list of buried jobs. 481 | * 482 | * @return string|boolean `false` on error otherwise the body of the job. 483 | */ 484 | public function peekBuried() { 485 | $this->_write('peek-buried'); 486 | return $this->_peekRead(); 487 | } 488 | 489 | /** 490 | * Handles response for all peek methods. 491 | * 492 | * @return string|boolean `false` on error otherwise the body of the job. 493 | */ 494 | protected function _peekRead() { 495 | $status = strtok($this->_read(), ' '); 496 | 497 | switch ($status) { 498 | case 'FOUND': 499 | return [ 500 | 'id' => (integer) strtok(' '), 501 | 'body' => $this->_read((integer) strtok(' ')) 502 | ]; 503 | case 'NOT_FOUND': 504 | default: 505 | $this->_error($status); 506 | return false; 507 | } 508 | } 509 | 510 | /** 511 | * Moves jobs into the ready queue (applies to the current tube). 512 | * 513 | * If there are buried jobs those get kicked only otherwise delayed 514 | * jobs get kicked. 515 | * 516 | * @param integer $bound Upper bound on the number of jobs to kick. 517 | * @return integer|boolean False on error otherwise number of jobs kicked. 518 | */ 519 | public function kick($bound) { 520 | $this->_write(sprintf('kick %d', $bound)); 521 | $status = strtok($this->_read(), ' '); 522 | 523 | switch ($status) { 524 | case 'KICKED': 525 | return (integer) strtok(' '); 526 | default: 527 | $this->_error($status); 528 | return false; 529 | } 530 | } 531 | 532 | /** 533 | * This is a variant of the kick command that operates with a single 534 | * job identified by its job id. If the given job id exists and is in a 535 | * buried or delayed state, it will be moved to the ready queue of the 536 | * the same tube where it currently belongs. 537 | * 538 | * @param integer $id The job id. 539 | * @return boolean `false` on error `true` otherwise. 540 | */ 541 | public function kickJob($id) { 542 | $this->_write(sprintf('kick-job %d', $id)); 543 | $status = strtok($this->_read(), ' '); 544 | 545 | switch ($status) { 546 | case 'KICKED': 547 | return true; 548 | case 'NOT_FOUND': 549 | default: 550 | $this->_error($status); 551 | return false; 552 | } 553 | } 554 | 555 | /* Stats Commands */ 556 | 557 | /** 558 | * Gives statistical information about the specified job if it exists. 559 | * 560 | * @param integer $id The job id. 561 | * @return string|boolean `false` on error otherwise a string with a yaml formatted dictionary. 562 | */ 563 | public function statsJob($id) { 564 | $this->_write(sprintf('stats-job %d', $id)); 565 | return $this->_statsRead(); 566 | } 567 | 568 | /** 569 | * Gives statistical information about the specified tube if it exists. 570 | * 571 | * @param string $tube Name of the tube. 572 | * @return string|boolean `false` on error otherwise a string with a yaml formatted dictionary. 573 | */ 574 | public function statsTube($tube) { 575 | $this->_write(sprintf('stats-tube %s', $tube)); 576 | return $this->_statsRead(); 577 | } 578 | 579 | /** 580 | * Gives statistical information about the system as a whole. 581 | * 582 | * @return string|boolean `false` on error otherwise a string with a yaml formatted dictionary. 583 | */ 584 | public function stats() { 585 | $this->_write('stats'); 586 | return $this->_statsRead(); 587 | } 588 | 589 | /** 590 | * Returns a list of all existing tubes. 591 | * 592 | * @return string|boolean `false` on error otherwise a string with a yaml formatted list. 593 | */ 594 | public function listTubes() { 595 | $this->_write('list-tubes'); 596 | return $this->_statsRead(); 597 | } 598 | 599 | /** 600 | * Returns the tube currently being used by the producer. 601 | * 602 | * @return string|boolean `false` on error otherwise a string with the name of the tube. 603 | */ 604 | public function listTubeUsed() { 605 | $this->_write('list-tube-used'); 606 | $status = strtok($this->_read(), ' '); 607 | 608 | switch ($status) { 609 | case 'USING': 610 | return strtok(' '); 611 | default: 612 | $this->_error($status); 613 | return false; 614 | } 615 | } 616 | 617 | /** 618 | * Returns a list of tubes currently being watched by the worker. 619 | * 620 | * @return string|boolean `false` on error otherwise a string with a yaml formatted list. 621 | */ 622 | public function listTubesWatched() { 623 | $this->_write('list-tubes-watched'); 624 | return $this->_statsRead(); 625 | } 626 | 627 | /** 628 | * Handles responses for all stat methods. 629 | * 630 | * @param boolean $decode Whether to decode data before returning it or not. Default is `true`. 631 | * @return array|string|boolean `false` on error otherwise statistical data. 632 | */ 633 | protected function _statsRead($decode = true) { 634 | $status = strtok($this->_read(), ' '); 635 | 636 | switch ($status) { 637 | case 'OK': 638 | $data = $this->_read((integer) strtok(' ')); 639 | return $decode ? $this->_decode($data) : $data; 640 | default: 641 | $this->_error($status); 642 | return false; 643 | } 644 | } 645 | 646 | /** 647 | * Decodes YAML data. This is a super naive decoder which just works on 648 | * a subset of YAML which is commonly returned by beanstalk. 649 | * 650 | * @param string $data The data in YAML format, can be either a list or a dictionary. 651 | * @return array An (associative) array of the converted data. 652 | */ 653 | protected function _decode($data) { 654 | $data = array_slice(explode("\n", $data), 1); 655 | $result = []; 656 | 657 | foreach ($data as $key => $value) { 658 | if ($value[0] === '-') { 659 | $value = ltrim($value, '- '); 660 | } elseif (strpos($value, ':') !== false) { 661 | list($key, $value) = explode(':', $value); 662 | $value = ltrim($value, ' '); 663 | } 664 | if (is_numeric($value)) { 665 | $value = (integer) $value == $value ? (integer) $value : (float) $value; 666 | } 667 | $result[$key] = $value; 668 | } 669 | return $result; 670 | } 671 | } 672 | 673 | ?> -------------------------------------------------------------------------------- /tests/ConnectTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped($message); 26 | } 27 | $this->subject = new Client(compact('host', 'port')); 28 | 29 | if (!$this->subject->connect()) { 30 | $message = "Need a running beanstalkd server at {$host}:{$port}."; 31 | $this->markTestSkipped($message); 32 | } 33 | } 34 | 35 | public function testConnection() { 36 | $this->subject->disconnect(); 37 | 38 | $result = $this->subject->connect(); 39 | $this->assertTrue($result); 40 | 41 | $result = $this->subject->connected; 42 | $this->assertTrue($result); 43 | 44 | $result = $this->subject->disconnect(); 45 | $this->assertTrue($result); 46 | 47 | $result = $this->subject->connected; 48 | $this->assertFalse($result); 49 | } 50 | } 51 | 52 | ?> -------------------------------------------------------------------------------- /tests/ProducerTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped($message); 26 | } 27 | $this->subject = new Client(compact('host', 'port')); 28 | 29 | if (!$this->subject->connect()) { 30 | $message = "Need a running beanstalkd server at {$host}:{$port}."; 31 | $this->markTestSkipped($message); 32 | } 33 | 34 | foreach ($this->subject->listTubes() as $tube) { 35 | $this->subject->useTube($tube); 36 | 37 | while ($job = $this->subject->peekReady()) { 38 | $this->subject->delete($job['id']); 39 | } 40 | while ($job = $this->subject->peekBuried()) { 41 | $this->subject->delete($job['id']); 42 | } 43 | } 44 | $this->subject->useTube('default'); 45 | } 46 | 47 | public function testPut() { 48 | $this->subject->useTube('test'); 49 | 50 | $this->subject->put(0, 0, 100, 'test'); 51 | $this->subject->put(0, 0, 100, 'test'); 52 | 53 | $result = $this->subject->statsTube('test'); 54 | $this->assertEquals(2, $result['current-jobs-urgent']); 55 | } 56 | 57 | public function testUseTube() { 58 | $result = $this->subject->useTube('test0'); 59 | $this->assertEquals('test0', $result); 60 | 61 | $result = $this->subject->useTube('test1'); 62 | $this->assertEquals('test1', $result); 63 | } 64 | 65 | public function testReserveWithoutTimeout() { 66 | $this->subject->put(0, 0, 100, 'test0'); 67 | 68 | $result = $this->subject->reserve(); 69 | $this->assertEquals('test0', $result['body']); 70 | } 71 | 72 | public function testReserveWithTimeout() { 73 | $start = microtime(true); 74 | 75 | $this->subject->reserve(1); 76 | 77 | $result = microtime(true) - $start; 78 | $this->assertEquals(1, (integer) $result); 79 | } 80 | 81 | public function testExceedFreadDefaultChunkSize() { 82 | $this->subject->put(0, 0, 100, str_repeat('0', 8192 + 4)); 83 | $result = $this->subject->reserve(1); 84 | 85 | $this->subject->delete($result['id']); 86 | $this->assertEquals(8192 + 4, strlen($result['body'])); 87 | 88 | $this->subject->put(0, 0, 100, str_repeat('0', 8192 * 4)); 89 | $result = $this->subject->reserve(1); 90 | 91 | $this->subject->delete($result['id']); 92 | $this->assertEquals(8192 * 4, strlen($result['body'])); 93 | } 94 | 95 | public function testExceedMaxJobSize() { 96 | $this->subject->put(0, 0, 100, str_repeat('0', 65536 - 1)); 97 | $result = $this->subject->reserve(5); 98 | 99 | $this->subject->delete($result['id']); 100 | $this->assertEquals(65536 - 1, strlen($result['body'])); 101 | 102 | $this->subject->put(0, 0, 100, str_repeat('0', 65536)); 103 | $result = $this->subject->reserve(1); 104 | 105 | $this->assertFalse($result); 106 | } 107 | 108 | public function testHighFrequencyPut() { 109 | $this->subject->useTube('test'); 110 | 111 | for ($i = 0; $i < 10000; $i++) { 112 | $this->subject->put(0, 0, 100, 'test' . $i); 113 | } 114 | $result = $this->subject->statsTube('test'); 115 | $this->assertEquals(10000, $result['current-jobs-urgent']); 116 | } 117 | } 118 | 119 | ?> -------------------------------------------------------------------------------- /tests/PutBench.php: -------------------------------------------------------------------------------- 1 | getenv('TEST_BEANSTALKD_HOST'), 20 | 'port' => getenv('TEST_BEANSTALKD_PORT') 21 | ]); 22 | for ($i = 0; $i < 100000; $i++) { 23 | $connection->put(1024, 0, 60, $i); 24 | } 25 | 26 | ?> --------------------------------------------------------------------------------