diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c55784d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +composer.lock +/vendor/ diff --git a/README.md b/README.md index 9688f28..d30cb8e 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,13 @@ This software library provides the capability to easily deploy the EST API. ##Example of an Abnormal Returns Calculatior (ARC) launch ```php +define('API_URL', 'http://api.est.dev'); +define('API_KEY', 'key1234567890'); +define('STATUS_DONE', 3); +define('STATUS_ERROR', 4); + +require './../vendor/autoload.php'; + $parameters = [ 'result_file_type' => 'xls', 'benchmark_model' => 'mm', @@ -48,21 +55,44 @@ $parameters = [ ] ]; -$api = new EventStudyTools\ApiWrapper\ApiWrapper('arc', $apiEndpointUrl))); +$api = new \EventStudyTools\ApiWrapper\ApiWrapper(API_URL); -if ($api->authentication($apiKey)) { - $api->configureTask(new EventStudyTools\ApiWrapper\ApplicationInput\ArcApplicationInput($parameters)); - $api->uploadFile('firm_data', './firmData.csv')); - $api->uploadFile('market_data', './marketData.csv')); - $api->uploadFile('request_file', './requestFile.csv')); +if ($api->authentication(API_KEY)) { + $api->configureTask(new \EventStudyTools\ApiWrapper\ApplicationInput\ArcApplicationInput($parameters)); + $api->uploadFile('firm_data', './firm_data.csv'); + $api->uploadFile('market_data', './market_data.csv'); + $api->uploadFile('request_file', './request_file.csv'); $api->commitData(); - $result = $api->processTask(); + + do { + sleep(15); + $status = $api->getTaskStatus(); + } while (!in_array($status, array(STATUS_DONE, STATUS_ERROR))); + + switch ($status) { + case STATUS_DONE: + $results = $api->getTaskResults(); + var_dump($results); + break; + + case STATUS_ERROR: + echo "Task \"" . $api->getToken() . "\" was terminated with error\n"; + break; + } } ``` ##Example of a Computer-Aided Text Analysis (CATA) launch ```php +define('API_URL', 'http://api.est.dev'); +define('API_KEY', 'key1234567890'); +define('BASE_PATH', __DIR__); +define('STATUS_DONE', 3); +define('STATUS_ERROR', 4); + +require BASE_PATH . '/../vendor/autoload.php'; + $parameters = [ 'datasources' => [ 'text_data' => 'csv_zip', @@ -70,13 +100,28 @@ $parameters = [ ] ]; -$api = new EventStudyTools\ApiWrapper\ApiWrapper('cata', $apiEndpointUrl))); +$api = new \EventStudyTools\ApiWrapper\ApiWrapper(API_URL); -if ($api->authentication($apiKey)) { - $api->configureTask(new EventStudyTools\ApiWrapper\ApplicationInput\CataApplicationInput($parameters)); +if ($api->authentication(API_KEY)) { + $api->configureTask(new \EventStudyTools\ApiWrapper\ApplicationInput\CataApplicationInput($parameters)); $api->uploadFile('text_data', './texts.csv.zip'); $api->uploadFile('keywords_data', './dictionary.csv.zip'); $api->commitData(); - $result = $api->processTask(); + + do { + sleep(15); + $status = $api->getTaskStatus(); + } while (!in_array($status, array(STATUS_DONE, STATUS_ERROR))); + + switch ($status) { + case STATUS_DONE: + $results = $api->getTaskResults(); + var_dump($results); + break; + + case STATUS_ERROR: + echo "Task \"" . $api->getToken() . "\" was terminated with error\n"; + break; + } } ``` \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..5f6d4af --- /dev/null +++ b/composer.json @@ -0,0 +1,17 @@ +{ + "name": "EventStudyTools/ApiWrapper", + "type": "library", + "description": "EventStudyTools (EST) API PHP Wrapper", + "homepage": "https://github.com/EventStudyTools/api-wrapper.php", + "keywords": ["EventStudyTools"], + "minimum-stability": "stable", + "license": "MIT", + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "EventStudyTools\\ApiWrapper\\": "src/" + } + } +} diff --git a/examples/1.php b/examples/1.php new file mode 100644 index 0000000..1543536 --- /dev/null +++ b/examples/1.php @@ -0,0 +1,83 @@ + 'xls', + 'benchmark_model' => 'mm', + 'return_type' => 'log', + 'non_trading_days' => 'later', + 'test_statistics' => [ + 'art' => '1', + 'cart' => '1', + 'aart' => '1', + 'caart' => '1', + 'abhart' => '1', + 'aarptlz' => '1', + 'caarptlz' => '1', + 'aaraptlz' => '1', + 'caaraptlz' => '1', + 'aarbmpz' => '1', + 'caarbmpz' => '1', + 'aarabmpz' => '1', + 'caarabmpz' => '1', + 'aarskewadjt' => '1', + 'caarskewadjt' => '1', + 'abharskewadjt' => '1', + 'aarrankz' => '1', + 'caarrankz' => '1', + 'aargrankt' => '1', + 'caargrankt' => '1', + 'aargrankz' => '1', + 'caargrankz' => '1', + 'aargsignz' => '1', + 'caargsignz' => '1', + 'aarcdat' => '1', + 'aarjackknivet' => '1', + ], + 'datasources' => [ + 'request_file' => 'xlsx', + 'firm_data' => 'csv_zip', + 'market_data' => 'xls_zip' + ] +]; + +$api = new \EventStudyTools\ApiWrapper\ApiWrapper(API_URL); + +if ($api->authentication(API_KEY)) { + $api->configureTask(new \EventStudyTools\ApiWrapper\ApplicationInput\ArcApplicationInput($parameters)); + $api->uploadFile('firm_data', BASE_PATH . '/data/firm_data.csv.zip'); + $api->uploadFile('market_data', BASE_PATH . '/data/market_data.xls.zip'); + $api->uploadFile('request_file', BASE_PATH . '/data/request_file.xlsx'); + $api->commitData(); + + do { + sleep(15); + $status = $api->getTaskStatus(); + } while (!in_array($status->status, array(STATUS_DONE, STATUS_ERROR))); + + switch ($status->status) { + case STATUS_DONE: + $results = $api->getTaskResults(); + + echo "Task \"" . $api->getToken() . "\" was terminated successfully\n"; + echo "Links:\n"; + if (!empty($results->log)) echo "log: " . $results->log . "\n"; + foreach ($results->results as $result) echo $result . "\n"; + + break; + + case STATUS_ERROR: + echo "Task \"" . $api->getToken() . "\" was terminated with error: " . $status->message . "\n"; + break; + + default: + echo "Invalid status \"" . $status->status . "\"\n"; + break; + } +} \ No newline at end of file diff --git a/examples/data/firm_data.csv.zip b/examples/data/firm_data.csv.zip new file mode 100644 index 0000000..f1ead0b Binary files /dev/null and b/examples/data/firm_data.csv.zip differ diff --git a/examples/data/market_data.xls.zip b/examples/data/market_data.xls.zip new file mode 100644 index 0000000..9cfc491 Binary files /dev/null and b/examples/data/market_data.xls.zip differ diff --git a/examples/data/request_file.xlsx b/examples/data/request_file.xlsx new file mode 100644 index 0000000..0b022ce Binary files /dev/null and b/examples/data/request_file.xlsx differ diff --git a/ApiWrapper.php b/src/ApiWrapper.php similarity index 60% rename from ApiWrapper.php rename to src/ApiWrapper.php index 6782545..307a116 100644 --- a/ApiWrapper.php +++ b/src/ApiWrapper.php @@ -1,7 +1,7 @@ token; + $this->token = ''; + $this->apiServerUrl = $apiEndpoint; } /** - * @param $apiEndpoint + * @return string */ - function __construct($apiEndpoint) { - $this->token = ''; - $this->apiServerUrl = $apiEndpoint; + public function getToken() + { + return $this->token; } /** * @param string $apiKey * @param bool $debug + * @throws \Exception * @return bool */ - function authentication($apiKey, $debug=false) + function authentication($apiKey, $debug = false) { $ch = curl_init($this->apiServerUrl . "/task/create"); curl_setopt($ch, CURLOPT_POST, true); @@ -48,7 +50,10 @@ function authentication($apiKey, $debug=false) ); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $result = curl_exec($ch); + $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); if ($debug) { @@ -56,14 +61,14 @@ function authentication($apiKey, $debug=false) exit; } - if (!$result === false && !preg_match("~checkAndNormalizeResponse($result, $httpcode, __METHOD__); - if (isset($temp->token) && !empty($temp->token)) { - $this->token = $temp->token; - } + if (!is_object($result) || empty($result->token)) { + throw new \Exception(__METHOD__ . ': authentication failed'); } + $this->token = $result->token; + return !empty($this->token); } @@ -73,9 +78,9 @@ function authentication($apiKey, $debug=false) * @return mixed * @throws \Exception */ - function configureTask(ApplicationInputInterface $input, $debug=false) { - - if (empty($this->apiServerUrl) || empty($this->token) || !is_object($input)) { + function configureTask(ApplicationInputInterface $input, $debug = false) + { + if (empty($this->token) || !is_object($input)) { throw new \Exception(__METHOD__ . ": required parameters aren't set"); } @@ -96,6 +101,8 @@ function configureTask(ApplicationInputInterface $input, $debug=false) { curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); + $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); if ($debug) { @@ -103,11 +110,13 @@ function configureTask(ApplicationInputInterface $input, $debug=false) { exit; } - if ($result === false || preg_match("~checkAndNormalizeResponse($result, $httpcode, __METHOD__); + + if ($result !== true) { throw new \Exception(__METHOD__ . ': configuration error'); } - return json_decode($result); + return true; } /** @@ -116,33 +125,32 @@ function configureTask(ApplicationInputInterface $input, $debug=false) { * @param int $partNumber * @throws \Exception */ - function uploadFile($fileKey, $fileName, $partNumber=0) + function uploadFile($fileKey, $fileName, $partNumber = 0) { - - if (empty($this->apiServerUrl) || empty($this->token) || empty($fileKey) || empty($fileName)) { + if (empty($this->token) || empty($fileKey) || empty($fileName)) { throw new \Exception(__METHOD__ . ': configuration error'); } - if ( !($fd = fopen($fileName, 'r') ) ) { + if (!($fd = fopen($fileName, 'r'))) { throw new \Exception(__METHOD__ . ': cannot read file ' . $fileName); } /* * @todo define class field for maxChunkSize */ - $maxChunkSize = 40 * 1024 *1024; + $maxChunkSize = 40 * 1024 * 1024; /* * +1000 is a dirty hack. I don't know why splitfile is set to write 41943040 but filesize yields 41943055 * @todo understand why it happens and fix on more smart manner */ - if ( filesize($fileName) > $maxChunkSize +1000) { + if (filesize($fileName) > $maxChunkSize + 1000) { fclose($fd); - $files =array(); + $files = array(); try { $files = $this->splitFile($fileName, $maxChunkSize); - foreach($files as $key => $value) { + foreach ($files as $key => $value) { $this->uploadFile($fileKey, $value, $key); } } catch (\Exception $ex) { @@ -174,22 +182,28 @@ function uploadFile($fileKey, $fileName, $partNumber=0) curl_setopt($ch, CURLOPT_INFILE, $fd); curl_setopt($ch, CURLOPT_INFILESIZE, filesize($fileName)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $result = curl_exec($ch); + $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); fclose($fd); - if ($result === false || preg_match("~checkAndNormalizeResponse($result, $httpcode, __METHOD__ . ' (' . $fileKey . ')'); + + if ($result !== true) { + throw new \Exception(__METHOD__ . ' (' . $fileKey . ')' . ': configuration error'); } } /** * @param $parts */ - protected function deleteFileParts($parts) { + protected function deleteFileParts($parts) + { if (!empty($parts)) { - foreach($parts as $file) { + foreach ($parts as $file) { unlink($file); } } @@ -201,26 +215,26 @@ protected function deleteFileParts($parts) { * @return array * @throws \Exception */ - protected function splitFile($fileName, $maxChunkSize) { - - $i=0; + protected function splitFile($fileName, $maxChunkSize) + { + $i = 0; $partFileNames = array(); $handle = fopen($fileName, 'r'); - while (!feof ($handle)) { + while (!feof($handle)) { $buffer = ''; $i++; - $partFileName =$fileName . '.part' . $i; + $partFileName = $fileName . '.part' . $i; $partHandle = fopen($partFileName, "w"); if (!$partHandle) { fclose($handle); fclose($partHandle); - throw new \Exception(" Cannot write file part $partFileName"); + throw new \Exception("Cannot write file part $partFileName"); } - while(strlen($buffer) < $maxChunkSize && !feof($handle)) { + while (strlen($buffer) < $maxChunkSize && !feof($handle)) { $buffer .= fgets($handle); } @@ -234,7 +248,7 @@ protected function splitFile($fileName, $maxChunkSize) { fclose($partHandle); } - fclose ($handle); + fclose($handle); return $partFileNames; } @@ -245,7 +259,7 @@ protected function splitFile($fileName, $maxChunkSize) { */ function commitData() { - if (empty($this->apiServerUrl) || empty($this->token)) { + if (empty($this->token)) { throw new \Exception(__METHOD__ . ': Configuration validation error'); } @@ -264,32 +278,34 @@ function commitData() ); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $result = curl_exec($ch); + $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); - if ($result === false || preg_match("~checkAndNormalizeResponse($result, $httpcode, __METHOD__); + + if (!is_object($result) || empty($result->log)) { + throw new \Exception(__METHOD__ . ': response is invalid'); } - return json_decode($result); + return $result; } /** - * @param bool $debug if want see API output more than get results + * @param $exceptionOnError + * @return integer * @throws \Exception - * @return mixed */ - function processTask($debug = false) + function getTaskStatus($exceptionOnError = false) { - if (empty($this->apiServerUrl) || empty($this->token)) { - throw new \Exception(__METHOD__ . ': configuration error'); + if (empty($this->token)) { + throw new \Exception(__METHOD__ . ': Configuration validation error'); } + $ch = curl_init($this->apiServerUrl . "/task/status"); - $ch = curl_init($this->apiServerUrl . "/task/process"); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); - curl_setopt($ch, CURLOPT_POSTFIELDS, ''); curl_setopt( $ch, CURLOPT_HTTPHEADER, @@ -298,37 +314,52 @@ function processTask($debug = false) "X-Task-Key: $this->token", ) ); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $result = curl_exec($ch); $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); - if ($debug) { - var_dump($result); - exit; - } + $result = $this->checkAndNormalizeResponse($result, $httpcode, __METHOD__, $exceptionOnError); + + return $result; + } - /* - * Handle any unusual output - */ - if ($result === false || preg_match("~token)) { + throw new \Exception(__METHOD__ . ': Configuration validation error'); } - /* - * Handle standard error output - */ - if ($httpcode != 200) { - $result = json_decode($result); + $ch = curl_init($this->apiServerUrl . "/results/" . $this->getToken()); - if (isset($result->error)) { - throw new \Exception($result->error); - } else { - throw new \Exception("Application launch error"); - } + curl_setopt( + $ch, + CURLOPT_HTTPHEADER, + array( + ) + ); + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + $result = curl_exec($ch); + $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + curl_close($ch); + + $result = $this->checkAndNormalizeResponse($result, $httpcode, __METHOD__); + + if (empty($result) || empty($result->results)) { + throw new \Exception(__METHOD__ . ': result is empty'); } - return json_decode($result); + return $result; } /** @@ -353,4 +384,40 @@ function getApiVersion() return $version; } + + /** + * Normalize and check response + * @param $response + * @param $httpcode + * @param $method + * @param $exceptionOnError + * @return mixed + * @throws \Exception + */ + protected function checkAndNormalizeResponse($response, $httpcode, $method, $exceptionOnError = true) + { + if ($response === false) { + if ($exceptionOnError) { + throw new \Exception($method . ': request to api failed'); + } else { + return false; + } + } + + $response = json_decode($response); + + if ($httpcode != 200 || (is_object($response) && !empty($response->error))) { + if ($exceptionOnError) { + if (is_object($response) && !empty($response->error)) { + throw new \Exception($method . ': ' . $response->error); + } else { + throw new \Exception($method . ': Application error'); + } + } else { + return false; + } + } + + return $response; + } } \ No newline at end of file diff --git a/ApplicationInput/AVyCApplicationInput.php b/src/ApplicationInput/AVyCApplicationInput.php similarity index 96% rename from ApplicationInput/AVyCApplicationInput.php rename to src/ApplicationInput/AVyCApplicationInput.php index b644390..7c16f5c 100644 --- a/ApplicationInput/AVyCApplicationInput.php +++ b/src/ApplicationInput/AVyCApplicationInput.php @@ -1,5 +1,5 @@