├── .gitignore
├── src
├── ResqueException.php
├── Version.php
├── Exception
│ ├── JobLogicException.php
│ ├── JobClassNotFoundException.php
│ ├── JobIdException.php
│ ├── JobInvalidException.php
│ └── DirtyExitException.php
├── Client
│ ├── PredisClient.php
│ ├── CredisClient.php
│ └── ClientInterface.php
├── Console
│ ├── Helper
│ │ ├── RedisHelper.php
│ │ └── LoggerHelper.php
│ ├── Command.php
│ ├── QueueListCommand.php
│ ├── QueueClearCommand.php
│ ├── WorkerCommand.php
│ ├── ConsoleRunner.php
│ └── EnqueueCommand.php
├── JobInterface.php
├── Failure
│ ├── BackendInterface.php
│ └── RedisBackend.php
├── Job
│ ├── StatusFactory.php
│ └── Status.php
├── Statistic.php
├── AbstractJob.php
├── Resque.php
└── Worker.php
├── test
├── Test
│ ├── NoPerformJob.php
│ ├── MinimalJob.php
│ ├── FailingJob.php
│ ├── Job.php
│ └── Settings.php
├── ResqueTest.php
├── bootstrap.php
├── ClientTest.php
├── Test.php
├── StatisticTest.php
├── AbstractJobTest.php
├── Job
│ └── StatusTest.php
└── WorkerTest.php
├── phpunit.xml
├── .scrutinizer.yml
├── resque
├── LICENSE
├── cli-config.php
├── .travis.yml
├── composer.json
├── CHANGELOG.md
├── README.md
└── composer.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | vendor/
3 | composer.phar
4 |
--------------------------------------------------------------------------------
/src/ResqueException.php:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | test
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Exception/JobIdException.php:
--------------------------------------------------------------------------------
1 | performed = true;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/test/Test/FailingJob.php:
--------------------------------------------------------------------------------
1 |
12 | * @license http://www.opensource.org/licenses/mit-license.php
13 | */
14 | class DirtyExitException extends ResqueException
15 | {
16 | }
17 |
--------------------------------------------------------------------------------
/test/ResqueTest.php:
--------------------------------------------------------------------------------
1 | assertFalse($this->resque->workerExists('blah'));
10 | }
11 |
12 | public function testEmptyWorkerIds()
13 | {
14 | $this->assertInternalType('array', $this->resque->getWorkerIds());
15 | }
16 |
17 | public function testEmptyWorkerPids()
18 | {
19 | $this->assertInternalType('array', $this->resque->getWorkerPids());
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/test/bootstrap.php:
--------------------------------------------------------------------------------
1 | addPsr4('Resque\\', __DIR__);
5 |
6 | $build = __DIR__ . '/../build';
7 |
8 | $logger = new Monolog\Logger('test');
9 | $logger->pushHandler(new Monolog\Handler\StreamHandler($build . '/test.log'));
10 |
11 | $settings = new Resque\Test\Settings();
12 | $settings->setLogger($logger);
13 | $settings->fromEnvironment();
14 | $settings->setBuildDir($build);
15 | $settings->checkBuildDir();
16 | $settings->dumpConfig();
17 | $settings->catchSignals();
18 | $settings->startRedis(); // registers shutdown function
19 |
20 | Resque\Test::setSettings($settings);
21 |
--------------------------------------------------------------------------------
/test/ClientTest.php:
--------------------------------------------------------------------------------
1 | 'abc',
11 | 'two' => 'def',
12 | 'three' => 123,
13 | 'four' => 1.0 / 3
14 | );
15 |
16 | $success = $this->redis->hmset('some_other_key', $values);
17 | $hash = $this->redis->hgetall('some_other_key');
18 |
19 | $this->assertTrue((boolean)$success, 'HMSET command returns success');
20 | $this->assertEquals($values, $hash, 'HGETALL command returns array');
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/resque:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | performed = true;
14 | }
15 |
16 | /**
17 | * @return int See Resque\Job\Status::STATUS_*
18 | */
19 | public function getStatusCode()
20 | {
21 | return $this->getStatus()->get();
22 | }
23 |
24 | /**
25 | * @return string ID of the recreated job
26 | */
27 | public function recreate()
28 | {
29 | return parent::recreate();
30 | }
31 |
32 | /**
33 | * @return \Resque\Job\Status
34 | */
35 | public function getStatus()
36 | {
37 | return parent::getStatus();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Console/Helper/RedisHelper.php:
--------------------------------------------------------------------------------
1 | client = $client;
24 | }
25 |
26 | /**
27 | * @return ClientInterface
28 | */
29 | public function getClient()
30 | {
31 | return $this->client;
32 | }
33 |
34 | /**
35 | * @return string
36 | */
37 | public function getName()
38 | {
39 | return 'redis';
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Console/Helper/LoggerHelper.php:
--------------------------------------------------------------------------------
1 | logger = $logger;
24 | }
25 |
26 | /**
27 | * @return LoggerInterface
28 | */
29 | public function getLogger()
30 | {
31 | return $this->logger;
32 | }
33 |
34 | /**
35 | * @return string
36 | */
37 | public function getName()
38 | {
39 | return 'logger';
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/JobInterface.php:
--------------------------------------------------------------------------------
1 | connected;
24 | }
25 |
26 | /**
27 | * Disconnects the client
28 | */
29 | public function disconnect()
30 | {
31 | $this->close();
32 | }
33 |
34 | /**
35 | * Alias to exec() for pipeline compatibility with Predis
36 | */
37 | public function execute()
38 | {
39 | $this->__call('exec', array());
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Failure/BackendInterface.php:
--------------------------------------------------------------------------------
1 |
13 | * @license http://www.opensource.org/licenses/mit-license.php
14 | */
15 | interface BackendInterface
16 | {
17 | /**
18 | * Initialize a failed job class and save it (where appropriate).
19 | *
20 | * @param array $payload Object containing details of the failed job.
21 | * @param \Exception $exception Instance of the exception that was thrown by the failed job.
22 | * @param Worker $worker Instance of Worker that received the job.
23 | * @param string $queue The name of the queue the job was fetched from.
24 | * @return void
25 | */
26 | public function receiveFailure($payload, Exception $exception, Worker $worker, $queue);
27 | }
28 |
--------------------------------------------------------------------------------
/src/Job/StatusFactory.php:
--------------------------------------------------------------------------------
1 | resque = $resque;
19 | }
20 |
21 | /**
22 | * @param String $id
23 | * @return \Resque\Job\Status
24 | */
25 | public function forId($id)
26 | {
27 | return new Status($id, $this->resque);
28 | }
29 |
30 | /**
31 | * @param JobInterface $job
32 | * @return Status
33 | * @throws \Resque\Exception\JobIdException
34 | */
35 | public function forJob(JobInterface $job)
36 | {
37 | $payload = $job->getPayload();
38 |
39 | if (empty($payload['id'])) {
40 | throw new JobIdException('Job has no ID in payload, cannot get Status object');
41 | }
42 |
43 | return $this->forId($payload['id']);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Console/Command.php:
--------------------------------------------------------------------------------
1 | getHelper('redis')->getClient();
19 | }
20 |
21 | /**
22 | * @param OutputInterface $output
23 | * @return \Resque\Resque
24 | */
25 | public function getResque(OutputInterface $output)
26 | {
27 | $resque = new Resque($this->getRedis());
28 |
29 | if (($helper = $this->getHelper('logger'))) {
30 | /* @var LoggerHelper $helper */
31 | $resque->setLogger($helper->getLogger());
32 | } else {
33 | $resque->setLogger(new ConsoleLogger($output));
34 | }
35 |
36 | return $resque;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | (c) Chris Boulton
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/src/Console/QueueListCommand.php:
--------------------------------------------------------------------------------
1 | setName('queue:list')
19 | ->setDescription('Outputs information about queues');
20 | }
21 |
22 | /**
23 | * @param InputInterface $input
24 | * @param OutputInterface $output
25 | * @return int
26 | */
27 | protected function execute(InputInterface $input, OutputInterface $output)
28 | {
29 | $resque = $this->getResque($output);
30 | $queues = $resque->queues();
31 |
32 | foreach ($queues as $queue) {
33 | $size = $resque->size($queue);
34 | $output->writeln($queue . " " . $size);
35 | }
36 |
37 | if (!count($queues)) {
38 | $output->writeln('No queues');
39 | return 1; // If no queues, return error exit code
40 | }
41 |
42 | return 0;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Console/QueueClearCommand.php:
--------------------------------------------------------------------------------
1 | setName('queue:clear')
20 | ->setDescription('Clears a specified queue')
21 | ->addArgument(
22 | 'queue',
23 | InputArgument::REQUIRED,
24 | 'The name of the queue to clear'
25 | );
26 | }
27 |
28 | /**
29 | * @param InputInterface $input
30 | * @param OutputInterface $output
31 | * @return int
32 | */
33 | protected function execute(InputInterface $input, OutputInterface $output)
34 | {
35 | $resque = $this->getResque($output);
36 | $queue = $input->getArgument('queue');
37 |
38 | $cleared = $resque->size($queue);
39 | $resque->clearQueue($queue);
40 |
41 | $output->writeln('Cleared ' . $cleared . ' jobs on queue ' . $queue);
42 | return 0;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/cli-config.php:
--------------------------------------------------------------------------------
1 | 'tcp',
27 | 'host' => '127.0.0.1',
28 | 'port' => 6379
29 | ));
30 |
31 | /*
32 | * You can optionally customize the PSR3 logger used on the CLI. The
33 | * default is the Console component's ConsoleLogger
34 | *
35 | * $logger = new Monolog\Logger('resque');
36 | */
37 |
38 | return \Resque\Console\ConsoleRunner::createHelperSet($predis/*, $logger*/);
39 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.3
5 | - 5.4
6 | - 5.5
7 |
8 | env:
9 | - RESQUE_CLIENT_TYPE=predis
10 | - RESQUE_CLIENT_TYPE=credis
11 | # - RESQUE_CLIENT_TYPE=phpredis (broken by https://github.com/nicolasff/phpredis/issues/474)
12 | - RESQUE_CLIENT_TYPE=phpiredis
13 |
14 | before_script:
15 | - sh -c "if [ \"$RESQUE_CLIENT_TYPE\" = 'phpredis' ]; then ant phpredis; fi"
16 | - sh -c "if [ \"$RESQUE_CLIENT_TYPE\" = 'phpredis' ]; then echo 'extension=redis.so' >> `php --ini | grep 'Loaded Configuration' | sed -e \"s|.*:\s*||\"`; fi"
17 | - sh -c "if [ \"$RESQUE_CLIENT_TYPE\" = 'phpiredis' ]; then ant phpiredis; fi"
18 | - sh -c "if [ \"$RESQUE_CLIENT_TYPE\" = 'phpiredis' ]; then echo 'extension=phpiredis.so' >> `php --ini | grep 'Loaded Configuration' | sed -e \"s|.*:\s*||\"`; fi"
19 | - composer install
20 |
21 | script: sh -c "if [ \"$RESQUE_CLIENT_TYPE\" = 'predis' -a \"$TRAVIS_PHP_VERSION\" = '5.5' ]; then phpunit --coverage-clover=coverage.clover; else phpunit; fi"
22 |
23 | after_script:
24 | - sh -c "if [ \"$RESQUE_CLIENT_TYPE\" = 'predis' -a \"$TRAVIS_PHP_VERSION\" = '5.5' ]; then wget https://scrutinizer-ci.com/ocular.phar; fi"
25 | - sh -c "if [ \"$RESQUE_CLIENT_TYPE\" = 'predis' -a \"$TRAVIS_PHP_VERSION\" = '5.5' ]; then php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi"
26 | - cat build/redis.log
27 | - cat build/test.log
28 |
--------------------------------------------------------------------------------
/src/Console/WorkerCommand.php:
--------------------------------------------------------------------------------
1 | setName('worker')
21 | ->setDescription('Runs a Resque worker')
22 | ->addOption(
23 | 'queue',
24 | 'Q',
25 | InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
26 | 'A queue to listen to and run jobs from'
27 | )
28 | ->addOption(
29 | 'interval',
30 | 'i',
31 | InputOption::VALUE_REQUIRED,
32 | 'Interval in seconds to wait for between reserving jobs',
33 | 5
34 | );
35 | }
36 |
37 | /**
38 | * @param InputInterface $input
39 | * @param OutputInterface $output
40 | * @return int
41 | */
42 | protected function execute(InputInterface $input, OutputInterface $output)
43 | {
44 | $resque = $this->getResque($output);
45 | $queues = $input->getOption('queue');
46 |
47 | $worker = new Worker($resque, $queues);
48 | $worker->work($input->getOption('interval'));
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "_-x-vim-formatting": " vim: set ft=javascript ts=4 sw=4 et : ",
3 |
4 | "name": "vend/resque",
5 | "description": "Namespaced port of chrisbolton/php-resque, supports Predis, more DI",
6 | "license": "MIT",
7 |
8 | "authors": [
9 | {
10 | "name": "Chris Boulton",
11 | "email": "chris@bigcommerce.com"
12 | },
13 | {
14 | "name": "Vend Devteam",
15 | "email": "devteam@vendhq.com"
16 | }
17 | ],
18 |
19 | "autoload": {
20 | "psr-4": {
21 | "Resque\\": "src"
22 | }
23 | },
24 |
25 | "bin": [
26 | "resque"
27 | ],
28 |
29 | "require": {
30 | "php": ">=5.3.7",
31 | "ext-pcntl": "*",
32 | "psr/log": ">=1.0.0",
33 | "symfony/console": ">=2.5.6"
34 | },
35 |
36 | "require-dev": {
37 | "colinmollenhour/credis": "1.3.*",
38 | "predis/predis": ">=0.8.6",
39 | "phpunit/phpunit": "4.3.*",
40 | "monolog/monolog": "1.7.*"
41 | },
42 |
43 | "extra": {
44 | "branch-alias": {
45 | "dev-master": "2.1.x-dev",
46 | "dev-2.0": "2.0.x-dev"
47 | }
48 | },
49 |
50 | "suggest": {
51 | "ext-proctitle": "Allows php-resque to rename the title of UNIX processes to show the status of a worker.",
52 | "predis/predis": "Suggested Redis client",
53 | "ext-phpiredis": "Better performance for Predis",
54 | "colinmoullenhour/credis": "Alternative Redis client",
55 | "ext-phpredis": "Better performance for Credis"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/test/Test.php:
--------------------------------------------------------------------------------
1 | redis = self::$settings->getClient();
45 | $this->redis->flushdb();
46 |
47 | $this->logger = self::$settings->getLogger();
48 |
49 | $this->resque = new Resque($this->redis);
50 | $this->resque->setLogger($this->logger);
51 | }
52 |
53 | public function tearDown()
54 | {
55 | if ($this->redis) {
56 | $this->redis->flushdb();
57 |
58 | if ($this->redis->isConnected()) {
59 | $this->logger->notice('Shutting down connected Redis instance in tearDown()');
60 | $this->redis->disconnect();
61 | }
62 | }
63 | }
64 |
65 | protected function getWorker($queues)
66 | {
67 | $worker = new Worker($this->resque, $queues);
68 | $worker->setLogger($this->logger);
69 | $worker->register();
70 |
71 | return $worker;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Console/ConsoleRunner.php:
--------------------------------------------------------------------------------
1 | set(new RedisHelper($client));
28 |
29 | if ($logger) {
30 | $helper->set(new LoggerHelper($logger));
31 | }
32 |
33 | return $helper;
34 | }
35 |
36 | /**
37 | * Runs the application using the given HelperSet
38 | *
39 | * This method is responsible for creating the console application, adding
40 | * relevant commands, and running it. Other code is responsible for producing
41 | * the HelperSet itself (your cli-config.php or bootstrap code), and for
42 | * calling this method (the actual bin command file).
43 | *
44 | * @param HelperSet $helperSet
45 | * @return integer 0 if everything went fine, or an error code
46 | */
47 | public static function run(HelperSet $helperSet)
48 | {
49 | $application = new Application('Resque Console Tool', Version::VERSION);
50 | $application->setCatchExceptions(true);
51 | $application->setHelperSet($helperSet);
52 |
53 | $application->add(new QueueListCommand());
54 | $application->add(new QueueClearCommand());
55 | $application->add(new EnqueueCommand());
56 | $application->add(new WorkerCommand());
57 |
58 | return $application->run();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/test/StatisticTest.php:
--------------------------------------------------------------------------------
1 |
10 | * @license http://www.opensource.org/licenses/mit-license.php
11 | */
12 | class StatisticTest extends Test
13 | {
14 | /**
15 | * @var Statistic
16 | */
17 | protected $statistic;
18 |
19 | public function setUp()
20 | {
21 | parent::setUp();
22 |
23 | $this->statistic = new Statistic($this->resque, __CLASS__);
24 | }
25 |
26 | public function tearDown()
27 | {
28 | if ($this->statistic) {
29 | $this->statistic->clear();
30 | $this->statistic = null;
31 | }
32 |
33 | parent::tearDown();
34 | }
35 |
36 | protected function assertStatisticValueByClient($value, $message = '')
37 | {
38 | $this->assertEquals($value, $this->redis->get('resque:stat:' . __CLASS__), $message);
39 | }
40 |
41 | public function testStatCanBeIncremented()
42 | {
43 | $this->statistic->incr();
44 | $this->statistic->incr();
45 | $this->assertStatisticValueByClient(2);
46 | }
47 |
48 | public function testStatCanBeIncrementedByX()
49 | {
50 | $this->statistic->incr(10);
51 | $this->statistic->incr(11);
52 | $this->assertStatisticValueByClient(21);
53 | }
54 |
55 | public function testStatCanBeDecremented()
56 | {
57 | $this->statistic->incr(22);
58 | $this->statistic->decr();
59 | $this->assertStatisticValueByClient(21);
60 | }
61 |
62 | public function testStatCanBeDecrementedByX()
63 | {
64 | $this->statistic->incr(22);
65 | $this->statistic->decr(11);
66 | $this->assertStatisticValueByClient(11);
67 | }
68 |
69 | public function testGetStatByName()
70 | {
71 | $this->statistic->incr(100);
72 | $this->assertEquals(100, $this->statistic->get());
73 | }
74 |
75 | public function testGetUnknownStatReturns0()
76 | {
77 | $statistic = new Statistic($this->resque, 'some_unknown_statistic');
78 | $this->assertEquals(0, $statistic->get());
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Statistic.php:
--------------------------------------------------------------------------------
1 |
10 | * @license http://www.opensource.org/licenses/mit-license.php
11 | */
12 | class Statistic
13 | {
14 | const KEY = 'stat:';
15 |
16 | protected $resque;
17 | protected $statistic;
18 |
19 | /**
20 | * Constructor
21 | *
22 | * @param Resque $resque
23 | * @param string $statistic
24 | */
25 | public function __construct(Resque $resque, $statistic)
26 | {
27 | $this->resque = $resque;
28 | $this->statistic = $statistic;
29 | }
30 |
31 | /**
32 | * Gets the key for a statistic
33 | *
34 | * @return string
35 | */
36 | public function getKey()
37 | {
38 | return $this->resque->getKey(self::KEY . $this->statistic);
39 | }
40 |
41 | /**
42 | * Get the value of the supplied statistic counter for the specified statistic.
43 | *
44 | * @return integer Value of the statistic.
45 | */
46 | public function get()
47 | {
48 | return (int)$this->resque->getClient()->get($this->getKey());
49 | }
50 |
51 | /**
52 | * Increment the value of the specified statistic by a certain amount (default is 1)
53 | *
54 | * @param int $by The amount to increment the statistic by.
55 | * @return boolean True if successful, false if not.
56 | */
57 | public function incr($by = 1)
58 | {
59 | return (bool)$this->resque->getClient()->incrby($this->getKey(), $by);
60 | }
61 |
62 | /**
63 | * Decrement the value of the specified statistic by a certain amount (default is 1)
64 | *
65 | * @param int $by The amount to decrement the statistic by.
66 | * @return boolean True if successful, false if not.
67 | */
68 | public function decr($by = 1)
69 | {
70 | return (bool)$this->resque->getClient()->decrby($this->getKey(), $by);
71 | }
72 |
73 | /**
74 | * Delete a statistic with the given name.
75 | *
76 | * @return boolean True if successful, false if not.
77 | */
78 | public function clear()
79 | {
80 | return (bool)$this->resque->getClient()->del($this->getKey());
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/test/AbstractJobTest.php:
--------------------------------------------------------------------------------
1 |
13 | * @license http://www.opensource.org/licenses/mit-license.php
14 | */
15 | class AbstractJobTest extends Test
16 | {
17 | /**
18 | * @var Worker
19 | */
20 | protected $worker;
21 |
22 | public function setUp()
23 | {
24 | parent::setUp();
25 |
26 | // Register a worker to test with
27 | $this->worker = new Worker($this->resque, 'jobs');
28 | $this->worker->setLogger($this->logger);
29 | $this->worker->register();
30 | }
31 |
32 | public function testJobCanBeQueued()
33 | {
34 | $this->assertTrue((bool)$this->resque->enqueue('jobs', 'Resque\Test\Job'));
35 | }
36 |
37 | public function testQueuedJobCanBeReserved()
38 | {
39 | $this->resque->enqueue('jobs', 'Resque\Test\Job');
40 |
41 | $worker = $this->getWorker('jobs');
42 |
43 | $job = $worker->reserve('jobs');
44 |
45 | if ($job == false) {
46 | $this->fail('Job could not be reserved.');
47 | }
48 |
49 | $this->assertEquals('jobs', $job->getQueue());
50 | $this->assertEquals('Resque\Test\Job', $job['class']);
51 | }
52 |
53 | /**
54 | * @expectedException \InvalidArgumentException
55 | */
56 | public function testObjectArgumentsCannotBePassedToJob()
57 | {
58 | $args = new \stdClass;
59 | $args->test = 'somevalue';
60 | $this->resque->enqueue('jobs', 'Resque\Test\Job', $args);
61 | }
62 |
63 | public function testFailedJobExceptionsAreCaught()
64 | {
65 | $this->resque->clearQueue('jobs');
66 |
67 | $job = new FailingJob('jobs', array(
68 | 'class' => 'Resque\Test\FailingJob',
69 | 'args' => null,
70 | 'id' => 'failing_test_job'
71 | ));
72 | $job->setResque($this->resque);
73 |
74 | $this->worker->perform($job);
75 |
76 | $failed = new Statistic($this->resque, 'failed');
77 | $workerFailed = new Statistic($this->resque, 'failed:' . (string)$this->worker);
78 |
79 | $this->assertEquals(1, $failed->get());
80 | $this->assertEquals(1, $workerFailed->get());
81 | }
82 |
83 | public function testInvalidJobReservesNull()
84 | {
85 | $this->resque->enqueue('jobs', 'Resque\Test\NoPerformJob');
86 | $job = $this->worker->reserve();
87 | $this->assertNull($job);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/Console/EnqueueCommand.php:
--------------------------------------------------------------------------------
1 | setName('enqueue')
21 | ->setDescription('Enqueues a job into a queue')
22 | ->addArgument(
23 | 'queue',
24 | InputArgument::REQUIRED,
25 | 'The name of the queue where the job should be enqueued'
26 | )
27 | ->addArgument(
28 | 'class',
29 | InputArgument::REQUIRED,
30 | 'The class name of the job to enqueue'
31 | )
32 | ->addOption(
33 | 'payload',
34 | 'p',
35 | InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED,
36 | 'A payload to send with the job',
37 | null
38 | )
39 | ->addOption(
40 | 'json',
41 | 'j',
42 | InputOption::VALUE_REQUIRED,
43 | 'A JSON payload to send with the job (specify as a JSON encoded string)',
44 | null
45 | )
46 | ->addOption(
47 | 'track',
48 | 't',
49 | InputOption::VALUE_NONE,
50 | 'If present, the job will be tracked'
51 | );
52 | }
53 |
54 | /**
55 | * @param InputInterface $input
56 | * @param OutputInterface $output
57 | * @return int
58 | */
59 | protected function execute(InputInterface $input, OutputInterface $output)
60 | {
61 | $resque = $this->getResque($output);
62 | $queue = $input->getArgument('queue');
63 |
64 | $id = $resque->enqueue(
65 | $queue,
66 | $input->getArgument('class'),
67 | $this->getPayload($input),
68 | $input->getOption('track')
69 | );
70 |
71 | if ($id) {
72 | $message = sprintf('Enqueued job as %s to %s', $id, $queue);
73 | } else {
74 | $message = sprintf('Enqueued job to %s', $queue);
75 | }
76 |
77 | $output->writeln($message);
78 | return 0;
79 | }
80 |
81 | /**
82 | * @param InputInterface $input
83 | * @return array|mixed
84 | */
85 | protected function getPayload(InputInterface $input)
86 | {
87 | if ($input->hasOption('payload') &&
88 | 0 < count($input->getOption('payload'))) {
89 | return $input->getOption('payload');
90 | }
91 |
92 | if ($input->hasOption('json')) {
93 | return json_decode($input->getOption('json'), true);
94 | }
95 |
96 | return array();
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Client/ClientInterface.php:
--------------------------------------------------------------------------------
1 |
137 | */
138 | public function hgetall($key);
139 |
140 | /**
141 | * @param string $key
142 | * @param array $hash
143 | * @return boolean|\Predis\Response\ResponseInterface
144 | */
145 | public function hmset($key, array $hash);
146 |
147 | /**
148 | * @param string $key
149 | * @param string $field
150 | * @param integer $increment
151 | * @return integer The value at field after the increment operation
152 | */
153 | public function hincrby($key, $field, $increment);
154 | }
155 |
--------------------------------------------------------------------------------
/src/Failure/RedisBackend.php:
--------------------------------------------------------------------------------
1 |
14 | * @license http://www.opensource.org/licenses/mit-license.php
15 | */
16 | class RedisBackend implements BackendInterface
17 | {
18 | /**
19 | * Initialize a failed job class and save it (where appropriate).
20 | *
21 | * @param object $payload Object containing details of the failed job.
22 | * @param Exception $exception Instance of the exception that was thrown by the failed job.
23 | * @param Worker $worker Instance of Worker that received the job.
24 | * @param string $queue The name of the queue the job was fetched from.
25 | */
26 | public function receiveFailure($payload, Exception $exception, Worker $worker, $queue)
27 | {
28 | $data = new stdClass;
29 | $data->failed_at = strftime('%a %b %d %H:%M:%S %Z %Y');
30 | $data->payload = $payload;
31 | $data->exception = $this->getClass($exception);
32 | $data->error = $this->getErrorMessage($this->getDistalCause($exception));
33 | $data->worker = (string)$worker;
34 | $data->queue = $queue;
35 | $data->backtrace = $this->getBacktrace($exception);
36 |
37 | $data = json_encode($data);
38 | $worker->getResque()->getClient()->rpush($worker->getResque()->getKey('failed'), $data);
39 | }
40 |
41 | /**
42 | * Gets the backtrace for the exception
43 | *
44 | * The backtrace area is the only part of the failure that's shown on
45 | * multiple lines by resque-web. So, we'll also use it to mention the
46 | * wrapping exceptions.
47 | *
48 | * @param \Exception $exception
49 | * @return array
50 | */
51 | protected function getBacktrace(\Exception $exception)
52 | {
53 | $backtrace = array();
54 |
55 | $backtrace[] = '---';
56 | $backtrace[] = $this->getErrorMessage($exception);
57 | $backtrace[] = '---';
58 |
59 | // Allow marshalling of the trace: PHP marks getTraceAsString as final :-(
60 | if (method_exists($exception, 'getPreviousTraceAsString')) {
61 | $backtrace = array_merge($backtrace, explode("\n", $exception->getPreviousTraceAsString()));
62 | } else {
63 | $backtrace = array_merge($backtrace, explode("\n", $exception->getTraceAsString()));
64 | }
65 |
66 | if (($previous = $exception->getPrevious())) {
67 | $backtrace = array_merge($backtrace, $this->getBacktrace($previous)); // Recurse
68 | }
69 |
70 | return $backtrace;
71 | }
72 |
73 | /**
74 | * Find the ultimate cause exception, by following previous members right back
75 | *
76 | * @param Exception $exception
77 | * @return Exception
78 | */
79 | protected function getDistalCause(\Exception $exception)
80 | {
81 | if (($previous = $exception->getPrevious())) {
82 | return $this->getDistalCause($previous);
83 | }
84 | return $exception;
85 | }
86 |
87 | /**
88 | * Find the class names of the exceptions
89 | *
90 | * @param Exception $exception
91 | * @return string
92 | */
93 | protected function getClass(\Exception $exception)
94 | {
95 | $message = '';
96 |
97 | if (($previous = $exception->getPrevious())) {
98 | $message = $this->getClass($previous) . ' < '; // Recurse
99 | }
100 |
101 | // Let the exception lie about its class: to support marshalling exceptions
102 | if (method_exists($exception, 'getClass')) {
103 | $message .= $exception->getClass();
104 | } else {
105 | $message .= get_class($exception);
106 | }
107 |
108 | return $message;
109 | }
110 |
111 | /**
112 | * Gets a single string error message from the exception
113 | *
114 | * @param \Exception $exception
115 | * @return string
116 | */
117 | protected function getErrorMessage(\Exception $exception)
118 | {
119 | return $exception->getMessage() . ' at ' . $exception->getFile() . ':' . $exception->getLine();
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/AbstractJob.php:
--------------------------------------------------------------------------------
1 |
18 | * @license http://www.opensource.org/licenses/mit-license.php
19 | */
20 | abstract class AbstractJob implements ArrayAccess, IteratorAggregate, JobInterface
21 | {
22 | /**
23 | * @var string The ID of the job
24 | */
25 | protected $id;
26 |
27 | /**
28 | * @var string The name of the queue that this job belongs to.
29 | */
30 | protected $queue;
31 |
32 | /**
33 | * @var Resque The instance of Resque this job belongs to.
34 | */
35 | protected $resque;
36 |
37 | /**
38 | * @var array Containing details of the job.
39 | */
40 | protected $payload;
41 |
42 | /**
43 | * Instantiate a new instance of a job.
44 | *
45 | * @param string $queue The queue that the job belongs to.
46 | * @param array $payload array containing details of the job.
47 | */
48 | public function __construct($queue, array $payload)
49 | {
50 | $this->queue = $queue;
51 | $this->payload = $payload;
52 | $this->id = isset($payload['id']) ? $payload['id'] : null;
53 | }
54 |
55 | /**
56 | * @param Resque $resque
57 | */
58 | public function setResque(Resque $resque)
59 | {
60 | $this->resque = $resque;
61 | }
62 |
63 | /**
64 | * @return string
65 | */
66 | public function getQueue()
67 | {
68 | return $this->queue;
69 | }
70 |
71 | /**
72 | * Gets a status instance for this job
73 | *
74 | * @throws InvalidArgumentException
75 | * @throws JobLogicException
76 | * @return \Resque\Job\Status
77 | */
78 | protected function getStatus()
79 | {
80 | if (!$this->resque) {
81 | throw new JobLogicException('Job has no Resque instance: cannot get status');
82 | }
83 |
84 | return $this->resque->getStatusFactory()->forJob($this);
85 | }
86 |
87 | public function getId()
88 | {
89 | return $this->id;
90 | }
91 |
92 | public function getPayload()
93 | {
94 | return $this->payload;
95 | }
96 |
97 | /**
98 | * Execute the job
99 | *
100 | * @return bool
101 | */
102 | abstract public function perform();
103 |
104 | /**
105 | * Re-queue the current job.
106 | *
107 | * @return string ID of the recreated job
108 | */
109 | protected function recreate()
110 | {
111 | $status = $this->getStatus();
112 | $tracking = $status->isTracking();
113 |
114 | $new = $this->resque->enqueue(
115 | $this->queue,
116 | $this->payload['class'],
117 | $this->payload['args'],
118 | $tracking
119 | );
120 |
121 | if ($tracking) {
122 | $status->update(Status::STATUS_RECREATED);
123 | $status->setAttribute('recreated_as', $new);
124 | }
125 |
126 | return $new;
127 | }
128 |
129 | /**
130 | * Generate a string representation used to describe the current job.
131 | *
132 | * @return string The string representation of the job.
133 | */
134 | public function __toString()
135 | {
136 | $name = array(
137 | 'Job{' . $this->queue .'}'
138 | );
139 |
140 | if(!empty($this->id)) {
141 | $name[] = 'ID: ' . $this->id;
142 | }
143 |
144 | $name[] = $this->payload['class'];
145 |
146 | if(!empty($this->payload['args'])) {
147 | $name[] = json_encode($this->payload['args']);
148 | }
149 |
150 | return '(' . implode(' | ', $name) . ')';
151 | }
152 |
153 | /**
154 | * @see ArrayAccess::offsetSet()
155 | */
156 | public function offsetSet($offset, $value)
157 | {
158 | if (is_null($offset)) {
159 | $this->payload[] = $value;
160 | } else {
161 | $this->payload[$offset] = $value;
162 | }
163 | }
164 |
165 | /**
166 | * @see ArrayAccess::offsetExists()
167 | */
168 | public function offsetExists($offset)
169 | {
170 | return isset($this->payload[$offset]);
171 | }
172 |
173 | /**
174 | * @see ArrayAccess::offsetUnset()
175 | */
176 | public function offsetUnset($offset)
177 | {
178 | unset($this->payload[$offset]);
179 | }
180 |
181 | /**
182 | * @see ArrayAccess::offsetGet()
183 | */
184 | public function offsetGet($offset)
185 | {
186 | return isset($this->payload[$offset]) ? $this->payload[$offset] : null;
187 | }
188 |
189 | /**
190 | * @see IteratorAggregate::getIterator()
191 | */
192 | public function getIterator()
193 | {
194 | return new \ArrayIterator($this->payload);
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/test/Job/StatusTest.php:
--------------------------------------------------------------------------------
1 |
17 | * @license http://www.opensource.org/licenses/mit-license.php
18 | */
19 | class StatusTest extends Test
20 | {
21 | public function tearDown()
22 | {
23 | parent::tearDown();
24 |
25 | $this->resque->clearQueue('jobs');
26 | }
27 |
28 | public function testConstructor()
29 | {
30 | $status = new Status(uniqid(__CLASS__, true), $this->resque);
31 | }
32 |
33 | public function testJobStatusCanBeTracked()
34 | {
35 | $this->resque->clearQueue('jobs');
36 | $token = $this->resque->enqueue('jobs', 'Resque\Test\Job', null, true);
37 |
38 | $status = new Status($token, $this->resque);
39 | $this->assertTrue($status->isTracking());
40 | }
41 |
42 | public function testJobStatusIsReturnedViaJobInstance()
43 | {
44 | $this->resque->clearQueue('jobs');
45 |
46 | $token = $this->resque->enqueue('jobs', 'Resque\Test\Job', null, true);
47 |
48 | $worker = $this->getWorker('jobs');
49 | $job = $worker->reserve();
50 |
51 | if (!$job) {
52 | $this->fail('Could not get job');
53 | }
54 |
55 | $status = $this->resque->getStatusFactory()->forJob($job);
56 | $status = $status->get();
57 |
58 | $this->assertEquals(Status::STATUS_WAITING, $status);
59 | }
60 |
61 | public function testQueuedJobReturnsQueuedStatus()
62 | {
63 | $token = $this->resque->enqueue('jobs', 'Resque\Test\Job', null, true);
64 | $status = new Status($token, $this->resque);
65 | $this->assertEquals(Status::STATUS_WAITING, $status->get());
66 | }
67 |
68 | public function testQueuedJobReturnsCreatedAndUpdatedKeys()
69 | {
70 | $token = $this->resque->enqueue('jobs', 'Resque\Test\Job', null, true);
71 | $status = new Status($token, $this->resque);
72 | $this->assertGreaterThan(0, $status->getCreated());
73 | $this->assertGreaterThan(0, $status->getUpdated());
74 | }
75 |
76 | public function testStartingQueuedJobUpdatesUpdatedAtStatus()
77 | {
78 | $this->resque->clearQueue('jobs');
79 |
80 | $token = $this->resque->enqueue('jobs', 'Resque\Test\Job', null, true);
81 |
82 | $status = new Status($token, $this->resque);
83 | $old_created = $status->getCreated();
84 | $old_updated = $status->getUpdated();
85 |
86 | sleep(1);
87 |
88 | $worker = $this->getWorker('jobs');
89 |
90 | $job = $worker->reserve();
91 |
92 | if (!$job) {
93 | $this->fail('Cannot get job');
94 | }
95 |
96 | $worker->workingOn($job);
97 |
98 | $status = new Status($token, $this->resque);
99 | $this->assertEquals(Status::STATUS_RUNNING, $status->get());
100 | $this->assertEquals($old_created, $status->getCreated());
101 | $this->assertGreaterThan($old_updated, $status->getUpdated());
102 | }
103 |
104 | public function testRunningJobReturnsRunningStatus()
105 | {
106 | $this->resque->clearQueue('jobs');
107 |
108 | $token = $this->resque->enqueue('jobs', 'Resque\Test\Job', null, true);
109 |
110 | $worker = $this->getWorker('jobs');
111 |
112 | $job = $worker->reserve();
113 |
114 | if (!$job) {
115 | $this->fail('Cannot get job');
116 | }
117 |
118 | $worker->workingOn($job);
119 |
120 | $status = new Status($token, $this->resque);
121 | $this->assertEquals(Status::STATUS_RUNNING, $status->get());
122 | }
123 |
124 | public function testFailedJobReturnsFailedStatus()
125 | {
126 | $pid = getmypid();
127 |
128 | $this->resque->clearQueue('jobs');
129 |
130 | $token = $this->resque->enqueue('jobs', 'Resque\Test\FailingJob', null, true);
131 |
132 | $worker = $this->getWorker('jobs');
133 |
134 | $status = new Status($token, $this->resque);
135 | $before = $status->get();
136 |
137 | $pid2 = getmypid();
138 |
139 | $worker->work(0);
140 |
141 | $pid3 = getmypid();
142 | $status = new Status($token, $this->resque);
143 | $after = $status->get();
144 |
145 | $this->assertEquals(Status::STATUS_FAILED, $after);
146 | }
147 |
148 | public function testCompletedJobReturnsCompletedStatus()
149 | {
150 | $this->resque->clearQueue('jobs');
151 |
152 | $token = $this->resque->enqueue('jobs', 'Resque\Test\Job', null, true);
153 |
154 | $worker = $this->getWorker('jobs');
155 | $worker->work(0);
156 |
157 | $status = new Status($token, $this->resque);
158 | $this->assertEquals(Status::STATUS_COMPLETE, $status->get());
159 | }
160 |
161 | public function testStatusIsNotTrackedWhenToldNotTo()
162 | {
163 | $token = $this->resque->enqueue('jobs', 'Resque\Test\Job', null, false);
164 | $status = new Status($token, $this->resque);
165 | $this->assertFalse($status->isTracking());
166 | }
167 |
168 | public function testStatusTrackingCanBeStopped()
169 | {
170 | $status = new Status('test', $this->resque);
171 | $status->create();
172 | $this->assertEquals(Status::STATUS_WAITING, $status->get());
173 |
174 | $status->stop();
175 | $this->assertNull($status->get());
176 | }
177 |
178 | /*
179 | public function testRecreatedJobWithTrackingStillTracksStatus()
180 | {
181 | $worker = $this->getWorker('jobs');
182 | $originalToken = $this->resque->enqueue('jobs', 'Resque\Test\Job', null, true);
183 |
184 | $job = $worker->reserve();
185 |
186 | if (!$job) {
187 | $this->fail('Could not reserve job');
188 | }
189 |
190 | // Mark this job as being worked on to ensure that the new status is still
191 | // waiting.
192 | $worker->workingOn($job);
193 |
194 | // Now recreate it
195 | $newToken = $job->recreate();
196 |
197 | // Make sure we've got a new job returned
198 | $this->assertNotEquals($originalToken, $newToken);
199 |
200 | // Now check the status of the new job
201 | $newJob = $worker->reserve();
202 |
203 | if (!$newJob) {
204 | $this->fail('Could not get newJob');
205 | }
206 |
207 | $this->assertEquals(Status::STATUS_WAITING, $newJob->getStatus()->get());
208 | }
209 | */
210 | }
211 |
--------------------------------------------------------------------------------
/test/Test/Settings.php:
--------------------------------------------------------------------------------
1 | testPid = getmypid();
28 | $this->fromDefaults();
29 | }
30 |
31 | protected function fromDefaults()
32 | {
33 | $this->buildDir = __DIR__ . '/../../build';
34 | $this->port = '6379';
35 | $this->host = 'localhost';
36 | $this->bind = '127.0.0.1';
37 | $this->db = 0;
38 | $this->prefix = '';
39 | $this->clientType = 'predis';
40 | }
41 |
42 | public function fromEnvironment()
43 | {
44 | $env = array(
45 | 'client_type' => 'clientType',
46 | 'host' => 'host',
47 | 'port' => 'port',
48 | 'bind' => 'bind',
49 | 'build_dir' => 'buildDir',
50 | 'run' => 'run',
51 | 'db' => 'db',
52 | 'prefix' => 'prefix'
53 | );
54 |
55 | foreach ($env as $var => $setting) {
56 | $name = 'RESQUE_' . strtoupper($var);
57 |
58 | if (isset($_SERVER[$name])) {
59 | $this->$setting = $_SERVER[$name];
60 | }
61 | }
62 | }
63 |
64 | public function setBuildDir($dir)
65 | {
66 | $this->buildDir = $dir;
67 | }
68 |
69 | public function startRedis()
70 | {
71 | $this->dumpRedisConfig();
72 | $this->registerShutdown();
73 |
74 | $this->logger->notice('Starting redis server in {buildDir}', array('buildDir' => $this->buildDir));
75 | exec('cd ' . $this->buildDir . '; redis-server ' . $this->buildDir . '/redis.conf', $output, $return);
76 | usleep(500000);
77 |
78 | if ($return != 0) {
79 | throw new \RuntimeException('Cannot start redis-server');
80 | }
81 | }
82 |
83 | protected function getRedisConfig()
84 | {
85 | return array(
86 | 'daemonize' => 'yes',
87 | 'pidfile' => './redis.pid',
88 | 'port' => $this->port,
89 | 'bind' => $this->bind,
90 | 'timeout' => 0,
91 | 'dbfilename' => 'dump.rdb',
92 | 'dir' => $this->buildDir,
93 | 'loglevel' => 'debug',
94 | 'logfile' => './redis.log'
95 | );
96 | }
97 |
98 | /**
99 | * @return ClientInterface
100 | * @throws \InvalidArgumentException
101 | */
102 | public function getClient()
103 | {
104 | switch ($this->clientType) {
105 | case 'predis':
106 | return new \Predis\Client(array(
107 | 'host' => $this->host,
108 | 'port' => $this->port,
109 | 'db' => $this->db,
110 | 'prefix' => $this->prefix
111 | ));
112 | case 'phpiredis':
113 | return new \Predis\Client(array(
114 | 'host' => $this->host,
115 | 'port' => $this->port,
116 | 'db' => $this->db,
117 | 'prefix' => $this->prefix
118 | ), array(
119 | 'tcp' => 'Predis\Connection\PhpiredisStreamConnection',
120 | 'unix' => 'Predis\Connection\PhpiredisSocketConnection'
121 | ));
122 | case 'credis':
123 | case 'phpredis':
124 | $client = new \Resque\Client\CredisClient($this->host, $this->port);
125 | $client->setCloseOnDestruct(false);
126 | return $client;
127 | default:
128 | throw new \InvalidArgumentException('Invalid or unknown client type: ' . $this->clientType);
129 | }
130 | }
131 |
132 | public function checkBuildDir()
133 | {
134 | if (!is_dir($this->buildDir)) {
135 | mkdir($this->buildDir);
136 | }
137 |
138 | if (!is_dir($this->buildDir)) {
139 | throw new RuntimeException('Could not create build dir: ' . $this->buildDir);
140 | }
141 | }
142 |
143 | protected function dumpRedisConfig()
144 | {
145 | $file = $this->buildDir . '/redis.conf';
146 | $conf = '';
147 |
148 | foreach ($this->getRedisConfig() as $name => $value) {
149 | $conf .= "$name $value\n";
150 | }
151 |
152 | $this->logger->info('Dumping redis config {config} to {file}', array('file' => $file, 'config' => $conf));
153 | file_put_contents($file, $conf);
154 | }
155 |
156 | // Override INT and TERM signals, so they do a clean shutdown and also
157 | // clean up redis-server as well.
158 | public function catchSignals()
159 | {
160 | if (function_exists('pcntl_signal')) {
161 | pcntl_signal(SIGINT, function () {
162 | $this->logger->debug('SIGINT received');
163 | exit;
164 | });
165 |
166 | pcntl_signal(SIGTERM, function () {
167 | $this->logger->debug('SIGTERM received');
168 | exit;
169 | });
170 | }
171 | }
172 |
173 | public function killRedis()
174 | {
175 | $pid = getmypid();
176 |
177 | $this->logger->notice('Attempting to kill redis from {pid}', array('pid' => $pid));
178 |
179 | if ($pid === null || $this->testPid !== $pid) {
180 | $this->logger->warning('Refusing to kill redis from forked worker');
181 | return; // don't kill from a forked worker
182 | }
183 |
184 | $pidFile = $this->buildDir . '/redis.pid';
185 |
186 | if (file_exists($pidFile)) {
187 | $pid = trim(file_get_contents($pidFile));
188 | posix_kill((int) $pid, 9);
189 |
190 | if(is_file($pidFile)) {
191 | unlink($pidFile);
192 | }
193 | }
194 |
195 | $filename = $this->buildDir . '/dump.rdb';
196 |
197 | if (is_file($filename)) {
198 | unlink($filename);
199 | }
200 | }
201 |
202 | protected function registerShutdown()
203 | {
204 | $this->logger->info('Registered shutdown function');
205 | register_shutdown_function(array($this, 'killRedis'));
206 | }
207 |
208 | /**
209 | * Sets a logger instance on the object
210 | *
211 | * @param LoggerInterface $logger
212 | * @return null
213 | */
214 | public function setLogger(LoggerInterface $logger)
215 | {
216 | $this->logger = $logger;
217 | }
218 |
219 | /**
220 | * @return LoggerInterface
221 | */
222 | public function getLogger()
223 | {
224 | return $this->logger;
225 | }
226 |
227 | /**
228 | * Dumps configuration to a file
229 | *
230 | * @return void
231 | */
232 | public function dumpConfig()
233 | {
234 | $file = $this->buildDir . \DIRECTORY_SEPARATOR . 'settings.json';
235 | $config = json_encode(get_object_vars($this), JSON_PRETTY_PRINT);
236 |
237 | $this->logger->info('Dumping test config {config} to {file}', array('file' => $file, 'config' => $config));
238 | file_put_contents($file, $config);
239 | }
240 | }
241 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Forked version to add namespacing
2 |
3 | ### 2.0.0 ##
4 |
5 | **Note:**: vend/php-resque introduces namespacing and other backward incompatible changes to the API.
6 |
7 | The code is still released under the same license. With vend/php-resque, we've
8 | chosen to increment the major version number to reflect the changes, but this
9 | is a fork that we use at Vend, where we use Predis, and using the original
10 | chrisboulton/php-resque version wasn't an option.
11 |
12 | #### Namespacing
13 |
14 | All resque code is loaded from the `Resque` namespace. This means a slight
15 | change in layout to support PSR-0 (given, for instance, `Resque` is now
16 | `Resque\Resque`)
17 |
18 | #### Backend coupling removed
19 |
20 | Direct Credis coupling has been removed: Resque is now free of coupling to any Redis
21 | client library, meaning you can use it with Predis, Credis, redisent, etc.
22 |
23 | Instead of this library creating a Redis client instance for you, you'll be
24 | expected to provide a Redis client object to the Resque instance
25 | as a dependency. This helps eliminate global state, and means the library can
26 | be readily integrated into, for example, your existing Depedency Injection
27 | container or factory methods.
28 |
29 | For Credis clients, a lightweight wrapper is provided (this is chiefly used to
30 | implement a reconnect method). Predis clients can be
31 | used directly with no wrapping class. With these two main client types,
32 | support for phpiredis, phpredis
33 | and webdis is provided indirectly.
34 |
35 | You can use any client that implements the *methods* in Resque\Client\ClientInterface
36 | - your client need not *actually* implement the interface (many Redis clients
37 | use `__call()`).
38 |
39 | #### Protected API improvements and general SOLIDness
40 |
41 | Hidden global state removed: connection state is no longer hidden behind
42 | Resque::redis() static call. The Resque object is now instantiated like any
43 | normal object, and uses dependency injection in its constructor.
44 |
45 | The mostly private API of the previous versions has been opened up
46 | considerably, meaning more opportunity for extending classes. At Vend, we use
47 | this to send jobs to an FPM pool of workers, rather than forking for every job.
48 |
49 | #### Worker shuffles queues
50 |
51 | The worker shuffles the queues it will dequeue from on every pass of the main loop.
52 | This means each queue a worker runs has an equal chance to be dequeued from. (The
53 | previous behaviour was to work the queues in the order specified).
54 |
55 | #### Statistic object wraps main value
56 |
57 | Gone is the static `Statistic::get/set` API. A Statistic object now wraps the counter
58 | value, which can be retrieved via `$statistic->get()`.
59 |
60 | #### Job implementation made more flexible
61 |
62 | Jobs now only need implement JobInterface. They can optionally do this by extending
63 | AbstractJob. Job instances are directly created by the Worker, rather than being attached
64 | to a tightly-coupled concrete Job implementation.
65 |
66 | ## chrisboulton/php-resque
67 |
68 | ### 1.3 (2013-??-??) ##
69 |
70 | #### Redisent (Redis Library) Replaced with Credis
71 |
72 | Redisent has always been the Redis backend for php-resque because of its lightweight nature. Unfortunately, Redisent is largely unmaintained.
73 |
74 | [Credis](https://github.com/colinmollenhour/credis) is a fork of Redisent, which among other improvements will automatically use the [phpredis](https://github.com/nicolasff/phpredis) native PHP extension if it is available. (you want this for speed, trust me)
75 |
76 | php-resque now utilizes Credis for all Redis based operations. Credis automatically required and installed as a Composer dependency.
77 |
78 | #### Composer Support
79 |
80 | Composer support has been improved and is now the recommended method for including php-resque in your project. Details on Composer support can be found in the Getting Started section of the readme.
81 |
82 | #### Other Improvements/Changes
83 |
84 | * **COMPATIBILITY BREAKING**: The bundled worker manager `resque.php` has been moved to `bin/resque`, and is available as `vendor/bin/resque` when php-resque is installed as a Composer package.
85 | * Restructure tests and test bootstrapping. Autoload tests via Composer (install test dependencies with `composer install --dev`)
86 | * Add `SETEX` to list of commands which supply a key as the first argument in Redisent (danhunsaker)
87 | * Fix an issue where a lost connection to Redis could cause an infinite loop (atorres757)
88 | * Add a helper method to `Resque_Redis` to remove the namespace applied to Redis keys (tonypiper)
89 |
90 | ### 1.2 (2012-10-13)
91 |
92 | **Note:** This release is largely backwards compatible with php-resque 1.1. The next release will introduce backwards incompatible changes (moving from Redisent to Credis), and will drop compatibility with PHP 5.2.
93 |
94 | * Allow alternate redis database to be selected when calling setBackend by supplying a second argument (patrickbajao)
95 | * Use `require_once` when including php-resque after the app has been included in the sample resque.php to prevent include conflicts (andrewjshults)
96 | * Wrap job arguments in an array to improve compatibility with ruby resque (warezthebeef)
97 | * Fix a bug where the worker would spin out of control taking the server with it, if the redis connection was interrupted even briefly. Use SIGPIPE to trap this scenario cleanly. (d11wtq)
98 | * Added support of Redis prefix (namespaces) (hlegius)
99 | * When reserving jobs, check if the payload received from popping a queue is a valid object (fix bug whereby jobs are reserved based on an erroneous payload) (salimane)
100 | * Re-enable autoload for class_exists in Job.php (humancopy)
101 | * Fix lost jobs when there is more than one worker process started by the same parent process (salimane)
102 | * Move include for resque before APP_INCLUDE is loaded in, so that way resque is available for the app
103 | * Avoid working with dirty worker IDs (salimane)
104 | * Allow UNIX socket to be passed to Resque when connecting to Redis (pedroarnal)
105 | * Fix typographical errors in PHP docblocks (chaitanyakuber)
106 | * Set the queue name on job instances when jobs are executed (chaitanyakuber)
107 | * Fix and add tests for Resque_Event::stopListening (ebernhardson)
108 | * Documentation cleanup (maetl)
109 | * Pass queue name to afterEvent callback
110 | * Only declare RedisException if it doesn't already exist (Matt Heath)
111 | * Add support for Composer
112 | * Fix missing and incorrect paths for Resque and Resque_Job_Status classes in demo (jjfrey)
113 | * Disable autoload for the RedisException class_exists call (scragg0x)
114 | * General tidy up of comments and files/folders
115 |
116 | ### 1.1 (2011-03-27)
117 |
118 | * Update Redisent library for Redis 2.2 compatibility. Redis 2.2 is now required. (thedotedge)
119 | * Trim output of `ps` to remove any prepended whitespace (KevBurnsJr)
120 | * Use `getenv` instead of `$_ENV` for better portability across PHP configurations (hobodave)
121 | * Add support for sub-second queue check intervals (KevBurnsJr)
122 | * Ability to specify a cluster/multiple redis servers and consistent hash between them (dceballos)
123 | * Change arguments for jobs to be an array as they're easier to work with in PHP.
124 | * Implement ability to have setUp and tearDown methods for jobs, called before and after every single run.
125 | * Fix `APP_INCLUDE` environment variable not loading correctly.
126 | * Jobs are no longer defined as static methods, and classes are instantiated first. This change is NOT backwards compatible and requires job classes are updated.
127 | * Job arguments are passed to the job class when it is instantiated, and are accessible by $this->args. This change will break existing job classes that rely on arguments that have not been updated.
128 | * Bundle sample script for managing php-resque instances using monit
129 | * Fix undefined variable `$child` when exiting on non-forking operating systems
130 | * Add `PIDFILE` environment variable to write out a PID for single running workers
131 |
132 | ### 1.0 (2010-04-18) ##
133 |
134 | * Initial release
135 |
--------------------------------------------------------------------------------
/test/WorkerTest.php:
--------------------------------------------------------------------------------
1 |
11 | * @license http://www.opensource.org/licenses/mit-license.php
12 | */
13 | class WorkerTest extends Test
14 | {
15 | public function testWorkerRegistersInList()
16 | {
17 | $worker = $this->getWorker('*');
18 |
19 | // Make sure the worker is in the list
20 | $this->assertTrue((bool)$this->redis->sismember('resque:workers', (string)$worker));
21 | }
22 |
23 | public function testGetAllWorkers()
24 | {
25 | $num = 3;
26 | // Register a few workers
27 | for($i = 0; $i < $num; ++$i) {
28 | $worker = $this->getWorker('queue_' . $i);
29 | }
30 |
31 | // Now try to get them
32 | $this->assertEquals($num, count($this->resque->getWorkerIds()));
33 | }
34 |
35 | public function testGetWorkerById()
36 | {
37 | $worker = $this->getWorker('*');
38 |
39 | $newWorker = new Worker($this->resque, '*');
40 | $newWorker->setId((string)$worker);
41 |
42 | $this->assertEquals((string)$worker, (string)$newWorker);
43 | }
44 |
45 |
46 | public function testWorkerCanUnregister()
47 | {
48 | $worker = $this->getWorker('*');
49 | $worker->unregister();
50 |
51 | $this->assertFalse($this->resque->workerExists((string)$worker));
52 | $this->assertEquals(array(), $this->resque->getWorkerIds());
53 | $this->assertEquals(array(), $this->redis->smembers('resque:workers'));
54 | }
55 |
56 | public function testPausedWorkerDoesNotPickUpJobs()
57 | {
58 | $this->resque->clearQueue('jobs');
59 |
60 | $worker = $this->getWorker('*');
61 | $worker->pauseProcessing();
62 |
63 | $this->resque->enqueue('jobs', 'Resque\Test\Job');
64 |
65 | $worker->work(0);
66 | $worker->work(0);
67 |
68 | $this->assertEquals(0, $worker->getStatistic('processed')->get());
69 | }
70 |
71 | /*
72 | public function testResumedWorkerPicksUpJobs()
73 | {
74 | $this->resque->clearQueue('jobs');
75 |
76 | $worker = $this->getWorker('*');
77 | $worker->pauseProcessing();
78 |
79 | $this->resque->enqueue('jobs', 'Resque\Test\Job');
80 | $worker->work(0);
81 |
82 | $this->assertEquals(0, $worker->getStatistic('processed')->get());
83 |
84 | $worker->unPauseProcessing();
85 | $worker->work(0);
86 |
87 | $this->assertEquals(1, $worker->getStatistic('processed')->get());
88 | }*/
89 |
90 | protected function clearQueues(array $queues)
91 | {
92 | foreach ($queues as $queue) {
93 | $this->resque->clearQueue($queue);
94 | }
95 | }
96 |
97 | public function testWorkerCanWorkOverMultipleQueues()
98 | {
99 | $queues = array(
100 | 'queue1',
101 | 'queue2'
102 | );
103 |
104 | $this->clearQueues($queues);
105 |
106 | $worker = $this->getWorker($queues);
107 |
108 | $this->resque->enqueue($queues[0], 'Resque\Test\Job');
109 | $this->resque->enqueue($queues[1], 'Resque\Test\Job');
110 |
111 | $job = $worker->reserve();
112 |
113 | if (!$job) {
114 | $this->fail('Cannot reserve job');
115 | }
116 |
117 | $this->assertTrue(in_array($job->getQueue(), $queues), 'Job from valid queues');
118 |
119 | $job = $worker->reserve();
120 |
121 | if (!$job) {
122 | $this->fail('Cannot reserve job');
123 | }
124 |
125 | $this->assertTrue(in_array($job->getQueue(), $queues), 'Job from valid queues');
126 | }
127 |
128 | public function testWildcardQueueWorkerWorksAllQueues()
129 | {
130 | $queues = array(
131 | 'queue1',
132 | 'queue2'
133 | );
134 |
135 | $this->clearQueues($queues);
136 |
137 | $worker = $this->getWorker('*');
138 |
139 | $this->resque->enqueue($queues[0], 'Resque\Test\Job');
140 | $this->resque->enqueue($queues[1], 'Resque\Test\Job');
141 |
142 | $job = $worker->reserve();
143 |
144 | if (!$job) {
145 | $this->fail('Cannot reserve job');
146 | }
147 |
148 | $this->assertTrue(in_array($job->getQueue(), $queues), 'Job from valid queues');
149 |
150 | $job = $worker->reserve();
151 |
152 | if (!$job) {
153 | $this->fail('Cannot reserve job');
154 | }
155 |
156 | $this->assertTrue(in_array($job->getQueue(), $queues), 'Job from valid queues');
157 | }
158 |
159 | public function testWorkerDoesNotWorkOnUnknownQueues()
160 | {
161 | $worker = $this->getWorker('queue1');
162 |
163 | $this->resque->enqueue('queue2', 'Resque\Test\Job');
164 |
165 | $this->assertNull($worker->reserve());
166 | }
167 |
168 | public function testWorkerClearsItsStatusWhenNotWorking()
169 | {
170 | $this->resque->enqueue('jobs', 'Resque\Test\Job');
171 | $worker = $this->getWorker('jobs');
172 | $job = $worker->reserve();
173 |
174 | if (!$job) {
175 | $this->fail('Could not reserve job');
176 | }
177 |
178 | $worker->workingOn($job);
179 | $worker->doneWorking();
180 | $this->assertEquals(array(), $worker->job());
181 | }
182 |
183 | public function testWorkerRecordsWhatItIsWorkingOn()
184 | {
185 | $worker = $this->getWorker('jobs');
186 |
187 | $payload = array(
188 | 'class' => 'Resque\Test\Job',
189 | 'id' => 'test'
190 | );
191 |
192 | $job = new Job('jobs', $payload);
193 | $worker->workingOn($job);
194 |
195 | $job = $worker->job();
196 |
197 | if (!is_array($job)) {
198 | $this->fail('Could not get job being worked on');
199 | }
200 |
201 | $this->assertEquals('jobs', $job['queue']);
202 |
203 | if(!isset($job['run_at'])) {
204 | $this->fail('Job does not have run_at time');
205 | }
206 |
207 | $this->assertEquals($payload, $job['payload']);
208 | }
209 |
210 | /*
211 | public function testWorkerErasesItsStatsWhenShutdown()
212 | {
213 | $this->resque->enqueue('jobs', 'Resque\Test\Job');
214 | $this->resque->enqueue('jobs', 'Resque\Test\FailingJob');
215 |
216 | $worker = $this->getWorker('jobs');
217 |
218 | $worker->work(0);
219 | $worker->shutdown();
220 | $worker->work(0);
221 |
222 | $this->resque->clearQueue('jobs');
223 |
224 | $this->assertEquals(0, $worker->getStatistic('processed')->get());
225 | $this->assertEquals(0, $worker->getStatistic('failed')->get());
226 | }*/
227 |
228 | public function testWorkerCleansUpDeadWorkersOnStartup()
229 | {
230 | // Register a good worker
231 | $goodWorker = new Worker($this->resque, 'jobs');
232 | $goodWorker->setLogger($this->logger);
233 | $goodWorker->register();
234 | $goodWorker = $this->getWorker('jobs');
235 |
236 | $workerId = explode(':', $goodWorker);
237 |
238 | // Register some bad workers
239 | $worker = new Worker($this->resque, 'jobs');
240 | $worker->setLogger($this->logger);
241 | $worker->setId($workerId[0].':1:jobs');
242 | $worker->register();
243 |
244 | $worker = new Worker($this->resque, array('high', 'low'));
245 | $worker->setLogger($this->logger);
246 | $worker->setId($workerId[0].':2:high,low');
247 | $worker->register();
248 |
249 | $this->assertEquals(3, count($this->resque->getWorkerIds()));
250 |
251 | $goodWorker->pruneDeadWorkers();
252 |
253 | // There should only be $goodWorker left now
254 | $this->assertEquals(1, count($this->resque->getWorkerIds()));
255 | }
256 |
257 | public function testDeadWorkerCleanUpDoesNotCleanUnknownWorkers()
258 | {
259 | // Register a bad worker on this machine
260 | $worker = new Worker($this->resque, 'jobs');
261 | $worker->setLogger($this->logger);
262 | $workerId = explode(':', $worker);
263 | $worker->setId($workerId[0].':1:jobs');
264 | $worker->register();
265 |
266 | // Register some other false workers
267 | $worker = new Worker($this->resque, 'jobs');
268 | $worker->setLogger($this->logger);
269 | $worker->setId('my.other.host:1:jobs');
270 | $worker->register();
271 |
272 | $this->assertEquals(2, count($this->resque->getWorkerIds()));
273 |
274 | $worker->pruneDeadWorkers();
275 |
276 | // my.other.host should be left
277 | $workers = $this->resque->getWorkerIds();
278 | $this->assertEquals(1, count($workers));
279 | $this->assertEquals((string)$worker, (string)$workers[0]);
280 | }
281 |
282 | public function testWorkerFailsUncompletedJobsOnExit()
283 | {
284 | $backend = $this->getMockForAbstractClass('Resque\Failure\BackendInterface');
285 |
286 | $backend->expects($this->once())
287 | ->method('receiveFailure');
288 |
289 | $this->resque->setFailureBackend($backend);
290 |
291 | $worker = $this->getWorker('jobs');
292 |
293 | $job = new Job('jobs', array(
294 | 'class' => 'Resque\Test\Job',
295 | 'id' => __METHOD__
296 | ));
297 | $job->setResque($this->resque);
298 |
299 | $worker->workingOn($job);
300 | $worker->unregister();
301 | }
302 |
303 | public function testBlockingListPop()
304 | {
305 | $worker = $this->getWorker('jobs');
306 |
307 | $this->resque->enqueue('jobs', 'Resque\Test\Job');
308 | $this->resque->enqueue('jobs', 'Resque\Test\Job');
309 |
310 | $i = 1;
311 | while($job = $worker->reserve(true, 1))
312 | {
313 | $this->assertEquals('Resque\Test\Job', $job['class']);
314 |
315 | if($i == 2) {
316 | break;
317 | }
318 |
319 | $i++;
320 | }
321 |
322 | $this->assertEquals(2, $i);
323 | }
324 |
325 | public function testReestablishRedisConnection()
326 | {
327 | $worker = $this->getWorker('jobs');
328 | $worker->reestablishRedisConnection();
329 | }
330 | }
331 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Resque for PHP
2 | ## Namespaced Fork
3 |
4 | [](https://travis-ci.org/vend/php-resque)
5 | [](https://packagist.org/packages/vend/resque)
6 | [](https://packagist.org/packages/vend/resque)
7 | [](https://packagist.org/packages/vend/resque)
8 |
9 | [Resque](https://github.com/resque/resque) is a Redis-backed library for creating background jobs, placing
10 | those jobs on one or more queues, and processing them later.
11 |
12 | This is a PHP fork of the Resque worker and job classes. This makes it compatible with the
13 | resque-web interface, and with other Resque libraries. (You could enqueue jobs from Ruby and dequeue
14 | them in PHP, for instance).
15 |
16 | This library (`vend/resque`) is a fork of [chrisboulton/php-resque](https://github.com/chrisboulton/php-resque) at around
17 | version 1.3, that has been refactored to remove global state, add namespacing, and improve
18 | decoupling. This makes it easier to use and extend.
19 |
20 | ## Getting Started
21 |
22 | Add `vend/resque` to your application's composer.json.
23 |
24 | ```json
25 | {
26 | "require": {
27 | "vend/resque": "~2.1.0"
28 | }
29 | }
30 | ```
31 |
32 | ## Requirements
33 |
34 | * PHP 5.3+
35 | * A Redis client library (for instance, [Predis](https://github.com/nrk/predis) or [Credis](https://github.com/colinmollenhour/credis))
36 |
37 | ## Jobs
38 |
39 | ### Queueing Jobs
40 |
41 | Jobs are queued as follows:
42 |
43 | ```php
44 | use Resque\Resque;
45 | use Predis\Client;
46 |
47 | $resque = new Resque(new Client());
48 | $resque->enqueue('default_queue', 'App\Job', array('foo' => 'bar'), true);
49 | ```
50 |
51 | In order the arguments are: queue, job class, job payload, whether to enable tracking.
52 |
53 | ### Defining Jobs
54 |
55 | Each job should be in its own class, and implement the `Resque\JobInterface`. (This is pretty easy,
56 | and only really requires a single custom method: `perform()`.) Most of the time, you'll want to
57 | use the default implementation of a Job, and extend the `Resque\AbstractJob` instead of implementing
58 | the interface yourself:
59 |
60 | ```php
61 | namespace App;
62 |
63 | use Resque\AbstractJob;
64 |
65 | class Job extends AbstractJob
66 | {
67 | public function perform()
68 | {
69 | // work work work
70 | $this->doSomething($this->payload['foo']);
71 | }
72 | }
73 | ```
74 |
75 | Any exception thrown by a job will result in the job failing - be
76 | careful here and make sure you handle the exceptions that shouldn't
77 | result in a job failing.
78 |
79 | ### Job Status Tracking
80 |
81 | vend/resque has the ability to perform basic status tracking of a queued
82 | job. The status information will allow you to check if a job is in the
83 | queue, is currently being run, has finished, or has failed.
84 |
85 | To track the status of a job, pass `true` as the fourth argument to
86 | `Resque\Resque::enqueue`. An ID used for tracking the job status will be
87 | returned:
88 |
89 | ```php
90 | $id = $resque->enqueue('default_queue', 'App\Job', $payload, true);
91 | echo $id; // [0-9a-f]{32}
92 | ```
93 |
94 | To fetch the status of a job:
95 |
96 | ```php
97 | $factory = new Resque\Job\StatusFactory($resque);
98 |
99 | // Pass the ID returned from enqueue
100 | $status = $factory->forId($id);
101 |
102 | // Alternatively, to get the status for a Job instance:
103 | $status = $factory->forJob($job);
104 |
105 | // Outputs the status as a string: 'waiting', 'running', 'complete', etc.
106 | echo $status->getStatus();
107 | ```
108 |
109 | The Status object contains methods for adding other attributes to the
110 | tracked status. (For instance, you might use the status object to track
111 | errors, completion information, iterations, etc.)
112 |
113 | #### Statuses
114 |
115 | Job statuses are defined as constants in the `Resque\Job\Status` class.
116 | Valid statuses include:
117 |
118 | * `Resque\Job\Status::STATUS_WAITING` - Job is still queued
119 | * `Resque\Job\Status::STATUS_RUNNING` - Job is currently running
120 | * `Resque\Job\Status::STATUS_FAILED` - Job has failed
121 | * `Resque\Job\Status::STATUS_COMPLETE` - Job is complete
122 |
123 | Statuses are available for up to 24 hours after a job has completed
124 | or failed, and are then automatically expired. A status can also
125 | forcefully be expired by calling the `stop()` method on a status
126 | class.
127 |
128 | ## Console
129 |
130 | You are free you implement your own daemonization/worker pool strategy by
131 | subclassing the `Worker` class. For playing around, and low throughput
132 | applications, you might like to use the built-in console commands.
133 |
134 | This version of the library uses the Symfony2 Console component. But it
135 | must be configured with the details of your Redis connection. We do this
136 | in much the same way that the Doctrine2 Console component gets details
137 | of your database connection: via a `cli-config.php` file.
138 |
139 | There is an example `cli-config.php` in this repository.
140 |
141 | If you're running a full-stack web application, you'd generally use your
142 | locator/service container to fill in the Redis client connection in this
143 | file. (The default is to use Predis, and to connect to 127.0.0.1:6379).
144 |
145 | ### Basic Usage
146 |
147 | ```
148 | resque
149 |
150 | Available commands:
151 | enqueue Enqueues a job into a queue
152 | help Displays help for a command
153 | list Lists commands
154 | worker Runs a Resque worker
155 | queue
156 | queue:clear Clears a specified queue
157 | queue:list Outputs information about queues
158 | ```
159 |
160 | ### Enqueueing
161 |
162 | This command will enqueue a `Some\Test\Job` job onto the `default` queue.
163 | Watch out for the single quotes around the class name: when specifying backslashes
164 | on the command line, you'll probably have to avoid your shell escaping them.
165 |
166 | ```
167 | resque enqueue default 'Some\Test\Job' -t
168 | ```
169 |
170 | ### Worker
171 |
172 | This command will run a simple pre-forking worker on two queues:
173 |
174 | ```
175 | resque worker -Q default -Q some_other_queue
176 | ```
177 |
178 | (`-q` means quiet, `-Q` specifies queues). You can also specify no queues, or
179 | the special queue `'*'` (watch for shell expansion).
180 |
181 |
182 | ### Queue Information
183 |
184 | There are a couple of useful commands for getting information about the queues. This
185 | will show a list of queues and how many jobs are waiting on each:
186 |
187 | ```
188 | resque queue:list
189 | ```
190 |
191 | This command will clear a specified queue:
192 |
193 | ```
194 | resque queue:clear default
195 | ```
196 |
197 | ### Logging
198 |
199 | The library now uses PSR3. When running as a console component, you can customise
200 | the logger to use in `cli-config.php`. (For instance, you might like to send your
201 | worker logs to Monolog.)
202 |
203 | ## Why Fork?
204 |
205 | Unfortunately, several things about the existing versions of php-resque made it
206 | a candidate for refactoring:
207 |
208 | * php-resque supported the Credis connection library and had hard-coded this
209 | support by using static accessors. This meant more advanced features such as
210 | replication and pipelining were unavailable.
211 | * Now, Resque for PHP supports any client object that implements a suitable
212 | subset of Redis commands. No type-checking is done on the passed in connection,
213 | meaning you're free to use Predis, or whatever you like.
214 | * While the public API of `php-resque` was alright, the protected API was pretty
215 | much useless. This made it hard to extend worker classes to dispatch jobs differently.
216 | * Important state (the underlying connection) was thrown into the global scope
217 | by hiding it behind static accessors (`Resque::redis()`). This makes things
218 | easier for the library author (because he/she need not think about dependencies)
219 | but also statically ties together the classes in the library: it makes
220 | testing and extending the library hard.
221 | * There's no reason to do this: `Resque` instances should simply use DI and
222 | take a client connection as a required constructor argument.
223 | * This improvement also allows the connection to be mocked without extending
224 | the `Resque` class.
225 | * And it lets you reuse your existing connection to Redis, if you have one.
226 | No need to open a new connection just to enqueue a job.
227 | * Statistic classes were static for no good reason.
228 | * To work, statistics need a connection to Redis, a name, and several methods
229 | (get, set). State and methods to manipulate it? Sounds like a task for
230 | objects! *Not* just static methods, that don't encapsulate any of the state.
231 | * Because these were static calls, the `Resque_Stat` class was hard-coded into
232 | several other classes, and could not be easily extended.
233 | * The library is now fully namespaced and compatible with PSR-0. The top level
234 | namespace is `Resque`.
235 | * The events system has been removed. There is now little need for it.
236 | It seems like the events system was just a workaround due to the poor
237 | extensibility of the Worker class. The library should allow you to extend any
238 | class you like, and no method should be too long or arduous to move into a
239 | subclass.
240 |
241 | ## Contributors ##
242 |
243 | Here's the contributor list from earlier versions, at [chrisboulton/php-resque](https://github.com/chrisboulton/php-resque):
244 |
245 | * @chrisboulton
246 | * @acinader
247 | * @ajbonner
248 | * @andrewjshults
249 | * @atorres757
250 | * @benjisg
251 | * @cballou
252 | * @chaitanyakuber
253 | * @charly22
254 | * @CyrilMazur
255 | * @d11wtq
256 | * @danhunsaker
257 | * @dceballos
258 | * @ebernhardson
259 | * @hlegius
260 | * @hobodave
261 | * @humancopy
262 | * @JesseObrien
263 | * @jjfrey
264 | * @jmathai
265 | * @joshhawthorne
266 | * @KevBurnsJr
267 | * @lboynton
268 | * @maetl
269 | * @matteosister
270 | * @MattHeath
271 | * @mickhrmweb
272 | * @Olden
273 | * @patrickbajao
274 | * @pedroarnal
275 | * @ptrofimov
276 | * @rajibahmed
277 | * @richardkmiller
278 | * @Rockstar04
279 | * @ruudk
280 | * @salimane
281 | * @scragg0x
282 | * @scraton
283 | * @thedotedge
284 | * @tonypiper
285 | * @trimbletodd
286 | * @warezthebeef
287 |
--------------------------------------------------------------------------------
/src/Resque.php:
--------------------------------------------------------------------------------
1 |
41 | */
42 | protected $options;
43 |
44 | /**
45 | * @var LoggerInterface
46 | */
47 | protected $logger;
48 |
49 | /**
50 | * @var BackendInterface
51 | */
52 | protected $failures;
53 |
54 | /**
55 | * @var StatusFactory
56 | */
57 | protected $statuses;
58 |
59 | /**
60 | * Constructor
61 | * @param ClientInterface $client
62 | */
63 | public function __construct($client, array $options = array())
64 | {
65 | $this->client = $client;
66 | $this->logger = new NullLogger();
67 |
68 | $this->configure($options);
69 | }
70 |
71 | /**
72 | * Configures the options of the resque background queue system
73 | *
74 | * @param array $options
75 | * @return void
76 | */
77 | public function configure(array $options)
78 | {
79 | $this->options = array_merge(array(
80 | 'pgrep' => 'pgrep -f',
81 | 'pgrep_pattern' => '[r]esque[^-]',
82 | 'prefix' => 'resque:',
83 | 'statistic_class' => 'Resque\Statistic'
84 | ), $options);
85 | }
86 |
87 | /**
88 | * Gets the underlying Redis client
89 | *
90 | * The Redis client can be any object that implements a suitable subset
91 | * of Redis commands.
92 | *
93 | * @return ClientInterface
94 | */
95 | public function getClient()
96 | {
97 | return $this->client;
98 | }
99 |
100 | /**
101 | * @param \Resque\Failure\BackendInterface $backend
102 | */
103 | public function setFailureBackend(BackendInterface $backend)
104 | {
105 | $this->failures = $backend;
106 | }
107 |
108 | /**
109 | * @return BackendInterface
110 | */
111 | public function getFailureBackend()
112 | {
113 | if (!isset($this->failures)) {
114 | $this->failures = new RedisBackend();
115 | }
116 |
117 | return $this->failures;
118 | }
119 |
120 | /**
121 | * @param StatusFactory $factory
122 | */
123 | public function setStatusFactory(StatusFactory $factory)
124 | {
125 | $this->statuses = $factory;
126 | }
127 |
128 | /**
129 | * @return StatusFactory
130 | */
131 | public function getStatusFactory()
132 | {
133 | if (!isset($this->statuses)) {
134 | $this->statuses = new StatusFactory($this);
135 | }
136 |
137 | return $this->statuses;
138 | }
139 |
140 | /**
141 | * Causes the client to reconnect to the Redis server
142 | *
143 | * @return void
144 | */
145 | public function reconnect()
146 | {
147 | if ($this->client->isConnected()) {
148 | $this->client->disconnect();
149 | }
150 | $this->client->connect();
151 | }
152 |
153 | /**
154 | * Causes the client to connect to the Redis server
155 | */
156 | public function connect()
157 | {
158 | $this->client->connect();
159 | }
160 |
161 | /**
162 | * Disconnects the client from the Redis server
163 | */
164 | public function disconnect()
165 | {
166 | $this->client->disconnect();
167 | }
168 |
169 | /**
170 | * Logs a message
171 | *
172 | * @param string $message
173 | * @param string $priority
174 | * @return void
175 | */
176 | public function log($message, $priority = LogLevel::INFO)
177 | {
178 | $this->logger->log($message, $priority);
179 | }
180 |
181 | /**
182 | * Gets a namespaced/prefixed key for the given key suffix
183 | *
184 | * @param string $key
185 | * @return string
186 | */
187 | public function getKey($key)
188 | {
189 | return $this->options['prefix'] . $key;
190 | }
191 |
192 | /**
193 | * Push a job to the end of a specific queue. If the queue does not
194 | * exist, then create it as well.
195 | *
196 | * @param string $queue The name of the queue to add the job to.
197 | * @param array $item Job description as an array to be JSON encoded.
198 | */
199 | public function push($queue, $item)
200 | {
201 | // Add the queue to the list of queues
202 | $this->getClient()->sadd($this->getKey(self::QUEUES_KEY), $queue);
203 |
204 | // Add the job to the specified queue
205 | $this->getClient()->rpush($this->getKey(self::QUEUE_KEY . $queue), json_encode($item));
206 | }
207 |
208 | /**
209 | * Pop an item off the end of the specified queue, decode it and
210 | * return it.
211 | *
212 | * @param string $queue The name of the queue to fetch an item from.
213 | * @return array Decoded item from the queue.
214 | */
215 | public function pop($queue)
216 | {
217 | $item = $this->getClient()->lpop($this->getKey(self::QUEUE_KEY . $queue));
218 |
219 | if (!$item) {
220 | return null;
221 | }
222 |
223 | return json_decode($item, true);
224 | }
225 |
226 | /**
227 | * Clears the whole of a queue
228 | *
229 | * @param string $queue
230 | */
231 | public function clearQueue($queue)
232 | {
233 | $this->getClient()->del($this->getKey(self::QUEUE_KEY . $queue));
234 | }
235 |
236 | /**
237 | * Return the size (number of pending jobs) of the specified queue.
238 | *
239 | * @param $queue name of the queue to be checked for pending jobs
240 | *
241 | * @return int The size of the queue.
242 | */
243 | public function size($queue)
244 | {
245 | return $this->getClient()->llen($this->getKey(self::QUEUE_KEY . $queue));
246 | }
247 |
248 | /**
249 | * Create a new job and save it to the specified queue.
250 | *
251 | * @param string $queue The name of the queue to place the job in.
252 | * @param string $class The name of the class that contains the code to execute the job.
253 | * @param array $args Any optional arguments that should be passed when the job is executed.
254 | * @param boolean $trackStatus Set to true to be able to monitor the status of a job.
255 | * @throws \InvalidArgumentException
256 | * @return string
257 | */
258 | public function enqueue($queue, $class, $args = null, $trackStatus = false)
259 | {
260 | if ($args !== null && !is_array($args)) {
261 | throw new InvalidArgumentException(
262 | 'Supplied $args must be an array.'
263 | );
264 | }
265 |
266 | $id = md5(uniqid('', true));
267 |
268 | $this->push($queue, array(
269 | 'class' => $class,
270 | 'args' => $args,
271 | 'id' => $id,
272 | ));
273 |
274 | if ($trackStatus) {
275 | $status = new Status($id, $this);
276 | $status->create();
277 | }
278 |
279 | return $id;
280 | }
281 |
282 | /**
283 | * Get an array of all known queues.
284 | *
285 | * @return array
286 | */
287 | public function queues()
288 | {
289 | return $this->getSetMembers(self::QUEUES_KEY);
290 | }
291 |
292 | /**
293 | * Gets an array of all known worker IDs
294 | *
295 | * @return array
296 | */
297 | public function getWorkerIds()
298 | {
299 | return $this->getSetMembers(self::WORKERS_KEY);
300 | }
301 |
302 | /**
303 | * @param string $suffix Partial key (don't pass to getKey() - let this method do it for you)
304 | * @return array
305 | */
306 | protected function getSetMembers($suffix)
307 | {
308 | $members = $this->getClient()->smembers($this->getKey($suffix));
309 |
310 | if (!is_array($members)) {
311 | $members = array();
312 | }
313 |
314 | return $members;
315 | }
316 |
317 | /**
318 | * @param string $id Worker ID
319 | * @return bool
320 | */
321 | public function workerExists($id)
322 | {
323 | return in_array($id, $this->getWorkerIds());
324 | }
325 |
326 | /**
327 | * Return an array of process IDs for all of the Resque workers currently
328 | * running on this machine.
329 | *
330 | * Expects pgrep to be in the path, and for it to inspect full argument
331 | * lists using -f
332 | *
333 | * @return array Array of Resque worker process IDs.
334 | */
335 | public function getWorkerPids()
336 | {
337 | $command = $this->options['pgrep'] . ' ' . escapeshellarg($this->options['pgrep_pattern']);
338 |
339 | $pids = array();
340 | $output = null;
341 | $return = null;
342 |
343 | exec($command, $output, $return);
344 |
345 | /*
346 | * Exit codes:
347 | * 0 One or more processes were matched
348 | * 1 No processes were matched
349 | * 2 Invalid options were specified on the command line
350 | * 3 An internal error occurred
351 | */
352 | if (($return !== 0 && $return !== 1) || empty($output) || !is_array($output)) {
353 | $this->logger->warning('Unable to determine worker PIDs');
354 | return array();
355 | }
356 |
357 | foreach ($output as $line) {
358 | $line = explode(' ', trim($line), 2);
359 |
360 | if (!$line[0] || !is_numeric($line[0])) {
361 | continue;
362 | }
363 |
364 | $pids[] = (int)$line[0];
365 | }
366 |
367 | return $pids;
368 | }
369 |
370 | /**
371 | * Gets a statistic
372 | *
373 | * @param string $name
374 | * @return \Resque\Statistic
375 | */
376 | public function getStatistic($name)
377 | {
378 | return new Statistic($this, $name);
379 | }
380 |
381 | /**
382 | * @param LoggerInterface $logger
383 | * @return void
384 | */
385 | public function setLogger(LoggerInterface $logger)
386 | {
387 | $this->logger = $logger;
388 | }
389 |
390 | /**
391 | * @return LoggerInterface
392 | */
393 | public function getLogger()
394 | {
395 | return $this->logger;
396 | }
397 | }
398 |
--------------------------------------------------------------------------------
/src/Job/Status.php:
--------------------------------------------------------------------------------
1 |
16 | * @author Chris Boulton
17 | * @copyright (c) 2010 Chris Boulton
18 | * @license http://www.opensource.org/licenses/mit-license.php
19 | */
20 | class Status
21 | {
22 | /**#@+
23 | * How many seconds until a status entry should expire
24 | *
25 | * In the previous implementation, incomplete statuses were not given a
26 | * TTL at all. This can make Redis treat the keys differently, depending
27 | * on your maxmemory-policy (for example, volative-lru will only remove
28 | * keys with an expire set).
29 | *
30 | * @var int
31 | */
32 | const COMPLETE_TTL = 86400; // 24 hours
33 | const INCOMPLETE_TTL = 604800; // A week
34 | /**#@-*/
35 |
36 | /**#@+
37 | * Status codes
38 | *
39 | * @var int
40 | */
41 | const STATUS_WAITING = 1;
42 | const STATUS_RUNNING = 2;
43 | const STATUS_FAILED = 3;
44 | const STATUS_COMPLETE = 4;
45 | const STATUS_RECREATED = 5;
46 | /**#@-*/
47 |
48 | /**
49 | * An array of valid statuses
50 | *
51 | * @var array
52 | */
53 | public static $valid = array(
54 | self::STATUS_WAITING => 'waiting',
55 | self::STATUS_RUNNING => 'running',
56 | self::STATUS_FAILED => 'failed',
57 | self::STATUS_COMPLETE => 'complete',
58 | self::STATUS_RECREATED => 'recreated'
59 | );
60 |
61 | /**
62 | * An array of complete statuses
63 | *
64 | * @var array
65 | */
66 | public static $complete = array(
67 | self::STATUS_FAILED,
68 | self::STATUS_COMPLETE,
69 | self::STATUS_RECREATED
70 | );
71 |
72 | /**
73 | * @var string The ID of the job this status class refers back to.
74 | */
75 | protected $id;
76 |
77 | /**
78 | * Whether the status has been loaded from the database
79 | *
80 | * @var boolean
81 | */
82 | protected $loaded = false;
83 |
84 | /**
85 | * @var array
86 | */
87 | protected $attributes = array();
88 |
89 | /**
90 | * @var \Resque\Client\ClientInterface
91 | */
92 | protected $client;
93 |
94 | /**
95 | * @var boolean|null Cache variable if the status of this job is being
96 | * monitored or not. True/false when checked at least
97 | * once or null if not checked yet.
98 | */
99 | protected $isTracking = null;
100 |
101 | /**
102 | * Setup a new instance of the job monitor class for the supplied job ID.
103 | *
104 | * @param string $id The ID of the job to manage the status for.
105 | * @param \Resque\Resque $resque
106 | */
107 | public function __construct($id, Resque $resque)
108 | {
109 | $this->id = $id;
110 | $this->client = $resque->getClient();
111 | }
112 |
113 | /**
114 | * @return string
115 | */
116 | public function getId()
117 | {
118 | return $this->id;
119 | }
120 |
121 | /**
122 | * Create a new status monitor item for the supplied job ID. Will create
123 | * all necessary keys in Redis to monitor the status of a job.
124 | */
125 | public function create()
126 | {
127 | $this->isTracking = true;
128 |
129 | $this->setAttributes(array(
130 | 'status' => self::STATUS_WAITING,
131 | 'created' => time(),
132 | 'updated' => time()
133 | ));
134 | }
135 |
136 | /**
137 | * Sets all the given attributes
138 | *
139 | * @param array $attributes
140 | * @return mixed
141 | */
142 | public function setAttributes(array $attributes)
143 | {
144 | $this->attributes = array_merge($this->attributes, $attributes);
145 |
146 | $set = array();
147 | foreach ($attributes as $name => $value) {
148 | if ($name == 'status') {
149 | $this->update($value);
150 | continue;
151 | }
152 | $set[$name] = $value;
153 | }
154 |
155 | return call_user_func(array($this->client, 'hmset'), $this->getHashKey(), $set);
156 | }
157 |
158 | /**
159 | * Sets an attribute
160 | *
161 | * @param string $name
162 | * @param string $value
163 | */
164 | public function setAttribute($name, $value)
165 | {
166 | if ($name == 'status') {
167 | $this->update($value);
168 | } else {
169 | $this->attributes[$name] = $value;
170 | $this->client->hmset($this->getHashKey(), array(
171 | $name => $value,
172 | 'updated' => time()
173 | ));
174 | }
175 | }
176 |
177 | /**
178 | * Increments an attribute
179 | *
180 | * The attribute should be an integer field
181 | *
182 | * @param string $name
183 | * @param integer $by
184 | * @return integer The value after incrementing (see hincrby)
185 | */
186 | public function incrementAttribute($name, $by = 1)
187 | {
188 | $pipeline = $this->client->pipeline();
189 | $pipeline->hincrby($this->getHashKey(), $name, $by);
190 | $pipeline->hset($this->getHashKey(), 'updated', time());
191 | $result = $pipeline->execute();
192 |
193 | return $this->attributes[$name] = $result[0];
194 | }
195 |
196 | /**
197 | * Update the status indicator for the current job with a new status.
198 | *
199 | * This method is called from setAttribute/s so that the expiry can be
200 | * properly updated.
201 | *
202 | * @param int $status The status of the job (see constants in Resque\Job\Status)
203 | * @throws \InvalidArgumentException
204 | * @return boolean
205 | */
206 | public function update($status)
207 | {
208 | if (!isset(self::$valid[$status])) {
209 | throw new InvalidArgumentException('Invalid status');
210 | }
211 |
212 | if (!$this->isTracking()) {
213 | return false;
214 | }
215 |
216 | $this->attributes['status'] = $status;
217 | $this->attributes['updated'] = time();
218 |
219 | $success = $this->client->hmset($this->getHashKey(), array(
220 | 'status' => $this->attributes['status'],
221 | 'updated' => $this->attributes['updated']
222 | ));
223 |
224 | // Expire the status for completed jobs after 24 hours
225 | if (in_array($status, self::$complete)) {
226 | $this->client->expire($this->getHashKey(), self::COMPLETE_TTL);
227 | } else {
228 | $this->client->expire($this->getHashKey(), self::INCOMPLETE_TTL);
229 | }
230 |
231 | return (boolean)$success;
232 | }
233 |
234 | /**
235 | * Check if we're actually checking the status of the loaded job status
236 | * instance.
237 | *
238 | * @return boolean True if the status is being monitored, false if not.
239 | */
240 | public function isTracking()
241 | {
242 | if ($this->isTracking === null) {
243 | $this->isTracking = (boolean)$this->client->exists($this->getHashKey());
244 | if ($this->isTracking) {
245 | $this->load();
246 | }
247 | }
248 | return $this->isTracking;
249 | }
250 |
251 | /**
252 | * Loads all status attributes
253 | *
254 | * @throws LogicException
255 | */
256 | public function load()
257 | {
258 | if ($this->loaded) {
259 | throw new \LogicException('The status is already loaded. Use another instance.');
260 | }
261 |
262 | $this->attributes = array_merge($this->attributes, $this->client->hgetall($this->getHashKey()));
263 | $this->loaded = true;
264 | }
265 |
266 | /**
267 | * @return array
268 | */
269 | public function getAll()
270 | {
271 | if ($this->loaded) {
272 | return $this->attributes;
273 | }
274 |
275 | return $this->client->hgetall($this->getHashKey());
276 | }
277 |
278 | /**
279 | * Gets the time this status was updated
280 | */
281 | public function getUpdated()
282 | {
283 | return $this->getAttribute('updated');
284 | }
285 |
286 | /**
287 | * Gets the time this status was created
288 | */
289 | public function getCreated()
290 | {
291 | return $this->getAttribute('created');
292 | }
293 |
294 | /**
295 | * Fetch the status for the job being monitored.
296 | *
297 | * For consistency, this would be called getStatus(), but for BC, it's
298 | * just get().
299 | *
300 | * @return null|integer
301 | */
302 | public function get()
303 | {
304 | return $this->getAttribute('status');
305 | }
306 |
307 | /**
308 | * @return string
309 | */
310 | public function getStatus()
311 | {
312 | $status = $this->get();
313 |
314 | if (isset(self::$valid[$status])) {
315 | return self::$valid[$status];
316 | }
317 |
318 | return 'unknown';
319 | }
320 |
321 | /**
322 | * Gets a single attribute value
323 | *
324 | * @param string $name
325 | * @param mixed $default
326 | * @return mixed
327 | */
328 | public function getAttribute($name, $default = null)
329 | {
330 | if ($this->loaded) {
331 | return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
332 | }
333 |
334 | // Could be just hget, but Credis will return false?!
335 | $attributes = $this->client->hGetAll($this->getHashKey());
336 | return isset($attributes[$name]) ? $attributes[$name] : $default;
337 | }
338 |
339 | /**
340 | * Stop tracking the status of a job.
341 | *
342 | * @return void
343 | */
344 | public function stop()
345 | {
346 | $this->client->del($this->getHashKey());
347 | }
348 |
349 | /**
350 | * A new key, because we're now using a hash format to store the status
351 | *
352 | * Used from outside this class to do status processing more efficiently
353 | *
354 | * @return string
355 | */
356 | public function getHashKey()
357 | {
358 | return 'job:' . $this->id . ':status/hash';
359 | }
360 |
361 | /**
362 | * Accessor to return valid statuses
363 | *
364 | * @return array
365 | */
366 | public function getValid()
367 | {
368 | return self::$valid;
369 | }
370 |
371 | /**
372 | * Accessor to return complete statuses
373 | *
374 | * @return array
375 | */
376 | public function getComplete()
377 | {
378 | return self::$complete;
379 | }
380 |
381 | /**
382 | * Convenience method to to check if a resque job has a complete status
383 | */
384 | public function isComplete()
385 | {
386 | return in_array($this->get(), self::$complete);
387 | }
388 | }
389 |
--------------------------------------------------------------------------------
/src/Worker.php:
--------------------------------------------------------------------------------
1 |
21 | * @license http://www.opensource.org/licenses/mit-license.php
22 | */
23 | class Worker implements LoggerAwareInterface
24 | {
25 | /**
26 | * @var string String identifying this worker.
27 | */
28 | protected $id;
29 |
30 | /**
31 | * @var LoggerInterface Logging object that implements the PSR-3 LoggerInterface
32 | */
33 | protected $logger;
34 |
35 | /**
36 | * @var array Array of all associated queues for this worker.
37 | */
38 | protected $queues = array();
39 |
40 | /**
41 | * Whether the worker should refresh the list of queues on reserve, or just go with the queues it has been given
42 | *
43 | * Passing * as a queue name will cause the worker to listen on all queues, and also refresh them (so that you
44 | * don't need to restart workers when you add a queue)
45 | *
46 | * @var boolean
47 | */
48 | protected $refreshQueues = false;
49 |
50 | /**
51 | * @var boolean True if on the next iteration, the worker should shutdown.
52 | */
53 | protected $shutdown = false;
54 |
55 | /**
56 | * @var boolean True if this worker is paused.
57 | */
58 | protected $paused = false;
59 |
60 | /**
61 | * @var JobInterface Current job, if any, being processed by this worker.
62 | */
63 | protected $currentJob = null;
64 |
65 | /**
66 | * @var array
67 | */
68 | protected $options = array();
69 |
70 | /**
71 | * @var int Process ID of child worker processes.
72 | */
73 | private $child = null;
74 |
75 | /**
76 | * @var Resque
77 | */
78 | protected $resque;
79 |
80 | /**
81 | * Instantiate a new worker, given a list of queues that it should be working
82 | * on. The list of queues should be supplied in the priority that they should
83 | * be checked for jobs (first come, first served)
84 | *
85 | * Passing a single '*' allows the worker to work on all queues.
86 | * You can easily add new queues dynamically and have them worked on using this method.
87 | *
88 | * @param Resque $resque
89 | * @param string|array $queues String with a single queue name, array with multiple.
90 | * @param array $options
91 | */
92 | public function __construct(Resque $resque, $queues, array $options = array())
93 | {
94 | $this->configure($options);
95 |
96 | $this->queues = is_array($queues) ? $queues : array($queues);
97 | $this->resque = $resque;
98 | $this->logger = $this->resque->getLogger();
99 |
100 | if (in_array('*', $this->queues) || empty($this->queues)) {
101 | $this->refreshQueues = true;
102 | $this->queues = $resque->queues();
103 | }
104 |
105 | $this->configureId();
106 | }
107 |
108 | /**
109 | * Configures options for the worker
110 | *
111 | * @param array $options
112 | * Including
113 | * Worker identification
114 | * - server_name => string, default is FQDN hostname
115 | * - pid => int, default is current PID
116 | * - id_format => string, suitable for sprintf
117 | * - id_location_preg => string, Perl compat regex, gets hostname and PID
118 | * out of worker ID
119 | * - shuffle_queues => bool, whether to shuffle the queues on reserve, so we evenly check all queues
120 | * - sort_queues => bool, whether to check the queues in alphabetical order (mutually exclusive with shuffle_queues)
121 | */
122 | protected function configure(array $options)
123 | {
124 | $this->options = array_merge(array(
125 | 'server_name' => null,
126 | 'pid' => null,
127 | 'ps' => '/bin/ps',
128 | 'ps_args' => array('-o', 'pid,state', '-p'),
129 | 'id_format' => '%s:%d:%s',
130 | 'id_location_preg' => '/^([^:]+?):([0-9]+):/',
131 | 'shuffle_queues' => true,
132 | 'sort_queues' => false
133 | ), $options);
134 |
135 | if (!$this->options['server_name']) {
136 | $this->options['server_name'] = function_exists('gethostname') ? gethostname() : php_uname('n');
137 | }
138 |
139 | if (!$this->options['pid']) {
140 | $this->options['pid'] = getmypid();
141 | }
142 | }
143 |
144 | /**
145 | * Configures the ID of this worker
146 | */
147 | protected function configureId()
148 | {
149 | $this->id = sprintf(
150 | $this->options['id_format'],
151 | $this->options['server_name'],
152 | $this->options['pid'],
153 | implode(',', $this->queues)
154 | );
155 | }
156 |
157 | /**
158 | * @return Client\ClientInterface
159 | */
160 | protected function getClient()
161 | {
162 | return $this->resque->getClient();
163 | }
164 |
165 | /**
166 | * @param string $queue
167 | * @param array $payload
168 | * @throws JobClassNotFoundException
169 | * @throws JobInvalidException
170 | * @return JobInterface
171 | */
172 | protected function createJobInstance($queue, array $payload)
173 | {
174 | if (!class_exists($payload['class'])) {
175 | throw new JobClassNotFoundException(
176 | 'Could not find job class ' . $payload['class'] . '.'
177 | );
178 | }
179 |
180 | if (!is_subclass_of($payload['class'], 'Resque\JobInterface')) {
181 | throw new JobInvalidException();
182 | }
183 |
184 | $job = new $payload['class']($queue, $payload);
185 |
186 | if (method_exists($job, 'setResque')) {
187 | $job->setResque($this->resque);
188 | }
189 |
190 | return $job;
191 | }
192 |
193 | /**
194 | * Parses a hostname and PID out of a string worker ID
195 | *
196 | * If you change the format of the ID, you should also change the definition
197 | * of this method.
198 | *
199 | * This method *always* parses the ID of the worker, rather than figuring out
200 | * the current processes' PID/hostname. This means you can use setId() to
201 | * interrogate the properties of other workers given their ID.
202 | *
203 | * @throws Exception
204 | * @return array
205 | */
206 | protected function getLocation()
207 | {
208 | $matches = array();
209 |
210 | if (!preg_match($this->options['id_location_preg'], $this->getId(), $matches)) {
211 | throw new Exception('Incompatible ID format: unable to determine worker location');
212 | }
213 |
214 | if (!isset($matches[1]) || !$matches[1]) {
215 | throw new Exception('Invalid ID: invalid hostname');
216 | }
217 |
218 | if (!isset($matches[2]) || !$matches[2] || !is_numeric($matches[2])) {
219 | throw new Exception('Invalid ID: invalid PID');
220 | }
221 |
222 | return array($matches[1], (int)$matches[2]);
223 | }
224 |
225 | /**
226 | * Set the ID of this worker to a given ID string.
227 | *
228 | * @param string $id ID for the worker.
229 | */
230 | public function setId($id)
231 | {
232 | $this->id = $id;
233 | }
234 |
235 | /**
236 | * Gives access to the main Resque system this worker belongs to
237 | *
238 | * @return \Resque\Resque
239 | */
240 | public function getResque()
241 | {
242 | return $this->resque;
243 | }
244 |
245 | /**
246 | * Given a worker ID, check if it is registered/valid.
247 | *
248 | * @param string $id ID of the worker.
249 | * @return boolean True if the worker exists, false if not.
250 | */
251 | public function exists($id)
252 | {
253 | return (bool)$this->resque->getClient()->sismember('workers', $id);
254 | }
255 |
256 | /**
257 | * The primary loop for a worker which when called on an instance starts
258 | * the worker's life cycle.
259 | *
260 | * Queues are checked every $interval (seconds) for new jobs.
261 | *
262 | * @param int $interval How often to check for new jobs across the queues.
263 | */
264 | public function work($interval = 5)
265 | {
266 | $this->updateProcLine('Starting');
267 | $this->startup();
268 |
269 | while (true) {
270 | if ($this->shutdown) {
271 | $this->unregister();
272 | return;
273 | }
274 |
275 | // Attempt to find and reserve a job
276 | $job = false;
277 | if (!$this->paused) {
278 | $job = $this->reserve();
279 | }
280 |
281 | if (!$job) {
282 | // For an interval of 0, continue now - helps with unit testing etc
283 | if ($interval == 0) {
284 | break;
285 | }
286 |
287 | // If no job was found, we sleep for $interval before continuing and checking again
288 | if ($this->paused) {
289 | $this->updateProcLine('Paused');
290 | } else {
291 | $this->updateProcLine('Waiting for ' . implode(',', $this->queues));
292 | }
293 |
294 | usleep($interval * 1000000);
295 | continue;
296 | }
297 |
298 | $this->logger->info('got {job}', array('job' => $job));
299 | $this->workingOn($job);
300 |
301 | $this->child = null;
302 | $this->child = $this->fork();
303 |
304 | // Forked and we're the child. Run the job.
305 | if (!$this->child) {
306 | $status = 'Processing ' . $job->getQueue() . ' since ' . strftime('%F %T');
307 | $this->updateProcLine($status);
308 | $this->logger->notice($status);
309 | $this->perform($job);
310 |
311 | exit(0);
312 | } elseif ($this->child > 0) {
313 | // Parent process, sit and wait
314 | $status = 'Forked ' . $this->child . ' at ' . strftime('%F %T');
315 | $this->updateProcLine($status);
316 | $this->logger->info($status);
317 |
318 | // Wait until the child process finishes before continuing
319 | pcntl_wait($status);
320 | $exitStatus = pcntl_wexitstatus($status);
321 |
322 | if ($exitStatus !== 0) {
323 | $this->failJob($job, new DirtyExitException(
324 | 'Job exited with exit code ' . $exitStatus
325 | ));
326 | } else {
327 | $this->logger->debug('Job returned status code {code}', array('code' => $exitStatus));
328 | }
329 | }
330 |
331 | $this->doneWorking();
332 | }
333 | }
334 |
335 | /**
336 | * Process a single job.
337 | *
338 | * @param JobInterface $job The job to be processed.
339 | */
340 | public function perform(JobInterface $job)
341 | {
342 | try {
343 | $job->perform();
344 | } catch (Exception $e) {
345 | $this->logger->notice('{job} failed: {exception}', array(
346 | 'job' => $job,
347 | 'exception' => $e
348 | ));
349 | $this->failJob($job, $e);
350 | return;
351 | }
352 |
353 | try {
354 | $this->resque->getStatusFactory()->forJob($job)->update(Status::STATUS_COMPLETE);
355 | } catch (JobIdException $e) {
356 | $this->logger->warning('Could not mark job complete: no ID in payload - {exception}', array('exception' => $e));
357 | }
358 |
359 | $payload = $job->getPayload();
360 |
361 | $this->logger->notice('Finished job {queue}/{class} (ID: {id})', array(
362 | 'queue' => $job->getQueue(),
363 | 'class' => get_class($job),
364 | 'id' => isset($payload['id']) ? $payload['id'] : 'unknown'
365 | ));
366 |
367 | $this->logger->debug('Done with {job}', array('job' => $job));
368 | }
369 |
370 | /**
371 | * Marks the given job as failed
372 | *
373 | * This happens whenever the job's perform() method emits an exception
374 | *
375 | * @param JobInterface|array $job
376 | * @param Exception $exception
377 | */
378 | protected function failJob($job, Exception $exception)
379 | {
380 | if ($job instanceof JobInterface) {
381 | $payload = $job->getPayload();
382 | $queue = $job->getQueue();
383 | } else {
384 | $payload = $job;
385 | $queue = isset($job['queue']) ? $job['queue'] : null;
386 | }
387 |
388 | $id = isset($job['id']) ? $job['id'] : null;
389 |
390 | if ($id) {
391 | try {
392 | $status = $this->resque->getStatusFactory()->forId($id);
393 | $status->update(Status::STATUS_FAILED);
394 | } catch (JobIdException $e) {
395 | $this->logger->warning($e);
396 | }
397 | } // else no status to update
398 |
399 | $this->resque->getFailureBackend()->receiveFailure(
400 | $payload,
401 | $exception,
402 | $this,
403 | $queue
404 | );
405 |
406 | $this->getResque()->getStatistic('failed')->incr();
407 | $this->getStatistic('failed')->incr();
408 | }
409 |
410 | /**
411 | * Prepares the list of queues for a job to reserved
412 | *
413 | * Updates/sorts/shuffles the array ahead of the call to reserve a job from one of them
414 | *
415 | * @return void
416 | */
417 | protected function refreshQueues()
418 | {
419 | if ($this->refreshQueues) {
420 | $this->queues = $this->resque->queues();
421 | }
422 |
423 | if (!$this->queues) {
424 | if ($this->refreshQueues) {
425 | $this->logger->info('Refreshing queues dynamically, but there are no queues yet');
426 | } else {
427 | $this->logger->notice('Not listening to any queues, and dynamic queue refreshing is disabled');
428 | $this->shutdownNow();
429 | }
430 | }
431 |
432 | // Each call to reserve, we check the queues in a different order
433 | if ($this->options['shuffle_queues']) {
434 | shuffle($this->queues);
435 | } elseif ($this->options['sort_queues']) {
436 | sort($this->queues);
437 | }
438 | }
439 |
440 | /**
441 | * Attempt to find a job from the top of one of the queues for this worker.
442 | *
443 | * @return JobInterface|null Instance of JobInterface if a job is found, null if not.
444 | */
445 | public function reserve()
446 | {
447 | $this->refreshQueues();
448 |
449 | $this->logger->debug('Attempting to reserve job from {queues}', array(
450 | 'queues' => empty($this->queues) ? 'empty queue list' : implode(', ', $this->queues)
451 | ));
452 |
453 | foreach ($this->queues as $queue) {
454 | $payload = $this->resque->pop($queue);
455 |
456 | if (!is_array($payload)) {
457 | continue;
458 | }
459 |
460 | $payload['queue'] = $queue;
461 |
462 | try {
463 | $job = $this->createJobInstance($queue, $payload);
464 | } catch (ResqueException $exception) {
465 | $this->failJob($payload, $exception);
466 | return null;
467 | }
468 |
469 | if ($job) {
470 | $this->logger->info('Found job on {queue}', array('queue' => $queue));
471 | return $job;
472 | }
473 | }
474 |
475 | return null;
476 | }
477 |
478 | /**
479 | * Attempt to fork a child process from the parent to run a job in.
480 | *
481 | * Return values are those of pcntl_fork().
482 | *
483 | * @throws \RuntimeException
484 | * @throws \Exception
485 | * @return int -1 if the fork failed, 0 for the forked child, the PID of the child for the parent.
486 | */
487 | private function fork()
488 | {
489 | if (!function_exists('pcntl_fork')) {
490 | throw new \Exception('pcntl not available, could not fork');
491 | }
492 |
493 | // Immediately before a fork, disconnect the redis client
494 | $this->resque->disconnect();
495 |
496 | $this->logger->notice('Forking...');
497 |
498 | $pid = (int)pcntl_fork();
499 |
500 | // And reconnect
501 | $this->resque->connect();
502 |
503 | if ($pid === -1) {
504 | throw new RuntimeException('Unable to fork child worker.');
505 | }
506 |
507 | return $pid;
508 | }
509 |
510 | /**
511 | * Perform necessary actions to start a worker.
512 | */
513 | protected function startup()
514 | {
515 | $this->registerSigHandlers();
516 | $this->pruneDeadWorkers();
517 | $this->register();
518 | }
519 |
520 | /**
521 | * On supported systems (with the PECL proctitle module installed), update
522 | * the name of the currently running process to indicate the current state
523 | * of a worker.
524 | *
525 | * @param string $status The updated process title.
526 | */
527 | protected function updateProcLine($status)
528 | {
529 | if (function_exists('setproctitle')) {
530 | setproctitle('resque-' . Version::VERSION . ': ' . $status);
531 | }
532 | }
533 |
534 | /**
535 | * Register signal handlers that a worker should respond to.
536 | *
537 | * TERM: Shutdown immediately and stop processing jobs.
538 | * INT: Shutdown immediately and stop processing jobs.
539 | * QUIT: Shutdown after the current job finishes processing.
540 | * USR1: Kill the forked child immediately and continue processing jobs.
541 | */
542 | protected function registerSigHandlers()
543 | {
544 | if (!function_exists('pcntl_signal')) {
545 | $this->logger->warning('Cannot register signal handlers');
546 | return;
547 | }
548 |
549 | declare(ticks = 1);
550 | pcntl_signal(SIGTERM, array($this, 'shutDownNow'));
551 | pcntl_signal(SIGINT, array($this, 'shutDownNow'));
552 | pcntl_signal(SIGQUIT, array($this, 'shutdown'));
553 | pcntl_signal(SIGUSR1, array($this, 'killChild'));
554 | pcntl_signal(SIGUSR2, array($this, 'pauseProcessing'));
555 | pcntl_signal(SIGCONT, array($this, 'unPauseProcessing'));
556 | pcntl_signal(SIGPIPE, array($this, 'reestablishRedisConnection'));
557 |
558 | $this->logger->notice('Registered signals');
559 | }
560 |
561 | /**
562 | * Signal handler callback for USR2, pauses processing of new jobs.
563 | */
564 | public function pauseProcessing()
565 | {
566 | $this->logger->notice('USR2 received; pausing job processing');
567 | $this->paused = true;
568 | }
569 |
570 | /**
571 | * Signal handler callback for CONT, resumes worker allowing it to pick
572 | * up new jobs.
573 | */
574 | public function unPauseProcessing()
575 | {
576 | $this->logger->notice('CONT received; resuming job processing');
577 | $this->paused = false;
578 | }
579 |
580 | /**
581 | * Signal handler for SIGPIPE, in the event the redis connection has gone away.
582 | * Attempts to reconnect to redis, or raises an Exception.
583 | */
584 | public function reestablishRedisConnection()
585 | {
586 | $this->logger->notice('SIGPIPE received; attempting to reconnect');
587 | $this->resque->reconnect();
588 | }
589 |
590 | /**
591 | * Schedule a worker for shutdown. Will finish processing the current job
592 | * and when the timeout interval is reached, the worker will shut down.
593 | */
594 | public function shutdown()
595 | {
596 | $this->shutdown = true;
597 | $this->logger->notice('Exiting...');
598 | }
599 |
600 | /**
601 | * Force an immediate shutdown of the worker, killing any child jobs
602 | * currently running.
603 | */
604 | public function shutdownNow()
605 | {
606 | $this->shutdown();
607 | $this->killChild();
608 | }
609 |
610 | /**
611 | * Kill a forked child job immediately. The job it is processing will not
612 | * be completed.
613 | */
614 | public function killChild()
615 | {
616 | if (!$this->child) {
617 | $this->logger->notice('No child to kill.');
618 | return;
619 | }
620 |
621 | $this->logger->notice('Killing child at {pid}', array('pid' => $this->child));
622 |
623 | $command = escapeshellcmd($this->options['ps']);
624 |
625 | foreach ($this->options['ps_args'] as $arg) {
626 | $command .= ' ' . escapeshellarg($arg);
627 | }
628 |
629 | if (exec('ps ' . $this->child, $output, $returnCode) && $returnCode != 1) {
630 | $this->logger->notice('Killing child at ' . $this->child);
631 | posix_kill($this->child, SIGKILL);
632 | $this->child = null;
633 | } else {
634 | $this->logger->notice('Child ' . $this->child . ' not found, restarting.');
635 | $this->shutdown();
636 | }
637 | }
638 |
639 | /**
640 | * Look for any workers which should be running on this server and if
641 | * they're not, remove them from Redis.
642 | *
643 | * This is a form of garbage collection to handle cases where the
644 | * server may have been killed and the Resque workers did not die gracefully
645 | * and therefore leave state information in Redis.
646 | */
647 | public function pruneDeadWorkers()
648 | {
649 | $pids = $this->resque->getWorkerPids();
650 | $ids = $this->resque->getWorkerIds();
651 |
652 | foreach ($ids as $id) {
653 | $worker = clone $this;
654 | $worker->setId($id);
655 |
656 | list($host, $pid) = $worker->getLocation();
657 |
658 | // Ignore workers on other hosts
659 | if ($host != $this->options['server_name']) {
660 | continue;
661 | }
662 |
663 | // Ignore this process
664 | if ($pid == $this->options['pid']) {
665 | continue;
666 | }
667 |
668 | // Ignore workers still running
669 | if (in_array($pid, $pids)) {
670 | continue;
671 | }
672 |
673 | $this->logger->warning('Pruning dead worker: {id}', array('id' => $id));
674 | $worker->unregister();
675 | }
676 | }
677 |
678 | /**
679 | * Gets the ID of this worker
680 | */
681 | public function getId()
682 | {
683 | return $this->id;
684 | }
685 |
686 | /**
687 | * Register this worker in Redis.
688 | */
689 | public function register()
690 | {
691 | $this->logger->debug('Registering worker ' . $this->getId());
692 | $this->resque->getClient()->sadd($this->resque->getKey(Resque::WORKERS_KEY), $this->getId());
693 | $this->resque->getClient()->set($this->getJobKey() . ':started', strftime('%a %b %d %H:%M:%S %Z %Y'));
694 | }
695 |
696 | /**
697 | * Unregister this worker in Redis. (shutdown etc)
698 | */
699 | public function unregister()
700 | {
701 | $this->logger->debug('Unregistering worker ' . $this->getId());
702 |
703 | if ($this->currentJob) {
704 | $this->failJob($this->currentJob, new DirtyExitException());
705 | }
706 |
707 | $this->resque->getClient()->srem($this->resque->getKey(Resque::WORKERS_KEY), $this->getId());
708 | $this->resque->getClient()->del($this->getJobKey());
709 | $this->resque->getClient()->del($this->getJobKey() . ':started');
710 |
711 | $this->getStatistic('processed')->clear();
712 | $this->getStatistic('failed')->clear();
713 | $this->getStatistic('shutdown')->clear();
714 | }
715 |
716 | /**
717 | * Tell Redis which job we're currently working on.
718 | *
719 | * @param JobInterface $job Job instance we're working on.
720 | */
721 | public function workingOn(JobInterface $job)
722 | {
723 | if (method_exists($job, 'setWorker')) {
724 | $job->setWorker($this);
725 | }
726 |
727 | $this->currentJob = $job;
728 |
729 | $this->resque->getStatusFactory()->forJob($job)->update(Status::STATUS_RUNNING);
730 |
731 | $data = json_encode(array(
732 | 'queue' => $job->getQueue(),
733 | 'run_at' => strftime('%a %b %d %H:%M:%S %Z %Y'),
734 | 'payload' => $job->getPayload()
735 | ));
736 |
737 | $this->resque->getClient()->set($this->getJobKey(), $data);
738 | }
739 |
740 | /**
741 | * Notify Redis that we've finished working on a job, clearing the working
742 | * state and incrementing the job stats.
743 | */
744 | public function doneWorking()
745 | {
746 | $this->currentJob = null;
747 | $this->resque->getStatistic('processed')->incr();
748 | $this->getStatistic('processed')->incr();
749 | $this->resque->getClient()->del($this->getJobKey());
750 | }
751 |
752 | /**
753 | * Generate a string representation of this worker.
754 | *
755 | * @return string String identifier for this worker instance.
756 | * @deprecated Just use getId(). Explicit, simpler, less magic.
757 | */
758 | public function __toString()
759 | {
760 | return $this->id;
761 | }
762 |
763 | /**
764 | * Gets the key for where this worker will store its active job
765 | *
766 | * @return string
767 | */
768 | protected function getJobKey()
769 | {
770 | return $this->getResque()->getKey('worker:' . $this->getId());
771 | }
772 |
773 | /**
774 | * Return an object describing the job this worker is currently working on.
775 | *
776 | * @return array Object with details of current job.
777 | */
778 | public function job()
779 | {
780 | $job = $this->resque->getClient()->get($this->getJobKey());
781 |
782 | if (!$job) {
783 | return array();
784 | } else {
785 | return json_decode($job, true);
786 | }
787 | }
788 |
789 | /**
790 | * Get a statistic belonging to this worker.
791 | *
792 | * @param string $name Statistic to fetch.
793 | * @return Statistic
794 | */
795 | public function getStatistic($name)
796 | {
797 | return new Statistic($this->resque, $name. ':' . $this->getId());
798 | }
799 |
800 | /**
801 | * Inject the logging object into the worker
802 | *
803 | * @param LoggerInterface $logger
804 | * @return void
805 | */
806 | public function setLogger(LoggerInterface $logger)
807 | {
808 | $this->logger = $logger;
809 | }
810 | }
811 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5 | "This file is @generated automatically"
6 | ],
7 | "hash": "04cb0cf92e92dbc220792caf6141c3c9",
8 | "packages": [
9 | {
10 | "name": "psr/log",
11 | "version": "1.0.0",
12 | "source": {
13 | "type": "git",
14 | "url": "https://github.com/php-fig/log.git",
15 | "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b"
16 | },
17 | "dist": {
18 | "type": "zip",
19 | "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b",
20 | "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b",
21 | "shasum": ""
22 | },
23 | "type": "library",
24 | "autoload": {
25 | "psr-0": {
26 | "Psr\\Log\\": ""
27 | }
28 | },
29 | "notification-url": "https://packagist.org/downloads/",
30 | "license": [
31 | "MIT"
32 | ],
33 | "authors": [
34 | {
35 | "name": "PHP-FIG",
36 | "homepage": "http://www.php-fig.org/"
37 | }
38 | ],
39 | "description": "Common interface for logging libraries",
40 | "keywords": [
41 | "log",
42 | "psr",
43 | "psr-3"
44 | ],
45 | "time": "2012-12-21 11:40:51"
46 | },
47 | {
48 | "name": "symfony/console",
49 | "version": "v2.5.6",
50 | "target-dir": "Symfony/Component/Console",
51 | "source": {
52 | "type": "git",
53 | "url": "https://github.com/symfony/Console.git",
54 | "reference": "6f177fca24200a5b97aef5ce7a5c98124a0f0db0"
55 | },
56 | "dist": {
57 | "type": "zip",
58 | "url": "https://api.github.com/repos/symfony/Console/zipball/6f177fca24200a5b97aef5ce7a5c98124a0f0db0",
59 | "reference": "6f177fca24200a5b97aef5ce7a5c98124a0f0db0",
60 | "shasum": ""
61 | },
62 | "require": {
63 | "php": ">=5.3.3"
64 | },
65 | "require-dev": {
66 | "psr/log": "~1.0",
67 | "symfony/event-dispatcher": "~2.1"
68 | },
69 | "suggest": {
70 | "psr/log": "For using the console logger",
71 | "symfony/event-dispatcher": ""
72 | },
73 | "type": "library",
74 | "extra": {
75 | "branch-alias": {
76 | "dev-master": "2.5-dev"
77 | }
78 | },
79 | "autoload": {
80 | "psr-0": {
81 | "Symfony\\Component\\Console\\": ""
82 | }
83 | },
84 | "notification-url": "https://packagist.org/downloads/",
85 | "license": [
86 | "MIT"
87 | ],
88 | "authors": [
89 | {
90 | "name": "Symfony Community",
91 | "homepage": "http://symfony.com/contributors"
92 | },
93 | {
94 | "name": "Fabien Potencier",
95 | "email": "fabien@symfony.com"
96 | }
97 | ],
98 | "description": "Symfony Console Component",
99 | "homepage": "http://symfony.com",
100 | "time": "2014-10-05 13:57:04"
101 | }
102 | ],
103 | "packages-dev": [
104 | {
105 | "name": "colinmollenhour/credis",
106 | "version": "1.3",
107 | "source": {
108 | "type": "git",
109 | "url": "https://github.com/colinmollenhour/credis.git",
110 | "reference": "af95629093b98eb8193b3ee18b277109a25e30a2"
111 | },
112 | "dist": {
113 | "type": "zip",
114 | "url": "https://api.github.com/repos/colinmollenhour/credis/zipball/af95629093b98eb8193b3ee18b277109a25e30a2",
115 | "reference": "af95629093b98eb8193b3ee18b277109a25e30a2",
116 | "shasum": ""
117 | },
118 | "require": {
119 | "php": ">=5.3.0"
120 | },
121 | "type": "library",
122 | "autoload": {
123 | "classmap": [
124 | "Client.php",
125 | "Cluster.php"
126 | ]
127 | },
128 | "notification-url": "https://packagist.org/downloads/",
129 | "license": [
130 | "MIT"
131 | ],
132 | "authors": [
133 | {
134 | "name": "Colin Mollenhour",
135 | "email": "colin@mollenhour.com"
136 | }
137 | ],
138 | "description": "Credis is a lightweight interface to the Redis key-value store which wraps the phpredis library when available for better performance.",
139 | "homepage": "https://github.com/colinmollenhour/credis",
140 | "time": "2013-11-01 22:46:58"
141 | },
142 | {
143 | "name": "doctrine/instantiator",
144 | "version": "1.0.4",
145 | "source": {
146 | "type": "git",
147 | "url": "https://github.com/doctrine/instantiator.git",
148 | "reference": "f976e5de371104877ebc89bd8fecb0019ed9c119"
149 | },
150 | "dist": {
151 | "type": "zip",
152 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f976e5de371104877ebc89bd8fecb0019ed9c119",
153 | "reference": "f976e5de371104877ebc89bd8fecb0019ed9c119",
154 | "shasum": ""
155 | },
156 | "require": {
157 | "php": ">=5.3,<8.0-DEV"
158 | },
159 | "require-dev": {
160 | "athletic/athletic": "~0.1.8",
161 | "ext-pdo": "*",
162 | "ext-phar": "*",
163 | "phpunit/phpunit": "~4.0",
164 | "squizlabs/php_codesniffer": "2.0.*@ALPHA"
165 | },
166 | "type": "library",
167 | "extra": {
168 | "branch-alias": {
169 | "dev-master": "1.0.x-dev"
170 | }
171 | },
172 | "autoload": {
173 | "psr-0": {
174 | "Doctrine\\Instantiator\\": "src"
175 | }
176 | },
177 | "notification-url": "https://packagist.org/downloads/",
178 | "license": [
179 | "MIT"
180 | ],
181 | "authors": [
182 | {
183 | "name": "Marco Pivetta",
184 | "email": "ocramius@gmail.com",
185 | "homepage": "http://ocramius.github.com/"
186 | }
187 | ],
188 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
189 | "homepage": "https://github.com/doctrine/instantiator",
190 | "keywords": [
191 | "constructor",
192 | "instantiate"
193 | ],
194 | "time": "2014-10-13 12:58:55"
195 | },
196 | {
197 | "name": "monolog/monolog",
198 | "version": "1.7.0",
199 | "source": {
200 | "type": "git",
201 | "url": "https://github.com/Seldaek/monolog.git",
202 | "reference": "6225b22de9dcf36546be3a0b2fa8e3d986153f57"
203 | },
204 | "dist": {
205 | "type": "zip",
206 | "url": "https://api.github.com/repos/Seldaek/monolog/zipball/6225b22de9dcf36546be3a0b2fa8e3d986153f57",
207 | "reference": "6225b22de9dcf36546be3a0b2fa8e3d986153f57",
208 | "shasum": ""
209 | },
210 | "require": {
211 | "php": ">=5.3.0",
212 | "psr/log": "~1.0"
213 | },
214 | "require-dev": {
215 | "aws/aws-sdk-php": "~2.4.8",
216 | "doctrine/couchdb": "dev-master",
217 | "mlehner/gelf-php": "1.0.*",
218 | "phpunit/phpunit": "~3.7.0",
219 | "raven/raven": "0.5.*",
220 | "ruflin/elastica": "0.90.*"
221 | },
222 | "suggest": {
223 | "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
224 | "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
225 | "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
226 | "ext-mongo": "Allow sending log messages to a MongoDB server",
227 | "mlehner/gelf-php": "Allow sending log messages to a GrayLog2 server",
228 | "raven/raven": "Allow sending log messages to a Sentry server",
229 | "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
230 | },
231 | "type": "library",
232 | "extra": {
233 | "branch-alias": {
234 | "dev-master": "1.7.x-dev"
235 | }
236 | },
237 | "autoload": {
238 | "psr-0": {
239 | "Monolog": "src/"
240 | }
241 | },
242 | "notification-url": "https://packagist.org/downloads/",
243 | "license": [
244 | "MIT"
245 | ],
246 | "authors": [
247 | {
248 | "name": "Jordi Boggiano",
249 | "email": "j.boggiano@seld.be",
250 | "homepage": "http://seld.be",
251 | "role": "Developer"
252 | }
253 | ],
254 | "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
255 | "homepage": "http://github.com/Seldaek/monolog",
256 | "keywords": [
257 | "log",
258 | "logging",
259 | "psr-3"
260 | ],
261 | "time": "2013-11-14 19:48:31"
262 | },
263 | {
264 | "name": "phpunit/php-code-coverage",
265 | "version": "2.0.11",
266 | "source": {
267 | "type": "git",
268 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
269 | "reference": "53603b3c995f5aab6b59c8e08c3a663d2cc810b7"
270 | },
271 | "dist": {
272 | "type": "zip",
273 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/53603b3c995f5aab6b59c8e08c3a663d2cc810b7",
274 | "reference": "53603b3c995f5aab6b59c8e08c3a663d2cc810b7",
275 | "shasum": ""
276 | },
277 | "require": {
278 | "php": ">=5.3.3",
279 | "phpunit/php-file-iterator": "~1.3",
280 | "phpunit/php-text-template": "~1.2",
281 | "phpunit/php-token-stream": "~1.3",
282 | "sebastian/environment": "~1.0",
283 | "sebastian/version": "~1.0"
284 | },
285 | "require-dev": {
286 | "ext-xdebug": ">=2.1.4",
287 | "phpunit/phpunit": "~4.1"
288 | },
289 | "suggest": {
290 | "ext-dom": "*",
291 | "ext-xdebug": ">=2.2.1",
292 | "ext-xmlwriter": "*"
293 | },
294 | "type": "library",
295 | "extra": {
296 | "branch-alias": {
297 | "dev-master": "2.0.x-dev"
298 | }
299 | },
300 | "autoload": {
301 | "classmap": [
302 | "src/"
303 | ]
304 | },
305 | "notification-url": "https://packagist.org/downloads/",
306 | "include-path": [
307 | ""
308 | ],
309 | "license": [
310 | "BSD-3-Clause"
311 | ],
312 | "authors": [
313 | {
314 | "name": "Sebastian Bergmann",
315 | "email": "sb@sebastian-bergmann.de",
316 | "role": "lead"
317 | }
318 | ],
319 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
320 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
321 | "keywords": [
322 | "coverage",
323 | "testing",
324 | "xunit"
325 | ],
326 | "time": "2014-08-31 06:33:04"
327 | },
328 | {
329 | "name": "phpunit/php-file-iterator",
330 | "version": "1.3.4",
331 | "source": {
332 | "type": "git",
333 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
334 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb"
335 | },
336 | "dist": {
337 | "type": "zip",
338 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb",
339 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb",
340 | "shasum": ""
341 | },
342 | "require": {
343 | "php": ">=5.3.3"
344 | },
345 | "type": "library",
346 | "autoload": {
347 | "classmap": [
348 | "File/"
349 | ]
350 | },
351 | "notification-url": "https://packagist.org/downloads/",
352 | "include-path": [
353 | ""
354 | ],
355 | "license": [
356 | "BSD-3-Clause"
357 | ],
358 | "authors": [
359 | {
360 | "name": "Sebastian Bergmann",
361 | "email": "sb@sebastian-bergmann.de",
362 | "role": "lead"
363 | }
364 | ],
365 | "description": "FilterIterator implementation that filters files based on a list of suffixes.",
366 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
367 | "keywords": [
368 | "filesystem",
369 | "iterator"
370 | ],
371 | "time": "2013-10-10 15:34:57"
372 | },
373 | {
374 | "name": "phpunit/php-text-template",
375 | "version": "1.2.0",
376 | "source": {
377 | "type": "git",
378 | "url": "https://github.com/sebastianbergmann/php-text-template.git",
379 | "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a"
380 | },
381 | "dist": {
382 | "type": "zip",
383 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a",
384 | "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a",
385 | "shasum": ""
386 | },
387 | "require": {
388 | "php": ">=5.3.3"
389 | },
390 | "type": "library",
391 | "autoload": {
392 | "classmap": [
393 | "Text/"
394 | ]
395 | },
396 | "notification-url": "https://packagist.org/downloads/",
397 | "include-path": [
398 | ""
399 | ],
400 | "license": [
401 | "BSD-3-Clause"
402 | ],
403 | "authors": [
404 | {
405 | "name": "Sebastian Bergmann",
406 | "email": "sb@sebastian-bergmann.de",
407 | "role": "lead"
408 | }
409 | ],
410 | "description": "Simple template engine.",
411 | "homepage": "https://github.com/sebastianbergmann/php-text-template/",
412 | "keywords": [
413 | "template"
414 | ],
415 | "time": "2014-01-30 17:20:04"
416 | },
417 | {
418 | "name": "phpunit/php-timer",
419 | "version": "1.0.5",
420 | "source": {
421 | "type": "git",
422 | "url": "https://github.com/sebastianbergmann/php-timer.git",
423 | "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c"
424 | },
425 | "dist": {
426 | "type": "zip",
427 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c",
428 | "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c",
429 | "shasum": ""
430 | },
431 | "require": {
432 | "php": ">=5.3.3"
433 | },
434 | "type": "library",
435 | "autoload": {
436 | "classmap": [
437 | "PHP/"
438 | ]
439 | },
440 | "notification-url": "https://packagist.org/downloads/",
441 | "include-path": [
442 | ""
443 | ],
444 | "license": [
445 | "BSD-3-Clause"
446 | ],
447 | "authors": [
448 | {
449 | "name": "Sebastian Bergmann",
450 | "email": "sb@sebastian-bergmann.de",
451 | "role": "lead"
452 | }
453 | ],
454 | "description": "Utility class for timing",
455 | "homepage": "https://github.com/sebastianbergmann/php-timer/",
456 | "keywords": [
457 | "timer"
458 | ],
459 | "time": "2013-08-02 07:42:54"
460 | },
461 | {
462 | "name": "phpunit/php-token-stream",
463 | "version": "1.3.0",
464 | "source": {
465 | "type": "git",
466 | "url": "https://github.com/sebastianbergmann/php-token-stream.git",
467 | "reference": "f8d5d08c56de5cfd592b3340424a81733259a876"
468 | },
469 | "dist": {
470 | "type": "zip",
471 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/f8d5d08c56de5cfd592b3340424a81733259a876",
472 | "reference": "f8d5d08c56de5cfd592b3340424a81733259a876",
473 | "shasum": ""
474 | },
475 | "require": {
476 | "ext-tokenizer": "*",
477 | "php": ">=5.3.3"
478 | },
479 | "require-dev": {
480 | "phpunit/phpunit": "~4.2"
481 | },
482 | "type": "library",
483 | "extra": {
484 | "branch-alias": {
485 | "dev-master": "1.3-dev"
486 | }
487 | },
488 | "autoload": {
489 | "classmap": [
490 | "src/"
491 | ]
492 | },
493 | "notification-url": "https://packagist.org/downloads/",
494 | "license": [
495 | "BSD-3-Clause"
496 | ],
497 | "authors": [
498 | {
499 | "name": "Sebastian Bergmann",
500 | "email": "sebastian@phpunit.de"
501 | }
502 | ],
503 | "description": "Wrapper around PHP's tokenizer extension.",
504 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
505 | "keywords": [
506 | "tokenizer"
507 | ],
508 | "time": "2014-08-31 06:12:13"
509 | },
510 | {
511 | "name": "phpunit/phpunit",
512 | "version": "4.3.5",
513 | "source": {
514 | "type": "git",
515 | "url": "https://github.com/sebastianbergmann/phpunit.git",
516 | "reference": "2dab9d593997db4abcf58d0daf798eb4e9cecfe1"
517 | },
518 | "dist": {
519 | "type": "zip",
520 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2dab9d593997db4abcf58d0daf798eb4e9cecfe1",
521 | "reference": "2dab9d593997db4abcf58d0daf798eb4e9cecfe1",
522 | "shasum": ""
523 | },
524 | "require": {
525 | "ext-dom": "*",
526 | "ext-json": "*",
527 | "ext-pcre": "*",
528 | "ext-reflection": "*",
529 | "ext-spl": "*",
530 | "php": ">=5.3.3",
531 | "phpunit/php-code-coverage": "~2.0",
532 | "phpunit/php-file-iterator": "~1.3.2",
533 | "phpunit/php-text-template": "~1.2",
534 | "phpunit/php-timer": "~1.0.2",
535 | "phpunit/phpunit-mock-objects": "~2.3",
536 | "sebastian/comparator": "~1.0",
537 | "sebastian/diff": "~1.1",
538 | "sebastian/environment": "~1.0",
539 | "sebastian/exporter": "~1.0",
540 | "sebastian/version": "~1.0",
541 | "symfony/yaml": "~2.0"
542 | },
543 | "suggest": {
544 | "phpunit/php-invoker": "~1.1"
545 | },
546 | "bin": [
547 | "phpunit"
548 | ],
549 | "type": "library",
550 | "extra": {
551 | "branch-alias": {
552 | "dev-master": "4.3.x-dev"
553 | }
554 | },
555 | "autoload": {
556 | "classmap": [
557 | "src/"
558 | ]
559 | },
560 | "notification-url": "https://packagist.org/downloads/",
561 | "include-path": [
562 | "",
563 | "../../symfony/yaml/"
564 | ],
565 | "license": [
566 | "BSD-3-Clause"
567 | ],
568 | "authors": [
569 | {
570 | "name": "Sebastian Bergmann",
571 | "email": "sebastian@phpunit.de",
572 | "role": "lead"
573 | }
574 | ],
575 | "description": "The PHP Unit Testing framework.",
576 | "homepage": "http://www.phpunit.de/",
577 | "keywords": [
578 | "phpunit",
579 | "testing",
580 | "xunit"
581 | ],
582 | "time": "2014-11-11 10:11:09"
583 | },
584 | {
585 | "name": "phpunit/phpunit-mock-objects",
586 | "version": "2.3.0",
587 | "source": {
588 | "type": "git",
589 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
590 | "reference": "c63d2367247365f688544f0d500af90a11a44c65"
591 | },
592 | "dist": {
593 | "type": "zip",
594 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/c63d2367247365f688544f0d500af90a11a44c65",
595 | "reference": "c63d2367247365f688544f0d500af90a11a44c65",
596 | "shasum": ""
597 | },
598 | "require": {
599 | "doctrine/instantiator": "~1.0,>=1.0.1",
600 | "php": ">=5.3.3",
601 | "phpunit/php-text-template": "~1.2"
602 | },
603 | "require-dev": {
604 | "phpunit/phpunit": "~4.3"
605 | },
606 | "suggest": {
607 | "ext-soap": "*"
608 | },
609 | "type": "library",
610 | "extra": {
611 | "branch-alias": {
612 | "dev-master": "2.3.x-dev"
613 | }
614 | },
615 | "autoload": {
616 | "classmap": [
617 | "src/"
618 | ]
619 | },
620 | "notification-url": "https://packagist.org/downloads/",
621 | "license": [
622 | "BSD-3-Clause"
623 | ],
624 | "authors": [
625 | {
626 | "name": "Sebastian Bergmann",
627 | "email": "sb@sebastian-bergmann.de",
628 | "role": "lead"
629 | }
630 | ],
631 | "description": "Mock Object library for PHPUnit",
632 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
633 | "keywords": [
634 | "mock",
635 | "xunit"
636 | ],
637 | "time": "2014-10-03 05:12:11"
638 | },
639 | {
640 | "name": "predis/predis",
641 | "version": "v1.0.0",
642 | "source": {
643 | "type": "git",
644 | "url": "https://github.com/nrk/predis.git",
645 | "reference": "d4be306d0aca28b5633b96adef03b29fea569c2f"
646 | },
647 | "dist": {
648 | "type": "zip",
649 | "url": "https://api.github.com/repos/nrk/predis/zipball/d4be306d0aca28b5633b96adef03b29fea569c2f",
650 | "reference": "d4be306d0aca28b5633b96adef03b29fea569c2f",
651 | "shasum": ""
652 | },
653 | "require": {
654 | "php": ">=5.3.2"
655 | },
656 | "require-dev": {
657 | "phpunit/phpunit": "~4.0"
658 | },
659 | "suggest": {
660 | "ext-curl": "Allows access to Webdis when paired with phpiredis",
661 | "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol"
662 | },
663 | "type": "library",
664 | "extra": {
665 | "branch-alias": {
666 | "dev-master": "1.0-dev"
667 | }
668 | },
669 | "autoload": {
670 | "psr-4": {
671 | "Predis\\": "src/"
672 | }
673 | },
674 | "notification-url": "https://packagist.org/downloads/",
675 | "license": [
676 | "MIT"
677 | ],
678 | "authors": [
679 | {
680 | "name": "Daniele Alessandri",
681 | "email": "suppakilla@gmail.com",
682 | "homepage": "http://clorophilla.net"
683 | }
684 | ],
685 | "description": "Flexible and feature-complete PHP client library for Redis",
686 | "homepage": "http://github.com/nrk/predis",
687 | "keywords": [
688 | "nosql",
689 | "predis",
690 | "redis"
691 | ],
692 | "time": "2014-08-01 09:59:50"
693 | },
694 | {
695 | "name": "sebastian/comparator",
696 | "version": "1.0.1",
697 | "source": {
698 | "type": "git",
699 | "url": "https://github.com/sebastianbergmann/comparator.git",
700 | "reference": "e54a01c0da1b87db3c5a3c4c5277ddf331da4aef"
701 | },
702 | "dist": {
703 | "type": "zip",
704 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e54a01c0da1b87db3c5a3c4c5277ddf331da4aef",
705 | "reference": "e54a01c0da1b87db3c5a3c4c5277ddf331da4aef",
706 | "shasum": ""
707 | },
708 | "require": {
709 | "php": ">=5.3.3",
710 | "sebastian/diff": "~1.1",
711 | "sebastian/exporter": "~1.0"
712 | },
713 | "require-dev": {
714 | "phpunit/phpunit": "~4.1"
715 | },
716 | "type": "library",
717 | "extra": {
718 | "branch-alias": {
719 | "dev-master": "1.0.x-dev"
720 | }
721 | },
722 | "autoload": {
723 | "classmap": [
724 | "src/"
725 | ]
726 | },
727 | "notification-url": "https://packagist.org/downloads/",
728 | "license": [
729 | "BSD-3-Clause"
730 | ],
731 | "authors": [
732 | {
733 | "name": "Jeff Welch",
734 | "email": "whatthejeff@gmail.com"
735 | },
736 | {
737 | "name": "Volker Dusch",
738 | "email": "github@wallbash.com"
739 | },
740 | {
741 | "name": "Bernhard Schussek",
742 | "email": "bschussek@2bepublished.at"
743 | },
744 | {
745 | "name": "Sebastian Bergmann",
746 | "email": "sebastian@phpunit.de"
747 | }
748 | ],
749 | "description": "Provides the functionality to compare PHP values for equality",
750 | "homepage": "http://www.github.com/sebastianbergmann/comparator",
751 | "keywords": [
752 | "comparator",
753 | "compare",
754 | "equality"
755 | ],
756 | "time": "2014-05-11 23:00:21"
757 | },
758 | {
759 | "name": "sebastian/diff",
760 | "version": "1.2.0",
761 | "source": {
762 | "type": "git",
763 | "url": "https://github.com/sebastianbergmann/diff.git",
764 | "reference": "5843509fed39dee4b356a306401e9dd1a931fec7"
765 | },
766 | "dist": {
767 | "type": "zip",
768 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/5843509fed39dee4b356a306401e9dd1a931fec7",
769 | "reference": "5843509fed39dee4b356a306401e9dd1a931fec7",
770 | "shasum": ""
771 | },
772 | "require": {
773 | "php": ">=5.3.3"
774 | },
775 | "require-dev": {
776 | "phpunit/phpunit": "~4.2"
777 | },
778 | "type": "library",
779 | "extra": {
780 | "branch-alias": {
781 | "dev-master": "1.2-dev"
782 | }
783 | },
784 | "autoload": {
785 | "classmap": [
786 | "src/"
787 | ]
788 | },
789 | "notification-url": "https://packagist.org/downloads/",
790 | "license": [
791 | "BSD-3-Clause"
792 | ],
793 | "authors": [
794 | {
795 | "name": "Kore Nordmann",
796 | "email": "mail@kore-nordmann.de"
797 | },
798 | {
799 | "name": "Sebastian Bergmann",
800 | "email": "sebastian@phpunit.de"
801 | }
802 | ],
803 | "description": "Diff implementation",
804 | "homepage": "http://www.github.com/sebastianbergmann/diff",
805 | "keywords": [
806 | "diff"
807 | ],
808 | "time": "2014-08-15 10:29:00"
809 | },
810 | {
811 | "name": "sebastian/environment",
812 | "version": "1.2.0",
813 | "source": {
814 | "type": "git",
815 | "url": "https://github.com/sebastianbergmann/environment.git",
816 | "reference": "0d9bf79554d2a999da194a60416c15cf461eb67d"
817 | },
818 | "dist": {
819 | "type": "zip",
820 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/0d9bf79554d2a999da194a60416c15cf461eb67d",
821 | "reference": "0d9bf79554d2a999da194a60416c15cf461eb67d",
822 | "shasum": ""
823 | },
824 | "require": {
825 | "php": ">=5.3.3"
826 | },
827 | "require-dev": {
828 | "phpunit/phpunit": "~4.3"
829 | },
830 | "type": "library",
831 | "extra": {
832 | "branch-alias": {
833 | "dev-master": "1.2.x-dev"
834 | }
835 | },
836 | "autoload": {
837 | "classmap": [
838 | "src/"
839 | ]
840 | },
841 | "notification-url": "https://packagist.org/downloads/",
842 | "license": [
843 | "BSD-3-Clause"
844 | ],
845 | "authors": [
846 | {
847 | "name": "Sebastian Bergmann",
848 | "email": "sebastian@phpunit.de"
849 | }
850 | ],
851 | "description": "Provides functionality to handle HHVM/PHP environments",
852 | "homepage": "http://www.github.com/sebastianbergmann/environment",
853 | "keywords": [
854 | "Xdebug",
855 | "environment",
856 | "hhvm"
857 | ],
858 | "time": "2014-10-22 06:38:05"
859 | },
860 | {
861 | "name": "sebastian/exporter",
862 | "version": "1.0.2",
863 | "source": {
864 | "type": "git",
865 | "url": "https://github.com/sebastianbergmann/exporter.git",
866 | "reference": "c7d59948d6e82818e1bdff7cadb6c34710eb7dc0"
867 | },
868 | "dist": {
869 | "type": "zip",
870 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c7d59948d6e82818e1bdff7cadb6c34710eb7dc0",
871 | "reference": "c7d59948d6e82818e1bdff7cadb6c34710eb7dc0",
872 | "shasum": ""
873 | },
874 | "require": {
875 | "php": ">=5.3.3"
876 | },
877 | "require-dev": {
878 | "phpunit/phpunit": "~4.0"
879 | },
880 | "type": "library",
881 | "extra": {
882 | "branch-alias": {
883 | "dev-master": "1.0.x-dev"
884 | }
885 | },
886 | "autoload": {
887 | "classmap": [
888 | "src/"
889 | ]
890 | },
891 | "notification-url": "https://packagist.org/downloads/",
892 | "license": [
893 | "BSD-3-Clause"
894 | ],
895 | "authors": [
896 | {
897 | "name": "Jeff Welch",
898 | "email": "whatthejeff@gmail.com"
899 | },
900 | {
901 | "name": "Volker Dusch",
902 | "email": "github@wallbash.com"
903 | },
904 | {
905 | "name": "Bernhard Schussek",
906 | "email": "bschussek@2bepublished.at"
907 | },
908 | {
909 | "name": "Sebastian Bergmann",
910 | "email": "sebastian@phpunit.de"
911 | },
912 | {
913 | "name": "Adam Harvey",
914 | "email": "aharvey@php.net"
915 | }
916 | ],
917 | "description": "Provides the functionality to export PHP variables for visualization",
918 | "homepage": "http://www.github.com/sebastianbergmann/exporter",
919 | "keywords": [
920 | "export",
921 | "exporter"
922 | ],
923 | "time": "2014-09-10 00:51:36"
924 | },
925 | {
926 | "name": "sebastian/version",
927 | "version": "1.0.3",
928 | "source": {
929 | "type": "git",
930 | "url": "https://github.com/sebastianbergmann/version.git",
931 | "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43"
932 | },
933 | "dist": {
934 | "type": "zip",
935 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43",
936 | "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43",
937 | "shasum": ""
938 | },
939 | "type": "library",
940 | "autoload": {
941 | "classmap": [
942 | "src/"
943 | ]
944 | },
945 | "notification-url": "https://packagist.org/downloads/",
946 | "license": [
947 | "BSD-3-Clause"
948 | ],
949 | "authors": [
950 | {
951 | "name": "Sebastian Bergmann",
952 | "email": "sebastian@phpunit.de",
953 | "role": "lead"
954 | }
955 | ],
956 | "description": "Library that helps with managing the version number of Git-hosted PHP projects",
957 | "homepage": "https://github.com/sebastianbergmann/version",
958 | "time": "2014-03-07 15:35:33"
959 | },
960 | {
961 | "name": "symfony/yaml",
962 | "version": "v2.5.6",
963 | "target-dir": "Symfony/Component/Yaml",
964 | "source": {
965 | "type": "git",
966 | "url": "https://github.com/symfony/Yaml.git",
967 | "reference": "2d9f527449cabfa8543dd7fa3a466d6ae83d6726"
968 | },
969 | "dist": {
970 | "type": "zip",
971 | "url": "https://api.github.com/repos/symfony/Yaml/zipball/2d9f527449cabfa8543dd7fa3a466d6ae83d6726",
972 | "reference": "2d9f527449cabfa8543dd7fa3a466d6ae83d6726",
973 | "shasum": ""
974 | },
975 | "require": {
976 | "php": ">=5.3.3"
977 | },
978 | "type": "library",
979 | "extra": {
980 | "branch-alias": {
981 | "dev-master": "2.5-dev"
982 | }
983 | },
984 | "autoload": {
985 | "psr-0": {
986 | "Symfony\\Component\\Yaml\\": ""
987 | }
988 | },
989 | "notification-url": "https://packagist.org/downloads/",
990 | "license": [
991 | "MIT"
992 | ],
993 | "authors": [
994 | {
995 | "name": "Symfony Community",
996 | "homepage": "http://symfony.com/contributors"
997 | },
998 | {
999 | "name": "Fabien Potencier",
1000 | "email": "fabien@symfony.com"
1001 | }
1002 | ],
1003 | "description": "Symfony Yaml Component",
1004 | "homepage": "http://symfony.com",
1005 | "time": "2014-10-01 05:50:18"
1006 | }
1007 | ],
1008 | "aliases": [],
1009 | "minimum-stability": "stable",
1010 | "stability-flags": [],
1011 | "prefer-stable": false,
1012 | "prefer-lowest": false,
1013 | "platform": {
1014 | "php": ">=5.3.7",
1015 | "ext-pcntl": "*"
1016 | },
1017 | "platform-dev": []
1018 | }
1019 |
--------------------------------------------------------------------------------