Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 326 lines (284 sloc) 10.625 kb
f451e1a @thousandsofthem IronCore initial version
thousandsofthem authored
1 <?php
2 /**
3 * Core functionality for Iron.io products
4 *
5 * @link https://github.com/iron-io/iron_core_php
6 * @link http://www.iron.io/
7 * @link http://dev.iron.io/
8 * @version 0.0.1
9 * @package IronCore
0e5a86f @thousandsofthem added readme and license information
thousandsofthem authored
10 * @copyright BSD 2-Clause License. See LICENSE file.
f451e1a @thousandsofthem IronCore initial version
thousandsofthem authored
11 */
12
13 class IronCore{
14 protected $core_version = '0.0.1';
15
16 // should be overridden by child class
17 protected $client_version = null;
18 protected $client_name = null;
19 protected $product_name = null;
20 protected $default_values = null;
21
22 const HTTP_OK = 200;
23 const HTTP_CREATED = 201;
24 const HTTP_ACCEPTED = 202;
25
26 const POST = 'POST';
27 const GET = 'GET';
28 const DELETE = 'DELETE';
29
30 const header_accept = "application/json";
31 const header_accept_encoding = "gzip, deflate";
32
33 protected $url;
34 protected $token;
35 protected $api_version;
36 protected $version;
37 protected $project_id;
38 protected $headers;
39 protected $protocol;
40 protected $host;
41 protected $port;
42
43 public $max_retries = 5;
44 public $debug_enabled = false;
fe2b649 @thousandsofthem ssl_verifypeer support
thousandsofthem authored
45 public $ssl_verifypeer = true;
f451e1a @thousandsofthem IronCore initial version
thousandsofthem authored
46
47
48 protected static function dateRfc3339($timestamp = 0) {
49 if ($timestamp instanceof DateTime) {
50 $timestamp = $timestamp->getTimestamp();
51 }
52 if (!$timestamp) {
53 $timestamp = time();
54 }
55 return gmdate('c', $timestamp);
56 }
57
58 protected static function json_decode($response){
59 $data = json_decode($response);
60 if (function_exists('json_last_error')){
61 $json_error = json_last_error();
62 if($json_error != JSON_ERROR_NONE) {
63 throw new JSON_Exception($json_error);
64 }
65 }elseif($data === null){
66 throw new JSON_Exception("Common JSON error");
67 }
68 return $data;
69 }
70
71
72 protected static function homeDir(){
73 if ($home_dir = getenv('HOME')){
74 // *NIX
75 return $home_dir.DIRECTORY_SEPARATOR;
76 }else{
77 // Windows
78 return getenv('HOMEDRIVE').getenv('HOMEPATH').DIRECTORY_SEPARATOR;
79 }
80 }
81
82 protected function debug($var_name, $variable){
83 if ($this->debug_enabled){
84 echo "{$var_name}: ".var_export($variable,true)."\n";
85 }
86 }
87
88 protected function userAgent(){
89 return "{$this->client_name}-{$this->client_version} (iron_core-{$this->core_version})";
90 }
91
92 /**
93 * Load configuration
94 *
95 * @param array|string|null $config_file_or_options
96 * array of options or name of config file
97 * @return array
98 * @throws InvalidArgumentException
99 */
100 protected function getConfigData($config_file_or_options){
101 if(is_string($config_file_or_options)){
102 if (!file_exists($config_file_or_options)){
103 throw new InvalidArgumentException("Config file $config_file_or_options not found");
104 }
105 $this->loadConfigFile($config_file_or_options);
106 }elseif(is_array($config_file_or_options)){
107 $this->loadFromHash($config_file_or_options);
108 }
109
110 $this->loadConfigFile('iron.ini');
111 $this->loadConfigFile('iron.json');
112
113 $this->loadFromEnv(strtoupper($this->product_name));
114 $this->loadFromEnv('IRON');
115
116 $this->loadConfigFile(self::homeDir() . '.iron.ini');
117 $this->loadConfigFile(self::homeDir() . '.iron.json');
118
119 $this->loadFromHash($this->default_values);
120
121 if (empty($this->token) || empty($this->project_id)){
122 throw new InvalidArgumentException("token or project_id not found in any of the available sources");
123 }
124 }
125
126
127 protected function loadFromHash($options){
128 if (empty($options)) return;
129 $this->setVarIfValue('token', $options);
130 $this->setVarIfValue('project_id', $options);
131 $this->setVarIfValue('protocol', $options);
132 $this->setVarIfValue('host', $options);
133 $this->setVarIfValue('port', $options);
134 $this->setVarIfValue('api_version', $options);
135 }
136
137 protected function loadFromEnv($prefix){
138 $this->setVarIfValue('token', getenv($prefix. "_TOKEN"));
139 $this->setVarIfValue('project_id', getenv($prefix. "_PROJECT_ID"));
140 $this->setVarIfValue('protocol', getenv($prefix. "_SCHEME"));
141 $this->setVarIfValue('host', getenv($prefix. "_HOST"));
142 $this->setVarIfValue('port', getenv($prefix. "_PORT"));
143 $this->setVarIfValue('api_version', getenv($prefix. "_API_VERSION"));
144 }
145
146 protected function setVarIfValue($key, $options_or_value){
147 if (!empty($this->$key)) return;
148 if (is_array($options_or_value)){
149 if (!empty($options_or_value[$key])){
150 $this->$key = $options_or_value[$key];
151 }
152 }else{
153 if (!empty($options_or_value)){
154 $this->$key = $options_or_value;
155 }
156 }
157 }
158
159 protected function loadConfigFile($file){
160 if (!file_exists($file)) return;
161 $data = @parse_ini_file($file, true);
162 if ($data === false){
163 $data = json_decode(file_get_contents($file), true);
164 }
165 if (!is_array($data)){
166 throw new InvalidArgumentException("Config file $file not parsed");
167 };
168
169 if (!empty($data[$this->product_name])) $this->loadFromHash($data[$this->product_name]);
170 if (!empty($data['iron'])) $this->loadFromHash($data['iron']);
171 $this->loadFromHash($data);
172 }
173
174 protected function apiCall($type, $url, $params = array(), $raw_post_data = null){
175 $url = "{$this->url}$url";
176
177 $s = curl_init();
178 if (! isset($params['oauth'])) {
179 $params['oauth'] = $this->token;
180 }
181 switch ($type) {
182 case self::DELETE:
183 $fullUrl = $url . '?' . http_build_query($params);
184 $this->debug('apiCall fullUrl', $fullUrl);
185 curl_setopt($s, CURLOPT_URL, $fullUrl);
186 curl_setopt($s, CURLOPT_CUSTOMREQUEST, self::DELETE);
187 break;
188 case self::POST:
189 $this->debug('apiCall url', $url);
190 curl_setopt($s, CURLOPT_URL, $url);
191 curl_setopt($s, CURLOPT_POST, true);
192 if ($raw_post_data){
193 curl_setopt($s, CURLOPT_POSTFIELDS, $raw_post_data);
194 }else{
195 curl_setopt($s, CURLOPT_POSTFIELDS, json_encode($params));
196 }
197 break;
198 case self::GET:
199 $fullUrl = $url . '?' . http_build_query($params);
200 $this->debug('apiCall fullUrl', $fullUrl);
201 curl_setopt($s, CURLOPT_URL, $fullUrl);
202 break;
203 }
fe2b649 @thousandsofthem ssl_verifypeer support
thousandsofthem authored
204 curl_setopt($s, CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer);
f451e1a @thousandsofthem IronCore initial version
thousandsofthem authored
205 curl_setopt($s, CURLOPT_RETURNTRANSFER, true);
206 curl_setopt($s, CURLOPT_HTTPHEADER, $this->compiledHeaders());
207 return $this->callWithRetries($s);
208 }
209
210 protected function callWithRetries($s){
211 for ($retry = 0; $retry < $this->max_retries; $retry++){
212 $_out = curl_exec($s);
213 $status = curl_getinfo($s, CURLINFO_HTTP_CODE);
214 switch ($status) {
215 case self::HTTP_OK:
216 case self::HTTP_CREATED:
217 case self::HTTP_ACCEPTED:
218 curl_close($s);
219 return $_out;
220 case Http_Exception::INTERNAL_ERROR:
221 if (strpos($_out, "EOF") !== false){
222 self::waitRandomInterval($retry);
223 }else{
224 curl_close($s);
225 $this->reportHttpError($status, $_out);
226 }
227 break;
228 case Http_Exception::SERVICE_UNAVAILABLE:
229 self::waitRandomInterval($retry);
230 break;
231 default:
232 curl_close($s);
233 $this->reportHttpError($status, $_out);
234 }
235 }
236 curl_close($s);
237 return $this->reportHttpError(503, "Service unavailable");
238 }
239
240 protected function reportHttpError($status, $text){
241 throw new Http_Exception("http error: {$status} | {$text}", $status);
242 }
243
244 /**
245 * Wait for a random time between 0 and (4^currentRetry * 100) milliseconds
246 *
247 * @static
248 * @param int $retry currentRetry number
249 */
250 protected static function waitRandomInterval($retry){
251 $max_delay = pow(4, $retry)*100*1000;
252 usleep(rand(0, $max_delay));
253 }
254
255 protected function compiledHeaders(){
256 # Set default headers if no headers set.
257 if ($this->headers == null){
258 $this->setCommonHeaders();
259 }
260
261 $headers = array();
262 foreach ($this->headers as $k => $v){
263 $headers[] = "$k: $v";
264 }
265 return $headers;
266 }
267
268 protected function setCommonHeaders(){
269 $this->headers = array(
270 'Authorization' => "OAuth {$this->token}",
271 'User-Agent' => $this->userAgent(),
272 'Content-Type' => 'application/json',
273 'Accept' => self::header_accept,
274 'Accept-Encoding' => self::header_accept_encoding
275 );
276 }
277
278 }
279
280 /**
281 * The Http_Exception class represents an HTTP response status that is not 200 OK.
282 */
283 class Http_Exception extends Exception{
284 const NOT_MODIFIED = 304;
285 const BAD_REQUEST = 400;
286 const NOT_FOUND = 404;
287 const NOT_ALLOWED = 405;
288 const CONFLICT = 409;
289 const PRECONDITION_FAILED = 412;
290 const INTERNAL_ERROR = 500;
291 const SERVICE_UNAVAILABLE = 503;
292 }
293
294 /**
295 * The JSON_Exception class represents an failures of decoding json strings.
296 */
297 class JSON_Exception extends Exception {
298 public $error = null;
299 public $error_code = JSON_ERROR_NONE;
300
301 function __construct($error_code) {
302 $this->error_code = $error_code;
303 switch($error_code) {
304 case JSON_ERROR_DEPTH:
305 $this->error = 'Maximum stack depth exceeded.';
306 break;
307 case JSON_ERROR_CTRL_CHAR:
308 $this->error = "Unexpected control characted found.";
309 break;
310 case JSON_ERROR_SYNTAX:
311 $this->error = "Syntax error, malformed JSON";
312 break;
313 default:
314 $this->error = $error_code;
315 break;
316
317 }
318 parent::__construct();
319 }
320
321 function __toString() {
322 return $this->error;
323 }
324 }
325
Something went wrong with that request. Please try again.