├── .gitignore ├── .travis.yml ├── composer.json ├── example_cache.php ├── LICENSE ├── tests ├── README.md └── QuandlTest.php ├── examples.php ├── README.md └── Quandl.php /.gitignore: -------------------------------------------------------------------------------- 1 | dev.php 2 | /vendor 3 | composer.lock 4 | tmp.zip -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - '5.4' 4 | - '5.6' 5 | - '7.0' 6 | script: phpunit . 7 | notifications: 8 | email: false -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dannyben/php-quandl", 3 | "license": "MIT", 4 | "require": { 5 | "php": ">=5.4.0" 6 | }, 7 | "autoload": { 8 | "classmap": [ 9 | "Quandl.php" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example_cache.php: -------------------------------------------------------------------------------- 1 | cache_handler = 'cacheHandler'; 11 | $data = $quandl->getSymbol("WIKI/AAPL"); 12 | 13 | // A simple example of a cache handler. 14 | // This function will be called by the Quandl class. 15 | // When action == "get", you should return a cached 16 | // object or false. 17 | // When action == "set", you should perform the save 18 | // operation to your cache. 19 | function cacheHandler($action, $url, $data=null) { 20 | $cache_key = md5("quandl:$url"); 21 | $cache_file = __DIR__ . "/$cache_key"; 22 | 23 | if($action == "get" and file_exists($cache_file)) 24 | return file_get_contents($cache_file); 25 | else if($action == "set") 26 | file_put_contents($cache_file, $data); 27 | 28 | return false; 29 | } 30 | ?> -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Danny Ben Shitrit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | PHP Quandl Unit Tests 2 | ================================================== 3 | 4 | 5 | This folder contains PHPUnit unit tests. You do not need it for 6 | production and can safely delete it if you are not using PHPUnit. 7 | 8 | Run Tests 9 | -------------------------------------------------- 10 | 11 | $ phpunit --stop-on-failure . 12 | 13 | If you run the tests multiple times, you will eventually reach the Quandl call 14 | limit for key-less calls. 15 | 16 | In this case, you can simply set your own key in an environment variable: 17 | 18 | $ export QUANDL_KEY=your_key_here 19 | $ phpunit --stop-on-failure . 20 | 21 | To test a specific method: 22 | 23 | $ phpunit --stop-on-failure --filter PartialMethodName . 24 | 25 | 26 | Testing Bulk Downloads with a Premium Database 27 | -------------------------------------------------- 28 | 29 | The bulk download methods require a premium database, therefore, the tests 30 | for bulk downloads are skipped by default. 31 | 32 | To enable them, set the environment variable `QUANDL_PREMIUM` to a premium 33 | database you are subscribed to, prior to running the tests. For example: 34 | 35 | $ export QUANDL_KEY=your_key_here 36 | $ export QUANDL_PREMIUM=EOD 37 | $ phpunit --stop-on-failure . 38 | 39 | 40 | Travis 41 | -------------------------------------------------- 42 | 43 | If many tests are executed through Travis CI, they will eventually fail due 44 | to Quandl key-less API quota. You can define the `QUANDL_KEY` environmnt 45 | variable in the travis repo settings. 46 | 47 | -------------------------------------------------------------------------------- /examples.php: -------------------------------------------------------------------------------- 1 | getSymbol($symbol); 18 | } 19 | 20 | // Example 2: API Key + JSON 21 | function example2($api_key, $symbol) { 22 | $quandl = new Quandl($api_key); 23 | $quandl->format = "json"; 24 | return $quandl->getSymbol($symbol); 25 | } 26 | 27 | // Example 3: Date Range + Last URL 28 | function example3($api_key, $symbol) { 29 | $quandl = new Quandl($api_key); 30 | print $quandl->last_url; 31 | return $quandl->getSymbol($symbol, [ 32 | "trim_start" => "today-30 days", 33 | "trim_end" => "today", 34 | ]); 35 | } 36 | 37 | // Example 4: CSV + More parameters 38 | function example4($api_key, $symbol) { 39 | $quandl = new Quandl($api_key, "csv"); 40 | return $quandl->getSymbol($symbol, [ 41 | "sort_order" => "desc", // asc|desc 42 | "exclude_headers" => true, 43 | "rows" => 10, 44 | "column" => 4, // 4 = close price 45 | ]); 46 | } 47 | 48 | // Example 5: XML + Frequency 49 | function example5($api_key, $symbol) { 50 | $quandl = new Quandl($api_key, "xml"); 51 | return $quandl->getSymbol($symbol, [ 52 | "collapse" => "weekly" // none|daily|weekly|monthly|quarterly|annual 53 | ]); 54 | } 55 | 56 | // Example 6: Search 57 | function example6($api_key, $symbol) { 58 | $quandl = new Quandl($api_key); 59 | return $quandl->getSearch("crude oil"); 60 | } 61 | 62 | // Example 7: Symbol Lists 63 | function example7($api_key, $symbol) { 64 | $quandl = new Quandl($api_key, "csv"); 65 | return $quandl->getList("WIKI", 1, 10); 66 | } 67 | 68 | // Example 8: Meta Data 69 | function example8($api_key, $symbol) { 70 | $quandl = new Quandl($api_key); 71 | return $quandl->getMeta($symbol); 72 | } 73 | 74 | // Example 9: List of Databases 75 | function example9($api_key, $symbol=null) { 76 | $quandl = new Quandl($api_key); 77 | return $quandl->getDatabases(); 78 | } 79 | 80 | // Example 10: Direct Call (access any Quandl endpoint) 81 | function example10($api_key, $symbol=null) { 82 | $quandl = new Quandl($api_key); 83 | return $quandl->get('databases/WIKI'); 84 | } 85 | 86 | // Example 11: Error Handling 87 | function example11($api_key, $symbol) { 88 | $quandl = new Quandl($api_key, "csv"); 89 | $result = $quandl->getSymbol("DEBUG/INVALID"); 90 | if($quandl->error and !$result) 91 | return $quandl->error . " - " . $quandl->last_url; 92 | return $result; 93 | } 94 | ?> -------------------------------------------------------------------------------- /tests/QuandlTest.php: -------------------------------------------------------------------------------- 1 | "2014-01-01", "trim_end" => "2014-02-02"]; 17 | private $cache_file = false; 18 | private $premium_database = null; 19 | 20 | protected function setup() { 21 | if (getenv('QUANDL_KEY')) { 22 | $this->api_key = getenv('QUANDL_KEY'); 23 | } 24 | if (getenv('QUANDL_PREMIUM')) { 25 | $this->premium_database = getenv('QUANDL_PREMIUM'); 26 | } 27 | if (!ini_get('allow_url_fopen')) { 28 | print("Aborted.\nThe tests require 'allow_url_fopen'.\nSet it in your php.ini."); 29 | exit(1); 30 | } 31 | if (!function_exists('curl_version')) { 32 | print("Aborted.\nThe tests require curl and PHP curl.\nMake sure it is installed and configured."); 33 | exit(1); 34 | } 35 | } 36 | 37 | protected function tearDown() { 38 | $this->cache_file and unlink($this->cache_file); 39 | } 40 | 41 | public function testGet() { 42 | $quandl = new Quandl($this->api_key); 43 | $r = $quandl->get("datasets/WIKI/AAPL", ['rows' => 5]); 44 | 45 | $this->assertEquals('WIKI', $r->dataset->database_code); 46 | $this->assertEquals(5, count($r->dataset->data)); 47 | } 48 | 49 | public function testCsv() { 50 | $this->_testGetSymbol("csv", 2800); 51 | } 52 | 53 | public function testXml() { 54 | $this->_testGetSymbol("xml", 14000); 55 | } 56 | 57 | public function testJson() { 58 | $this->_testGetSymbol("json", 4200); 59 | } 60 | 61 | public function testObject() { 62 | $this->_testGetSymbol("object", 7400); 63 | } 64 | 65 | public function testCurl() { 66 | $this->_testGetSymbol("csv", 2800, true); 67 | } 68 | 69 | public function testBulk() { 70 | $this->_testBulk(); 71 | } 72 | 73 | public function testBulkWithCurl() { 74 | $this->_testBulk(true); 75 | } 76 | 77 | public function testInvalidUrl() { 78 | $quandl = new Quandl($this->api_key, "json"); 79 | $r = $quandl->getSymbol("INVALID/SYMBOL", $this->dates); 80 | $this->assertEquals($quandl->error, "Invalid URL"); 81 | } 82 | 83 | public function testGetList() { 84 | $quandl = new Quandl($this->api_key); 85 | $r = $quandl->getList("WIKI", 1, 10); 86 | $this->assertEquals(10, count($r->datasets)); 87 | $this->assertEquals("WIKI", $r->datasets[0]->database_code); 88 | } 89 | 90 | public function testGetSearch() { 91 | $quandl = new Quandl($this->api_key); 92 | $r = $quandl->getSearch("crud oil", 1, 10); 93 | $this->assertEquals(10, count($r->datasets)); 94 | } 95 | 96 | public function testGetMeta() { 97 | $quandl = new Quandl($this->api_key); 98 | $r = $quandl->getMeta("WIKI/AAPL"); 99 | $this->assertEquals('AAPL', $r->dataset->dataset_code); 100 | $this->assertEquals('WIKI', $r->dataset->database_code); 101 | } 102 | 103 | public function testGetDatabases() { 104 | $quandl = new Quandl($this->api_key); 105 | $r = $quandl->getDatabases(1, 5); 106 | $this->assertEquals(5, count($r->databases)); 107 | $this->assertTrue(array_key_exists('database_code', $r->databases[0])); 108 | } 109 | 110 | public function testCache() { 111 | $quandl = new Quandl($this->api_key); 112 | $quandl->cache_handler = array($this, "cacheHandler"); 113 | $r = $quandl->getSymbol($this->symbol, $this->dates); 114 | $count = count($r->dataset->data); 115 | $this->assertFalse($quandl->was_cached); 116 | 117 | $r = $quandl->getSymbol($this->symbol, $this->dates); 118 | $this->assertEquals($count, count($r->dataset->data)); 119 | 120 | $this->assertTrue($quandl->was_cached); 121 | } 122 | 123 | // --- 124 | 125 | public function cacheHandler($action, $url, $data=null) { 126 | $cache_key = md5("quandl:$url"); 127 | $cache_file = __DIR__ . "/$cache_key"; 128 | 129 | if($action == "get" and file_exists($cache_file)) 130 | return file_get_contents($cache_file); 131 | else if($action == "set") 132 | file_put_contents($cache_file, $data); 133 | 134 | $this->cache_file = $cache_file; 135 | 136 | return false; 137 | } 138 | 139 | private function _testGetSymbol($format, $length, $force_curl=false) { 140 | $quandl = new Quandl($this->api_key, $format); 141 | $quandl->force_curl = $quandl->no_ssl_verify = $force_curl; 142 | $r = $quandl->getSymbol($this->symbol, $this->dates); 143 | $quandl_format = $format; 144 | if(is_object($r)) { 145 | $r = serialize($r); 146 | $quandl_format = "json"; 147 | } 148 | 149 | $this->assertGreaterThan($length, strlen($r), "Length is shorter ($format)"); 150 | 151 | $this->assertEquals( 152 | "https://www.quandl.com/api/v3/datasets/{$this->symbol}.{$quandl_format}?trim_start={$this->dates['trim_start']}&trim_end={$this->dates['trim_end']}&auth_token={$this->api_key}", 153 | $quandl->last_url, "URL Mismatch ($format)"); 154 | } 155 | 156 | private function _testBulk($force_curl=false) { 157 | if (!$this->premium_database) { 158 | $this->markTestSkipped('Premium database is not available. Use QUANDL_PREMIUM environment variable to set a database for testing'); 159 | return; 160 | } 161 | 162 | $quandl = new Quandl($this->api_key); 163 | $quandl->force_curl = $quandl->no_ssl_verify = $force_curl; 164 | 165 | $filename = "tmp.zip"; 166 | 167 | @unlink($filename); 168 | $this->assertFileNotExists($filename); 169 | 170 | $r = $quandl->getBulk($this->premium_database, $filename); 171 | 172 | $this->assertFileExists($filename); 173 | $this->assertGreaterThan(800, filesize($filename)); 174 | } 175 | 176 | } 177 | ?> -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHP Quandl 2 | ========== 3 | 4 | [![Latest Stable Version](https://poser.pugx.org/dannyben/php-quandl/v/stable)](https://packagist.org/packages/dannyben/php-quandl) 5 | [![Build Status](https://travis-ci.com/DannyBen/php-quandl.svg?branch=master)](https://travis-ci.com/DannyBen/php-quandl) 6 | [![Maintainability](https://api.codeclimate.com/v1/badges/3fcbce3c55d20f908c8e/maintainability)](https://codeclimate.com/github/DannyBen/php-quandl/maintainability) 7 | 8 | --- 9 | 10 | This library provides easy access to the [Quandl API][1] using PHP. 11 | 12 | It provides several convenience methods to common Quandl API endpoints, as 13 | well as a generic method to access any of Quandl's endpoints directly. 14 | 15 | 16 | --- 17 | 18 | Geting Started 19 | -------------- 20 | 21 | Include the `Quandl.php` class in your code, and run one of the examples. 22 | 23 | To install with composer: 24 | 25 | ```sh 26 | $ composer require dannyben/php-quandl 27 | ``` 28 | 29 | Examples 30 | -------- 31 | 32 | This is a basic call. It will return a PHP object with price 33 | data for AAPL: 34 | 35 | ```php 36 | $api_key = "YOUR_KEY_HERE"; 37 | $quandl = new Quandl($api_key); 38 | $data = $quandl->getSymbol("WIKI/AAPL"); 39 | ``` 40 | 41 | You may pass any parameter that is mentioned in the Quandl 42 | documentation: 43 | 44 | ```php 45 | $quandl = new Quandl($api_key); 46 | $data = $quandl->getSymbol($symbol, [ 47 | "sort_order" => "desc", 48 | "rows" => 10, 49 | "column_index" => 4, 50 | ]); 51 | ``` 52 | 53 | The date range options get a special treatment. You may use 54 | any date string that PHP's `strtotime()` understands. 55 | 56 | ```php 57 | $quandl = new Quandl($api_key, "csv"); 58 | $data = $quandl->getSymbol($symbol, [ 59 | "trim_start" => "today-30 days", 60 | "trim_end" => "today", 61 | ]); 62 | ``` 63 | 64 | You can also search the entire Quandl database and get a list of 65 | supported symbols in a data source: 66 | 67 | ```php 68 | $quandl = new Quandl($api_key); 69 | $data = $quandl->getSearch("crude oil"); 70 | $data = $quandl->getList("WIKI", 1, 10); 71 | ``` 72 | 73 | To access any Quandl API endpoint directly, use the `get` method 74 | 75 | ```php 76 | $quandl = new Quandl($api_key); 77 | $data = $quandl->get("databases/WIKI"); 78 | ``` 79 | 80 | More examples can be found in the [examples.php](https://github.com/DannyBen/php-quandl/blob/master/examples.php) file 81 | 82 | Caching 83 | ------- 84 | 85 | You may provide the `quandl` object with a cache handler function. 86 | This function should be responsible for both reading from your cache and storing to it. 87 | 88 | See the [example_cache.php](https://github.com/DannyBen/php-quandl/blob/master/example_cache.php) file. 89 | 90 | 91 | Reference 92 | --------- 93 | 94 | ### Constructor 95 | 96 | The constructor accepts two optional parameters: `$api_key` and `$format`: 97 | 98 | ```php 99 | $quandl = new Quandl("YOUR KEY", "csv"); 100 | ``` 101 | 102 | You may also set these properties later (see below); 103 | 104 | 105 | 106 | 107 | 108 | 109 | ### Public Properties 110 | 111 | 112 | #### `$api_key` 113 | 114 | ```php 115 | $quandl->api_key = "YOUR KEY"; 116 | ``` 117 | Set your API key 118 | 119 | #### `$format` 120 | 121 | ```php 122 | $quandl->format = 'csv'; 123 | ``` 124 | 125 | Set the output format. Can be: `csv`, `xml`, `json`, and `object` 126 | (which will return a php object obtained with `json_decode()`). 127 | 128 | 129 | #### `$force_curl` 130 | 131 | ```php 132 | $quandl->force_curl = true; 133 | ``` 134 | 135 | Force download using curl. By default, we will try to download with 136 | `file_get_contents` if available, and fall back to `curl` only as a last 137 | resort. 138 | 139 | 140 | #### `$no_ssl_verify` 141 | 142 | ```php 143 | $quandl->no_ssl_verify = true; 144 | ``` 145 | 146 | Disables curl SSL verification. Set to true if you get an error saying 147 | "SSL certificate problem". 148 | 149 | 150 | #### `$timeout` 151 | 152 | ```php 153 | $quandl->timeout = 60; 154 | ``` 155 | 156 | Set the timeout for the download operations. 157 | 158 | 159 | #### `$last_url` 160 | 161 | ```php 162 | print $quandl->last_url; 163 | ``` 164 | 165 | Holds the last API URL as requested from Quandl, for debugging. 166 | 167 | 168 | #### `$error` 169 | 170 | ```php 171 | print $quandl->error; 172 | ``` 173 | 174 | In case there was an error getting the data from Quandl, the request 175 | response will be `false` and this property will contain the error message. 176 | 177 | #### `$was_cached` 178 | 179 | ```php 180 | print $quandl->was_cached; 181 | ``` 182 | 183 | When using a cache handler, this property will be set to `true` if the 184 | response came from the cache. 185 | 186 | 187 | 188 | 189 | ### Methods 190 | 191 | #### `get` 192 | 193 | ```php 194 | mixed get( string $path [, array $params ] ) 195 | 196 | // Examples 197 | $data = $quandl->get( 'datasets/EOD/QQQ' ); 198 | $data = $quandl->get( 'datasets/EOD/QQQ', ['rows' => 5] ); 199 | ``` 200 | 201 | Returns an object containing the response from any of Quandl's API 202 | endpoints. The format of the result depends on the value of 203 | `$quandl->format`. 204 | 205 | The optional parameters array is an associative `key => value` 206 | array with any of the parameters supported by Quandl. 207 | 208 | You do not need to pass `auth_token` in the array, it will be 209 | automatically appended. 210 | 211 | 212 | #### `getSymbol` 213 | 214 | ```php 215 | mixed getSymbol( string $symbol [, array $params ] ) 216 | 217 | // Examples 218 | $data = $quandl->getSymbol( 'WIKI/AAPL' ); 219 | $data = $quandl->getSymbol( 'WIKI/AAPL', ['rows' => 5] ); 220 | ``` 221 | 222 | Returns an object containing data for a given symbol. The format 223 | of the result depends on the value of `$quandl->format`. 224 | 225 | The optional parameters array is an associative `key => value` 226 | array with any of the parameters supported by Quandl. 227 | 228 | You do not need to pass `auth_token` in the array, it will be 229 | automatically appended. 230 | 231 | 232 | #### `getSearch` 233 | 234 | ```php 235 | mixed getSearch( string $query [, int $page, int $per_page] ) 236 | 237 | // Examples 238 | $data = $quandl->getSearch( "gold" ); 239 | $data = $quandl->getSearch( "gold", 1, 10 ); 240 | ``` 241 | 242 | Returns a search result object. Number of results per page is 243 | limited to 300 by default. 244 | 245 | Note that currently Quandl does not support CSV response for this 246 | node so if `$quandl->format` is "csv", this call will return a JSON 247 | string instead. 248 | 249 | 250 | #### `getList` 251 | 252 | ```php 253 | mixed getList( string $source [, int $page, int $per_page] ) 254 | 255 | // Examples 256 | $data = $quandl->getList( 'WIKI' ); 257 | $data = $quandl->getList( 'WIKI', 1, 10 ); 258 | ``` 259 | 260 | Returns a list of symbols in a given source. Number of results per page is 261 | limited to 300 by default. 262 | 263 | 264 | #### `getMeta` 265 | 266 | ```php 267 | mixed getMeta( string $source ) 268 | 269 | // Example 270 | $data = $quandl->getMeta( 'WIKI' ); 271 | ``` 272 | 273 | Returns metadata about a symbol. 274 | 275 | 276 | #### `getDatabases` 277 | 278 | ```php 279 | mixed getDatabases( [int $page, int $per_page] ) 280 | 281 | // Examples 282 | $data = $quandl->getDatabases(); 283 | $data = $quandl->getDatabases( 1, 10 ); 284 | ``` 285 | 286 | Returns a list of available databases. Number of results per page is 287 | limited to 100 by default. 288 | 289 | 290 | #### `getBulk` 291 | 292 | > This feature is only supported with premium databases. 293 | 294 | ```php 295 | boolean getBulk( string $database, string $path [, boolean $complete] ) 296 | 297 | // Examples 298 | boolean getBulk( 'EOD', 'eod-partial.zip' ); 299 | boolean getBulk( 'EOD', 'eod-full.zip', true ); 300 | ``` 301 | 302 | Downloads the entire database and saves it to a ZIP file. If `$complete` 303 | is true (false by default), it will download the entire database, otherwise, 304 | it will download the last day only. 305 | 306 | 307 | 308 | [1]: https://www.quandl.com/help/api 309 | -------------------------------------------------------------------------------- /Quandl.php: -------------------------------------------------------------------------------- 1 | 'https://www.quandl.com/api/v3/%s.%s?%s', 20 | "symbol" => 'https://www.quandl.com/api/v3/datasets/%s.%s?%s', 21 | "search" => 'https://www.quandl.com/api/v3/datasets.%s?%s', 22 | "list" => 'https://www.quandl.com/api/v3/datasets.%s?%s', 23 | "meta" => 'https://www.quandl.com/api/v3/datasets/%s/metadata.%s', 24 | "dbs" => 'https://www.quandl.com/api/v3/databases.%s?%s', 25 | "bulk" => 'https://www.quandl.com/api/v3/databases/%s/data?%s', 26 | ]; 27 | 28 | // --- API Methods 29 | 30 | public function __construct($api_key=null, $format="object") { 31 | $this->api_key = $api_key; 32 | $this->format = $format; 33 | } 34 | 35 | // get provides access to any Quandl API endpoint. There is no need 36 | // to include the format. 37 | public function get($path, $params=null) { 38 | $url = $this->getUrl("direct", $path, $this->getFormat(), 39 | $this->arrangeParams($params)); 40 | 41 | return $this->getData($url); 42 | } 43 | 44 | // getSymbol returns data for a given symbol. 45 | public function getSymbol($symbol, $params=null) { 46 | $url = $this->getUrl("symbol", $symbol, $this->getFormat(), $this->arrangeParams($params)); 47 | return $this->getData($url); 48 | } 49 | 50 | // getBulk downloads an entire database to a ZIP file. 51 | public function getBulk($database, $filename, $complete=false) { 52 | $params = []; 53 | $params['download_type'] = $complete ? 'complete' : 'partial'; 54 | $url = $this->getUrl("bulk", $database, $this->arrangeParams($params)); 55 | return $this->downloadToFile($url, $filename); 56 | } 57 | 58 | // getMeta returns metadata for a given symbol. 59 | public function getMeta($symbol) { 60 | $url = $this->getUrl("meta", $symbol, $this->getFormat()); 61 | return $this->getData($url); 62 | } 63 | 64 | // getDatabases returns the list of databases. Quandl limits it to 65 | // 100 per page at most. 66 | public function getDatabases($page=1, $per_page=100) { 67 | $params = [ 68 | "per_page" => $per_page, 69 | "page" => $page, 70 | ]; 71 | $url = $this->getUrl("dbs", $this->getFormat(), 72 | $this->arrangeParams($params)); 73 | 74 | return $this->getData($url); 75 | } 76 | 77 | // getSearch returns results for a search query. 78 | // CSV output is not supported with this node so if format 79 | // is set to CSV, the result will fall back to object mode. 80 | public function getSearch($query, $page=1, $per_page=300) { 81 | $params = [ 82 | "per_page" => $per_page, 83 | "page" => $page, 84 | "query" => $query, 85 | ]; 86 | $url = $this->getUrl("search", $this->getFormat(true), 87 | $this->arrangeParams($params)); 88 | 89 | return $this->getData($url); 90 | } 91 | 92 | // getList returns the list of symbols for a given source. 93 | public function getList($source, $page=1, $per_page=300) { 94 | $params = [ 95 | "query" => "*", 96 | "database_code" => $source, 97 | "per_page" => $per_page, 98 | "page" => $page, 99 | ]; 100 | $url = $this->getUrl("list", $this->getFormat(), 101 | $this->arrangeParams($params)); 102 | 103 | return $this->getData($url); 104 | } 105 | 106 | // --- Private Methods 107 | 108 | // getFormat returns one of the three formats supported by Quandl. 109 | // It is here for two reasons: 110 | // 1) we also allow "object" format. this will be sent to Quandl 111 | // as "json" but the getData method will return a json_decoded 112 | // output. 113 | // 2) some Quandl nodes do not support CSV (namely search). 114 | private function getFormat($omit_csv=false) { 115 | if (($this->format == "csv" and $omit_csv) or $this->format == "object") 116 | return "json"; 117 | 118 | return $this->format; 119 | } 120 | 121 | // getUrl receives a kind that points to a URL template and 122 | // a variable number of parameters, which will be replaced 123 | // in the template. 124 | private function getUrl($kind) { 125 | $template = self::$url_templates[$kind]; 126 | $args = array_slice(func_get_args(), 1); 127 | $this->last_url = trim(vsprintf($template, $args), "?&"); 128 | return $this->last_url; 129 | } 130 | 131 | // getData executes the download operation and returns the result 132 | // as is, or json-decoded if "object" type was requested. 133 | private function getData($url) { 134 | $result = $this->executeDownload($url); 135 | return $this->format == "object" ? json_decode($result) : $result; 136 | } 137 | 138 | // executeDownload gets a URL, and returns the downloaded document 139 | // either from cache (if cache_handler is set) or from Quandl. 140 | private function executeDownload($url) { 141 | if ($this->cache_handler == null) 142 | $data = $this->download($url); 143 | else 144 | $data = $this->attemptGetFromCache($url); 145 | 146 | return $data; 147 | } 148 | 149 | // attemptGetFromCache is called if a cache_handler is available. 150 | // It will call the cache handler with a get request, return the 151 | // document if found, and will ask it to store the downloaded 152 | // object where applicable. 153 | private function attemptGetFromCache($url) { 154 | $this->was_cached = false; 155 | $data = call_user_func($this->cache_handler, "get", $url); 156 | if ($data) { 157 | $this->was_cached = true; 158 | } 159 | else { 160 | $data = $this->download($url); 161 | $data and call_user_func($this->cache_handler, "set", $url, $data); 162 | } 163 | 164 | return $data; 165 | } 166 | 167 | // arrangeParams converts a parameters array to a query string. 168 | // In addition, we add some patches: 169 | // 1) trim_start and trim_end are converted from any plain 170 | // language syntax to Quandl format 171 | // 2) api_key is appended 172 | private function arrangeParams($params) { 173 | $this->api_key and $params['auth_token'] = $this->api_key; 174 | if (!$params) return $params; 175 | 176 | foreach(["trim_start", "trim_end"] as $v) { 177 | if (isset($params[$v]) ) 178 | $params[$v] = self::convertToQuandlDate($params[$v]); 179 | } 180 | 181 | return http_build_query($params); 182 | } 183 | 184 | // convertToQuandlDate converts any time string supported by 185 | // PHP (e.g. "today-30 days") to the format needed by Quandl 186 | private static function convertToQuandlDate($time_str) { 187 | return date("Y-m-d", strtotime($time_str)); 188 | } 189 | 190 | // download fetches $url with file_get_contents or curl fallback 191 | // You can force curl download by setting $force_curl to true. 192 | // You can disable SSL verification for curl by setting 193 | // $no_ssl_verify to true (solves "SSL certificate problem") 194 | private function download($url) { 195 | $mode = $this->downloadMode(); 196 | 197 | if ($mode == 'simple') return $this->simpleDownload($url); 198 | if ($mode == 'curl') return $this->curlDownload($url); 199 | 200 | $this->error = "Cannot download. Please enable allow_url_fopen or curl."; 201 | return false; 202 | } 203 | 204 | // downloadToFile fetches $url with file_get_contents or curl fallback 205 | // You can force curl download by setting $force_curl to true. 206 | // You can disable SSL verification for curl by setting 207 | // $no_ssl_verify to true (solves "SSL certificate problem") 208 | private function downloadToFile($url, $path) { 209 | $mode = $this->downloadMode(); 210 | 211 | if ($mode == 'simple') return $this->simpleDownloadFile($url, $path); 212 | if ($mode == 'curl') return $this->curlDownloadFile($url, $path); 213 | 214 | $this->error = "Cannot download. Please enable allow_url_fopen or curl."; 215 | return false; 216 | } 217 | 218 | // downloadMode determines if we can download with 219 | // file_get_contents/fopen or curl. 220 | private function downloadMode() { 221 | if (ini_get('allow_url_fopen') and !$this->force_curl) 222 | return 'simple'; 223 | 224 | if (function_exists('curl_version')) 225 | return 'curl'; 226 | 227 | return 'unknown'; 228 | } 229 | 230 | // simpleDownload gets a URL using file_get_contents and returns its content 231 | private function simpleDownload($url) { 232 | // Set timeout, doesnt seem to work with ini_set 233 | // $this->timeout and ini_set('default_socket_timeout', $this->timeout); 234 | if ($this->timeout) { 235 | $context = stream_context_create( ['http' => ['timeout' => $this->timeout]] ); 236 | $data = @file_get_contents($url, false, $context); 237 | } 238 | else { 239 | $data = @file_get_contents($url); 240 | } 241 | 242 | $data or $this->error = ($this->timeout ? "Invalid URL or timed out" : "Invalid URL"); 243 | return $data; 244 | } 245 | 246 | // simpleDownloadFile downloads a file with fopen and saves the content to 247 | // disk. 248 | private function simpleDownloadFile($url, $path) { 249 | if ($this->timeout) { 250 | $context = stream_context_create( ['http' => ['timeout' => $this->timeout]] ); 251 | $success = @file_put_contents($path, fopen($url, 'r', false, $context)); 252 | } 253 | else { 254 | $success = @file_put_contents($path, fopen($url, 'r')); 255 | } 256 | 257 | $success or $this->error = ($this->timeout ? "Invalid URL or timed out" : "Invalid URL"); 258 | return $success; 259 | } 260 | 261 | // curlDownload is the curl equivalent of simpleDownload. 262 | private function curlDownload($url) { 263 | $options = [ 264 | CURLOPT_URL => $url, 265 | CURLOPT_RETURNTRANSFER => true 266 | ]; 267 | 268 | return $this->curlExecute($options); 269 | } 270 | 271 | // curlDownloadFile is the curl equivalent of simpleDownloadFile. 272 | private function curlDownloadFile($url, $path) { 273 | $fp = fopen($path, 'w+'); 274 | 275 | $options = [ 276 | CURLOPT_URL => $url, 277 | CURLOPT_FILE => $fp, 278 | CURLOPT_FOLLOWLOCATION => true 279 | ]; 280 | 281 | $response = $this->curlExecute($options); 282 | 283 | fclose($fp); 284 | 285 | return $response; 286 | } 287 | 288 | // curlExecute handles generic curl execution, for DRYing the two other 289 | // functions that rely on curl. 290 | private function curlExecute($options) { 291 | $curl = curl_init(); 292 | 293 | curl_setopt_array($curl, $options); 294 | 295 | $this->timeout and curl_setopt($curl, CURLOPT_TIMEOUT, $this->timeout); 296 | $this->no_ssl_verify and curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); 297 | 298 | $response = curl_exec($curl); 299 | $error = curl_error($curl); 300 | $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); 301 | 302 | curl_close($curl); 303 | 304 | if ($http_code == "404") { 305 | $response = false; 306 | $this->error = "Invalid URL"; 307 | } 308 | else if ($error) { 309 | $response = false; 310 | $this->error = $error; 311 | } 312 | 313 | return $response; 314 | } 315 | } 316 | --------------------------------------------------------------------------------