├── 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 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](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 | [![Build Status](https://secure.travis-ci.org/CoderKungfu/php-queue.png?branch=master)](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 | --------------------------------------------------------------------------------