Skip to content

Commit f451e1a

Browse files
IronCore initial version
1 parent 62a0fdd commit f451e1a

File tree

3 files changed

+329
-0
lines changed

3 files changed

+329
-0
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
*.swp
2+
config.ini
3+
.DS_Store
4+
*.esproj/
5+
.idea/

IronCore.class.php

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

README renamed to README.md

File renamed without changes.

0 commit comments

Comments
 (0)