Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 478 lines (423 sloc) 15.106 kB
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
1 <?php
b5816c7 @thousandsofthem Timeout bug fix. Expires_in bug fix. Readme updated. Phpdoc banners.
thousandsofthem authored
2 /**
3 * PHP client for IronMQ
4 * IronMQ is a scalable, reliable, high performance message queue in the cloud.
5 *
6 * @link https://github.com/iron-io/iron_mq_php
7 * @link http://www.iron.io/products/mq
a105c20 @paddyforan Updated to default to HTTPS. Fixes #1.
paddyforan authored
8 * @link http://dev.iron.io/
b5816c7 @thousandsofthem Timeout bug fix. Expires_in bug fix. Readme updated. Phpdoc banners.
thousandsofthem authored
9 * @version 1.0
10 * @package IronMQPHP
11 * @copyright Feel free to copy, steal, take credit for, or whatever you feel like doing with this code. ;)
12 */
13
14 /**
15 * The Http_Exception class represents an HTTP response status that is not 200 OK.
16 */
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
17 class Http_Exception extends Exception{
18 const NOT_MODIFIED = 304;
19 const BAD_REQUEST = 400;
20 const NOT_FOUND = 404;
21 const NOT_ALOWED = 405;
22 const CONFLICT = 409;
23 const PRECONDITION_FAILED = 412;
24 const INTERNAL_ERROR = 500;
59caceb @thousandsofthem Fixes #3. Handle only 503 errors.
thousandsofthem authored
25 const SERVICE_UNAVAILABLE = 503;
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
26 }
1c0e04b @paddyforan Better JSON error handling.
paddyforan authored
27
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
28 class IronMQ_Exception extends Exception{
29
30 }
31
b5816c7 @thousandsofthem Timeout bug fix. Expires_in bug fix. Readme updated. Phpdoc banners.
thousandsofthem authored
32 /**
33 * The JSON_Exception class represents an failures of decoding json strings.
34 */
1c0e04b @paddyforan Better JSON error handling.
paddyforan authored
35 class JSON_Exception extends Exception {
36 public $error = null;
37 public $error_code = JSON_ERROR_NONE;
38
39 function __construct($error_code) {
40 $this->error_code = $error_code;
41 switch($error_code) {
42 case JSON_ERROR_DEPTH:
43 $this->error = 'Maximum stack depth exceeded.';
44 break;
45 case JSON_ERROR_CTRL_CHAR:
46 $this->error = "Unexpected control characted found.";
47 break;
48 case JSON_ERROR_SYNTAX:
49 $this->error = "Syntax error, malformed JSON";
50 break;
51 }
52 parent::__construct();
53 }
54
55 function __toString() {
56 return $this->error;
57 }
58 }
59
60
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
61 class IronMQ_Message {
62 private $body;
63 private $timeout;
64 private $delay;
65 private $expires_in;
66
67 const max_expires_in = 2592000;
68
69 /**
b5816c7 @thousandsofthem Timeout bug fix. Expires_in bug fix. Readme updated. Phpdoc banners.
thousandsofthem authored
70 * Create a new message.
71 *
5190513 @paddyforan Updated message posting to use a better API.
paddyforan authored
72 * @param array|string $message
73 * An array of message properties or a string of the message body.
74 * Fields in message array:
75 * Required:
76 * - body: The message data, as a string.
77 * Optional:
78 * - timeout: Timeout, in seconds. After timeout, item will be placed back on queue. Defaults to 60.
79 * - delay: The item will not be available on the queue until this many seconds have passed. Defaults to 0.
80 * - expires_in: How long, in seconds, to keep the item on the queue before it is deleted. Defaults to 604800 (7 days). Maximum is 2592000 (30 days).
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
81 */
5190513 @paddyforan Updated message posting to use a better API.
paddyforan authored
82 function __construct($message) {
0bca771 @paddyforan Fixed bug with supplying just a message body.
paddyforan authored
83 if(is_string($message)) {
84 $this->setBody($message);
85 } elseif(is_array($message)) {
86 $this->setBody($message['body']);
87 if(array_key_exists("timeout", $message)) {
88 $this->setTimeout($message['timeout']);
89 }
90 if(array_key_exists("delay", $message)) {
91 $this->setDelay($message['delay']);
92 }
93 if(array_key_exists("expires_in", $message)) {
94 $this->setExpiresIn($message['expires_in']);
95 }
5190513 @paddyforan Updated message posting to use a better API.
paddyforan authored
96 }
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
97 }
98
99 public function getBody() {
100 return $this->body;
101 }
102
103 public function setBody($body) {
104 if(empty($body)) {
105 throw new InvalidArgumentException("Please specify a body");
106 } else {
107 $this->body = $body;
108 }
109 }
110
111 public function getTimeout() {
b5816c7 @thousandsofthem Timeout bug fix. Expires_in bug fix. Readme updated. Phpdoc banners.
thousandsofthem authored
112 if(!empty($this->timeout) || $this->timeout === 0) {# 0 is considered empty, but we want people to be able to set a timeout of 0
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
113 return $this->timeout;
114 } else {
115 return null;
116 }
117 }
118
119 public function setTimeout($timeout) {
120 $this->timeout = $timeout;
121 }
122
123 public function getDelay() {
124 if(!empty($this->delay) || $this->delay == 0) {# 0 is considered empty, but we want people to be able to set a delay of 0
125 return $this->delay;
126 } else {
127 return null;
128 }
129 }
130
131 public function setDelay($delay) {
132 $this->delay = $delay;
133 }
134
135 public function getExpiresIn() {
136 return $this->expires_in;
137 }
138
139 public function setExpiresIn($expires_in) {
b5816c7 @thousandsofthem Timeout bug fix. Expires_in bug fix. Readme updated. Phpdoc banners.
thousandsofthem authored
140 if($expires_in > self::max_expires_in) {
141 throw new InvalidArgumentException("Expires In can't be greater than ".self::max_expires_in.".");
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
142 } else {
143 $this->expires_in = $expires_in;
144 }
145 }
146
147 public function asArray() {
148 $array = array();
149 $array['body'] = $this->getBody();
150 if($this->getTimeout() != null) {
151 $array['timeout'] = $this->getTimeout();
152 }
153 if($this->getDelay() != null) {
154 $array['delay'] = $this->getDelay();
155 }
156 if($this->getExpiresIn() != null) {
157 $array['expires_in'] = $this->getExpiresIn();
158 }
159 return $array;
160 }
161 }
162
163 class IronMQ{
164
165 //Header Constants
166 const header_user_agent = "IronMQ PHP v0.1";
167 const header_accept = "application/json";
168 const header_accept_encoding = "gzip, deflate";
169 const HTTP_OK = 200;
170 const HTTP_CREATED = 201;
171 const HTTP_ACEPTED = 202;
172
173 const POST = 'POST';
174 const GET = 'GET';
175 const DELETE = 'DELETE';
176
66c5d3b @thousandsofthem Fixes #3. API call exponential backoff/retry
thousandsofthem authored
177 public $debug_enabled = false;
178 public $max_retries = 5;
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
179
a27ffd3 @thousandsofthem Revamp using $project_id.
thousandsofthem authored
180 private $required_config_fields = array('token','project_id');
0884aca @thousandsofthem All except token marked as optional. Code cleanup.
thousandsofthem authored
181 private $default_values = array(
a105c20 @paddyforan Updated to default to HTTPS. Fixes #1.
paddyforan authored
182 'protocol' => 'https',
0884aca @thousandsofthem All except token marked as optional. Code cleanup.
thousandsofthem authored
183 'host' => 'mq-aws-us-east-1.iron.io',
d0beb8c @paddyforan Updated to fix port mismatch error.
paddyforan authored
184 'port' => '443',
0884aca @thousandsofthem All except token marked as optional. Code cleanup.
thousandsofthem authored
185 'api_version' => '1',
186 );
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
187
188 private $url;
189 private $token;
190 private $api_version;
191 private $version;
192 private $project_id;
193
194 /**
195 * @param string|array $config_file_or_options
196 * Array of options or name of config file.
197 * Fields in options array or in config:
0884aca @thousandsofthem All except token marked as optional. Code cleanup.
thousandsofthem authored
198 *
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
199 * Required:
200 * - token
0884aca @thousandsofthem All except token marked as optional. Code cleanup.
thousandsofthem authored
201 * - project_id
a27ffd3 @thousandsofthem Revamp using $project_id.
thousandsofthem authored
202 * Optional:
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
203 * - protocol
204 * - host
205 * - port
206 * - api_version
207 */
208 function __construct($config_file_or_options){
209 $config = $this->getConfigData($config_file_or_options);
210 $token = $config['token'];
a27ffd3 @thousandsofthem Revamp using $project_id.
thousandsofthem authored
211 $project_id = $config['project_id'];
0884aca @thousandsofthem All except token marked as optional. Code cleanup.
thousandsofthem authored
212
213 $protocol = empty($config['protocol']) ? $this->default_values['protocol'] : $config['protocol'];
214 $host = empty($config['host']) ? $this->default_values['host'] : $config['host'];
215 $port = empty($config['port']) ? $this->default_values['port'] : $config['port'];
216 $api_version = empty($config['api_version'])? $this->default_values['api_version'] : $config['api_version'];
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
217
218 $this->url = "$protocol://$host:$port/$api_version/";
219 $this->token = $token;
220 $this->api_version = $api_version;
221 $this->version = $api_version;
0884aca @thousandsofthem All except token marked as optional. Code cleanup.
thousandsofthem authored
222 $this->project_id = $project_id;
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
223 }
224
b5816c7 @thousandsofthem Timeout bug fix. Expires_in bug fix. Readme updated. Phpdoc banners.
thousandsofthem authored
225 /**
226 * Switch active project
227 *
228 * string @param $project_id Project ID
229 * @throws InvalidArgumentException
230 */
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
231 public function setProjectId($project_id) {
232 if (!empty($project_id)){
233 $this->project_id = $project_id;
234 }
235 if (empty($this->project_id)){
236 throw new InvalidArgumentException("Please set project_id");
237 }
238 }
239
a27ffd3 @thousandsofthem Revamp using $project_id.
thousandsofthem authored
240 public function getQueues($page = 0){
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
241 $url = "projects/{$this->project_id}/queues";
9d6b447 @paddyforan Stopped hard-coding the params into the URL and started passing them …
paddyforan authored
242 $params = array();
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
243 if($page > 0) {
9d6b447 @paddyforan Stopped hard-coding the params into the URL and started passing them …
paddyforan authored
244 $params['page'] = $page;
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
245 }
246 $this->setJsonHeaders();
0884aca @thousandsofthem All except token marked as optional. Code cleanup.
thousandsofthem authored
247 return self::json_decode($this->apiCall(self::GET, $url, $params));
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
248 }
249
b5816c7 @thousandsofthem Timeout bug fix. Expires_in bug fix. Readme updated. Phpdoc banners.
thousandsofthem authored
250 /**
251 * Get information about queue.
252 * Also returns queue size.
253 *
254 * @param string $queue_name
255 * @return mixed
256 */
a27ffd3 @thousandsofthem Revamp using $project_id.
thousandsofthem authored
257 public function getQueue($queue_name) {
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
258 $url = "projects/{$this->project_id}/queues/{$queue_name}";
259 $this->setJsonHeaders();
0884aca @thousandsofthem All except token marked as optional. Code cleanup.
thousandsofthem authored
260 return self::json_decode($this->apiCall(self::GET, $url));
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
261 }
262
9ee346a @thousandsofthem Readme updated.
thousandsofthem authored
263 /**
264 * Push a message on the queue
265 *
b5816c7 @thousandsofthem Timeout bug fix. Expires_in bug fix. Readme updated. Phpdoc banners.
thousandsofthem authored
266 * Examples:
267 * <code>
268 * $ironmq->postMessage("test_queue", "Hello world");
269 * </code>
270 * <code>
271 * $ironmq->postMessage("test_queue", array(
272 * "body" => "Test Message"
273 * "timeout" => 120,
274 * 'delay' => 2,
275 * 'expires_in' => 2*24*3600 # 2 days
276 * ));
277 * </code>
278 *
9ee346a @thousandsofthem Readme updated.
thousandsofthem authored
279 * @param string $queue_name Name of the queue.
280 * @param array|string $message
281 * @return mixed
282 */
a27ffd3 @thousandsofthem Revamp using $project_id.
thousandsofthem authored
283 public function postMessage($queue_name, $message) {
5190513 @paddyforan Updated message posting to use a better API.
paddyforan authored
284 $msg = new IronMQ_Message($message);
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
285 $req = array(
286 "messages" => array($msg->asArray())
287 );
288 $this->setCommonHeaders();
289 $url = "projects/{$this->project_id}/queues/{$queue_name}/messages";
290 $res = $this->apiCall(self::POST, $url, $req);
0884aca @thousandsofthem All except token marked as optional. Code cleanup.
thousandsofthem authored
291 return self::json_decode($res);
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
292 }
293
b5816c7 @thousandsofthem Timeout bug fix. Expires_in bug fix. Readme updated. Phpdoc banners.
thousandsofthem authored
294 /**
295 * Push multiple messages on the queue
296 *
297 * @param string $queue_name Name of the queue.
298 * @param array $messages array of messages, each message same as for postMessage() method
299 * @return mixed
300 */
a27ffd3 @thousandsofthem Revamp using $project_id.
thousandsofthem authored
301 public function postMessages($queue_name, $messages) {
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
302 $req = array(
303 "messages" => array()
304 );
305 foreach($messages as $message) {
5190513 @paddyforan Updated message posting to use a better API.
paddyforan authored
306 $msg = new IronMQ_Message($message);
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
307 array_push($req['messages'], $msg->asArray());
308 }
309 $this->setCommonHeaders();
310 $url = "projects/{$this->project_id}/queues/{$queue_name}/messages";
311 $res = $this->apiCall(self::POST, $url, $req);
0884aca @thousandsofthem All except token marked as optional. Code cleanup.
thousandsofthem authored
312 return self::json_decode($res);
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
313 }
314
a27ffd3 @thousandsofthem Revamp using $project_id.
thousandsofthem authored
315 public function getMessages($queue_name, $count=1) {
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
316 $url = "projects/{$this->project_id}/queues/{$queue_name}/messages";
9d6b447 @paddyforan Stopped hard-coding the params into the URL and started passing them …
paddyforan authored
317 $params = array();
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
318 if($count > 1) {
9d6b447 @paddyforan Stopped hard-coding the params into the URL and started passing them …
paddyforan authored
319 $params['count'] = $count;
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
320 }
321 $this->setJsonHeaders();
9d6b447 @paddyforan Stopped hard-coding the params into the URL and started passing them …
paddyforan authored
322 $response = $this->apiCall(self::GET, $url, $params);
ceea47f @paddyforan Updated getMessages to return null if there are no messages to return.
paddyforan authored
323 $result = self::json_decode($response);
324 if(count($result->messages) < 1) {
325 return null;
326 } else {
327 return $result;
328 }
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
329 }
330
a27ffd3 @thousandsofthem Revamp using $project_id.
thousandsofthem authored
331 public function getMessage($queue_name) {
332 return $this->getMessages($queue_name, 1);
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
333 }
334
a27ffd3 @thousandsofthem Revamp using $project_id.
thousandsofthem authored
335 public function deleteMessage($queue_name, $message_id) {
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
336 $this->setCommonHeaders();
337 $url = "projects/{$this->project_id}/queues/{$queue_name}/messages/{$message_id}";
338 return $this->apiCall(self::DELETE, $url);
339 }
340
341 /* PRIVATE FUNCTIONS */
342
343 private function compiledHeaders(){
344
345 # Set default headers if no headers set.
346 if ($this->headers == null){
347 $this->setCommonHeaders();
348 }
349
350 $headers = array();
351 foreach ($this->headers as $k => $v){
352 $headers[] = "$k: $v";
353 }
354 return $headers;
355 }
356
357 private function apiCall($type, $url, $params = array()){
358 $url = "{$this->url}$url";
359
360 $s = curl_init();
361 if (! isset($params['oauth'])) {
362 $params['oauth'] = $this->token;
363 }
364 switch ($type) {
365 case self::DELETE:
366 $fullUrl = $url . '?' . http_build_query($params);
367 $this->debug('apiCall fullUrl', $fullUrl);
368 curl_setopt($s, CURLOPT_URL, $fullUrl);
369 curl_setopt($s, CURLOPT_CUSTOMREQUEST, self::DELETE);
370 break;
371 case self::POST:
372 $this->debug('apiCall url', $url);
373 curl_setopt($s, CURLOPT_URL, $url);
374 curl_setopt($s, CURLOPT_POST, true);
375 curl_setopt($s, CURLOPT_POSTFIELDS, json_encode($params));
376 break;
377 case self::GET:
378 $fullUrl = $url . '?' . http_build_query($params);
379 $this->debug('apiCall fullUrl', $fullUrl);
380 curl_setopt($s, CURLOPT_URL, $fullUrl);
381 break;
382 }
383
384 curl_setopt($s, CURLOPT_RETURNTRANSFER, true);
385 curl_setopt($s, CURLOPT_HTTPHEADER, $this->compiledHeaders());
66c5d3b @thousandsofthem Fixes #3. API call exponential backoff/retry
thousandsofthem authored
386
387 return $this->callWithRetries($s);
388 }
389
390
391 private function callWithRetries($s){
392 for ($retry = 0; $retry < $this->max_retries; $retry++){
393 $_out = curl_exec($s);
394 $status = curl_getinfo($s, CURLINFO_HTTP_CODE);
395 switch ($status) {
396 case self::HTTP_OK:
397 case self::HTTP_CREATED:
398 case self::HTTP_ACEPTED:
399 curl_close($s);
400 return $_out;
59caceb @thousandsofthem Fixes #3. Handle only 503 errors.
thousandsofthem authored
401 case Http_Exception::SERVICE_UNAVAILABLE:
66c5d3b @thousandsofthem Fixes #3. API call exponential backoff/retry
thousandsofthem authored
402 // wait for a random delay between 0 and (4^currentRetry * 100) milliseconds
403 $max_delay = pow(4, $retry)*100*1000;
404 usleep(rand(0, $max_delay));
59caceb @thousandsofthem Fixes #3. Handle only 503 errors.
thousandsofthem authored
405 break;
406 default:
407 throw new Http_Exception("http error: {$status} | {$_out}", $status);
66c5d3b @thousandsofthem Fixes #3. API call exponential backoff/retry
thousandsofthem authored
408 }
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
409 }
59caceb @thousandsofthem Fixes #3. Handle only 503 errors.
thousandsofthem authored
410 throw new Http_Exception("http error: Service unavailable | ", 503);
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
411 }
412
413
414 /**
415 * @param array|string $config_file_or_options
416 * array of options or name of config file
417 * @return array
418 * @throws InvalidArgumentException
419 */
420 private function getConfigData($config_file_or_options){
421 if (is_string($config_file_or_options)){
422 $ini = parse_ini_file($config_file_or_options, true);
423 if ($ini === false){
424 throw new InvalidArgumentException("Config file $config_file_or_options not found");
425 }
426 if (empty($ini['iron_mq'])){
427 throw new InvalidArgumentException("Config file $config_file_or_options has no section 'iron_mq'");
428 }
429 $config = $ini['iron_mq'];
430 }elseif(is_array($config_file_or_options)){
431 $config = $config_file_or_options;
432 }else{
433 throw new InvalidArgumentException("Wrong parameter type");
434 }
435 foreach ($this->required_config_fields as $field){
436 if (empty($config[$field])){
437 throw new InvalidArgumentException("Required config key missing: '$field'");
438 }
439 }
440 return $config;
441 }
442
443 private function setCommonHeaders(){
444 $this->headers = array(
445 'Authorization' => "OAuth {$this->token}",
446 'User-Agent' => self::header_user_agent,
447 'Content-Type' => 'application/json',
448 'Accept' => self::header_accept,
449 'Accept-Encoding' => self::header_accept_encoding
450 );
451 }
452
453 private function setJsonHeaders(){
454 $this->setCommonHeaders();
455 }
456
457 private function setPostHeaders(){
458 $this->setCommonHeaders();
459 $this->headers['Content-Type'] ='multipart/form-data';
460 }
461
462 private function debug($var_name, $variable){
463 if ($this->debug_enabled){
464 echo "{$var_name}: ".var_export($variable,true)."\n";
465 }
466 }
0884aca @thousandsofthem All except token marked as optional. Code cleanup.
thousandsofthem authored
467
468 private static function json_decode($response){
469 $data = json_decode($response);
470 $json_error = json_last_error();
471 if($json_error != JSON_ERROR_NONE) {
472 throw new JSON_Exception($json_error);
473 }
474 return $data;
475 }
476
248b1d5 @paddyforan Translated IronWorker PHP lib to work with IronMQ.
paddyforan authored
477 }
Something went wrong with that request. Please try again.