├── demo
├── queues
│ ├── logs
│ │ └── .htaccess
│ ├── SampleQueue.php
│ └── BeanstalkSampleQueue.php
├── runners
│ ├── logs
│ │ └── .htaccess
│ ├── SampleRunner.php
│ ├── BeanstalkSampleDaemon.php
│ └── README.md
├── htdocs
│ ├── index.php
│ ├── .htaccess
│ └── index_secured.php
├── config.php
├── workers
│ └── SampleWorker.php
└── cli.php
├── test
├── PHPQueue
│ ├── Backend
│ │ ├── downloads
│ │ │ └── .htaccess
│ │ ├── localfs_docroot
│ │ │ └── .htaccess
│ │ ├── cc_logo.jpg
│ │ ├── PDOSqliteTest.php
│ │ ├── PDOMysqlTest.php
│ │ ├── WindowsAzureServiceBusTest.php
│ │ ├── CSVTest.php
│ │ ├── AmazonSQSV1Test.php
│ │ ├── AmazonSQSV2Test.php
│ │ ├── MongoDBTest.php
│ │ ├── BeanstalkdTest.php
│ │ ├── IronMQTest.php
│ │ ├── StompTest.php
│ │ ├── MemcacheTest.php
│ │ ├── AmazonS3V1Test.php
│ │ ├── WindowsAzureBlobTest.php
│ │ ├── LocalFSTest.php
│ │ ├── AmazonS3V2Test.php
│ │ ├── PDOBaseTest.php
│ │ └── PredisTest.php
│ └── BaseTest.php
├── bootstrap.php
└── Demo
│ └── Workers
│ └── SampleWorkerTest.php
├── src
└── PHPQueue
│ ├── Interfaces
│ ├── Auth.php
│ ├── Config.php
│ ├── FifoQueueStore.php
│ └── AtomicReadBuffer.php
│ ├── Exception
│ ├── Exception.php
│ ├── JsonException.php
│ ├── BackendException.php
│ ├── JobNotFoundException.php
│ ├── QueueNotFoundException.php
│ └── WorkerNotFoundException.php
│ ├── Json.php
│ ├── Worker.php
│ ├── Backend
│ ├── Proxy.php
│ ├── FS.php
│ ├── AmazonS3.php
│ ├── AmazonSQS.php
│ ├── Base.php
│ ├── CSV.php
│ ├── Beanstalkd.php
│ ├── MongoDB.php
│ ├── Memcache.php
│ ├── IronMQ.php
│ ├── Aws
│ │ ├── AmazonSQSV1.php
│ │ ├── AmazonSQSV2.php
│ │ └── AmazonS3V1.php
│ ├── Stomp.php
│ ├── WindowsAzureServiceBus.php
│ ├── WindowsAzureBlob.php
│ ├── LocalFS.php
│ ├── PDO.php
│ └── Predis.php
│ ├── Helpers.php
│ ├── Logger.php
│ ├── JobQueue.php
│ ├── Job.php
│ ├── Daemon.php
│ ├── Cli.php
│ ├── Runner.php
│ ├── REST.php
│ └── Base.php
├── .gitignore
├── phpunit.travis.xml
├── .travis.yml
├── phpunit.xml.dist
├── composer.json
└── README.md
/demo/queues/logs/.htaccess:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/runners/logs/.htaccess:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/PHPQueue/Backend/downloads/.htaccess:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/PHPQueue/Backend/localfs_docroot/.htaccess:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/htdocs/index.php:
--------------------------------------------------------------------------------
1 | run();
9 |
--------------------------------------------------------------------------------
/src/PHPQueue/Exception/JobNotFoundException.php:
--------------------------------------------------------------------------------
1 | data;
11 | $jobData['var2'] = "Welcome back!";
12 | $this->result_data = $jobData;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/PHPQueue/Interfaces/Config.php:
--------------------------------------------------------------------------------
1 | 'sqlite::memory:'
10 | , 'db_table' => 'pdotest'
11 | );
12 | $this->object = new PDO($options);
13 | // Create table
14 | $this->assertTrue($this->object->createTable('pdotest'));
15 | $this->object->clearAll();
16 | }
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/src/PHPQueue/Json.php:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | src
10 |
11 |
12 |
13 |
14 | test
15 | test/PHPQueue/Backend/PDOTest.php
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/PHPQueue/Worker.php:
--------------------------------------------------------------------------------
1 | input_data = $inputData;
13 | $this->result_data = null;
14 | }
15 | /**
16 | * @param \PHPQueue\Job $jobObject
17 | */
18 | public function runJob($jobObject){}
19 | public function afterJob(){}
20 |
21 | public function onSuccess(){}
22 | public function onError(\Exception $ex){}
23 | }
24 |
--------------------------------------------------------------------------------
/src/PHPQueue/Interfaces/FifoQueueStore.php:
--------------------------------------------------------------------------------
1 | add --data '{"boo":"bar","foo":"car"}'
5 | // php cli.php work
6 | require_once __DIR__ . '/config.php';
7 |
8 | $queue_name = $argv[1];
9 | $action = $argv[2];
10 | $options = array('queue'=>$queue_name);
11 | $c = new PHPQueue\Cli($options);
12 |
13 | switch ($action) {
14 | case 'add':
15 | $payload_json = $argv[4];
16 | $payload = json_decode($payload_json, true);
17 | $c->add($payload);
18 | break;
19 | case 'work':
20 | $c->work();
21 | break;
22 | case 'get':
23 | break;
24 | default:
25 | echo "Error: No action declared...\n";
26 | break;
27 | }
28 |
--------------------------------------------------------------------------------
/test/Demo/Workers/SampleWorkerTest.php:
--------------------------------------------------------------------------------
1 | object = \PHPQueue\Base::getWorker('Sample');
10 | }
11 |
12 | public function testRunJob()
13 | {
14 | $data1 = array(
15 | 'worker' => 'Sample'
16 | , 'data' => array('var1'=>"Milo")
17 | );
18 | $job = new \PHPQueue\Job($data1);
19 | $this->object->runJob($job);
20 | $this->assertEquals(
21 | array('var1'=>"Milo",'var2'=>"Welcome back!")
22 | , $this->object->result_data
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/PHPQueue/Backend/Proxy.php:
--------------------------------------------------------------------------------
1 | backend = $backend;
11 | }
12 |
13 | public function getBackend()
14 | {
15 | return $this->backend;
16 | }
17 |
18 | public function __get($property)
19 | {
20 | return $this->getBackend()->{$property};
21 | }
22 |
23 | public function __set($property, $value)
24 | {
25 | $this->getBackend()->{$property} = $value;
26 | }
27 |
28 | public function __call($method, $arguments)
29 | {
30 | return call_user_func_array(array($this->getBackend(), $method), $arguments);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/test/PHPQueue/Backend/PDOMysqlTest.php:
--------------------------------------------------------------------------------
1 | 'mysql:host=localhost;dbname=phpqueuetest'
10 | , 'db_table' => 'pdotest'
11 | , 'pdo_options' => array(
12 | \PDO::ATTR_PERSISTENT => true
13 | )
14 | );
15 |
16 | // Check that the database exists, and politely skip if not.
17 | try {
18 | new \PDO($options['connection_string']);
19 | } catch ( \PDOException $ex ) {
20 | $this->markTestSkipped('Database access failed: ' . $ex->getMessage());
21 | }
22 |
23 | $this->object = new PDO($options);
24 | // Create table
25 | $this->assertTrue($this->object->createTable('pdotest'));
26 | $this->object->clearAll();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | #
2 | # this file provides configuration for Travis Continuous Integration
3 | # written by Sam-Mauris Yong
4 | #
5 |
6 | language: php
7 |
8 | dist: trusty
9 |
10 | matrix:
11 | include:
12 | - php: 5.3
13 | dist: precise
14 | - php: 5.4
15 | - php: 5.5
16 | - php: 5.6
17 | - php: 7.0
18 | - php: hhvm
19 | allow_failures:
20 | - php: 5.3
21 | dist: precise
22 | - php: 7.0
23 | - php: hhvm
24 |
25 | mysql:
26 | database: phpqueuetest
27 | username: root
28 | encoding: utf8
29 |
30 | before_install: echo "extension=memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
31 |
32 | before_script:
33 | - mysql -e 'create database phpqueuetest;'
34 | - composer self-update
35 | - composer install --no-dev
36 |
37 | script: phpunit --coverage-text -c phpunit.travis.xml
38 |
39 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
20 | src
21 |
22 |
23 |
24 |
25 | test
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/PHPQueue/Helpers.php:
--------------------------------------------------------------------------------
1 | $code
10 | , 'data' => $data
11 | );
12 | if (!empty($message)) $return['message'] = $message;
13 | } elseif ( is_object($data) ) {
14 | $return = new \stdClass();
15 | $return->code = $code;
16 | $return->data = $data;
17 | if (!empty($message)) $return->message = $message;
18 | } else {
19 | $return = new \stdClass();
20 | $return->code = $code;
21 | if (!empty($message)) $return->message = $message;
22 | }
23 |
24 | return $return;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/PHPQueue/Backend/FS.php:
--------------------------------------------------------------------------------
1 | container = $container_name;
22 |
23 | return true;
24 | }
25 |
26 | return false;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/PHPQueue/Logger.php:
--------------------------------------------------------------------------------
1 | pushHandler(new \Monolog\Handler\StreamHandler($logPath, $logLevel));
18 | self::$all_logs[$logName] = $logger;
19 | }
20 |
21 | return self::$all_logs[$logName];
22 | }
23 |
24 | public static function cycleLog($logName, $logLevel = Logger::WARNING, $logPath=null)
25 | {
26 | if (!empty(self::$all_logs[$logName])) {
27 | unset(self::$all_logs[$logName]);
28 | self::createLogger($logName, $logLevel, $logPath);
29 | }
30 |
31 | return self::$all_logs[$logName];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/PHPQueue/JobQueue.php:
--------------------------------------------------------------------------------
1 | job_id = $jobId;
20 | if (!empty($data)) {
21 | if (is_array($data)) {
22 | $this->worker = $data['worker'];
23 | $this->data = $data['data'];
24 | } elseif (is_object($data)) {
25 | $this->worker = $data->worker;
26 | $this->data = $data->data;
27 | } else {
28 | try {
29 | $data = json_decode($data, true);
30 | $this->worker = $data['worker'];
31 | $this->data = $data['data'];
32 | } catch (\Exception $ex) {}
33 | }
34 | }
35 | }
36 |
37 | public function isSuccessful()
38 | {
39 | return ($this->status == self::OK);
40 | }
41 |
42 | public function onSuccessful()
43 | {
44 | $this->status = self::OK;
45 | }
46 |
47 | public function onError()
48 | {
49 | $this->status = self::NOT_OK;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/demo/queues/SampleQueue.php:
--------------------------------------------------------------------------------
1 | file_path)) {
11 | $data = unserialize(file_get_contents($this->file_path));
12 | if (is_array($data)) {
13 | $this->jobs = $data;
14 | }
15 | }
16 | }
17 |
18 | public function __destruct()
19 | {
20 | @file_put_contents($this->file_path, serialize($this->jobs));
21 | }
22 |
23 | public function addJob($newJob = null)
24 | {
25 | parent::addJob($newJob);
26 | array_unshift($this->jobs, $newJob);
27 |
28 | return true;
29 | }
30 |
31 | public function getJob($jobId = null)
32 | {
33 | parent::getJob();
34 | if ( empty($this->jobs) ) {
35 | throw new Exception("No more jobs.");
36 | }
37 | $jobData = array_pop($this->jobs);
38 | $nextJob = new \PHPQueue\Job();
39 | $nextJob->data = $jobData;
40 | $nextJob->worker = 'Sample';
41 |
42 | return $nextJob;
43 | }
44 |
45 | public function getQueueSize()
46 | {
47 | parent::getQueueSize();
48 |
49 | return count($this->jobs);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/PHPQueue/Backend/AmazonS3.php:
--------------------------------------------------------------------------------
1 | options = $options;
15 | }
16 |
17 | /**
18 | * @throws \PHPQueue\Exception\BackendException
19 | * @return \PHPQueue\Backend\Aws\AmazonS3V1|\PHPQueue\Backend\Aws\AmazonS3V2
20 | */
21 | public function getBackend()
22 | {
23 | if (null === $this->backend) {
24 | if (class_exists('\Aws\S3\S3Client')) { // SDK v2
25 | $this->backend = new AmazonS3V2($this->options);
26 | } elseif (class_exists('\AmazonS3')) { // SDK v1
27 | $this->backend = new AmazonS3V1($this->options);
28 | } else {
29 | throw new BackendException('AWS PHP SDK not found.');
30 | }
31 | }
32 |
33 | return $this->backend;
34 | }
35 |
36 | /**
37 | * @throws \PHPQueue\Exception\BackendException
38 | */
39 | public function setBackend($backend)
40 | {
41 | if (!$backend instanceof AmazonS3V1 && !$backend instanceof AmazonS3V2) {
42 | throw new BackendException('Backend must be instance of AmazonS3V1 or AmazonS3V2.');
43 | }
44 |
45 | $this->backend = $backend;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/PHPQueue/Backend/AmazonSQS.php:
--------------------------------------------------------------------------------
1 | options = $options;
15 | }
16 |
17 | /**
18 | * @throws \PHPQueue\Exception\BackendException
19 | * @return \PHPQueue\Backend\Aws\AmazonSQSV1|\PHPQueue\Backend\Aws\AmazonSQSV2
20 | */
21 | public function getBackend()
22 | {
23 | if (null === $this->backend) {
24 | if (class_exists('\Aws\Sqs\SqsClient')) { // SDK v2
25 | $this->backend = new AmazonSQSV2($this->options);
26 | } elseif (class_exists('\AmazonSQS')) { // SDK v1
27 | $this->backend = new AmazonSQSV1($this->options);
28 | } else {
29 | throw new BackendException('AWS PHP SDK not found.');
30 | }
31 | }
32 |
33 | return $this->backend;
34 | }
35 |
36 | /**
37 | * @throws \PHPQueue\Exception\BackendException
38 | */
39 | public function setBackend($backend)
40 | {
41 | if (!$backend instanceof AmazonSQSV1 && !$backend instanceof AmazonSQSV2) {
42 | throw new BackendException('Backend must be instance of AmazonSQSV1 or AmazonSQSV2.');
43 | }
44 |
45 | $this->backend = $backend;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/demo/runners/BeanstalkSampleDaemon.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php
2 | $pid_file,
25 | ),
26 | function($stdin, $stdout, $sterr) {
27 | class BeanstalkSample extends PHPQueue\Runner{}
28 | $runner = new BeanstalkSample('BeanstalkSample', array('logPath'=>__DIR__ . '/logs/'));
29 | $runner->run();
30 | }
31 | );
32 | Clio\Console::output('%g[OK]%n');
33 | } catch (Exception $ex) {
34 | Clio\Console::output('%r[FAILED]%n');
35 | }
36 | break;
37 | case 'stop':
38 | Clio\Console::stdout('Stopping... ');
39 | try {
40 | Clio\Daemon::kill($pid_file, true);
41 | Clio\Console::output('%g[OK]%n');
42 | } catch (Exception $ex) {
43 | Clio\Console::output('%r[FAILED]%n');
44 | }
45 | break;
46 | default:
47 | Clio\Console::output("Unknown action.");
48 | break;
49 | }
50 |
--------------------------------------------------------------------------------
/demo/htdocs/index_secured.php:
--------------------------------------------------------------------------------
1 | / -H "Content-Type: application/json" -H "Authorization: Token ki*ksjdu^GDjc\nk" -d '{"people":["Talia","Tabitha","Tolver"]}'
8 | *
9 | * curl -XPUT http:/// -H "Authorization: Token ki*ksjdu^GDjc\nk"
10 | *
11 | */
12 | require_once dirname(__DIR__) . '/config.php';
13 | class SecuredREST implements \PHPQueue\Interfaces\Auth
14 | {
15 | public static $valid_token = 'ki*ksjdu^GDjc\nk';
16 |
17 | public function isAuth()
18 | {
19 | $token = $this->getToken();
20 | if ( !empty($token) && ($token == self::$valid_token)) {
21 | return true;
22 | }
23 |
24 | return false;
25 | }
26 |
27 | private function getToken()
28 | {
29 | $authHeader = null;
30 | if ( function_exists( 'apache_request_headers' ) ) {
31 | $apacheHeaders = apache_request_headers();
32 | if ( isset( $apacheHeaders['Authorization'] ) )
33 | $authHeader = $apacheHeaders['Authorization'];
34 | } else {
35 | if ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) )
36 | $authHeader = $_SERVER['HTTP_AUTHORIZATION'];
37 | }
38 | if ( isset( $authHeader ) ) {
39 | $m = array();
40 | $tokenPattern = '/^(?PToken)\s(?P[a-zA-Z0-9\!\@\#\$\%\^\&\*\(\)\\\]+)$/';
41 | $match = preg_match( $tokenPattern, $authHeader, $m );
42 | if ($match > 0) {
43 | return $m['token'];
44 | }
45 | }
46 |
47 | return false;
48 | }
49 | }
50 | $options = array('auth'=>new SecuredREST);
51 | PHPQueue\REST::defaultRoutes($options);
52 |
--------------------------------------------------------------------------------
/src/PHPQueue/Backend/Base.php:
--------------------------------------------------------------------------------
1 | last_job_id = $jobId;
20 | }
21 | }
22 | public function afterGet()
23 | {
24 | $id = $this->last_job_id;
25 | $this->open_items[$id] = $this->last_job;
26 | }
27 |
28 | public function beforeClear($jobId=null)
29 | {
30 | if (!empty($jobId)) {
31 | $this->last_job_id = $jobId;
32 | }
33 | }
34 |
35 | public function beforeRelease($jobId=null)
36 | {
37 | if (!empty($jobId)) {
38 | $this->last_job_id = $jobId;
39 | }
40 | }
41 | public function release($jobId=null){}
42 | public function afterClearRelease()
43 | {
44 | $id = $this->last_job_id;
45 | unset($this->open_items[$id]);
46 | }
47 |
48 | public function onError($ex){}
49 |
50 | public function isJobOpen($jobId)
51 | {
52 | if (empty($this->open_items[$jobId])) {
53 | throw new \PHPQueue\Exception\JobNotFoundException("Job was not previously retrieved.");
54 | }
55 | }
56 |
57 | public function getConnection()
58 | {
59 | if (is_null($this->connection)) {
60 | $this->connect();
61 | }
62 |
63 | return $this->connection;
64 | }
65 |
66 | public function setConnection($connection)
67 | {
68 | $this->connection = $connection;
69 |
70 | return true;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "coderkungfu/php-queue",
3 | "description": "A unified front-end for different queuing backends. Includes a REST server, CLI interface and daemon runners.",
4 | "keywords": ["queue", "transaction"],
5 | "homepage": "http://github.com/CoderKungfu/php-queue",
6 | "type": "library",
7 | "license": "MIT",
8 | "version": "1.0.2",
9 | "authors": [
10 | {
11 | "name": "Michael Cheng",
12 | "email": "mcheng.work@gmail.com"
13 | }
14 | ],
15 | "require": {
16 | "php": ">=5.3.0",
17 | "monolog/monolog": "*",
18 | "clio/clio": "0.1.*"
19 | },
20 | "require-dev": {
21 | "mrpoundsign/pheanstalk-5.3": "dev-master",
22 | "aws/aws-sdk-php": ">=2.8",
23 | "amazonwebservices/aws-sdk-for-php": "dev-master",
24 | "predis/predis": "1.*",
25 | "iron-io/iron_mq": "dev-master",
26 | "ext-memcache": "*",
27 | "microsoft/windowsazure": ">=0.4.0"
28 | },
29 | "suggest": {
30 | "predis/predis": "For Redis backend support",
31 | "mrpoundsign/pheanstalk-5.3": "For Beanstalkd backend support",
32 | "aws/aws-sdk-php": "For AWS SQS backend support",
33 | "amazonwebservices/aws-sdk-for-php": "For AWS SQS backend support (legacy version)",
34 | "pecl-mongodb": "For MongoDB backend support",
35 | "clio/clio": "Support for daemonizing PHP CLI runner",
36 | "iron-io/iron_mq": "For IronMQ backend support",
37 | "microsoft/windowsazure": "For Windows Azure Service Bus backend support",
38 | "Respect/Rest": "For a REST server to post job data",
39 | "fusesource/stomp-php": "For the STOMP backend"
40 | },
41 | "autoload": {
42 | "psr-0": {"PHPQueue": "src/"}
43 | },
44 | "extra": {
45 | "branch-alias": {
46 | "dev-master": "1.0.0-dev"
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/test/PHPQueue/Backend/WindowsAzureServiceBusTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Windows Azure not installed');
12 | } else {
13 | $options = array(
14 | 'connection_string' => 'Endpoint=https://noobqueue.servicebus.windows.net/;SharedSecretIssuer=owner;SharedSecretValue=72smuycIAYp7H2HvN4WleJzMrykNb45AKo+IVwcWCoQ='
15 | , 'queue' => 'myqueue'
16 | );
17 | $this->object = new WindowsAzureServiceBus($options);
18 | }
19 | }
20 |
21 | public function testAdd()
22 | {
23 | $data = array('1','Willy','Wonka');
24 | $result = $this->object->add($data);
25 | $this->assertTrue($result);
26 | }
27 |
28 | /**
29 | * @depends testAdd
30 | */
31 | public function testGet()
32 | {
33 | $result = $this->object->get();
34 | $this->assertNotEmpty($result);
35 | $this->assertEquals(array('1','Willy','Wonka'), $result);
36 | $this->object->release($this->object->last_job_id);
37 | }
38 |
39 | /**
40 | * @depends testAdd
41 | */
42 | public function testClear()
43 | {
44 | try {
45 | $jobId = 'xxx';
46 | $this->object->clear($jobId);
47 | $this->fail("Should not be able to delete.");
48 | } catch (\Exception $ex) {
49 | $this->assertTrue(true);
50 | }
51 |
52 | $result = $this->object->get();
53 | $this->assertNotEmpty($result);
54 | $jobId = $this->object->last_job_id;
55 | $result = $this->object->clear($jobId);
56 | $this->assertTrue($result);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/test/PHPQueue/Backend/CSVTest.php:
--------------------------------------------------------------------------------
1 | $filename);
13 | $this->object = new CSV($opt);
14 | }
15 |
16 | public function testAdd()
17 | {
18 | $data = array('1','Willy','Wonka');
19 | $result = $this->object->add($data);
20 | $this->assertTrue($result);
21 |
22 | $data = array('2','Charlie','Chaplin');
23 | $result = $this->object->add($data);
24 | $this->assertTrue($result);
25 |
26 | $data = array('3','Apple','Wong');
27 | $result = $this->object->add($data);
28 | $this->assertTrue($result);
29 | }
30 |
31 | /**
32 | * @depends testAdd
33 | */
34 | public function testGet()
35 | {
36 | $result = $this->object->get();
37 | $this->assertNotEmpty($result);
38 | $this->assertEquals(array('1','Willy','Wonka'), $result);
39 |
40 | $result = $this->object->get(3);
41 | $this->assertNotEmpty($result);
42 | $this->assertEquals(array('3','Apple','Wong'), $result);
43 |
44 | $result = $this->object->get();
45 | $this->assertNotEmpty($result);
46 | $this->assertEquals(array('2','Charlie','Chaplin'), $result);
47 |
48 | $data = array('4','Cherian','George');
49 | $result = $this->object->add($data);
50 | $this->assertTrue($result);
51 |
52 | $result = $this->object->get();
53 | $this->assertNotEmpty($result);
54 | $this->assertEquals(array('3','Apple','Wong'), $result);
55 |
56 | $result = $this->object->get();
57 | $this->assertNotEmpty($result);
58 | $this->assertEquals(array('4','Cherian','George'), $result);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/demo/queues/BeanstalkSampleQueue.php:
--------------------------------------------------------------------------------
1 | '127.0.0.1'
7 | , 'tube' => 'queue1'
8 | );
9 | private $queueWorker = 'Sample';
10 | private $resultLog;
11 |
12 | public function __construct()
13 | {
14 | $this->dataSource = \PHPQueue\Base::backendFactory('Beanstalkd', $this->sourceConfig);
15 | $this->resultLog = \PHPQueue\Logger::createLogger(
16 | 'BeanstalkSampleLogger'
17 | , PHPQueue\Logger::INFO
18 | , __DIR__ . '/logs/results.log'
19 | );
20 | }
21 |
22 | public function addJob($newJob = null, $DEFAULT_PRIORITY=1024, $DEFAULT_DELAY=0, $DEFAULT_TTR=60)
23 | {
24 | $formatted_data = array('worker'=>$this->queueWorker, 'data'=>$newJob);
25 | $this->dataSource->add($formatted_data, $DEFAULT_PRIORITY, $DEFAULT_DELAY, $DEFAULT_TTR);
26 |
27 | return true;
28 | }
29 |
30 | public function getJob($jobId = null)
31 | {
32 | $data = $this->dataSource->get();
33 | $nextJob = new \PHPQueue\Job($data, $this->dataSource->last_job_id);
34 | $this->last_job_id = $this->dataSource->last_job_id;
35 |
36 | return $nextJob;
37 | }
38 |
39 | public function updateJob($jobId = null, $resultData = null)
40 | {
41 | $this->resultLog->info('Result: ID='.$jobId, $resultData);
42 | }
43 |
44 | public function clearJob($jobId = null)
45 | {
46 | $this->dataSource->clear($jobId);
47 | }
48 |
49 | public function releaseJob($jobId = null)
50 | {
51 | $this->dataSource->release($jobId);
52 | }
53 |
54 | public function getQueueSize()
55 | {
56 | $pheanstalkResponseObject = $this->dataSource->getConnection()->statsTube($this->sourceConfig['tube']);
57 | return $pheanstalkResponseObject['current-jobs-ready'];
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/test/PHPQueue/Backend/AmazonSQSV1Test.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Amazon PHP SDK not installed');
13 | } else {
14 | $options = array(
15 | 'region' => \AmazonSQS::REGION_APAC_SE1,
16 | 'queue' => 'https://sqs.ap-southeast-1.amazonaws.com/524787626913/testqueue',
17 | 'sqs_options' => array(
18 | 'key' => 'your_sqs_key',
19 | 'secret' => 'your_sqs_secret'
20 | ),
21 | 'receiving_options' => array('VisibilityTimeout' => 0)
22 | );
23 | $this->object = new AmazonSQS();
24 | $this->object->setBackend(new Aws\AmazonSQSV1($options));
25 | }
26 | }
27 |
28 | public function testAdd()
29 | {
30 | $data = array('1','Willy','Wonka');
31 | $result = $this->object->add($data);
32 | $this->assertTrue($result);
33 | }
34 |
35 | /**
36 | * @depends testAdd
37 | */
38 | public function testGet()
39 | {
40 | $result = $this->object->get();
41 | $this->assertNotEmpty($result);
42 | $this->assertEquals(array('1','Willy','Wonka'), $result);
43 | $this->object->release($this->object->last_job_id);
44 | }
45 |
46 | /**
47 | * @depends testAdd
48 | */
49 | public function testClear()
50 | {
51 | try {
52 | $jobId = 'xxx';
53 | $this->object->clear($jobId);
54 | $this->fail("Should not be able to delete.");
55 | } catch (\Exception $ex) {
56 | $this->assertTrue(true);
57 | }
58 |
59 | $result = $this->object->get();
60 | $this->assertNotEmpty($result);
61 | $jobId = $this->object->last_job_id;
62 | $result = $this->object->clear($jobId);
63 | $this->assertTrue($result);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/test/PHPQueue/Backend/AmazonSQSV2Test.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Amazon PHP SDK 2 not installed');
13 | } else {
14 | $options = array(
15 | 'region' => 'ap-southeast-1',
16 | 'queue' => 'https://sqs.ap-southeast-1.amazonaws.com/524787626913/testqueue',
17 | 'sqs_options' => array(
18 | 'key' => 'your_sqs_key',
19 | 'secret' => 'your_sqs_secret'
20 | ),
21 | 'receiving_options' => array(
22 | 'VisibilityTimeout' => 0
23 | )
24 | );
25 | $this->object = new AmazonSQS();
26 | $this->object->setBackend(new Aws\AmazonSQSV2($options));
27 | }
28 | }
29 |
30 | public function testAdd()
31 | {
32 | $data = array('1','Willy','Wonka');
33 | $result = $this->object->add($data);
34 | $this->assertTrue($result);
35 | }
36 |
37 | /**
38 | * @depends testAdd
39 | */
40 | public function testGet()
41 | {
42 | $result = $this->object->get();
43 | $this->assertNotEmpty($result);
44 | $this->assertEquals(array('1','Willy','Wonka'), $result);
45 | $this->object->release($this->object->last_job_id);
46 | }
47 |
48 | /**
49 | * @depends testAdd
50 | */
51 | public function testClear()
52 | {
53 | try {
54 | $jobId = 'xxx';
55 | $this->object->clear($jobId);
56 | $this->fail("Should not be able to delete.");
57 | } catch (\Exception $ex) {
58 | $this->assertTrue(true);
59 | }
60 |
61 | $result = $this->object->get();
62 | $this->assertNotEmpty($result);
63 | $jobId = $this->object->last_job_id;
64 | $result = $this->object->clear($jobId);
65 | $this->assertTrue($result);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/PHPQueue/Interfaces/AtomicReadBuffer.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Mongo extension is not installed');
13 | } else {
14 | $options = array(
15 | 'server' => 'mongodb://localhost'
16 | , 'db' => 'testdb'
17 | , 'collection' => 'things'
18 | );
19 | $this->object = new MongoDB($options);
20 | }
21 | }
22 |
23 | public function tearDown()
24 | {
25 | if ($this->object) {
26 | $this->object->getDB()->drop();
27 | }
28 | parent::tearDown();
29 | }
30 |
31 | public function testAddGet()
32 | {
33 | $key = 'A0001';
34 | $data1 = array('name' => 'Michael');
35 | $result = $this->object->add($data1, $key);
36 | $this->assertTrue($result);
37 |
38 | $result = $this->object->get($key);
39 | $this->assertNotEmpty($result);
40 | $this->assertEquals($data1, $result);
41 |
42 | $data2 = array('1','Willy','Wonka');
43 | $result = $this->object->add($data2);
44 | $this->assertTrue($result);
45 |
46 | $last_id = $this->object->last_job_id;
47 |
48 | $result = $this->object->get($last_id);
49 | $this->assertNotEmpty($result);
50 | $this->assertEquals($data2, $result);
51 | }
52 |
53 | /**
54 | * @expectedException \PHPQueue\Exception\JobNotFoundException
55 | */
56 | public function testClearNonexistent()
57 | {
58 | $jobId = 'xxx';
59 | $result = $this->object->clear($jobId);
60 | }
61 |
62 | /**
63 | * @depends testAddGet
64 | */
65 | public function testClear()
66 | {
67 | $this->testAddGet();
68 |
69 | $jobId = 'A0001';
70 | $result = $this->object->clear($jobId);
71 | $this->assertTrue($result);
72 |
73 | $result = $this->object->get($jobId);
74 | $this->assertNull($result);
75 | }
76 |
77 | public function testSet()
78 | {
79 | $data = array(mt_rand(), 'Mr.', 'Jones');
80 | $this->object->set(4, $data);
81 | $this->assertEquals($data, $this->object->get(4));
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/test/PHPQueue/Backend/BeanstalkdTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('\Pheanstalk\Pheanstalk not installed');
12 | } else {
13 | $options = array(
14 | 'server' => '127.0.0.1'
15 | , 'tube' => 'testqueue-' . mt_rand()
16 | );
17 | $this->object = new Beanstalkd($options);
18 | }
19 | }
20 |
21 | public function testAdd()
22 | {
23 | $data = array(mt_rand(),'Willy','Wonka');
24 | $result = $this->object->add($data);
25 | $this->assertTrue($result);
26 | }
27 |
28 | /**
29 | * @depends testAdd
30 | */
31 | public function testGet()
32 | {
33 | $data = array(mt_rand(),'Willy','Wonka');
34 | $id = $this->object->push($data);
35 | $this->assertTrue($id > 0);
36 |
37 | $this->assertEquals($data, $this->object->get());
38 |
39 | $this->object->release($this->object->last_job_id);
40 | }
41 |
42 | /**
43 | * @depends testAdd
44 | */
45 | public function testClear()
46 | {
47 | try {
48 | $jobId = 'xxx';
49 | $this->object->clear($jobId);
50 | $this->fail("Should not be able to delete.");
51 | } catch (\Exception $ex) {
52 | $this->assertTrue(true);
53 | }
54 |
55 | $data = array(mt_rand(),'Willy','Wonka');
56 | $result = $this->object->add($data);
57 | $this->assertTrue($result);
58 |
59 | $this->assertEquals($data, $this->object->get());
60 | $jobId = $this->object->last_job_id;
61 | $result = $this->object->clear($jobId);
62 | $this->assertTrue($result);
63 |
64 | $this->assertNull($this->object->pop());
65 | }
66 |
67 | public function testPush()
68 | {
69 | $data = array(mt_rand(),'Willy','Wonka');
70 | $id = $this->object->push($data);
71 | $this->assertTrue($id > 0);
72 |
73 | $this->assertEquals($data, $this->object->get($id));
74 | }
75 |
76 | public function testPop()
77 | {
78 | $data = array(mt_rand(),'Willy','Wonka');
79 | $this->object->push($data);
80 |
81 | $this->assertEquals($data, $this->object->pop());
82 | }
83 |
84 | public function testPopEmpty()
85 | {
86 | $this->assertNull($this->object->pop());
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/test/PHPQueue/Backend/IronMQTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Iron MQ library not installed');
15 | }
16 | $options = array(
17 | 'queue' => 'test_queue',
18 | 'msg_options' => array('timeout'=>1)
19 | );
20 | $this->object = new IronMQ($options);
21 |
22 | $this->object->getConnection()->clearQueue($this->object->queue_name);
23 | }
24 |
25 | public function testAdd()
26 | {
27 | $data = array(mt_rand(),'Willy','Wonka');
28 | $result = $this->object->add($data);
29 | $this->assertTrue($result);
30 | }
31 |
32 | /**
33 | * @depends testAdd
34 | */
35 | public function testGet()
36 | {
37 | $data = array(mt_rand(),'Willy','Wonka');
38 | $result = $this->object->add($data);
39 | $this->assertTrue($result);
40 |
41 | $result = $this->object->get();
42 | $this->assertNotEmpty($result);
43 | $this->assertEquals(array('1','Willy','Wonka'), $result);
44 | $this->object->release($this->object->last_job_id);
45 | sleep(1);
46 | }
47 |
48 | /**
49 | * @depends testAdd
50 | */
51 | public function testClear()
52 | {
53 | try {
54 | $jobId = 'xxx';
55 | $this->object->clear($jobId);
56 | $this->fail("Should not be able to delete.");
57 | } catch (\Exception $ex) {
58 | $this->assertNotEquals("Should not be able to delete.", $ex->getMessage());
59 | }
60 |
61 | $data = array(mt_rand(),'Willy','Wonka');
62 | $result = $this->object->add($data);
63 | $this->assertTrue($result);
64 |
65 | $result = $this->object->get();
66 | $this->assertNotEmpty($result);
67 | $jobId = $this->object->last_job_id;
68 | $result = $this->object->clear($jobId);
69 | $this->assertTrue($result);
70 | }
71 |
72 | public function testPush()
73 | {
74 | $data = array(mt_rand(), 'Snow', 'Den');
75 |
76 | // Set message.
77 | $id = $this->object->push($data);
78 | $this->assertTrue($id > 0);
79 | $this->assertEquals($data, $this->object->get($id));
80 | }
81 |
82 | public function testPop()
83 | {
84 | $data = array(mt_rand(), 'Snow', 'Den');
85 |
86 | // Set message.
87 | $id = $this->object->push($data);
88 | $this->assertTrue($id > 0);
89 | $this->assertEquals($data, $this->object->pop());
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/PHPQueue/Backend/CSV.php:
--------------------------------------------------------------------------------
1 | preserveGetLine = true;
16 | if ( !empty($options['preserveGetLine']) ) {
17 | $this->preserveGetLine = (bool) $options['preserveGetLine'];
18 | }
19 |
20 | if ( !empty($options['filePath']) ) {
21 | $this->file_path = $options['filePath'];
22 | }
23 | }
24 |
25 | public function connect()
26 | {
27 | if ( !is_file($this->file_path) ) {
28 | file_put_contents($this->file_path, '');
29 | }
30 | if (is_writable($this->file_path)) {
31 | $this->put_handle = fopen($this->file_path, 'a');
32 | $this->get_handle = fopen($this->file_path, 'r+');
33 | $this->connection = true;
34 | } else {
35 | throw new BackendException(sprintf("File is not writable: %s", $this->file_path));
36 | }
37 | }
38 |
39 | public function get($jobId=null)
40 | {
41 | $this->beforeGet();
42 | $this->getConnection();
43 | if (!is_null($jobId)) {
44 | $curPos = ftell($this->get_handle);
45 | rewind($this->get_handle);
46 | while ($lineJob = fgetcsv($this->get_handle)) {
47 | if ($lineJob[0] == $jobId) {
48 | $data = $lineJob;
49 | break;
50 | }
51 | }
52 | fseek($this->get_handle, $curPos);
53 | } else {
54 | $data = fgetcsv($this->get_handle);
55 | }
56 | $this->last_job = $data;
57 | $this->last_job_id = time();
58 | $this->afterGet();
59 |
60 | return $data;
61 | }
62 |
63 | public function add($data=array())
64 | {
65 | $this->beforeAdd($data);
66 | $this->getConnection();
67 | if (!is_array($data)) {
68 | throw new BackendException("Data is not an array.");
69 | }
70 | $written_bytes = fputcsv($this->put_handle, $data);
71 |
72 | return ($written_bytes > 0);
73 | }
74 |
75 | public function clear($jobId=null)
76 | {
77 | $this->beforeClear($jobId);
78 | $this->afterClearRelease();
79 |
80 | return true;
81 | }
82 |
83 | public function release($jobId=null)
84 | {
85 | $this->beforeRelease($jobId);
86 | $data = $this->open_items[$jobId];
87 | $this->getConnection();
88 | $written_bytes = fputcsv($this->put_handle, $data);
89 | if ($written_bytes < 0) {
90 | throw new BackendException("Unable to release data.");
91 | }
92 | $this->last_job_id = $jobId;
93 | $this->afterClearRelease();
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/test/PHPQueue/Backend/StompTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('STOMP library not installed');
18 | } else {
19 | $options = array(
20 | 'uri' => 'tcp://127.0.0.1:61613',
21 | 'queue' => 'test_queue',
22 | 'read_timeout' => 1,
23 | );
24 | $this->object = new Stomp($options);
25 | }
26 |
27 | $this->unique = mt_rand();
28 | }
29 |
30 | public function tearDown()
31 | {
32 | if ($this->unclean) {
33 | // Gross. Clear the queue.
34 | try {
35 | while ($result = $this->object->pop()) {
36 | // pass
37 | }
38 | } catch (JobNotFoundException $ex) {
39 | // pass
40 | }
41 | }
42 |
43 | parent::tearDown();
44 | }
45 |
46 | /**
47 | * @medium
48 | */
49 | public function testPushPop()
50 | {
51 | $data = array('unique' => $this->unique);
52 | $this->unclean = true;
53 | $this->object->push($data);
54 |
55 | $this->assertEquals($data, $this->object->pop());
56 | $this->unclean = false;
57 | }
58 |
59 | /**
60 | * @medium
61 | */
62 | public function testSetGet()
63 | {
64 | $data = array('unique' => $this->unique);
65 | $this->unclean = true;
66 | $result = $this->object->set($this->unique, $data);
67 |
68 | $result = $this->object->get($this->unique);
69 | $this->assertEquals($data, $result);
70 | $this->unclean = false;
71 | }
72 |
73 | /**
74 | * @medium
75 | */
76 | public function testPopEmpty()
77 | {
78 | $this->assertNull($this->object->pop());
79 | }
80 |
81 | /**
82 | * @medium
83 | */
84 | public function testGetNonexistent()
85 | {
86 | $this->assertNull($this->object->get(mt_rand()));
87 | }
88 |
89 | /**
90 | * @medium
91 | */
92 | public function testMergeHeaders()
93 | {
94 | $data = array('unique' => $this->unique);
95 | $this->unclean = true;
96 | $this->object->push($data, array('fooHeader' => 5));
97 |
98 | $this->object->merge_headers = true;
99 | $result = $this->object->pop();
100 |
101 | $this->assertTrue(array_key_exists('fooHeader', $result));
102 | $this->assertEquals($result['fooHeader'], 5);
103 | $this->assertTrue(array_key_exists('unique', $result));
104 | $this->assertEquals($result['unique'], $this->unique);
105 | $this->unclean = false;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/test/PHPQueue/Backend/MemcacheTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Memcache not installed');
12 | }
13 |
14 | $options = array(
15 | 'servers' => array(
16 | array('localhost', 11211)
17 | ),
18 | 'expiry' => 600,
19 | );
20 |
21 | // Try to connect to Memcache, skip test politely if unavailable.
22 | try {
23 | $connection = new \Memcache();
24 | $connection->addserver($options['servers'][0][0], $options['servers'][0][1]);
25 | $success = $connection->set('test' . mt_rand(), 'foo', 1);
26 | if ( !$success ) {
27 | throw new \Exception("Couldn't store to Memcache");
28 | }
29 | } catch (\Exception $ex) {
30 | $this->markTestSkipped($ex->getMessage());
31 | }
32 |
33 | $this->object = new Memcache($options);
34 | }
35 |
36 | public function testAdd()
37 | {
38 | $key = 'A0001';
39 | $data = 'Michael';
40 | $result = $this->object->add($key, $data);
41 | $this->assertTrue($result);
42 |
43 | $key = 'A0001';
44 | $data = 'Michael Cheng';
45 | $result = $this->object->add($key, $data);
46 | $this->assertTrue($result);
47 |
48 | $key = 'A0002';
49 | $data = array('1','Willy','Wonka');
50 | $result = $this->object->add($key, $data);
51 | $this->assertTrue($result);
52 | }
53 |
54 | /**
55 | * @depends testAdd
56 | */
57 | public function testGet()
58 | {
59 | // TODO: fixtures.
60 | $this->testAdd();
61 |
62 | $result = $this->object->get('A0001');
63 | $this->assertNotEmpty($result);
64 | $this->assertEquals('Michael Cheng', $result);
65 |
66 | $result = $this->object->get('A0002');
67 | $this->assertNotEmpty($result);
68 | $this->assertEquals(array('1','Willy','Wonka'), $result);
69 | }
70 |
71 | public function testSet()
72 | {
73 | $data = array('4', 'Crepuscular');
74 | $this->object->set(4, $data);
75 | $this->assertEquals($data, $this->object->get(4));
76 | }
77 |
78 | /**
79 | * @depends testAdd
80 | */
81 | public function testClear()
82 | {
83 | $this->testAdd();
84 |
85 | try {
86 | $jobId = 'xxx';
87 | $this->object->clear($jobId);
88 | $this->fail("Should not be able to delete.");
89 | } catch (\Exception $ex) {
90 | $this->assertTrue(true);
91 | }
92 |
93 | $jobId = 'A0001';
94 | $result = $this->object->clear($jobId);
95 | $this->assertTrue($result);
96 |
97 | $result = $this->object->get($jobId);
98 | $this->assertEmpty($result);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/test/PHPQueue/Backend/AmazonS3V1Test.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Amazon PHP SDK not installed');
17 | } else {
18 | $options = array(
19 | 'region' => \AmazonS3::REGION_APAC_SE1,
20 | 'region_website' => \AmazonS3::REGION_APAC_SE1_WEBSITE,
21 | 'bucket' => $this->test_upload_bucket,
22 | 's3_options' => array(
23 | 'key' => 'your_s3_key',
24 | 'secret' => 'your_s3_secret'
25 | )
26 | );
27 | $this->object = new AmazonS3();
28 | $this->object->setBackend(new Aws\AmazonS3V1($options));
29 | }
30 | }
31 |
32 | public function testManageContainers()
33 | {
34 | $container_name = 'test'.time();
35 |
36 | $result = $this->object->listContainers();
37 | $count = count($result);
38 |
39 | $result = $this->object->createContainer($container_name);
40 | $this->assertTrue($result);
41 |
42 | $result = $this->object->listContainers();
43 | $this->assertEquals($count + 1, count($result));
44 |
45 | $result = $this->object->deleteContainer($container_name);
46 | $this->assertTrue($result);
47 |
48 | $result = $this->object->listContainers();
49 | $this->assertEquals($count, count($result));
50 | }
51 |
52 | public function testAdd()
53 | {
54 | sleep(1);
55 | $result = $this->object->createContainer($this->test_upload_bucket);
56 | $this->assertTrue($result);
57 |
58 | $this->object->setContainer($this->test_upload_bucket);
59 | $file = __DIR__ . '/cc_logo.jpg';
60 | $result = $this->object->putFile('image.jpg', $file);
61 | $this->assertTrue($result);
62 |
63 | $result = $this->object->listFiles();
64 | $this->assertNotEmpty($result);
65 | }
66 |
67 | /**
68 | * @depends testAdd
69 | */
70 | public function testGet()
71 | {
72 | $result = $this->object->fetchFile('image.jpg', __DIR__ . '/downloads');
73 | $this->assertNotEmpty($result);
74 | }
75 |
76 | /**
77 | * @depends testAdd
78 | * @expectedException \PHPQueue\Exception\BackendException
79 | */
80 | public function testClearInvalidName()
81 | {
82 | $fake_filename = 'xxx';
83 | $this->object->clear($fake_filename);
84 | $this->fail("Should not be able to delete.");
85 | }
86 |
87 | /**
88 | * @depends testAdd
89 | */
90 | public function testClear()
91 | {
92 | $result = $this->object->clear('image.jpg');
93 | $this->assertTrue($result);
94 |
95 | $result = $this->object->deleteContainer($this->test_upload_bucket);
96 | $this->assertTrue($result);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/PHPQueue/Daemon.php:
--------------------------------------------------------------------------------
1 | pid_file = $pid_file;
22 | $this->log_root = $log_root;
23 | }
24 |
25 | public function run()
26 | {
27 | global $argv;
28 | if (empty($argv[1]))
29 | {
30 | Console::output("Unknown action.");
31 | die();
32 | }
33 | if (empty($this->queue_name))
34 | {
35 | Console::output("Queue is not set.");
36 | die();
37 | }
38 | switch($argv[1])
39 | {
40 | case 'start':
41 | $this->start();
42 | break;
43 | case 'stop':
44 | $this->stop();
45 | break;
46 | case 'restart':
47 | $this->restart();
48 | break;
49 | default:
50 | Console::output("Unknown action.");
51 | break;
52 | }
53 | }
54 |
55 | protected function start()
56 | {
57 | Console::stdout('Starting... ');
58 | try
59 | {
60 | if (D::isRunning($this->pid_file)) {
61 | Console::output('%y[Already Running]%n');
62 | } else {
63 | $queue = $this->queue_name;
64 | $log_path = $this->log_root;
65 | D::work(array(
66 | 'pid' => $this->pid_file
67 | , 'stdout' => $this->stdout
68 | , 'stderr' => $this->stderr
69 | ),
70 | function($stdin, $stdout, $sterr) use ($queue, $log_path)
71 | {
72 | $runner = new Runner($queue, array('logPath'=>$log_path));
73 | $runner->run();
74 | }
75 | );
76 | Console::output('%g[OK]%n');
77 | }
78 | }
79 | catch (\Exception $ex)
80 | {
81 | Console::output('%r[FAILED]%n');
82 | }
83 | }
84 |
85 | protected function stop()
86 | {
87 | Console::stdout('Stopping... ');
88 | try
89 | {
90 | if (!D::isRunning($this->pid_file)) {
91 | Console::output('%y[Daemon not running]%n');
92 | } else {
93 | D::kill($this->pid_file, true);
94 | Console::output('%g[OK]%n');
95 | }
96 | }
97 | catch (\Exception $ex)
98 | {
99 | Console::output('%r[FAILED]%n');
100 | }
101 | }
102 |
103 | protected function restart()
104 | {
105 | $this->stop();
106 | $this->start();
107 | }
108 | }
--------------------------------------------------------------------------------
/test/PHPQueue/Backend/WindowsAzureBlobTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Windows Azure not installed');
15 | } else {
16 | $options = array(
17 | 'connection_string' => 'DefaultEndpointsProtocol=https;AccountName=noobsgblob;AccountKey=WHkYwMCHYFMB1EHu061XlD11XS7v0gzKWcYKh4s5YTTioWpyYIVOki2KYki42gekpaVLmKN9WaYc3elyvh/qpQ=='
18 | );
19 | $this->object = new WindowsAzureBlob($options);
20 | }
21 | }
22 |
23 | public function testManageContainers()
24 | {
25 | $container_name = 'test'.time();
26 |
27 | $result = $this->object->listContainers();
28 | $num = count($result);
29 |
30 | $result = $this->object->createContainer($container_name);
31 | $this->assertTrue($result);
32 |
33 | $result = $this->object->listContainers();
34 | $this->assertEquals($num + 1, count($result));
35 |
36 | $result = $this->object->deleteContainer($container_name);
37 | $this->assertTrue($result);
38 |
39 | $result = $this->object->listContainers();
40 | $this->assertEquals($num, count($result));
41 | }
42 |
43 | public function testAdd()
44 | {
45 | sleep(1);
46 | $container_name = 'testimg';
47 | $result = $this->object->createContainer($container_name);
48 | $this->assertTrue($result);
49 |
50 | $this->object->setContainer($container_name);
51 | $file = __DIR__ . '/cc_logo.jpg';
52 | $result = $this->object->putFile('image.jpg', $file);
53 | $this->assertTrue($result);
54 |
55 | $result = $this->object->listFiles();
56 | $this->assertNotEmpty($result);
57 | }
58 |
59 | /**
60 | * @depends testAdd
61 | */
62 | public function testGet()
63 | {
64 | $container_name = 'testimg';
65 | $this->object->setContainer($container_name);
66 | $result = $this->object->fetchFile('image.jpg', __DIR__ . '/downloads/image.jpg');
67 | $this->assertNotEmpty($result);
68 | }
69 |
70 | /**
71 | * @expectedException \PHPQueue\Exception\BackendException
72 | */
73 | public function testClearInvalidName()
74 | {
75 | $container_name = 'testimg';
76 | $this->object->setContainer($container_name);
77 |
78 | $fake_filename = 'xxx';
79 | $this->object->clear($fake_filename);
80 | $this->fail("Should not be able to delete.");
81 | }
82 |
83 | /**
84 | * @depends testAdd
85 | */
86 | public function testClear()
87 | {
88 | $container_name = 'testimg';
89 | $this->object->setContainer($container_name);
90 | $result = $this->object->clear('image.jpg');
91 | $this->assertTrue($result);
92 |
93 | $result = $this->object->deleteContainer($container_name);
94 | $this->assertTrue($result);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/PHPQueue/Backend/Beanstalkd.php:
--------------------------------------------------------------------------------
1 | server_uri = $options['server'];
21 | }
22 | if (!empty($options['tube'])) {
23 | $this->tube = $options['tube'];
24 | }
25 | }
26 |
27 | public function connect()
28 | {
29 | $this->connection = new \Pheanstalk\Pheanstalk($this->server_uri);
30 | }
31 |
32 | /**
33 | * @deprecated
34 | * @param array $data
35 | * @return boolean Status of saving
36 | */
37 | public function add($data=array(), $DEFAULT_PRIORITY=1024, $DEFAULT_DELAY=0, $DEFAULT_TTR=60)
38 | {
39 | $this->push($data, $DEFAULT_PRIORITY, $DEFAULT_DELAY, $DEFAULT_TTR);
40 | return true;
41 | }
42 |
43 | /**
44 | * @param array $data
45 | * @return integer Primary ID of the new record.
46 | */
47 | public function push($data, $DEFAULT_PRIORITY=1024, $DEFAULT_DELAY=0, $DEFAULT_TTR=60)
48 | {
49 | $this->beforeAdd();
50 | $response = $this->getConnection()->useTube($this->tube)->put(json_encode($data), $DEFAULT_PRIORITY, $DEFAULT_DELAY, $DEFAULT_TTR);
51 | if (!$response) {
52 | throw new BackendException("Unable to save job.");
53 | }
54 | return $response;
55 | }
56 |
57 | /**
58 | * @deprecated
59 | * @return array|null
60 | */
61 | public function get()
62 | {
63 | return $this->pop();
64 | }
65 |
66 | /**
67 | * @return array|null
68 | */
69 | public function pop()
70 | {
71 | $this->beforeGet();
72 | $newJob = $this->getConnection()->watch($this->tube)->reserve(self::$reserve_timeout);
73 | if (!$newJob) {
74 | return null;
75 | }
76 | $this->last_job = $newJob;
77 | $this->last_job_id = $newJob->getId();
78 | $this->afterGet();
79 |
80 | return json_decode($newJob->getData(), true);
81 | }
82 |
83 | public function clear($jobId=null)
84 | {
85 | $this->beforeClear($jobId);
86 | $this->isJobOpen($jobId);
87 | $theJob = $this->open_items[$jobId];
88 | $this->getConnection()->delete($theJob);
89 | $this->last_job_id = $jobId;
90 | $this->afterClearRelease();
91 |
92 | return true;
93 | }
94 |
95 | public function release($jobId=null)
96 | {
97 | $this->beforeRelease($jobId);
98 | $this->isJobOpen($jobId);
99 | $theJob = $this->open_items[$jobId];
100 | $this->getConnection()->release($theJob);
101 | $this->last_job_id = $jobId;
102 | $this->afterClearRelease();
103 |
104 | return true;
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/test/PHPQueue/BaseTest.php:
--------------------------------------------------------------------------------
1 | getSampleQueue();
22 | $this->assertInstanceOf('\\PHPQueue\\JobQueue', $result);
23 | }
24 |
25 | /**
26 | * @expectedException \PHPQueue\Exception\QueueNotFoundException
27 | */
28 | public function testCanFailWhenInvalidQueueNameAreGiven()
29 | {
30 | Base::getQueue('NonExistent');
31 | }
32 |
33 | public function testAddJob()
34 | {
35 | $queue = $this->getSampleQueue();
36 | $result = Base::addJob($queue, array('var1'=>"Hello, world!"));
37 | $this->assertTrue($result);
38 | $this->assertEquals(1, $queue->getQueueSize());
39 | $result = Base::getJob($queue); //clear
40 | }
41 |
42 | public function testNoNullJob()
43 | {
44 | $queue = $this->getSampleQueue();
45 | try {
46 | Base::addJob($queue, null);
47 | $this->fail("Should not be able to add to Queue");
48 | } catch (\Exception $ex) {
49 | $this->assertStringStartsWith("Invalid job data.", $ex->getMessage());
50 | }
51 | }
52 |
53 | public function testGetJob()
54 | {
55 | $queue = $this->getSampleQueue();
56 | $result = Base::addJob($queue, array('var1'=>"Hello, world!"));
57 | $queue = $this->getSampleQueue();
58 | $result = Base::getJob($queue);
59 | $this->assertInstanceOf('\\PHPQueue\\Job', $result);
60 | $this->assertEquals(0, $queue->getQueueSize());
61 | }
62 |
63 | public function testNoMoreJob()
64 | {
65 | $queue = $this->getSampleQueue();
66 | $result = Base::addJob($queue, array('var1'=>"Hello, world!"));
67 | $result = Base::getJob($queue); //clear
68 | try {
69 | $result = Base::getJob($queue);
70 | $this->fail("Should not be able to get job from Queue");
71 | } catch (\Exception $ex) {
72 | $this->assertStringStartsWith("No more jobs.", $ex->getMessage());
73 | }
74 | }
75 |
76 | /**
77 | * @expectedException \PHPQueue\Exception\WorkerNotFoundException
78 | */
79 | public function testCanFailWhenInvalidWorkerNameAreGiven()
80 | {
81 | Base::getWorker('NonExistent');
82 | }
83 |
84 | public function testCanGetWorker()
85 | {
86 | $result = Base::getWorker('Sample');
87 | $this->assertInstanceOf('\\PHPQueue\\Worker', $result);
88 | }
89 |
90 | public function testWorkJob()
91 | {
92 | $worker = Base::getWorker('Sample');
93 | $job = new Job();
94 | $job->worker = 'Sample';
95 | $job->data = array('var1'=>'Hello, world!');
96 | $result = Base::workJob($worker, $job);
97 | $this->assertEquals(array('var1'=>'Hello, world!', 'var2'=>"Welcome back!"), $result->result_data);
98 | $this->assertEquals(Job::OK, $job->status);
99 | $this->assertTrue($job->isSuccessful());
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/PHPQueue/Cli.php:
--------------------------------------------------------------------------------
1 | queue_name = $options['queue'];
14 | }
15 | }
16 |
17 | public function add($payload=array())
18 | {
19 | fwrite(STDOUT, "===========================================================\n");
20 | fwrite(STDOUT, "Adding Job...");
21 | $status = false;
22 | try {
23 | $queue = Base::getQueue($this->queue_name);
24 | $status = Base::addJob($queue, $payload);
25 | fwrite(STDOUT, "Done.\n");
26 | } catch (\Exception $ex) {
27 | fwrite(STDOUT, sprintf("Error: %s\n", $ex->getMessage()));
28 | throw $ex;
29 | }
30 |
31 | return $status;
32 | }
33 |
34 | public function peek()
35 | {
36 | $newJob = null;
37 | $queue = Base::getQueue($this->queue_name);
38 | try {
39 | $newJob = Base::getJob($queue);
40 | fwrite(STDOUT, "===========================================================\n");
41 | fwrite(STDOUT, "Next Job:\n");
42 | var_dump($newJob);
43 | fwrite(STDOUT, "\nReleasing Job...\n");
44 | $queue->releaseJob($newJob->job_id);
45 | } catch (\Exception $ex) {
46 | fwrite(STDOUT, "Error: " . $ex->getMessage() . "\n");
47 | }
48 | }
49 |
50 | public function work()
51 | {
52 | $newJob = null;
53 | $queue = Base::getQueue($this->queue_name);
54 | try {
55 | $newJob = Base::getJob($queue);
56 | fwrite(STDOUT, "===========================================================\n");
57 | fwrite(STDOUT, "Next Job:\n");
58 | var_dump($newJob);
59 | } catch (\Exception $ex) {
60 | fwrite(STDOUT, "Error: " . $ex->getMessage() . "\n");
61 | }
62 |
63 | if (empty($newJob)) {
64 | fwrite(STDOUT, "Notice: No Job found.\n");
65 |
66 | return;
67 | }
68 | try {
69 | if (empty($newJob->worker)) {
70 | throw new Exception("No worker declared.");
71 | }
72 | if (is_string($newJob->worker)) {
73 | $result_data = $this->processWorker($newJob->worker, $newJob);
74 | } elseif (is_array($newJob->worker)) {
75 | foreach ($newJob->worker as $worker_name) {
76 | $result_data = $this->processWorker($worker_name, $newJob);
77 | $newJob->data = $result_data;
78 | }
79 | }
80 | fwrite(STDOUT, "Updating job... \n");
81 |
82 | return Base::updateJob($queue, $newJob->job_id, $result_data);
83 | } catch (\Exception $ex) {
84 | fwrite(STDOUT, sprintf("\nError occured: %s\n", $ex->getMessage()));
85 | $queue->releaseJob($newJob->job_id);
86 | throw $ex;
87 | }
88 | }
89 |
90 | protected function processWorker($worker_name, $new_job)
91 | {
92 | fwrite(STDOUT, sprintf("Running worker (%s) now... ", $worker_name));
93 | $newWorker = Base::getWorker($worker_name);
94 | Base::workJob($newWorker, $new_job);
95 | fwrite(STDOUT, "Done.\n");
96 |
97 | return $newWorker->result_data;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/test/PHPQueue/Backend/LocalFSTest.php:
--------------------------------------------------------------------------------
1 | __DIR__ . '/localfs_docroot'
34 | );
35 | $this->object = new LocalFSMock($options);
36 | }
37 |
38 | public function testContainerNames()
39 | {
40 | $result = $this->object->getContainerPath('boo');
41 | $this->assertEquals(__DIR__ . '/localfs_docroot/boo', $result);
42 |
43 | $this->object->setContainer('mah');
44 | $result = $this->object->getFullPath('bah.jpg');
45 | $this->assertEquals(__DIR__ . '/localfs_docroot/mah/bah.jpg', $result);
46 |
47 | $result = $this->object->getCurrentContainerPath();
48 | $this->assertEquals(__DIR__ . '/localfs_docroot/mah', $result);
49 | }
50 |
51 | public function testManageContainers()
52 | {
53 | $container_name = 'test'.time();
54 |
55 | $result = $this->object->listContainers();
56 | $this->assertEmpty($result);
57 |
58 | $result = $this->object->createContainer($container_name);
59 | $this->assertTrue($result);
60 |
61 | $result = $this->object->listContainers();
62 | $this->assertEquals(1, count($result));
63 |
64 | $result = $this->object->deleteContainer($container_name);
65 | $this->assertTrue($result);
66 |
67 | $result = $this->object->listContainers();
68 | $this->assertEmpty($result);
69 | }
70 |
71 | public function testAdd()
72 | {
73 | sleep(1);
74 | $container_name = 'testimg';
75 | $result = $this->object->createContainer($container_name);
76 | $this->assertTrue($result);
77 |
78 | $this->object->setContainer($container_name);
79 | $file = __DIR__ . '/cc_logo.jpg';
80 | $result = $this->object->putFile('image.jpg', $file);
81 | $this->assertTrue($result);
82 |
83 | $result = $this->object->listFiles();
84 | $this->assertNotEmpty($result);
85 | }
86 |
87 | /**
88 | * @depends testAdd
89 | */
90 | public function testGet()
91 | {
92 | $container_name = 'testimg';
93 | $this->object->setContainer($container_name);
94 | $result = $this->object->fetchFile('image.jpg', __DIR__ . '/downloads');
95 | $this->assertNotEmpty($result);
96 | }
97 |
98 | /**
99 | * @expectedException \PHPQueue\Exception\BackendException
100 | */
101 | public function testClearInvalidName()
102 | {
103 | $container_name = 'testimg';
104 | $this->object->setContainer($container_name);
105 |
106 | $fake_filename = 'xxx';
107 | $this->object->clear($fake_filename);
108 | $this->fail("Should not be able to delete.");
109 | }
110 |
111 | /**
112 | * @depends testAdd
113 | */
114 | public function testClear()
115 | {
116 | $container_name = 'testimg';
117 | $this->object->setContainer($container_name);
118 | $result = $this->object->clear('image.jpg');
119 | $this->assertTrue($result);
120 |
121 | $result = $this->object->deleteContainer($container_name);
122 | $this->assertTrue($result);
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/PHPQueue/Backend/MongoDB.php:
--------------------------------------------------------------------------------
1 | true);
16 |
17 | public function __construct($options=array())
18 | {
19 | parent::__construct();
20 | if (!empty($options['server'])) {
21 | $this->server_uri = $options['server'];
22 | }
23 | if (!empty($options['db']) && is_string($options['db'])) {
24 | $this->db_name = $options['db'];
25 | }
26 | if (!empty($options['collection']) && is_string($options['collection'])) {
27 | $this->collection_name = $options['collection'];
28 | }
29 | if (!empty($options['mongo_options']) && is_array($options['mongo_options'])) {
30 | $this->mongo_options = array_merge($this->mongo_options, $options['mongo_options']);
31 | }
32 | }
33 |
34 | public function connect()
35 | {
36 | if (empty($this->server_uri)) {
37 | throw new BackendException("No server specified");
38 | }
39 | $this->connection = new MongoClient($this->server_uri, $this->mongo_options);
40 | }
41 |
42 | public function getDB()
43 | {
44 | if (empty($this->db_name) || !is_string($this->db_name)) {
45 | throw new BackendException("DB is invalid.");
46 | }
47 | $db = $this->db_name;
48 |
49 | return $this->getConnection()->$db;
50 | }
51 |
52 | public function getCollection()
53 | {
54 | if (empty($this->collection_name) || !is_string($this->collection_name)) {
55 | throw new BackendException("Collection is invalid.");
56 | }
57 | $db = $this->getDB();
58 | $collection = $this->collection_name;
59 |
60 | return $db->$collection;
61 | }
62 |
63 | /**
64 | * @deprecated
65 | */
66 | public function add($data=null, $key=null)
67 | {
68 | $this->set($key, $data);
69 | return true;
70 | }
71 |
72 | /**
73 | * @throws \PHPQueue\Exception\BackendException
74 | * @return boolean Deprecated (always true)
75 | */
76 | public function set($key, $data, $properties=array())
77 | {
78 | if (empty($data) || !is_array($data)) {
79 | throw new BackendException("No data.");
80 | }
81 | if (!isset($data['_id'])) {
82 | $data['_id'] = !empty($key) ? $key : uniqid();
83 | }
84 | $this->beforeAdd();
85 | $the_collection = $this->getCollection();
86 | $status = $the_collection->insert($data);
87 | if (!$status) {
88 | throw new BackendException("Unable to save data.");
89 | }
90 | $this->last_job_id = $data['_id'];
91 |
92 | // FIXME: always true.
93 | return $status;
94 | }
95 |
96 | /**
97 | * @param string $key
98 | * @return mixed
99 | */
100 | public function get($key=null)
101 | {
102 | $this->beforeGet($key);
103 | $cursor = $this->getCollection()->find(array('_id' => $key));
104 | if ($cursor->count() < 1) {
105 | return null;
106 | }
107 | $cursor->next();
108 | $data = $cursor->current();
109 | unset($data['_id']);
110 |
111 | return $data;
112 | }
113 |
114 | public function clear($key=null)
115 | {
116 | $this->beforeClear($key);
117 | $data = $this->get($key);
118 | if (is_null($data)) {
119 | throw new JobNotFoundException("Record not found.");
120 | }
121 | $this->getCollection()->remove(array('_id' => $key));
122 | $this->last_job_id = $key;
123 |
124 | return true;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/PHPQueue/Backend/Memcache.php:
--------------------------------------------------------------------------------
1 | servers = $options['servers'];
19 | }
20 | if (!empty($options['persistent'])) {
21 | $this->is_persistent = $options['persistent'];
22 | }
23 | if (!empty($options['compress']) && is_bool($options['compress'])) {
24 | $this->use_compression = $options['compress'];
25 | }
26 | if (!empty($options['expiry']) && is_numeric($options['expiry'])) {
27 | $this->expiry = $options['expiry'];
28 | }
29 | }
30 |
31 | public function connect()
32 | {
33 | if (empty($this->servers)) {
34 | throw new BackendException("No servers specified");
35 | }
36 | $this->connection = new \Memcache;
37 | foreach ($this->servers as $server) {
38 | if (is_string($server)) {
39 | // TODO: configure port
40 | $this->connection->addserver($server, 11211, $this->is_persistent);
41 | } elseif (is_array($server)) {
42 | call_user_func_array(array($this->connection, 'addserver'), $server);
43 | } else {
44 | throw new BackendException("Unknown Memcache server arguments.");
45 | }
46 | }
47 | }
48 |
49 | /**
50 | * @deprecated Use set($k, $v) and $this->expiry instead.
51 | * @param string $key
52 | * @param mixed $data
53 | * @param int $expiry
54 | * @return boolean
55 | * @throws \PHPQueue\Exception
56 | */
57 | public function add($key, $data, $expiry=null)
58 | {
59 | $this->set($key, $data, $expiry);
60 | return true;
61 | }
62 |
63 | /**
64 | * @param string $key
65 | * @param mixed $data
66 | * @param array|int $properties array is preferred, "expiry" is the only key used here. Deprecated int argument will also be used as expiry.
67 | * @throws \PHPQueue\Exception
68 | */
69 | public function set($key, $data, $properties=array())
70 | {
71 | if (is_array($properties) && isset($properties["expiry"])) {
72 | $expiry = $properties["expiry"];
73 | } else if (is_numeric($properties)) {
74 | $expiry = $properties;
75 | } else {
76 | $expiry = $this->expiry;
77 | }
78 | if (empty($key) && !is_string($key)) {
79 | throw new BackendException("Key is invalid.");
80 | }
81 | if (empty($data)) {
82 | throw new BackendException("No data.");
83 | }
84 | $this->beforeAdd();
85 | $status = $this->getConnection()->replace($key, json_encode($data), $this->use_compression, $expiry);
86 | if ($status == false) {
87 | $status = $this->getConnection()->set($key, json_encode($data), $this->use_compression, $expiry);
88 | }
89 | if (!$status) {
90 | throw new BackendException("Unable to save data.");
91 | }
92 | }
93 |
94 | /**
95 | * @param string $key
96 | * @return mixed
97 | */
98 | public function get($key)
99 | {
100 | $this->beforeGet($key);
101 |
102 | return json_decode($this->getConnection()->get($key), true);
103 | }
104 |
105 | public function clear($key)
106 | {
107 | $this->beforeClear($key);
108 | $this->getConnection()->delete($key);
109 | $this->last_job_id = $key;
110 |
111 | return true;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/PHPQueue/Backend/IronMQ.php:
--------------------------------------------------------------------------------
1 | 60
16 | , "delay" => 0
17 | , "expires_in" => 172800
18 | );
19 |
20 | public function __construct($options=array())
21 | {
22 | parent::__construct();
23 | if (!empty($options['token'])) {
24 | $this->token = $options['token'];
25 | }
26 | if (!empty($options['project_id'])) {
27 | $this->project_id = $options['project_id'];
28 | }
29 | if (!empty($options['queue'])) {
30 | $this->queue_name = $options['queue'];
31 | }
32 | if (!empty($options['msg_options']) && is_array($options['msg_options'])) {
33 | $this->default_send_options = array_merge($this->default_send_options, $options['msg_options']);
34 | }
35 | }
36 |
37 | public function connect()
38 | {
39 | if (!empty($this->token) && !empty($this->project_id)) {
40 | $options = array(
41 | 'token' => $this->token,
42 | 'project_id' => $this->project_id
43 | );
44 | $this->connection = new \IronMQ($options);
45 | } else {
46 | $this->connection = new \IronMQ();
47 | }
48 | }
49 |
50 | /**
51 | * @deprecated
52 | * @param array $data
53 | * @return boolean Status of saving
54 | * @throws \PHPQueue\Exception
55 | */
56 | public function add($data=array())
57 | {
58 | $this->push($data);
59 | return true;
60 | }
61 |
62 | /**
63 | * @param array $data
64 | * @return string Sent message ID
65 | * @throws \PHPQueue\Exception
66 | */
67 | public function push($data=array())
68 | {
69 | $this->beforeAdd();
70 | $body = array('body'=>json_encode($data));
71 | $payload = array_merge($this->default_send_options, $body);
72 | try {
73 | $response = $this->getConnection()->postMessage($this->queue_name, $payload);
74 | // FIXME: untested
75 | return $response->id;
76 | } catch (BackendException $ex) {
77 | throw $ex;
78 | }
79 | }
80 |
81 | /**
82 | * @deprecated
83 | * @return array
84 | * @throws \PHPQueue\Exception
85 | */
86 | public function get()
87 | {
88 | return $this->pop();
89 | }
90 |
91 | /**
92 | * @return array
93 | * @throws \PHPQueue\Exception
94 | */
95 | public function pop()
96 | {
97 | $this->beforeGet();
98 | $response = $this->getConnection()->getMessage($this->queue_name);
99 | $this->last_job = $response;
100 | $this->last_job_id = (string) $response->id;
101 | $this->afterGet();
102 |
103 | return json_decode($response->body);
104 | }
105 |
106 | /**
107 | * @param string $jobId
108 | * @return boolean
109 | * @throws \PHPQueue\Exception
110 | */
111 | public function clear($jobId=null)
112 | {
113 | $this->beforeClear($jobId);
114 | $this->isJobOpen($jobId);
115 | $response = $this->getConnection()->deleteMessage($this->queue_name, $jobId);
116 | $msg = json_decode($response, true);
117 | if ($msg['msg'] == 'Deleted') {
118 | $this->last_job_id = $jobId;
119 | $this->afterClearRelease();
120 |
121 | return true;
122 | }
123 |
124 | return false;
125 | }
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/src/PHPQueue/Backend/Aws/AmazonSQSV1.php:
--------------------------------------------------------------------------------
1 | 10,
15 | 'WaitTimeSeconds' => 3,
16 | 'MaxNumberOfMessages' => 1
17 | );
18 |
19 | public function __construct($options=array())
20 | {
21 | parent::__construct();
22 | if (!empty($options['region'])) {
23 | $this->region = $options['region'];
24 | }
25 | if (!empty($options['queue'])) {
26 | $this->queue_url = $options['queue'];
27 | }
28 | if (!empty($options['receiving_options']) && is_array($options['receiving_options'])) {
29 | $this->receiving_options = array_merge($this->receiving_options, $options['receiving_options']);
30 | }
31 | if (!empty($options['sqs_options']) && is_array($options['sqs_options'])) {
32 | $this->sqs_options = array_merge($this->sqs_options, $options['sqs_options']);
33 | }
34 | }
35 |
36 | public function connect()
37 | {
38 | $this->connection = new \AmazonSQS($this->sqs_options);
39 | $this->connection->set_region($this->region);
40 | }
41 |
42 | /**
43 | * @param array $data
44 | * @throws \PHPQueue\Exception\BackendException
45 | * @return boolean Status of saving
46 | */
47 | public function add($data=array())
48 | {
49 | $this->beforeAdd();
50 | $response = $this->getConnection()->send_message($this->queue_url, json_encode($data));
51 | if (!$response->isOK()) {
52 | $error = $response->body->Error;
53 | throw new BackendException((string) $error->Message, (int) $error->Code);
54 | }
55 |
56 | return true;
57 | }
58 |
59 | /**
60 | * @throws \PHPQueue\Exception\JobNotFoundException
61 | * @return array
62 | */
63 | public function get()
64 | {
65 | $this->beforeGet();
66 | $response = $this->getConnection()->receive_message($this->queue_url, $this->receiving_options);
67 | if (!$response->isOk()) {
68 | $error = $response->body->Error;
69 | throw new JobNotFoundException((string) $error->Message, (int) $error->Code);
70 | } else {
71 | if (empty($response->body->ReceiveMessageResult->Message)) {
72 | return null;
73 | }
74 | $message = $response->body->ReceiveMessageResult->Message;
75 | $this->last_job = $response;
76 | $this->last_job_id = (string) $message->ReceiptHandle;
77 | $this->afterGet();
78 |
79 | return json_decode((string) $message->Body, TRUE);
80 | }
81 | }
82 |
83 | /**
84 | * @param string $jobId
85 | * @throws \PHPQueue\Exception\BackendException
86 | * @return boolean
87 | */
88 | public function clear($jobId=null)
89 | {
90 | $this->beforeClear($jobId);
91 | $this->isJobOpen($jobId);
92 | $response = $this->getConnection()->delete_message($this->queue_url, $jobId);
93 | if (!$response->isOk()) {
94 | $error = $response->body->Error;
95 | throw new BackendException((string) $error->Message, (int) $error->Code);
96 | }
97 | $this->last_job_id = $jobId;
98 | $this->afterClearRelease();
99 |
100 | return true;
101 | }
102 |
103 | /**
104 | * @param string $jobId
105 | * @return boolean
106 | * @throws \PHPQueue\Exception\BackendException If job wasn't retrieved previously.
107 | */
108 | public function release($jobId=null)
109 | {
110 | $this->beforeRelease($jobId);
111 | $this->isJobOpen($jobId);
112 | $this->last_job_id = $jobId;
113 | $this->afterClearRelease();
114 |
115 | return true;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/PHPQueue/Backend/Stomp.php:
--------------------------------------------------------------------------------
1 | queue_name = $options['queue'];
34 | }
35 | if (!empty($options['uri'])) {
36 | $this->uri = $options['uri'];
37 | }
38 | if (isset($options['merge_headers'])) {
39 | $this->merge_headers = (bool)$options['merge_headers'];
40 | }
41 | if (isset($options['read_timeout'])) {
42 | $this->read_timeout = (int)$options['read_timeout'];
43 | }
44 | if (isset($options['ack'])) {
45 | $this->ack = (bool)$options['ack'];
46 | }
47 | }
48 |
49 | public function __destruct()
50 | {
51 | if ( $this->connection ) {
52 | $this->connection->disconnect();
53 | $this->connection = null;
54 | }
55 | }
56 |
57 | public function connect()
58 | {
59 | $this->connection = new FuseStomp($this->uri);
60 | $this->connection->connect();
61 | }
62 |
63 | /**
64 | * @param array $data
65 | * @param array $properties Optional headers.
66 | *
67 | * @throws \PHPQueue\Exception\BackendException
68 | */
69 | public function push($data=array(), $properties=array())
70 | {
71 | $this->beforeAdd();
72 |
73 | $body = json_encode($data);
74 | $result = $this->getConnection()->send($this->queue_name, $body, $properties);
75 | if (!$result) {
76 | throw new BackendException("Couldn't send a message!");
77 | }
78 | }
79 |
80 | /**
81 | * @return array|null
82 | * @throws \PHPQueue\Exception\BackendException
83 | */
84 | public function pop()
85 | {
86 | return $this->readFrame();
87 | }
88 |
89 | public function set($key, $data=array(), $properties=array())
90 | {
91 | $properties['correlation-id'] = $key;
92 | $this->push($data, $properties);
93 | }
94 |
95 | /**
96 | * @return array|null
97 | */
98 | public function get($key)
99 | {
100 | $properties = array(
101 | 'ack' => 'client',
102 | 'selector' => "JMSCorrelationID='{$key}' OR JMSMessageID='{$key}'",
103 | );
104 | return $this->readFrame($properties);
105 | }
106 |
107 | /**
108 | * @param array|null $properties Optional selectors.
109 | */
110 | protected function readFrame($properties = null)
111 | {
112 | $this->beforeGet();
113 | if ($properties === null) {
114 | $properties = array('ack' => 'client');
115 | }
116 | $result = $this->getConnection()->subscribe($this->queue_name, $properties);
117 | if (!$result) {
118 | throw new BackendException("No response when subscribing to queue {$this->queue_name}");
119 | }
120 | if ($this->read_timeout) {
121 | $this->getConnection()->setReadTimeout($this->read_timeout);
122 | }
123 | $response = $this->getConnection()->readFrame();
124 | $this->afterGet();
125 |
126 | if ($response && $this->ack) {
127 | $this->getConnection()->ack($response);
128 | }
129 |
130 | $this->getConnection()->unsubscribe($this->queue_name);
131 |
132 | if (!$response) {
133 | return null;
134 | }
135 |
136 | $message = json_decode($response->body, true);
137 | if ($this->merge_headers) {
138 | $message = array_merge($response->headers, $message);
139 | }
140 | return $message;
141 | }
142 |
143 | public function clear($key=null)
144 | {
145 | throw new BadMethodCallException('Not implemented.');
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/test/PHPQueue/Backend/AmazonS3V2Test.php:
--------------------------------------------------------------------------------
1 | self::$s3_key,
29 | 'secret' => self::$s3_secret,
30 | 'region' => self::$test_region
31 | ));
32 | try {
33 | self::$client->createBucket(array(
34 | 'Bucket' => self::$test_upload_bucket,
35 | 'LocationConstraint' => self::$test_region
36 | ));
37 | self::$client->waitUntilBucketExists(array(
38 | 'Bucket' => self::$test_upload_bucket
39 | ));
40 | } catch (\Aws\S3\Exception\BucketAlreadyOwnedByYouException $exception) {
41 | }
42 | }
43 |
44 | public function setUp()
45 | {
46 | parent::setUp();
47 |
48 | $options = array(
49 | 'region' => self::$test_region,
50 | 'region_website' => null,
51 | 'bucket' => self::$test_upload_bucket,
52 | 's3_options' => array(
53 | 'key' => self::$s3_key,
54 | 'secret' => self::$s3_secret
55 | )
56 | );
57 | $this->object = new AmazonS3();
58 | $this->object->setBackend(new Aws\AmazonS3V2($options));
59 | }
60 |
61 | /**
62 | * @expectedException \InvalidArgumentException
63 | */
64 | public function testSetInvalidConnection()
65 | {
66 | $this->object->setConnection(new \StdClass());
67 | $this->fail('Should not be able to set invalid connection.');
68 | }
69 |
70 | public function testSetConnection()
71 | {
72 | $this->object->setConnection(self::$client);
73 | $result = $this->object->listContainers();
74 | $this->assertGreaterThanOrEqual(1, count($result));
75 | }
76 |
77 | public function testManageContainers()
78 | {
79 | $container_name = 'test'.time();
80 |
81 | $result = $this->object->listContainers();
82 | $this->assertGreaterThanOrEqual(1, count($result));
83 | $count = count($result);
84 |
85 | $result = $this->object->createContainer($container_name);
86 | $this->assertTrue($result);
87 |
88 | $result = $this->object->listContainers();
89 | $this->assertEquals($count + 1, count($result));
90 |
91 | $result = $this->object->deleteContainer($container_name);
92 | $this->assertTrue($result);
93 |
94 | $result = $this->object->listContainers();
95 | $this->assertEquals($count, count($result));
96 | }
97 |
98 | public function testAdd()
99 | {
100 | $this->object->setContainer(self::$test_upload_bucket);
101 | $file = __DIR__ . '/cc_logo.jpg';
102 | $result = $this->object->putFile('image.jpg', $file);
103 | $this->assertTrue($result);
104 |
105 | $result = $this->object->listFiles();
106 | $this->assertNotEmpty($result);
107 | }
108 |
109 | /**
110 | * @depends testAdd
111 | */
112 | public function testGet()
113 | {
114 | $result = $this->object->fetchFile('image.jpg', __DIR__ . '/downloads');
115 | $this->assertNotEmpty($result);
116 | }
117 |
118 | /**
119 | * @depends testAdd
120 | */
121 | public function testClearInvalidName()
122 | {
123 | $fake_filename = 'xxx';
124 | $result = $this->object->clear($fake_filename);
125 | $this->assertTrue($result); // SDK v2 will return success even if the file does not exist
126 | }
127 |
128 | /**
129 | * @depends testAdd
130 | */
131 | public function testClear()
132 | {
133 | $result = $this->object->clear('image.jpg');
134 | $this->assertTrue($result);
135 | }
136 |
137 |
138 | public static function tearDownAfterClass()
139 | {
140 | parent::tearDownAfterClass();
141 |
142 | try {
143 | $clear = new \Aws\S3\Model\ClearBucket(self::$client, self::$test_upload_bucket);
144 | $clear->clear();
145 |
146 | self::$client->deleteBucket(array(
147 | 'Bucket' => self::$test_upload_bucket
148 | ));
149 | self::$client->waitUntilBucketNotExists(array(
150 | 'Bucket' => self::$test_upload_bucket
151 | ));
152 | } catch (\Exception $exception) {
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/PHPQueue/Backend/WindowsAzureServiceBus.php:
--------------------------------------------------------------------------------
1 | queue_name = $options['queue'];
23 | }
24 | if (!empty($options['connection_string'])) {
25 | $this->connection_string = $options['connection_string'];
26 | }
27 | }
28 |
29 | public function connect()
30 | {
31 | if (empty($this->connection_string)) {
32 | throw new BackendException("Connection string not specified.");
33 | }
34 | $this->connection = ServicesBuilder::getInstance()->createServiceBusService($this->connection_string);
35 | }
36 |
37 | /**
38 | * @return \WindowsAzure\ServiceBus\ServiceBusRestProxy
39 | */
40 | public function getConnection()
41 | {
42 | return parent::getConnection();
43 | }
44 |
45 | /**
46 | * @param array $data
47 | * @throws \PHPQueue\Exception\BackendException
48 | * @return boolean Status of saving
49 | */
50 | public function add($data=array())
51 | {
52 | $this->beforeAdd();
53 | $this->checkQueue();
54 | try {
55 | $message = new BrokeredMessage();
56 | $message->setBody(json_encode($data));
57 | $this->getConnection()->sendQueueMessage($this->queue_name, $message);
58 | } catch (ServiceException $ex) {
59 | throw new BackendException($ex->getMessage(), $ex->getCode());
60 | }
61 |
62 | return true;
63 | }
64 |
65 | /**
66 | * @throws \PHPQueue\Exception\JobNotFoundException
67 | * @return array
68 | */
69 | public function get()
70 | {
71 | $this->beforeGet();
72 | $this->checkQueue();
73 | try {
74 | $options = new ReceiveMessageOptions();
75 | $options->setPeekLock();
76 | $response = $this->getConnection()->receiveQueueMessage($this->queue_name, $options);
77 | if (empty($response)) {
78 | throw new JobNotFoundException('No message found.', 404);
79 | }
80 |
81 | $this->last_job = $response;
82 | $this->last_job_id = $response->getMessageId();
83 | $this->afterGet();
84 |
85 | return json_decode($response->getBody(), TRUE);
86 | } catch (ServiceException $ex) {
87 | throw new JobNotFoundException($ex->getMessage(), $ex->getCode());
88 | }
89 | }
90 |
91 | /**
92 | * @param string $jobId
93 | * @throws \PHPQueue\Exception\BackendException
94 | * @return boolean
95 | */
96 | public function clear($jobId=null)
97 | {
98 | $this->beforeClear($jobId);
99 | $this->isJobOpen($jobId);
100 | try {
101 | $this->getConnection()->deleteMessage($this->open_items[$jobId]);
102 | $this->last_job_id = $jobId;
103 | $this->afterClearRelease();
104 |
105 | return true;
106 | } catch (ServiceException $ex) {
107 | throw new BackendException($ex->getMessage(), $ex->getCode());
108 | }
109 | }
110 |
111 | /**
112 | * @param string $jobId
113 | * @throws \PHPQueue\Exception\BackendException
114 | * @return boolean
115 | */
116 | public function release($jobId=null)
117 | {
118 | $this->beforeRelease($jobId);
119 | $this->isJobOpen($jobId);
120 | try {
121 | $this->getConnection()->unlockMessage($this->open_items[$jobId]);
122 | $this->last_job_id = $jobId;
123 | $this->afterClearRelease();
124 |
125 | return true;
126 | } catch (ServiceException $ex) {
127 | throw new BackendException($ex->getMessage(), $ex->getCode());
128 | }
129 | }
130 |
131 | public function createQueue($queue_name)
132 | {
133 | try {
134 | $queueInfo = new QueueInfo($queue_name);
135 | $this->getConnection()->createQueue($queueInfo);
136 |
137 | return true;
138 | } catch (ServiceException $ex) {
139 | throw new BackendException($ex->getMessage(), $ex->getCode());
140 | }
141 | }
142 |
143 | private function checkQueue()
144 | {
145 | if (empty($this->queue_name)) {
146 | throw new BackendException("Queue name not specified.");
147 | }
148 | $queue_info = $this->getConnection()->getQueue($this->queue_name);
149 | if ($this->queue_name != $queue_info->getTitle()) {
150 | return $this->createQueue($this->queue_name);
151 | }
152 |
153 | return true;
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/PHPQueue/Backend/Aws/AmazonSQSV2.php:
--------------------------------------------------------------------------------
1 | null,
16 | 'secret' => null,
17 | 'region' => null
18 | );
19 | public $attribute_options = array(
20 | 'VisibilityTimeout' => 10
21 | );
22 | public $receiving_options = array(
23 | 'WaitTimeSeconds' => 3,
24 | 'MaxNumberOfMessages' => 1
25 | );
26 |
27 | public function __construct($options=array())
28 | {
29 | parent::__construct();
30 | if (!empty($options['region'])) {
31 | $this->region = $options['region'];
32 | }
33 | if (!empty($options['queue'])) {
34 | $this->queue_url = $options['queue'];
35 | }
36 | if (!empty($options['attribute_options']) && is_array($options['attribute_options'])) {
37 | $this->attribute_options = array_merge($this->attribute_options, $options['attribute_options']);
38 | }
39 | if (!empty($options['receiving_options']) && is_array($options['receiving_options'])) {
40 | $this->receiving_options = array_merge($this->receiving_options, $options['receiving_options']);
41 | }
42 | if (!empty($options['sqs_options']) && is_array($options['sqs_options'])) {
43 | $this->sqs_options = array_merge($this->sqs_options, $options['sqs_options']);
44 | }
45 | }
46 |
47 | /**
48 | * @param \Aws\Sqs\SqsClient
49 | * @return bool
50 | * @throws \InvalidArgumentException
51 | */
52 | public function setConnection($connection)
53 | {
54 | if (!$connection instanceof SqsClient) {
55 | throw new \InvalidArgumentException('Connection must be an instance of SqsClient.');
56 | }
57 |
58 | return parent::setConnection($connection);
59 | }
60 |
61 | public function connect()
62 | {
63 | $this->connection = SqsClient::factory(array(
64 | 'key' => $this->sqs_options['key'],
65 | 'secret' => $this->sqs_options['secret'],
66 | 'region' => $this->region
67 | ));
68 |
69 | $this->connection->setQueueAttributes(array(
70 | 'QueueUrl' => $this->queue_url,
71 | 'Attributes' => $this->attribute_options
72 | ));
73 | }
74 |
75 | /**
76 | * @param array $data
77 | * @throws \PHPQueue\Exception\BackendException
78 | * @return boolean Status of saving
79 | */
80 | public function add($data=array())
81 | {
82 | $this->beforeAdd();
83 | try {
84 | $this->getConnection()->sendMessage(array(
85 | 'QueueUrl' => $this->queue_url,
86 | 'MessageBody' => json_encode($data)
87 | ));
88 | } catch (SqsException $exception) {
89 | throw new BackendException($exception->getMessage(), $exception->getCode());
90 | }
91 |
92 | return true;
93 | }
94 |
95 | /**
96 | * @throws \PHPQueue\Exception\JobNotFoundException
97 | * @return array
98 | */
99 | public function get()
100 | {
101 | $this->beforeGet();
102 | try {
103 | $response = $this->getConnection()->receiveMessage(array_merge(array(
104 | 'QueueUrl' => $this->queue_url
105 | ), $this->receiving_options));
106 | } catch (SqsException $exception) {
107 | throw new BackendException($exception->getMessage(), $exception->getCode());
108 | }
109 |
110 | $message = $response->getPath('Messages/0');
111 | if (empty($message)) {
112 | return null;
113 | }
114 | $this->last_job = $response;
115 | $this->last_job_id = (string) $message['ReceiptHandle'];
116 | $this->afterGet();
117 |
118 | return json_decode((string) $message['Body'], TRUE);
119 | }
120 |
121 | /**
122 | * @param string $jobId
123 | * @throws \PHPQueue\Exception\BackendException
124 | * @return boolean
125 | */
126 | public function clear($jobId=null)
127 | {
128 | $this->beforeClear($jobId);
129 | $this->isJobOpen($jobId);
130 | try {
131 | $response = $this->getConnection()->deleteMessage(array(
132 | 'QueueUrl' => $this->queue_url,
133 | 'ReceiptHandle' => $jobId
134 | ));
135 | } catch (SqsException $exception) {
136 | throw new BackendException($exception->getMessage(), $exception->getCode());
137 | }
138 | $this->last_job_id = $jobId;
139 | $this->afterClearRelease();
140 |
141 | return true;
142 | }
143 |
144 | /**
145 | * @param string $jobId
146 | * @return boolean
147 | * @throws \PHPQueue\Exception\BackendException If job wasn't retrieved previously.
148 | */
149 | public function release($jobId=null)
150 | {
151 | $this->beforeRelease($jobId);
152 | $this->isJobOpen($jobId);
153 | $this->last_job_id = $jobId;
154 | $this->afterClearRelease();
155 |
156 | return true;
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/PHPQueue/Runner.php:
--------------------------------------------------------------------------------
1 | queue_name = $queue;
29 | }
30 | if (
31 | !empty($options['logPath'])
32 | && is_writable($options['logPath'])
33 | )
34 | {
35 | $this->log_path = $options['logPath'];
36 | }
37 | if ( !empty($options['logLevel']) ) {
38 | $this->log_level = $options['logLevel'];
39 | } else {
40 | $this->log_level = Logger::INFO;
41 | }
42 |
43 | return $this;
44 | }
45 |
46 | public function run()
47 | {
48 | $this->setup();
49 | $this->beforeLoop();
50 | $this->loop();
51 | }
52 |
53 | public function setup()
54 | {
55 | if (empty($this->log_path)) {
56 | $baseFolder = dirname(dirname(__DIR__));
57 | $this->log_path = sprintf(
58 | '%s/demo/runners/logs/'
59 | , $baseFolder
60 | );
61 | }
62 | $this->createLogger();
63 | }
64 |
65 | protected function beforeLoop()
66 | {
67 | if (empty($this->queue_name)) {
68 | throw new Exception('Queue name is invalid');
69 | }
70 | $this->queue = Base::getQueue($this->queue_name);
71 | }
72 |
73 | protected function loop()
74 | {
75 | while (true) {
76 | $this->checkAndCycleLog();
77 | $this->workJob();
78 | }
79 | }
80 |
81 | protected function checkAndCycleLog()
82 | {
83 | $this->current_log_check++;
84 | if ($this->current_log_check > $this->max_check_interval) {
85 | if ($this->current_date != date('Ymd')) {
86 | $this->logger->alert("Cycling log file now.");
87 | $this->logger = Logger::cycleLog(
88 | $this->queue_name
89 | , $this->log_level
90 | , $this->getFullLogPath()
91 | );
92 | }
93 | $this->current_log_check = 0;
94 | }
95 | }
96 |
97 | public function workJob()
98 | {
99 | $sleepTime = self::RUN_USLEEP;
100 | $newJob = null;
101 | try {
102 | $newJob = Base::getJob($this->queue);
103 | } catch (\Exception $ex) {
104 | $this->logger->error($ex->getMessage());
105 | $sleepTime = self::RUN_USLEEP * 5;
106 | }
107 | if (empty($newJob)) {
108 | $this->logger->notice("No Job found.");
109 | $sleepTime = self::RUN_USLEEP * 10;
110 | } else {
111 | try {
112 | if (empty($newJob->worker)) {
113 | throw new Exception("No worker declared.");
114 | }
115 | if (is_string($newJob->worker)) {
116 | $result_data = $this->processWorker($newJob->worker, $newJob);
117 | } elseif (is_array($newJob->worker)) {
118 | $this->logger->info(sprintf("Running chained new job (%s) with workers", $newJob->job_id), $newJob->worker);
119 | foreach ($newJob->worker as $worker_name) {
120 | $result_data = $this->processWorker($worker_name, $newJob);
121 | $newJob->data = $result_data;
122 | }
123 | }
124 |
125 | return Base::updateJob($this->queue, $newJob->job_id, $result_data);
126 | } catch (\Exception $ex) {
127 | $this->logger->error($ex->getMessage());
128 | $this->logger->info(sprintf('Releasing job (%s).', $newJob->job_id));
129 | $this->queue->releaseJob($newJob->job_id);
130 | $sleepTime = self::RUN_USLEEP * 5;
131 | }
132 | }
133 | $this->logger->info('Sleeping ' . ceil($sleepTime / 1000000) . ' seconds.');
134 | usleep($sleepTime);
135 | }
136 |
137 | protected function processWorker($worker_name, $new_job)
138 | {
139 | $this->logger->info(sprintf("Running new job (%s) with worker: %s", $new_job->job_id, $worker_name));
140 | $worker = Base::getWorker($worker_name);
141 | Base::workJob($worker, $new_job);
142 | $this->logger->info(sprintf('Worker is done. Updating job (%s). Result:', $new_job->job_id), $worker->result_data);
143 |
144 | return $worker->result_data;
145 | }
146 |
147 | protected function createLogger()
148 | {
149 | $this->logger = Logger::createLogger(
150 | $this->queue_name
151 | , $this->log_level
152 | , $this->getFullLogPath()
153 | );
154 | }
155 |
156 | /**
157 | * @return string
158 | */
159 | protected function getFullLogPath()
160 | {
161 | $this->current_date = date('Ymd');
162 |
163 | return sprintf('%s/RunnerLog-%s-%s.log', $this->log_path, $this->queue_name, $this->current_date);
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/test/PHPQueue/Backend/PDOBaseTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped( 'PDO extension is not installed' );
17 | }
18 | }
19 |
20 | public function tearDown()
21 | {
22 | if ($this->object) {
23 | $result = $this->object->deleteTable('pdotest');
24 | $this->assertTrue($result);
25 | }
26 |
27 | parent::tearDown();
28 | }
29 |
30 | public function testAddGet()
31 | {
32 |
33 | $data1 = array('2', 'Boo', 'Moeow');
34 | $data2 = array('1','Willy','Wonka');
35 |
36 | // Queue first message
37 | $this->assertTrue($this->object->add($data1));
38 | $this->assertEquals(1, $this->object->last_job_id);
39 |
40 | // Queue second message
41 | $this->assertTrue($this->object->add($data2));
42 |
43 | // Check get method
44 | $this->assertEquals($data2, $this->object->get($this->object->last_job_id));
45 |
46 | // Check get method with no message ID.
47 | $this->assertEquals($data1, $this->object->get());
48 | }
49 |
50 | /**
51 | * @depends testAddGet
52 | */
53 | public function testClear()
54 | {
55 | // TODO: Include test fixtures instead of relying on side effect.
56 | $this->testAddGet();
57 |
58 | $jobId = 1;
59 | $result = $this->object->clear($jobId);
60 | $this->assertTrue($result);
61 |
62 | $result = $this->object->get($jobId);
63 | $this->assertNull($result);
64 | }
65 |
66 | public function testSet()
67 | {
68 | $data = array(mt_rand(), 'Gas', 'Prom');
69 |
70 | // Set message.
71 | $this->object->set(3, $data);
72 |
73 | $this->assertEquals($data, $this->object->get(3));
74 | }
75 |
76 | public function testPush()
77 | {
78 | $data = array(mt_rand(), 'Snow', 'Den');
79 |
80 | // Set message.
81 | $id = $this->object->push($data);
82 | $this->assertTrue($id > 0);
83 | $this->assertEquals($data, $this->object->get($id));
84 | }
85 |
86 | public function testPop()
87 | {
88 | $data = array(mt_rand(), 'Snow', 'Den');
89 |
90 | // Set message.
91 | $id = $this->object->push($data);
92 | $this->assertTrue($id > 0);
93 | $this->assertEquals($data, $this->object->pop());
94 | }
95 |
96 | public function testPopEmpty()
97 | {
98 | $this->assertNull( $this->object->pop() );
99 | }
100 |
101 | /**
102 | * popAtomic should pop if the processor callback is successful.
103 | */
104 | public function testPopAtomicCommit()
105 | {
106 | $data = array(mt_rand(), 'Abbie', 'Hoffman');
107 |
108 | $this->object->push($data);
109 | $self = $this;
110 | $did_run = false;
111 | $callback = function ($message) use ($self, &$did_run, $data) {
112 | $self->assertEquals($data, $message);
113 | $did_run = true;
114 | };
115 | $this->assertEquals($data, $this->object->popAtomic($callback));
116 | $this->assertEquals(true, $did_run);
117 | // Record has really gone away.
118 | $this->assertEquals(null, $this->object->pop());
119 | }
120 |
121 | /**
122 | * popAtomic should not pop if the processor throws an error.
123 | */
124 | public function testPopAtomicRollback()
125 | {
126 | $data = array(mt_rand(), 'Abbie', 'Hoffman');
127 |
128 | $this->object->push($data);
129 | $self = $this;
130 | $callback = function ($message) use ($self, $data) {
131 | $self->assertEquals($data, $message);
132 | throw new \Exception("Foiled!");
133 | };
134 | try {
135 | $this->assertEquals($data, $this->object->popAtomic($callback));
136 | $this->fail("Should have failed by this point");
137 | } catch (\Exception $ex) {
138 | $this->assertEquals("Foiled!", $ex->getMessage());
139 | }
140 |
141 | // Punchline: data should still be available for the retry pop.
142 | $this->assertEquals($data, $this->object->pop());
143 | }
144 |
145 | /**
146 | * popAtomic should not call the callback if there are no messages
147 | */
148 | public function testPopAtomicEmpty()
149 | {
150 | $did_run = false;
151 | $callback = function ($unused) use (&$did_run) {
152 | $did_run = true;
153 | };
154 | $data = $this->object->popAtomic($callback);
155 | $this->assertNull($data, 'Should return null on an empty queue');
156 | $this->assertFalse($did_run, 'Should not call callback without a message');
157 | }
158 |
159 | /**
160 | * Should be able to push without creating the table first
161 | */
162 | public function testImplicitCreateTable()
163 | {
164 | $this->object->deleteTable('pdotest'); // created in setUp
165 | $data = array(mt_rand(), 'Daniel', 'Berrigan');
166 | try {
167 | $id = $this->object->push($data);
168 | $this->assertTrue($id > 0);
169 | $this->assertEquals($data, $this->object->get($id));
170 | } catch (\Exception $ex) {
171 | $this->fail('Should not throw exception when no table');
172 | }
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/demo/runners/README.md:
--------------------------------------------------------------------------------
1 | # PHP-Queue Runners #
2 |
3 | ## Basic CLI Runner ##
4 |
5 | Here is a basic runner for the a JobQueue named "Simple":
6 |
7 | ```php
8 | run();
13 | ?>
14 | ```
15 |
16 | Just run this in console:
17 |
18 | ```
19 | $ php SimpleRunner.php
20 | ```
21 |
22 | The runner will check the queue for new jobs and work the jobs. After working on the job, it will sleep for 1 second before processing the next job. If there are no more jobs in the queue, it will rest for 10 seconds.
23 |
24 | ### Breakdown ###
25 |
26 | 1. Create a new PHP file named "SimpleRunner.php" (no naming convention here).
27 | 2. Include the config file:
28 |
29 | ```php
30 | require_once '/path/to/your/config.php';
31 | ```
32 |
33 | 3. Create a new Runner class (extending `PHPQueue\Runner`). The queue name can be defined in the `$queue_name` attribute.
34 |
35 | ```php
36 | class SampleRunner extends PHPQueue\Runner
37 | {
38 | public $queue_name = 'Sample';
39 | }
40 | ```
41 |
42 | 4. Instantiate the class and call the `run()` method.
43 |
44 | ```php
45 | $runner = new SampleRunner();
46 | $runner->run();
47 | ```
48 |
49 | 5. Run the Runner.
50 |
51 | ```
52 | $ php SimpleRunner.php
53 | ```
54 |
55 | ## PHP Daemon ##
56 |
57 | Here's a basic script to start a PHP Daemon (using [`Clio\Clio`](https://packagist.org/packages/clio/clio)).
58 |
59 | ```php
60 | # BeanstalkSampleStart.php
61 | require_once '/path/to/your/config.php';
62 | Clio\Daemon::work(
63 | array(
64 | 'pid' => $pid_file,
65 | ),
66 | function($stdin, $stdout, $sterr)
67 | {
68 | class BeanstalkSample extends PHPQueue\Runner{}
69 | $runner = new BeanstalkSample(
70 | 'BeanstalkSample'
71 | , array('logPath'=>__DIR__ . '/logs/'));
72 | $runner->run();
73 | }
74 | );
75 | ```
76 |
77 | Here's a basic script to stop a PHP Daemon (using [`Clio\Clio`](https://packagist.org/packages/clio/clio)).
78 |
79 | ```php
80 | # BeanstalkSampleStop.php
81 | require_once '/path/to/your/config.php';
82 | Clio\Daemon::kill($pid_file, true);
83 | ```
84 |
85 | To start/stop the daemon:
86 |
87 | ```
88 | $ php BeanstalkSampleStart.php
89 | ```
90 |
91 | ```
92 | $ php BeanstalkSampleStop.php
93 | ```
94 |
95 | *__Note:__ On CentOS, you will need to install `php-process` package: `sudo yum install php-process`*
96 |
97 | ### The Proper Way ###
98 |
99 | All Linux background daemons run like this:
100 |
101 | ```
102 | $ /etc/init.d/httpd start
103 | ```
104 |
105 | You can make your PHP-Queue runner start the same way.
106 |
107 | #### CentOS ####
108 |
109 | 1. Combine the start and stop scripts into 1 file. And use PHP's `$argv` ([reference](http://www.php.net/manual/en/reserved.variables.argv.php)) to switch between executing `start` or `stop`:
110 |
111 | ```php
112 | require_once '/path/to/your/config.php';
113 | $pid_file = '/path/to/process.pid';
114 | if (empty($argv[1]))
115 | {
116 | fwrite(STDOUT, "Unknown action." . PHP_EOL);
117 | die();
118 | }
119 | switch($argv[1])
120 | {
121 | case 'start':
122 | fwrite(STDOUT, "Starting... ");
123 | Clio\Daemon::work(array(
124 | 'pid' => $pid_file,
125 | ),
126 | function($stdin, $stdout, $sterr)
127 | {
128 | class BeanstalkSample extends PHPQueue\Runner{}
129 | $runner = new BeanstalkSample('BeanstalkSample', array('logPath'=>__DIR__ . '/logs/'));
130 | $runner->run();
131 | }
132 | );
133 | fwrite(STDOUT, "[OK]" . PHP_EOL);
134 | break;
135 | case 'stop':
136 | fwrite(STDOUT, "Stopping... ");
137 | Clio\Daemon::kill($pid_file, true);
138 | fwrite(STDOUT, "[OK]" . PHP_EOL);
139 | break;
140 | default:
141 | fwrite(STDOUT, "Unknown action." . PHP_EOL);
142 | break;
143 | }
144 | ?>
145 | ```
146 |
147 | *__Note:__ Some `echo` statements were added to provide some on-screen feedback.*
148 |
149 | 2. Make your runner executable by adding this to the first line of the PHP script:
150 |
151 | ```
152 | #!/usr/bin/php
153 | ```
154 | and make the file executable under linux:
155 |
156 | ```
157 | $ chmod a+x BeanstalkSampleDaemon.php
158 | ```
159 |
160 | That way, you can call the script directly without involving PHP:
161 |
162 | ```
163 | $ ./BeanstalkSampleDaemon.php
164 | ```
165 |
166 | 3. Move the file to `/etc/init.d/` folder.
167 |
168 | ```
169 | $ mv BeanstalkSampleDaemon.php /etc/init.d/BeanstalkSampleDaemon
170 | ```
171 |
172 | *Notice that I moved it without the `.php` extension. You do not need the extension to be there as this file is now executable on its own.*
173 |
174 | 4. You can now run this script like a normal daemon:
175 |
176 | ```
177 | $ /etc/init.d/BeanstalkSampleDaemon start
178 | ```
179 |
180 | **Optional:**
181 |
182 | To make it into a service that starts on boot-up.
183 |
184 | 1. Add this to the top of your script:
185 |
186 | ```
187 | # !/usr/bin/php
188 | container = $options['container'];
20 | }
21 | if (!empty($options['connection_string'])) {
22 | $this->connection_string = $options['connection_string'];
23 | }
24 | }
25 |
26 | /**
27 | * @return \WindowsAzure\Blob\BlobRestProxy
28 | */
29 | public function getConnection()
30 | {
31 | return parent::getConnection();
32 | }
33 |
34 | public function connect()
35 | {
36 | if (empty($this->connection_string)) {
37 | throw new BackendException("Connection string not specified.");
38 | }
39 | $this->connection = ServicesBuilder::getInstance()->createBlobService($this->connection_string);
40 | }
41 |
42 | public function createContainer($container_name, $container_options=null)
43 | {
44 | if (empty($container_options)) {
45 | $container_options = new CreateContainerOptions();
46 | $container_options->setPublicAccess(PublicAccessType::CONTAINER_AND_BLOBS);
47 | }
48 | try {
49 | $this->getConnection()->createContainer($container_name, $container_options);
50 | } catch (ServiceException $ex) {
51 | throw new BackendException($ex->getMessage(), $ex->getCode());
52 | }
53 |
54 | return true;
55 | }
56 |
57 | public function deleteContainer($container_name)
58 | {
59 | try {
60 | $this->getConnection()->deleteContainer($container_name);
61 | } catch (ServiceException $ex) {
62 | throw new BackendException($ex->getMessage(), $ex->getCode());
63 | }
64 |
65 | return true;
66 | }
67 |
68 | public function listContainers()
69 | {
70 | $all_containers = array();
71 | try {
72 | $containers = $this->getConnection()->listContainers();
73 | $list_containers = $containers->getContainers();
74 | foreach ($list_containers as $container) {
75 | $all_containers[] = array(
76 | 'name' => $container->getName()
77 | , 'url' => $container->getUrl()
78 | , 'object' => $container
79 | );
80 | }
81 | } catch (ServiceException $ex) {
82 | throw new BackendException($ex->getMessage(), $ex->getCode());
83 | }
84 |
85 | return $all_containers;
86 | }
87 |
88 | public function listFiles()
89 | {
90 | $all_files = array();
91 | try {
92 | $blob_list = $this->getConnection()->listBlobs($this->container);
93 | $blobs = $blob_list->getBlobs();
94 | foreach ($blobs as $blob) {
95 | $all_files[] = array(
96 | 'name' => $blob->getName()
97 | , 'url' => $blob->getUrl()
98 | , 'object' => $blob
99 | );
100 | }
101 | } catch (ServiceException $ex) {
102 | throw new BackendException($ex->getMessage(), $ex->getCode());
103 | }
104 |
105 | return $all_files;
106 | }
107 |
108 | public function put($key, $data=null, $options=null)
109 | {
110 | try {
111 | $this->getConnection()->createBlockBlob($this->container, $key, $data, $options);
112 | } catch (ServiceException $ex) {
113 | throw new BackendException($ex->getMessage(), $ex->getCode());
114 | }
115 |
116 | return true;
117 | }
118 |
119 | public function putFile($key, $data=null, $options=null)
120 | {
121 | if (is_string($data) && is_file($data)) {
122 | $data = fopen($data, 'r');
123 | }
124 |
125 | return $this->put($key, $data, $options);
126 | }
127 |
128 | public function clear($key = null)
129 | {
130 | try {
131 | $this->getConnection()->deleteBlob($this->container, $key);
132 | } catch (ServiceException $ex) {
133 | throw new BackendException($ex->getMessage(), $ex->getCode());
134 | }
135 |
136 | return true;
137 | }
138 |
139 | public function fetch($key)
140 | {
141 | try {
142 | $blob = $this->getConnection()->getBlob($this->container, $key);
143 |
144 | return array(
145 | 'name' => $key
146 | , 'meta' => $blob->getMetadata()
147 | , 'object' => $blob
148 | );
149 | } catch (ServiceException $ex) {
150 | throw new BackendException($ex->getMessage(), $ex->getCode());
151 | }
152 | }
153 |
154 | public function fetchFile($key, $destination=null, $options=null)
155 | {
156 | $response = $this->fetch($key);
157 | $handle = $response['object']->getContentStream();
158 | $contents = '';
159 | while (!feof($handle)) {
160 | $contents .= fread($handle, 8192);
161 | }
162 | fclose($handle);
163 |
164 | return file_put_contents($destination, $contents);
165 | }
166 |
167 | public function copy($src_container, $src_file, $dest_container, $dest_file, $options=null)
168 | {
169 | try {
170 | return $this->getConnection()->copyBlob($src_container, $src_file, $dest_container, $dest_file, $options);
171 | } catch (ServiceException $ex) {
172 | throw new BackendException($ex->getMessage(), $ex->getCode());
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/test/PHPQueue/Backend/PredisTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Predis not installed');
15 | } else {
16 | $options = array(
17 | 'servers' => array('host' => '127.0.0.1', 'port' => 6379)
18 | , 'queue' => 'testqueue'
19 | );
20 | $this->object = new Predis($options);
21 | }
22 | }
23 |
24 | public function tearDown()
25 | {
26 | if ($this->object) {
27 | $this->object->getConnection()->flushall();
28 | }
29 | parent::tearDown();
30 | }
31 |
32 | public function testAddGet()
33 | {
34 | $data1 = array('full_name'=>'Michael Cheng');
35 | $result = $this->object->add($data1);
36 | $this->assertTrue($result);
37 |
38 | $data2 = array('full_name'=>'Andrew Chew');
39 | $result = $this->object->add($data2);
40 | $this->assertTrue($result);
41 |
42 | $data3 = array('full_name'=>'Peter Tiel');
43 | $result = $this->object->add($data3);
44 | $this->assertTrue($result);
45 |
46 | $result = $this->object->get();
47 | $this->assertEquals($data1, $result);
48 |
49 | $jobA = $this->object->last_job_id;
50 | $result = $this->object->release($jobA);
51 | $this->assertEquals(3, $this->object->getConnection()->llen($this->object->queue_name));
52 |
53 | $result = $this->object->get();
54 | $this->assertNotEmpty($result);
55 | $this->assertEquals($data2, $result);
56 |
57 | $jobB = $this->object->last_job_id;
58 | $this->object->clear($jobB);
59 | try {
60 | $result = $this->object->isJobOpen($jobB);
61 | $this->fail("Job should not still be open.");
62 | } catch (\Exception $ex) {
63 | $this->assertTrue(true);
64 | }
65 | $this->assertEquals(2, $this->object->getConnection()->llen($this->object->queue_name));
66 | }
67 |
68 | public function testSetKey()
69 | {
70 | $key = 'A0001';
71 | $data = 'Michael';
72 | $result = $this->object->setKey($key, $data);
73 | $this->assertTrue($result);
74 |
75 | $key = 'A0001';
76 | $data = 'Michael Cheng';
77 | $result = $this->object->setKey($key, $data);
78 | $this->assertTrue($result);
79 |
80 | $key = 'A0002';
81 | $data = 20333;
82 | $result = $this->object->setKey($key, $data);
83 | $this->assertTrue($result);
84 |
85 | $key = 'A0003';
86 | $data = array(1, 'Willy', 'Wonka');
87 | $result = $this->object->setKey($key, $data);
88 | $this->assertTrue($result);
89 | }
90 |
91 |
92 | /**
93 | * Shouldn't be able to save a nested structure
94 | *
95 | * @expectedException \PHPQueue\Exception\BackendException
96 | */
97 | public function testSetDeep()
98 | {
99 | $key = 'A0004';
100 | $data = array(mt_rand(), 'Willy', 'Wonka', 'boo'=>array(5,6,7));
101 | $this->object->set($key, $data);
102 | }
103 |
104 | /**
105 | * @ depends testSetKey
106 | */
107 | public function testGetKey()
108 | {
109 | $this->testSetKey();
110 |
111 | $result = $this->object->getKey('A0001');
112 | $this->assertNotEmpty($result);
113 | $this->assertEquals('Michael Cheng', $result);
114 |
115 | $result = $this->object->getKey('A0002');
116 | $this->assertNotEmpty($result);
117 | $this->assertEquals(20333, $result);
118 |
119 | $result = $this->object->getKey('A0003');
120 | $this->assertNotEmpty($result);
121 | $this->assertEquals(array(1, 'Willy', 'Wonka'), $result);
122 | }
123 |
124 | public function testIncrDecr()
125 | {
126 | $key = 'A0002';
127 | $data = 20333;
128 | $result = $this->object->setKey($key, $data);
129 | $this->assertTrue($result);
130 |
131 | $result = $this->object->incrKey('A0002');
132 | $this->assertEquals(20334, $result);
133 |
134 | $result = $this->object->getKey('A0002');
135 | $this->assertNotEmpty($result);
136 | $this->assertEquals(20334, $result);
137 |
138 | $result = $this->object->decrKey('A0002');
139 | $this->assertEquals(20333, $result);
140 |
141 | $result = $this->object->getKey('A0002');
142 | $this->assertNotEmpty($result);
143 | $this->assertEquals(20333, $result);
144 | }
145 |
146 | /**
147 | * @depends testSetKey
148 | */
149 | public function testClearKey()
150 | {
151 | $this->testSetKey();
152 |
153 | $jobId = 'A0001';
154 | $result = $this->object->clearKey($jobId, 'Try clearing A0001');
155 | $this->assertTrue($result);
156 |
157 | $result = $this->object->get($jobId, 'Check A0001');
158 | $this->assertNull($result);
159 |
160 | $jobId = 'A0003';
161 | $result = $this->object->clearKey($jobId, 'Try clearing A0003');
162 | $this->assertTrue($result);
163 |
164 | $result = $this->object->get($jobId, 'Check A0003');
165 | $this->assertNull($result);
166 | }
167 |
168 | public function testClearEmpty()
169 | {
170 | $jobId = 'xxx';
171 | $this->assertFalse($this->object->clear($jobId));
172 | }
173 |
174 | public function testSetGet()
175 | {
176 | $key = 'A0001';
177 | $data = 'Michael-' . mt_rand();
178 | $this->object->set($key, $data);
179 |
180 | $this->assertEquals($data, $this->object->get($key));
181 | }
182 |
183 | public function testPushPop()
184 | {
185 | $data = 'Weezle-' . mt_rand();
186 | $this->object->push($data);
187 |
188 | $this->assertEquals($data, $this->object->pop());
189 |
190 | // Check that we did remove the object.
191 | $this->assertNull($this->object->pop());
192 | }
193 |
194 | /**
195 | * @expectedException PHPQueue\Exception\JsonException
196 | */
197 | public function testPopBadJson()
198 | {
199 | // Bad JSON
200 | $data = '{"a": bad "Weezle-' . mt_rand() . '"}';
201 | $this->object->getConnection()->rpush($this->object->queue_name, $data);
202 |
203 | $this->object->pop();
204 |
205 | $this->fail();
206 | }
207 |
208 | public function testPopEmpty()
209 | {
210 | $this->assertNull($this->object->pop());
211 | }
212 |
213 | public function testPeek()
214 | {
215 | $data = 'Weezle-' . mt_rand();
216 | $this->object->push($data);
217 |
218 | $this->assertEquals($data, $this->object->peek());
219 |
220 | // Check that we didn't remove the object by peeking.
221 | $this->assertEquals($data, $this->object->pop());
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/src/PHPQueue/Backend/LocalFS.php:
--------------------------------------------------------------------------------
1 | doc_root = $options['doc_root'];
15 | }
16 | if (!empty($options['container'])) {
17 | $this->container = $options['container'];
18 | }
19 | }
20 |
21 | public function connect()
22 | {
23 | $this->connection = true;
24 | }
25 |
26 | public function clear($key = null)
27 | {
28 | $file_path = $this->getFullPath($key);
29 | if (!is_file($file_path)) {
30 | throw new BackendException('File does not exist: '.$file_path);
31 | }
32 | if (!is_writable($file_path)) {
33 | throw new BackendException('File is not deletable: '.$file_path);
34 | }
35 | $status = unlink($file_path);
36 | if (!$status) {
37 | throw new BackendException('Unable to delete file: '.$file_path);
38 | }
39 | clearstatcache();
40 |
41 | return $status;
42 | }
43 |
44 | public function createContainer($container_name)
45 | {
46 | $dir_path = $this->getContainerPath($container_name);
47 | if (is_dir($dir_path)) {
48 | return true;
49 | }
50 | $status = mkdir($dir_path, 0777, true);
51 | if (!$status) {
52 | throw new BackendException('Unable to create directory: '.$dir_path);
53 | }
54 | clearstatcache();
55 |
56 | return $status;
57 | }
58 |
59 | public function deleteContainer($container_name)
60 | {
61 | $dir_path = $this->getContainerPath($container_name);
62 | $status = rmdir($dir_path);
63 | if (!$status) {
64 | throw new BackendException('Unable to delete directory: '.$dir_path);
65 | }
66 | clearstatcache();
67 |
68 | return $status;
69 | }
70 |
71 | public function listContainers()
72 | {
73 | if (empty($this->doc_root)) {
74 | throw new BackendException('Document root is not set.');
75 | }
76 | $all_containers = array();
77 | $dir = new \DirectoryIterator($this->doc_root);
78 | foreach ($dir as $file_info) {
79 | if (!$file_info->isDot() && $file_info->isDir()) {
80 | $all_containers[] = array(
81 | 'name' => $file_info->getFilename()
82 | , 'url' => $file_info->getPathname()
83 | , 'object' => $file_info
84 | );
85 | }
86 | }
87 |
88 | return $all_containers;
89 | }
90 |
91 | public function listFiles()
92 | {
93 | if (empty($this->doc_root) || empty($this->container)) {
94 | throw new BackendException('Document root or Container not set.');
95 | }
96 | $all_files = array();
97 | $dir = new \DirectoryIterator($this->getCurrentContainerPath());
98 | foreach ($dir as $file_info) {
99 | if (!$file_info->isDot() && $file_info->isFile()) {
100 | $all_files[] = array(
101 | 'name' => $file_info->getFilename()
102 | , 'url' => $file_info->getPathname()
103 | , 'object' => $file_info
104 | );
105 | }
106 | }
107 |
108 | return $all_files;
109 | }
110 |
111 | public function copy($src_container, $src_file, $dest_container, $dest_file)
112 | {
113 | $src_path = $this->getContainerPath($src_container) . DIRECTORY_SEPARATOR . $src_file;
114 | $dest_path = $this->getContainerPath($dest_container) . DIRECTORY_SEPARATOR . $dest_file;
115 | $status = copy($src_path, $dest_path);
116 | if (!$status) {
117 | $msg = sprintf('Unable to copy file: %s to file: %s', $src_path, $dest_path);
118 | throw new BackendException($msg);
119 | }
120 | clearstatcache();
121 |
122 | return $status;
123 | }
124 |
125 | public function putFile($key, $file_path = null, $options = null)
126 | {
127 | $dest_path = $this->getFullPath($key);
128 | if (!is_file($file_path)) {
129 | throw new BackendException('Upload file does not exist: '.$file_path);
130 | }
131 | if (!is_writable($this->getCurrentContainerPath())) {
132 | throw new BackendException('Destination Container is not writable: '.$this->container);
133 | }
134 | if (is_file($dest_path) && !is_writable($dest_path)) {
135 | throw new BackendException('Destination file is not writable: '.$dest_path);
136 | }
137 | if (is_uploaded_file($file_path)) {
138 | $status = move_uploaded_file($file_path, $dest_path);
139 | } else {
140 | $status = copy($file_path, $dest_path);
141 | }
142 | if (!$status) {
143 | $msg = sprintf('Unable to put file: %s to file: %s', $file_path, $dest_path);
144 | throw new BackendException($msg);
145 | }
146 | clearstatcache();
147 |
148 | return $status;
149 | }
150 |
151 | public function fetchFile($key, $destination_path = null, $options = null)
152 | {
153 | $src_path = $this->getFullPath($key);
154 | if (!is_file($src_path)) {
155 | throw new BackendException('File does not exist: '.$src_path);
156 | }
157 | if (!is_writable($destination_path)) {
158 | throw new BackendException('Destination path is not writable: '.$destination_path);
159 | }
160 | $destination_file_path = $destination_path . DIRECTORY_SEPARATOR . $key;
161 | $status = copy($src_path, $destination_file_path);
162 | if (!$status) {
163 | $msg = sprintf('Unable to fetch file: %s to file: %s', $src_path, $destination_path);
164 | throw new BackendException($msg);
165 | }
166 | clearstatcache();
167 |
168 | return $status;
169 | }
170 |
171 | public function hasContainer($container)
172 | {
173 | $dir_path = $this->getContainerPath($container);
174 |
175 | return is_dir($dir_path);
176 | }
177 |
178 | protected function getContainerPath($directory_name)
179 | {
180 | if (empty($this->doc_root)) {
181 | throw new BackendException('Document root is not set.');
182 | }
183 | if (empty($directory_name)) {
184 | throw new BackendException('Invalid directory name.');
185 | }
186 |
187 | return $this->doc_root . DIRECTORY_SEPARATOR . $directory_name;
188 | }
189 |
190 | protected function getFullPath($key)
191 | {
192 | if (empty($this->doc_root) || empty($this->container)) {
193 | throw new BackendException('Document root or Container not set.');
194 | }
195 | if (empty($key)) {
196 | throw new BackendException('Invalid file name.');
197 | }
198 |
199 | return $this->getCurrentContainerPath() . DIRECTORY_SEPARATOR . $key;
200 | }
201 |
202 | protected function getCurrentContainerPath()
203 | {
204 | if (empty($this->container)) {
205 | throw new BackendException('Container not set.');
206 | }
207 |
208 | return $this->getContainerPath($this->container);
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/src/PHPQueue/REST.php:
--------------------------------------------------------------------------------
1 | / -d "var1=foo&var2=bar"
7 | * curl -XPOST http:/// -H "Content-Type: application/json" -d '{"var1":"foo","var2":"bar"}'
8 | * curl -XPUT http:///
9 | */
10 | namespace PHPQueue;
11 |
12 | use PHPQueue\Exception\Exception;
13 |
14 | class REST
15 | {
16 | public static $json_payload_key = null;
17 | public static $rest_server;
18 | public static $response_content = array(
19 | 'application/json' => 'json_encode'
20 | );
21 | public $auth_class = null;
22 |
23 | public function __construct($options=array())
24 | {
25 | if ( !empty($options['auth']) ) {
26 | $auth_class = $options['auth'];
27 | if (is_string($auth_class)) {
28 | if (!(strpos($auth_class, "\\") === 0)) {
29 | $auth_class = '\\' . $auth_class;
30 | }
31 | $auth_class = new $auth_class($options);
32 | }
33 | $this->auth_class = $auth_class;
34 | }
35 | }
36 |
37 | /**
38 | * Starts a Respect/REST server with default routes:
39 | */
40 | public static function defaultRoutes($options=array())
41 | {
42 | $router = !empty($options['router']) ? $options['router'] : '\PHPQueue\REST';
43 | if (is_string($router)) {
44 | $router = new $router($options);
45 | }
46 | $response_format = !empty($options['format'])
47 | ? array_merge(self::$response_content, $options['format'])
48 | : self::$response_content;
49 |
50 | self::startServer()
51 | ->always('Accept', $response_format)
52 | ->any('/*/**', function($queue=null, $actions=array()) use ($router, $options) {
53 | return $router->route($queue, $actions, $options);
54 | });
55 | }
56 |
57 | /**
58 | * Starts the Respect/REST server
59 | * @return \Respect\Rest\Router
60 | */
61 | public static function startServer()
62 | {
63 | if (empty(self::$rest_server)) {
64 | self::$rest_server = new \Respect\Rest\Router;
65 | }
66 |
67 | return self::$rest_server;
68 | }
69 |
70 | /**
71 | * Specify how a routed URL should be handled.
72 | * @param string $queue
73 | * @param array $actions
74 | * @param array $options array('auth'=>Object)
75 | * @return stdClass
76 | */
77 | public function route($queue=null, $actions=array(), $options=array())
78 | {
79 | try {
80 | $this->isAuth();
81 | } catch (\Exception $ex) {
82 | return $this->failed(401, $ex->getMessage());
83 | }
84 |
85 | $method = !empty($_GET['REQUEST_METHOD']) ? $_GET['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD'];
86 | switch ($method) {
87 | case 'POST':
88 | return $this->post($queue);
89 | break;
90 | case 'PUT':
91 | return $this->work($queue);
92 | break;
93 | default:
94 | return $this->failed(404, "Method not supported.");
95 | break;
96 | }
97 | }
98 |
99 | protected function isAuth()
100 | {
101 | if ( !is_null($this->auth_class) ) {
102 | if ( !is_a($this->auth_class, '\PHPQueue\Interfaces\Auth') ) {
103 | throw new Exception("Invalid Auth Object.");
104 | }
105 | if (!$this->auth_class->isAuth()) {
106 | throw new Exception("Not Authorized");
107 | }
108 | }
109 |
110 | return true;
111 | }
112 |
113 | /**
114 | * Handles a POST method
115 | * @param string $queueName
116 | * @return stdClass
117 | */
118 | protected function post($queueName=null)
119 | {
120 | $payload = $this->getPayload();
121 | try {
122 | $queue = Base::getQueue($queueName);
123 | Base::addJob($queue, $payload);
124 |
125 | return $this->successful();
126 | } catch (\Exception $ex) {
127 | return $this->failed($ex->getCode(), $ex->getMessage());
128 | }
129 | }
130 |
131 | /**
132 | * @return array
133 | */
134 | protected function getPayload()
135 | {
136 | $payload = array();
137 | switch ($_SERVER['CONTENT_TYPE']) {
138 | case 'application/json':
139 | $content = file_get_contents('php://input');
140 | $payload = json_decode($content, true);
141 | if ( !empty(self::$json_payload_key) && isset($payload['data']) ) {
142 | $payload = $payload['data'];
143 | }
144 | break;
145 | default:
146 | if (!empty($_POST)) {
147 | $payload = $_POST;
148 | }
149 | break;
150 | }
151 |
152 | return $payload;
153 | }
154 |
155 | /**
156 | * Trigger a worker for a queue. Next item in the queue will be retrieved and worked with the appropriate worker.
157 | * @param string $queueName
158 | * @return stdClass
159 | */
160 | protected function work($queueName=null)
161 | {
162 | $queue = Base::getQueue($queueName);
163 | try {
164 | $newJob = Base::getJob($queue);
165 | } catch (\Exception $ex) {
166 | return $this->failed(405, $ex->getMessage());
167 | }
168 |
169 | if (empty($newJob)) {
170 | return $this->failed(404, "No Job in queue.");
171 | }
172 | try {
173 | if (empty($newJob->worker)) {
174 | throw new Exception("No worker declared.");
175 | }
176 | if (is_string($newJob->worker)) {
177 | $result_data = $this->processWorker($newJob->worker, $newJob);
178 | } elseif (is_array($newJob->worker)) {
179 | foreach ($newJob->worker as $worker_name) {
180 | $result_data = $this->processWorker($worker_name, $newJob);
181 | $newJob->data = $result_data;
182 | }
183 | }
184 | Base::updateJob($queue, $newJob->job_id, $result_data);
185 |
186 | return $this->successful();
187 | } catch (\Exception $ex) {
188 | $queue->releaseJob($newJob->job_id);
189 |
190 | return $this->failed($ex->getCode(), $ex->getMessage());
191 | }
192 | }
193 |
194 | protected function processWorker($worker_name, $new_job)
195 | {
196 | $newWorker = Base::getWorker($worker_name);
197 | Base::workJob($newWorker, $new_job);
198 |
199 | return $newWorker->result_data;
200 | }
201 |
202 | /**
203 | * Convenience method for Successful call.
204 | * @return stdClass
205 | */
206 | protected function successful()
207 | {
208 | return self::respond(null, 200, "OK");
209 | }
210 |
211 | /**
212 | * Convenience method for Failed call.
213 | * @return stdClass
214 | */
215 | protected function failed($code=501, $reason="")
216 | {
217 | return self::respond(null, $code, $reason);
218 | }
219 |
220 | /**
221 | * Convenience method for a Data call.
222 | * @return stdClass
223 | */
224 | protected function showData($data=null)
225 | {
226 | return self::respond($data, 200, "OK");
227 | }
228 |
229 | /**
230 | * Main method for generating response stdClass.
231 | * @return stdClass
232 | */
233 | protected function respond($data=null, $code=200, $message="")
234 | {
235 | return Helpers::output($data, $code, $message);
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/src/PHPQueue/Base.php:
--------------------------------------------------------------------------------
1 | getMessage(), $ex->getCode());
34 | }
35 | }
36 |
37 | return self::$all_queues[$queue];
38 | }
39 |
40 | protected static function loadAndGetQueueClassName($queue_name)
41 | {
42 | $class_name = '';
43 | if (!is_null(self::$queue_path))
44 | {
45 | $class_name = self::loadAndReturnFullClassName(self::$queue_path, $queue_name, 'Queue');
46 | }
47 | if (empty($class_name) && !is_null(self::$queue_namespace))
48 | {
49 | $class_name = self::getValidNameSpacedClassName(self::$queue_namespace, $queue_name);
50 | }
51 |
52 | if (empty($class_name))
53 | throw new QueueNotFoundException("Queue file/class does not exist: $queue_name");
54 | return $class_name;
55 | }
56 |
57 | /**
58 | * @param JobQueue $queue
59 | * @param array $newJob
60 | * @return bool
61 | * @throws \InvalidArgumentException
62 | * @throws \Exception
63 | */
64 | public static function addJob(JobQueue $queue, $newJob)
65 | {
66 | if (empty($newJob)) {
67 | throw new \InvalidArgumentException("Invalid job data.");
68 | }
69 | $status = false;
70 | try {
71 | $queue->beforeAdd($newJob);
72 | $status = $queue->addJob($newJob);
73 | $queue->afterAdd();
74 | } catch (\Exception $ex) {
75 | $queue->onError($ex);
76 | throw $ex;
77 | }
78 |
79 | return $status;
80 | }
81 |
82 | /**
83 | * @param JobQueue $queue
84 | * @param string $jobId
85 | * @return null|Job
86 | * @throws \Exception
87 | */
88 | public static function getJob(JobQueue $queue, $jobId=null)
89 | {
90 | $job = null;
91 | try {
92 | $queue->beforeGet();
93 | $job = $queue->getJob($jobId);
94 | $queue->afterGet();
95 | } catch (\Exception $ex) {
96 | $queue->onError($ex);
97 | throw $ex;
98 | }
99 |
100 | return $job;
101 | }
102 |
103 | /**
104 | * @param \PHPQueue\JobQueue $queue
105 | * @param string $jobId
106 | * @param mixed $resultData
107 | * @return bool|void
108 | * @throws \Exception
109 | */
110 | public static function updateJob(JobQueue $queue, $jobId=null, $resultData=null)
111 | {
112 | $status = false;
113 | try {
114 | $queue->beforeUpdate();
115 | $queue->updateJob($jobId, $resultData);
116 | $status = $queue->clearJob($jobId);
117 | $queue->afterUpdate();
118 | } catch (\Exception $ex) {
119 | $queue->onError($ex);
120 | $queue->releaseJob($jobId);
121 | throw $ex;
122 | }
123 |
124 | return $status;
125 | }
126 |
127 | /**
128 | * @param string $worker_name
129 | * @return \PHPQueue\Worker
130 | * @throws \PHPQueue\Exception\WorkerNotFoundException
131 | * @throws \InvalidArgumentException
132 | */
133 | public static function getWorker($worker_name)
134 | {
135 | if (empty($worker_name)) {
136 | throw new \InvalidArgumentException("Worker name is empty");
137 | }
138 | if ( empty(self::$all_workers[$worker_name]) ) {
139 | $className = self::loadAndGetWorkerClassName($worker_name);
140 | try {
141 | self::$all_workers[$worker_name] = new $className();
142 | } catch (\Exception $ex) {
143 | throw new WorkerNotFoundException($ex->getMessage(), $ex->getCode());
144 | }
145 | }
146 |
147 | return self::$all_workers[$worker_name];
148 | }
149 |
150 | protected static function loadAndGetWorkerClassName($worker_name)
151 | {
152 | $class_name = '';
153 | if (!is_null(self::$worker_path))
154 | {
155 | $class_name = self::loadAndReturnFullClassName(self::$worker_path, $worker_name, 'Worker');
156 | }
157 | if (empty($class_name) && !is_null(self::$worker_namespace))
158 | {
159 | $class_name = self::getValidNameSpacedClassName(self::$worker_namespace, $worker_name);
160 | }
161 |
162 | if (empty($class_name))
163 | throw new WorkerNotFoundException("Worker file/class does not exist: $worker_name");
164 | return $class_name;
165 | }
166 |
167 | /**
168 | * @param \PHPQueue\Worker $worker
169 | * @param \PHPQueue\Job $job
170 | * @return \PHPQueue\Worker
171 | * @throws \Exception
172 | */
173 | public static function workJob(Worker $worker, Job $job)
174 | {
175 | try {
176 | $worker->beforeJob($job->data);
177 | $worker->runJob($job);
178 | $job->onSuccessful();
179 | $worker->afterJob();
180 | $worker->onSuccess();
181 | } catch (\Exception $ex) {
182 | $worker->onError($ex);
183 | $job->onError();
184 | throw $ex;
185 | }
186 |
187 | return $worker;
188 | }
189 |
190 | /**
191 | * Factory method to instantiate a copy of a backend class.
192 | * @param string $type Case-sensitive class name.
193 | * @param array $options Constuctor options.
194 | * @return \PHPQueue\Backend\Base Instantiation of concrete subclass.
195 | * @throws \InvalidArgumentException
196 | */
197 | public static function backendFactory($type, $options=array())
198 | {
199 | $backend_classname = '\\PHPQueue\\Backend\\' . $type;
200 | $obj = new $backend_classname($options);
201 | if ($obj instanceof $backend_classname) {
202 | return $obj;
203 | } else {
204 | throw new \InvalidArgumentException("Invalid Backend object.");
205 | }
206 | }
207 |
208 | /**
209 | * @param array|string $path_prefix
210 | * @param string $org_class_name
211 | * @param string $class_suffix
212 | * @return string
213 | */
214 | private static function loadAndReturnFullClassName($path_prefix, $org_class_name, $class_suffix='')
215 | {
216 | $full_class_name = '';
217 | if (!is_array($path_prefix)) $path_prefix = array($path_prefix);
218 |
219 | foreach($path_prefix as $path)
220 | {
221 | $classFile = $path . '/' . $org_class_name . $class_suffix . '.php';
222 | if (is_file($classFile)) {
223 | require_once $classFile;
224 | return "\\" . $org_class_name . $class_suffix;
225 | }
226 | }
227 | return $full_class_name;
228 | }
229 |
230 | /**
231 | * @param array|string $namespaces
232 | * @param string $org_class_name
233 | * @return string
234 | */
235 | protected static function getValidNameSpacedClassName($namespaces, $org_class_name)
236 | {
237 | $full_class_name = '';
238 | if (!is_array($namespaces)) $namespaces = array($namespaces);
239 |
240 | foreach($namespaces as $namespace)
241 | {
242 | if (!(strpos($namespace, "\\") === 0)) {
243 | $full_class_name = "\\";
244 | }
245 | $full_class_name .= $namespace . "\\" . $org_class_name;
246 | if (class_exists($full_class_name, true)) {
247 | return $full_class_name;
248 | }
249 | }
250 | return $full_class_name;
251 | }
252 | }
253 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PHP-Queue #
2 | [](https://gitter.im/CoderKungfu/php-queue?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
3 |
4 | A unified front-end for different queuing backends. Includes a REST server, CLI interface and daemon runners.
5 |
6 | [](https://travis-ci.org/CoderKungfu/php-queue)
7 |
8 | ## Why PHP-Queue? ##
9 |
10 | Implementing a queueing system (eg. Beanstalk, Amazon SQS, RabbitMQ) for your application can be painful:
11 |
12 | * Which one is most efficient? Performant?
13 | * Learning curve to effectively implement the queue backend & the libraries.
14 | * Time taken to develop the application codes.
15 | * Vendor locked in, making it impossible to switch.
16 | * Requires massive code change (ie. not flexible) when use case for the queue changes.
17 |
18 | PHP-Queue hopes to serve as an abstract layer between your application code and the implementation of the queue.
19 |
20 | ## Benefits ##
21 |
22 | * **Job Queue is Backend agnostic**
23 |
24 | Just refer to the queue by name, what it runs on is independent of the application code. Your code just asks for the next item in the `PHPQueue\JobQueue`, and you'll get a `PHPQueue\Job` object with the `data` and `jobId`.
25 |
26 | * **Flexible Job Queue implementation**
27 |
28 | You can decide whether each `PHPQueue\JobQueue` only carries 1 type of work or multiple types of target workers. You control the retrieval of the job data from the Queue Backend and how it is instantiated as a `PHPQueue\Job` object. Each `PHPQueue\Job` object carries information on which workers it is targeted for.
29 |
30 | * **Independent Workers**
31 |
32 | Workers are independent of Job Queues. All it needs to worry about is processing the input data and return the resulting data. The queue despatcher will handle the rest. Workers will also be chainable.
33 |
34 | * **Powerful**
35 |
36 | The framework is deliberately open-ended and can be adapted to your implementation. It doesn't get in the way of your queue system.
37 |
38 | We've build a simple REST server to let you post job data to your queue easily. We also included a CLI interface for adding and triggering workers. All of which you can sub-class and overwrite.
39 |
40 | You can also include our core library files into your application and do some powerful heavy lifting.
41 |
42 | Several backend drivers are bundled:
43 | * Memcache
44 | * Redis
45 | * MongoDB
46 | * CSV
47 | These can be used as the primary job queue server, or for abstract FIFO or key-value data access.
48 |
49 | ---
50 | ## Installation ##
51 |
52 | **Installing via Composer**
53 |
54 | [Composer](http://getcomposer.org) is a dependency management tool for PHP that allows you to declare the dependencies your project needs and installs them into your project. In order to use the [**PHP-Queue**](https://packagist.org/packages/coderkungfu/php-queue) through Composer, you must do the following:
55 |
56 | 1. Add `"coderkungfu/php-queue"` as a dependency in your project's `composer.json` file. Visit the [Packagist](https://packagist.org/packages/coderkungfu/php-queue) page for more details.
57 |
58 | 2. Download and install Composer.
59 |
60 | ```
61 | curl -s "http://getcomposer.org/installer" | php
62 | ```
63 | 3. Install your dependencies.
64 |
65 | ```
66 | php composer.phar install
67 | ```
68 |
69 | 4. All the dependencies should be downloaded into a `vendor` folder.
70 |
71 | 5. Require Composer's autoloader.
72 |
73 | ```php
74 |
77 | ```
78 |
79 | ## Getting Started ##
80 |
81 | You can have a look at the **Demo App** inside `.\vendor\coderkungfu\php-queue\src\demo\` folder for a recommended folder structure.
82 |
83 | * `htdocs` folder
84 | * .htaccess
85 | * index.php
86 | * `queues` folder
87 | * \Queue.php
88 | * `workers` folder
89 | * \Worker.php
90 | * `runners` folder
91 | * `cli.php` file
92 | * `config.php` file
93 |
94 | I would also recommend putting the autoloader statement and your app configs inside a separate `config.php` file.
95 |
96 | **Recommended `config.php` file content:**
97 |
98 | ```php
99 |
104 | ```
105 | **Altenative `config.php` file:**
106 |
107 | You can also declare your application's namespace for loading the Queues and Workers.
108 |
109 | ```php
110 |
115 | ```
116 | PHP-Queue will attempt to instantiate the `PHPQueue\JobQueue` and `PHPQueue\Worker` classes using your namespace - appended with the queue/worker name. (ie. `\MyFabulousApp\Queues\Facebook`).
117 |
118 | It might be advisable to use [Composer's Custom Autoloader](http://getcomposer.org/doc/01-basic-usage.md#autoloading) for this.
119 |
120 | **Note:**
121 | *If you declared `PHPQueue\Base::$queue_path` and/or `PHPQueue\Base::$worker_path` together with the namespace, the files will be loaded with `require_once` from those folder path __AND__ instantiated with the namespaced class names.*
122 |
123 | ## REST Server ##
124 |
125 | The default REST server can be used to interface directly with the queues and workers.
126 |
127 | Copy the `htdocs` folder in the **Demo App** into your installation. The `index.php` calls the `\PHPQueue\REST::defaultRoutes()` method - which prepares an instance of the `Respect\Rest` REST server. You might need to modify the path of `config.php` within the `index.php` file.
128 |
129 | **Recomended installation:** _use a new virtual host and map the `htdocs` as the webroot._
130 |
131 | 1. Add new job.
132 |
133 | ```
134 | # Form post
135 | curl -XPOST http://localhost// -d "var1=foo&var2=bar"
136 | ```
137 |
138 | ```
139 | # JSON post
140 | curl -XPOST http://localhost// -H "Content-Type: application/json" -d '{"var1":"foo","var2":"bar"}'
141 | ```
142 |
143 | 2. Trigger next job.
144 |
145 | ```
146 | curl -XPUT http://localhost//
147 | ```
148 |
149 | Read the [full documentation](https://github.com/Respect/Rest) on `Respect\Rest` to further customize to your application needs (eg. Basic Auth).
150 |
151 | ## Command Line Interface (CLI) ##
152 |
153 | Copy the `cli.php` file from the **Demo App** into your installation. This file implements the `\PHPQueue\Cli` class. You might need to modify the path of `config.php` within the `cli.php` file.
154 |
155 | 1. Add new job.
156 |
157 | ```
158 | $ php cli.php add --data '{"boo":"bar","foo":"car"}'
159 | ```
160 |
161 | 2. Trigger next job.
162 |
163 | ```
164 | $ php cli.php work
165 | ```
166 |
167 | You can extend the `PHPQueue\Cli` class to customize your own CLI batch jobs (eg. import data from a MySQL DB into a queue).
168 |
169 | ## Runners ##
170 |
171 | You can read more about the [Runners here](https://github.com/CoderKungfu/php-queue/blob/master/demo/runners/README.md).
172 |
173 | ## Interfaces ##
174 |
175 | The queue backends will support one or more of these interfaces:
176 |
177 | * AtomicReadBuffer
178 |
179 | This is the recommended way to consume messages. AtomicReadBuffer provides the
180 | popAtomic($callback) interface, which rolls back the popped record if the
181 | callback returns by exception. For example:
182 | $queue = new PHPQueue\Backend\PDO($options);
183 |
184 | $queue->popAtomic(function ($message) use ($processor) {
185 | $processor->churn($message);
186 | });
187 |
188 | The message will only be popped if churn() returns successfully.
189 |
190 | * FifoQueueStore
191 |
192 | A first in first out queue accessed by push and pop.
193 |
194 | ---
195 | ## License ##
196 |
197 | This software is released under the MIT License.
198 |
199 | Copyright (C) 2012 Michael Cheng Chi Mun
200 |
201 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
202 |
203 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
204 |
205 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
206 |
--------------------------------------------------------------------------------
/src/PHPQueue/Backend/PDO.php:
--------------------------------------------------------------------------------
1 | connection_string = $options['connection_string'];
24 | }
25 | if (!empty($options['db_user'])) {
26 | $this->db_user = $options['db_user'];
27 | }
28 | if (!empty($options['db_password'])) {
29 | $this->db_password = $options['db_password'];
30 | }
31 | if (!empty($options['queue'])) {
32 | $this->db_table = $options['queue'];
33 | }
34 | if (!empty($options['db_table'])) {
35 | $this->db_table = $options['db_table'];
36 | }
37 | if (!empty($options['pdo_options']) && is_array($options['pdo_options'])) {
38 | // Use + operator instead of array_merge to preserve integer keys
39 | $this->pdo_options = $options['pdo_options'] + $this->pdo_options;
40 | }
41 | }
42 |
43 | public function setTable($table_name=null)
44 | {
45 | if (empty($table_name)) {
46 | throw new BackendException('Invalid table name.');
47 | }
48 | $this->db_table = $table_name;
49 |
50 | return true;
51 | }
52 |
53 | public function connect()
54 | {
55 | $this->connection = new \PDO($this->connection_string, $this->db_user, $this->db_password, $this->pdo_options);
56 | }
57 |
58 | /**
59 | * @deprecated See push() instead.
60 | */
61 | public function add($data = null)
62 | {
63 | if (empty($data)) {
64 | throw new BackendException('No data.');
65 | }
66 | $this->push($data);
67 | return true;
68 | }
69 |
70 | public function push($data)
71 | {
72 | try {
73 | $success = $this->insert($data);
74 | if (!$success) {
75 | throw new BackendException(
76 | 'Statement failed: ' .
77 | implode(' - ', $this->getConnection()->errorInfo())
78 | );
79 | }
80 | } catch (\Exception $ex) {
81 | // TODO: Log original error and table creation attempt.
82 | $this->createTable($this->db_table);
83 |
84 | // Try again.
85 | if (!$this->insert($data)) {
86 | throw new BackendException('Statement failed: ' .
87 | implode(' - ', $this->getConnection()->errorInfo())
88 | );
89 | }
90 | }
91 |
92 | $this->last_job_id = $this->getConnection()->lastInsertId();
93 | return $this->last_job_id;
94 | }
95 |
96 | protected function insert($data)
97 | {
98 | $sql = sprintf('INSERT INTO `%s` (`data`, `timestamp`) VALUES (?, ?)', $this->db_table);
99 | $sth = $this->getConnection()->prepare($sql);
100 | if ($sth === false) {
101 | throw new \Exception('Could not prepare statement');
102 | }
103 | $_tmp = json_encode($data);
104 | $sth->bindValue(1, $_tmp, \PDO::PARAM_STR);
105 | $sth->bindValue(2, self::getTimeStamp(), \PDO::PARAM_STR);
106 | return $sth->execute();
107 | }
108 |
109 | public function set($id, $data, $properties=array())
110 | {
111 | $sql = sprintf('REPLACE INTO `%s` (`id`, `data`, `timestamp`) VALUES (?, ?, ?)', $this->db_table);
112 | $sth = $this->getConnection()->prepare($sql);
113 | $_tmp = json_encode($data);
114 | $sth->bindValue(1, $id, \PDO::PARAM_INT);
115 | $sth->bindValue(2, $_tmp, \PDO::PARAM_STR);
116 | $sth->bindValue(3, self::getTimeStamp(), \PDO::PARAM_STR);
117 | $sth->execute();
118 | }
119 |
120 | protected static function getTimeStamp()
121 | {
122 | $now = new \DateTime('now', new \DateTimeZone('UTC'));
123 | return $now->format('Y-m-d\TH:i:s.u');
124 | }
125 | /**
126 | * @return array|null The retrieved record, or null if nothing was found.
127 | */
128 | public function get($id=null)
129 | {
130 | if (empty($id)) {
131 | // Deprecated usage.
132 | return $this->pop();
133 | }
134 |
135 | $sql = sprintf('SELECT `id`, `data` FROM `%s` WHERE `id` = ?', $this->db_table);
136 | $sth = $this->getConnection()->prepare($sql);
137 | $sth->bindValue(1, $id, \PDO::PARAM_INT);
138 | $sth->execute();
139 |
140 | $result = $sth->fetch(\PDO::FETCH_ASSOC);
141 | if (!empty($result)) {
142 | $this->last_job_id = $result['id'];
143 | return json_decode($result['data'], true);
144 | }
145 |
146 | return null;
147 | }
148 |
149 | public function pop()
150 | {
151 | // Get oldest message.
152 | $sql = sprintf('SELECT `id`, `data` FROM `%s` WHERE 1 ORDER BY id ASC LIMIT 1', $this->db_table);
153 | $sth = $this->getConnection()->prepare($sql);
154 |
155 | // This will be false if the table or collection does not exist
156 | if ( ! $sth ) {
157 | return null;
158 | }
159 |
160 | $sth->execute();
161 |
162 | $result = $sth->fetch(\PDO::FETCH_ASSOC);
163 | if ($result) {
164 | $this->last_job_id = $result['id'];
165 | $this->clear($result['id']);
166 | return json_decode($result['data'], true);
167 | }
168 | return null;
169 | }
170 |
171 | public function popAtomic($callback) {
172 | try {
173 | $this->getConnection()->beginTransaction();
174 | $data = $this->pop();
175 |
176 | if ($data !== null) {
177 | if (!is_callable($callback)) {
178 | throw new \RuntimeException("Bad callback passed to " . __METHOD__);
179 | }
180 | call_user_func($callback, $data);
181 | }
182 |
183 | $this->getConnection()->commit();
184 | return $data;
185 | } catch (\Exception $ex) {
186 | $this->getConnection()->rollBack();
187 | throw $ex;
188 | }
189 | }
190 |
191 | public function clear($id = null)
192 | {
193 | if (empty($id)) {
194 | throw new BackendException('No ID.');
195 | }
196 | try {
197 | $sql = sprintf('DELETE FROM `%s` WHERE `id` = ?', $this->db_table);
198 | $sth = $this->getConnection()->prepare($sql);
199 | $sth->bindValue(1, $id, \PDO::PARAM_INT);
200 | $sth->execute();
201 | } catch (\Exception $ex) {
202 | throw new BackendException('Invalid ID.');
203 | }
204 |
205 | return true;
206 | }
207 |
208 | public function clearAll()
209 | {
210 | if (empty($this->db_table)) {
211 | throw new BackendException('Invalid table name.');
212 | }
213 | $sql = sprintf('TRUNCATE `%s`', $this->db_table);
214 | $this->getConnection()->exec($sql);
215 |
216 | return true;
217 | }
218 |
219 | public function createTable($table_name)
220 | {
221 | if (empty($table_name)) {
222 | throw new BackendException('Invalid table name.');
223 | }
224 | switch ($this->getDriverName()) {
225 | case 'mysql':
226 | $sql = sprintf("CREATE TABLE IF NOT EXISTS `%s` (
227 | `id` mediumint(20) NOT NULL AUTO_INCREMENT,
228 | `data` mediumtext NULL,
229 | `timestamp` datetime NOT NULL,
230 | PRIMARY KEY (`id`)
231 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;", $table_name);
232 | break;
233 | case 'sqlite':
234 | $sql = sprintf("CREATE TABLE IF NOT EXISTS `%s` (
235 | `id` INTEGER PRIMARY KEY,
236 | `data` text NULL,
237 | `timestamp` datetime NOT NULL
238 | );", $table_name);
239 | break;
240 | default:
241 | throw new BackendException('Unknown database driver: ' . $this->getDriverName());
242 | }
243 | $this->getConnection()->exec($sql);
244 |
245 | // FIXME: we already signal failure using exceptions, should return void.
246 | return true;
247 | }
248 |
249 | protected function getDriverName()
250 | {
251 | return $this->getConnection()->getAttribute(\PDO::ATTR_DRIVER_NAME);
252 | }
253 |
254 | public function deleteTable($table_name)
255 | {
256 | if (empty($table_name)) {
257 | throw new BackendException('Invalid table name.');
258 | }
259 | $sql = sprintf("DROP TABLE IF EXISTS `%s` ;", $table_name);
260 | $this->getConnection()->exec($sql);
261 |
262 | return true;
263 | }
264 |
265 | /**
266 | * @return \PDO
267 | */
268 | public function getConnection()
269 | {
270 | return parent::getConnection();
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/src/PHPQueue/Backend/Predis.php:
--------------------------------------------------------------------------------
1 | servers = $options['servers'];
40 | }
41 | if (!empty($options['redis_options']) && is_array($options['redis_options'])) {
42 | $this->redis_options = array_merge($this->redis_options, $options['redis_options']);
43 | }
44 | if (!empty($options['queue'])) {
45 | $this->queue_name = $options['queue'];
46 | }
47 | if (!empty($options['expiry'])) {
48 | $this->expiry = $options['expiry'];
49 | }
50 | }
51 |
52 | public function connect()
53 | {
54 | if (!$this->servers) {
55 | throw new BackendException("No servers specified");
56 | }
57 | $this->connection = new \Predis\Client($this->servers, $this->redis_options);
58 | }
59 |
60 | /** @deprecated */
61 | public function add($data=array())
62 | {
63 | if (!$data) {
64 | throw new BackendException("No data.");
65 | }
66 | $this->push($data);
67 | return true;
68 | }
69 |
70 | public function push($data)
71 | {
72 | $this->beforeAdd();
73 | if (!$this->hasQueue()) {
74 | throw new BackendException("No queue specified.");
75 | }
76 | $encoded_data = json_encode($data);
77 |
78 | // Note that we're ignoring the "new length" return value, cos I don't
79 | // see how to make it useful.
80 | $this->getConnection()->rpush($this->queue_name, $encoded_data);
81 | }
82 |
83 | /**
84 | * @return array|null
85 | */
86 | public function pop()
87 | {
88 | $data = null;
89 | $this->beforeGet();
90 | if (!$this->hasQueue()) {
91 | throw new BackendException("No queue specified.");
92 | }
93 | $data = $this->getConnection()->lpop($this->queue_name);
94 | if (!$data) {
95 | return null;
96 | }
97 | $this->last_job = $data;
98 | $this->last_job_id = time();
99 | $this->afterGet();
100 |
101 | return Json::safe_decode($data);
102 | }
103 |
104 | public function popAtomic($callback) {
105 | if (!$this->hasQueue()) {
106 | throw new BackendException("No queue specified.");
107 | }
108 |
109 | // Pop and process the first element, erring on the side of
110 | // at-least-once processing where the callback might get the same
111 | // element before it's popped in the case of a race.
112 | $options = array(
113 | 'cas' => true,
114 | 'watch' => $this->queue_name,
115 | 'retry' => 3,
116 | );
117 | $data = null;
118 | $self = $this;
119 | $this->getConnection()->transaction($options, function ($tx) use (&$data, $callback, $self) {
120 | // Begin transaction.
121 | $tx->multi();
122 |
123 | $data = $tx->lpop($self->queue_name);
124 | $data = Json::safe_decode($data);
125 | if ($data !== null) {
126 | call_user_func($callback, $data);
127 | }
128 | });
129 | return $data;
130 | }
131 |
132 | /**
133 | * Return the top element in the queue.
134 | *
135 | * @return array|null
136 | */
137 | public function peek()
138 | {
139 | $data = null;
140 | $this->beforeGet();
141 | if (!$this->hasQueue()) {
142 | throw new BackendException("No queue specified.");
143 | }
144 | $data_range = $this->getConnection()->lrange($this->queue_name, 0, 0);
145 | if (!$data_range) {
146 | return null;
147 | } else {
148 | // Unpack list.
149 | $data = $data_range[0];
150 | }
151 | if (!$data) {
152 | return null;
153 | }
154 | $this->last_job = $data;
155 | $this->last_job_id = time();
156 | $this->afterGet();
157 |
158 | return Json::safe_decode($data);
159 | }
160 |
161 | public function release($jobId=null)
162 | {
163 | $this->beforeRelease($jobId);
164 | if (!$this->hasQueue()) {
165 | throw new BackendException("No queue specified.");
166 | }
167 | $job_data = $this->open_items[$jobId];
168 | $status = $this->getConnection()->rpush($this->queue_name, $job_data);
169 | if (!is_int($status)) {
170 | throw new BackendException('Unable to save data: ' . $status->getMessage());
171 | }
172 | $this->last_job_id = $jobId;
173 | $this->afterClearRelease();
174 | }
175 |
176 | /** @deprecated */
177 | public function setKey($key=null, $data=null)
178 | {
179 | $this->set($key, $data);
180 | return true;
181 | }
182 |
183 | /**
184 | * @param string $key
185 | * @param array|string $data
186 | * @param array $properties
187 | * @throws \PHPQueue\Exception
188 | */
189 | public function set($key, $data, $properties=array())
190 | {
191 | if (!$key || !is_string($key)) {
192 | throw new BackendException("Key is invalid.");
193 | }
194 | if (!$data) {
195 | throw new BackendException("No data.");
196 | }
197 | $this->beforeAdd();
198 | try {
199 | $status = false;
200 | if (is_array($data)) {
201 | // FIXME: Assert
202 | $status = $this->getConnection()->hmset($key, $data);
203 | } elseif (is_string($data) || is_numeric($data)) {
204 | if ($this->expiry) {
205 | $status = $this->getConnection()->setex($key, $this->expiry, $data);
206 | } else {
207 | $status = $this->getConnection()->set($key, $data);
208 | }
209 | }
210 | if (!self::boolStatus($status)) {
211 | throw new BackendException('Unable to save data.: ' . $status->getMessage());
212 | }
213 | } catch (\Exception $ex) {
214 | throw new BackendException($ex->getMessage(), $ex->getCode());
215 | }
216 | }
217 |
218 | /** @deprecated */
219 | public function getKey($key=null)
220 | {
221 | return $this->get($key);
222 | }
223 |
224 | /**
225 | * @param string $key
226 | * @return mixed
227 | * @throws \Exception
228 | */
229 | public function get($key=null)
230 | {
231 | if (!$key) {
232 | // Deprecated usage.
233 | return $this->pop();
234 | }
235 | if (!$this->keyExists($key)) {
236 | return null;
237 | }
238 | $this->beforeGet($key);
239 | $type = $this->getConnection()->type($key);
240 | switch ($type) {
241 | case self::TYPE_STRING:
242 | $data = $this->getConnection()->get($key);
243 | break;
244 | case self::TYPE_HASH:
245 | if (func_num_args() > 2) {
246 | $field = func_get_arg(2);
247 | $data = $this->getConnection()->hmget($key, $field);
248 | } else {
249 | $data = $this->getConnection()->hgetall($key);
250 | }
251 | break;
252 | case self::TYPE_NONE:
253 | return null;
254 | default:
255 | throw new BackendException(sprintf("Data type (%s) not supported yet.", $type));
256 | break;
257 | }
258 |
259 | return $data;
260 | }
261 |
262 | /**
263 | * @deprecated
264 | */
265 | public function clearKey($key=null)
266 | {
267 | return $this->clear($key);
268 | }
269 |
270 | public function clear($key)
271 | {
272 | $this->beforeClear($key);
273 |
274 | $num_removed = $this->getConnection()->del($key);
275 |
276 | $this->afterClearRelease();
277 |
278 | return $num_removed > 0;
279 | }
280 |
281 | public function incrKey($key, $count=1)
282 | {
283 | if (!$this->keyExists($key)) {
284 | return false;
285 | }
286 | if ($count === 1) {
287 | $status = $this->getConnection()->incr($key);
288 | } else {
289 | $status = $this->getConnection()->incrby($key, $count);
290 | }
291 |
292 | return is_int($status);
293 | }
294 |
295 | public function decrKey($key, $count=1)
296 | {
297 | if (!$this->keyExists($key)) {
298 | return false;
299 | }
300 | if ($count === 1) {
301 | $status = $this->getConnection()->decr($key);
302 | } else {
303 | $status = $this->getConnection()->decrby($key, $count);
304 | }
305 |
306 | return is_int($status);
307 | }
308 |
309 | public function keyExists($key)
310 | {
311 | $this->beforeGet();
312 | return $this->getConnection()->exists($key);
313 | }
314 |
315 | public function hasQueue()
316 | {
317 | return !empty($this->queue_name);
318 | }
319 |
320 | protected static function boolStatus(ResponseInterface $status) {
321 | return ($status == 'OK' || $status == 'QUEUED');
322 | }
323 | }
324 |
--------------------------------------------------------------------------------
/src/PHPQueue/Backend/Aws/AmazonS3V1.php:
--------------------------------------------------------------------------------
1 | region = $options['region'];
24 | }
25 | if (!empty($options['region_website'])) {
26 | $this->region_website = $options['region_website'];
27 | }
28 | if (!empty($options['bucket'])) {
29 | $this->container = $options['bucket'];
30 | }
31 | if (!empty($options['s3_options']) && is_array($options['s3_options'])) {
32 | $this->s3_options = array_merge($this->s3_options, $options['s3_options']);
33 | }
34 | }
35 |
36 | /**
37 | * @return \AmazonS3
38 | */
39 | public function getConnection()
40 | {
41 | return parent::getConnection();
42 | }
43 |
44 | public function connect()
45 | {
46 | $this->connection = new \AmazonS3($this->s3_options);
47 | $this->connection->set_region($this->region);
48 | }
49 |
50 | /**
51 | * @param string $key
52 | * @return bool
53 | * @throws \PHPQueue\Exception\BackendException
54 | */
55 | public function clear($key = null)
56 | {
57 | if (empty($key)) {
58 | throw new BackendException('Invalid filename: ' . $key);
59 | }
60 | if (!$this->getConnection()->if_object_exists($this->container, $key)) {
61 | throw new BackendException('File not found: ' . $key);
62 | }
63 | $response = $this->getConnection()->delete_object($this->container, $key);
64 | if (!$response->isOk()) {
65 | $error = $response->body->Error;
66 | throw new BackendException((string) $error->Message, (int) $error->Code);
67 | }
68 |
69 | return true;
70 | }
71 |
72 | /**
73 | * @param string $container_name
74 | * @return bool
75 | * @throws \PHPQueue\Exception\BackendException
76 | */
77 | public function createContainer($container_name)
78 | {
79 | if (empty($container_name)) {
80 | throw new BackendException('Invalid Bucket name: ' . $container_name);
81 | }
82 | if ($this->getConnection()->if_bucket_exists($container_name)) {
83 | return true;
84 | }
85 | $response = $this->getConnection()->create_bucket($container_name, $this->region, $this->bucket_privacy);
86 | if (!$response->isOk()) {
87 | $error = $response->body->Error;
88 | throw new BackendException((string) $error->Message, (int) $error->Code);
89 | }
90 |
91 | return true;
92 | }
93 |
94 | /**
95 | * @param string $container_name
96 | * @return bool
97 | * @throws \PHPQueue\Exception\BackendException
98 | */
99 | public function deleteContainer($container_name)
100 | {
101 | if (empty($container_name)) {
102 | throw new BackendException('Invalid Bucket name: ' . $container_name);
103 | }
104 | if (!$this->getConnection()->if_bucket_exists($container_name)) {
105 | return true;
106 | }
107 | $response = $this->getConnection()->delete_bucket($container_name);
108 | if (!$response->isOk()) {
109 | $error = $response->body->Error;
110 | throw new BackendException((string) $error->Message, (int) $error->Code);
111 | }
112 |
113 | return true;
114 | }
115 |
116 | /**
117 | * @return array
118 | * @throws \PHPQueue\Exception\BackendException
119 | */
120 | public function listContainers()
121 | {
122 | $response = $this->getConnection()->list_buckets();
123 | if (!$response->isOk()) {
124 | $error = $response->body->Error;
125 | throw new BackendException((string) $error->Message, (int) $error->Code);
126 | }
127 | $all_containers = array();
128 | foreach ($response->body->Buckets->Bucket as $container) {
129 | $container_name = (string) $container->Name;
130 | $all_containers[] = array(
131 | 'name' => $container_name
132 | , 'url' => $this->getBucketWebsiteURL($container_name)
133 | , 'object' => $container
134 | );
135 | }
136 |
137 | return $all_containers;
138 | }
139 |
140 | /**
141 | * @return array
142 | * @throws \PHPQueue\Exception\BackendException
143 | */
144 | public function listFiles()
145 | {
146 | if (empty($this->container)) {
147 | throw new BackendException('No bucket specified.');
148 | }
149 | if (!$this->getConnection()->if_bucket_exists($this->container)) {
150 | throw new BackendException('Bucket does not exist: ' . $this->container);
151 | }
152 | $response = $this->getConnection()->get_object_list($this->container);
153 |
154 | $all_files = array();
155 | foreach ($response as $file) {
156 | $url = $this->getBucketWebsiteURL($this->container);
157 | $file_url = !empty($url) ? $url . '/' . $file : null;
158 | $all_files[] = array(
159 | 'name' => $file
160 | , 'url' => $file_url
161 | , 'object' => $file
162 | );
163 | }
164 |
165 | return $all_files;
166 | }
167 |
168 | /**
169 | * @param string $src_container
170 | * @param string $src_file
171 | * @param string $dest_container
172 | * @param string $dest_file
173 | * @return bool
174 | * @throws \PHPQueue\Exception\BackendException
175 | */
176 | public function copy($src_container, $src_file, $dest_container, $dest_file)
177 | {
178 | $src_array = array(
179 | 'bucket' => $src_container
180 | , 'filename' => $src_file
181 | );
182 | $dest_array = array(
183 | 'bucket' => $dest_container
184 | , 'filename' => $dest_file
185 | );
186 | if (!$this->getConnection()->if_bucket_exists($src_container)) {
187 | throw new BackendException('Bucket does not exist: ' . $src_container);
188 | }
189 | if (!$this->getConnection()->if_bucket_exists($dest_container)) {
190 | throw new BackendException('Bucket does not exist: ' . $dest_container);
191 | }
192 | if (!$this->getConnection()->if_object_exists($src_container, $src_file)) {
193 | throw new BackendException(sprintf('File does not exist in bucket (%s): %s', $src_container, $src_file));
194 | }
195 |
196 | $response = $this->getConnection()->copy_object($src_array, $dest_array);
197 | if (!$response->isOk()) {
198 | $error = $response->body->Error;
199 | throw new BackendException((string) $error->Message, (int) $error->Code);
200 | }
201 |
202 | return true;
203 | }
204 |
205 | /**
206 | * @param string $key
207 | * @param string $file_path
208 | * @param array $options
209 | * @return bool
210 | * @throws \PHPQueue\Exception\BackendException
211 | */
212 | public function putFile($key, $file_path = null, $options = array())
213 | {
214 | if (empty($key)) {
215 | throw new BackendException('Invalid filename: ' . $key);
216 | }
217 | if (!is_file($file_path)) {
218 | throw new BackendException('Upload file not found: ' . $file_path);
219 | }
220 | if (is_array($options)) {
221 | $options = array_merge($options, array('fileUpload'=>$file_path));
222 | } else {
223 | $options = array('fileUpload'=>$file_path);
224 | }
225 | $response = $this->getConnection()->create_object($this->container, $key, $options);
226 | if (!$response->isOk()) {
227 | $error = $response->body->Error;
228 | throw new BackendException((string) $error->Message, (int) $error->Code);
229 | }
230 |
231 | return true;
232 | }
233 |
234 | /**
235 | * @param string $key
236 | * @param string $destination_path
237 | * @param array $options
238 | * @return bool
239 | * @throws \PHPQueue\Exception\BackendException
240 | */
241 | public function fetchFile($key, $destination_path = null, $options = array())
242 | {
243 | if (empty($key)) {
244 | throw new BackendException('Invalid filename: ' . $key);
245 | }
246 | if (!is_writable($destination_path)) {
247 | throw new BackendException('Destination path is not writable: '.$destination_path);
248 | }
249 | $destination_file_path = $destination_path . DIRECTORY_SEPARATOR . $key;
250 | if (is_array($options)) {
251 | $options = array_merge($options, array('fileDownload'=>$destination_file_path));
252 | } else {
253 | $options = array('fileDownload'=>$destination_file_path);
254 | }
255 | $response = $this->getConnection()->get_object($this->container, $key, $options);
256 | if (!$response->isOk()) {
257 | $error = $response->body->Error;
258 | throw new BackendException((string) $error->Message, (int) $error->Code);
259 | }
260 |
261 | return true;
262 | }
263 |
264 | /**
265 | * @param $container
266 | * @return string
267 | * @throws \PHPQueue\Exception\BackendException
268 | */
269 | public function getBucketWebsiteURL($container)
270 | {
271 | if (empty($container)) {
272 | throw new BackendException('No bucket specified.');
273 | }
274 | if (empty($this->bucket_websites[$container])) {
275 | $response = $this->getConnection()->get_website_config($container);
276 | $website_url = ($response->isOk())
277 | ? sprintf('https://%s.%s', $container, $this->region_website)
278 | : null;
279 | $this->bucket_websites[$container] = $website_url;
280 | }
281 |
282 | return $this->bucket_websites[$container];
283 | }
284 |
285 | /**
286 | * @param $region string
287 | * @param $region_website string
288 | * @return bool
289 | */
290 | public function setRegion($region, $region_website)
291 | {
292 | $this->region = $region;
293 | $this->region_website = $region_website;
294 |
295 | return true;
296 | }
297 | }
298 |
--------------------------------------------------------------------------------