src/Echonest.php
<?php
namespace Chrismou\Echonest;
use GuzzleHttp\ClientInterface;
use Psr\Log\LoggerInterface;
class Echonest
{
/**
* @var \GuzzleHttp\ClientInterface
*/
protected $httpClient;
/**
* @var string
*/
protected $apiKey;
/**
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* @var int
*/
protected $rateLimit;
/**
* @var int
*/
protected $rateLimitRemaining;
/**
* @var string
*/
protected $lastRequestTimestamp;
/**
* @var string
*/
protected $apiUrl = 'http://developer.echonest.com/api/v4/';
/**
* @param ClientInterface $httpClient
* @param string $apiKey
* @param LoggerInterface|null $logger
*/
public function __construct(ClientInterface $httpClient, $apiKey, LoggerInterface $logger = null)
{
$this->httpClient = $httpClient;
$this->apiKey = $apiKey;
$this->logger = $logger;
}
/**
* Make a GET request to the Echonest API
*
* @param string $resource
* @param string $action
* @param array $urlParams
*
* @return \stdClass|null
* @throws Exception\TooManyAttemptsException
*/
public function get($resource, $action, array $params = [])
{
return $this->query('GET', $resource, $action, $params);
}
/**
* Make a POST request to the Echonest API
*
* @param string $resource
* @param string $action
* @param array $urlParams
* @param array $formParms
*
* @return \stdClass|null
* @throws Exception\TooManyAttemptsException
*/
public function post($resource, $action, array $params = [])
{
return $this->query('POST', $resource, $action, $params);
}
/**
* @param string $httpMethod
* @param string $resource
* @param string $action
* @param array $urlParams
* @param array $formParams
* @param bool $autoRateLimit
* @param int $maxAttempts
*
* @return \stdClass|null
* @throws Exception\TooManyAttemptsException
*/
public function query(
$httpMethod,
$resource,
$action,
array $params = [],
array $guzzleOptions = [],
$autoRateLimit = true,
$maxAttempts = 10
) {
if ($autoRateLimit) {
usleep($this->getRateLimitDelay());
}
$options = [];
if (strtolower($httpMethod) === 'post') {
$options['form_params'] = $params;
$urlParams = [];
} else {
$urlParams = $params;
}
$options = array_merge($guzzleOptions, $options);
for ($attempt=1; $attempt<=$maxAttempts; $attempt++) {
try {
$response = $this->doRequest(
$httpMethod,
$this->buildRequestUrl($resource, $action, $urlParams),
$options
);
// If it hasn't thrown an exception, we can assume it's been successful
break;
} catch (\Exception $e) {
$this->writeLog('warning', $e->getMessage());
}
}
if (!isset($response)) {
$message = "Echonest query abandoned after " . $maxAttempts . " failed attempts";
$this->writeLog('error', $message);
throw new \Chrismou\Echonest\Exception\TooManyAttemptsException($message);
}
$this->setRateLimitData($response);
return json_decode($response->getBody());
}
/**
* @param string $httpMethod
* @param string $requestUrl
* @param array $options
*
* @return \Psr\Http\Message\ResponseInterface
*/
protected function doRequest($httpMethod, $requestUrl, array $options)
{
return $this->httpClient->request(
$httpMethod,
$requestUrl,
$options
);
}
/**
* @param \Psr\Http\Message\ResponseInterface
*/
protected function setRateLimitData(\Psr\Http\Message\ResponseInterface $response)
{
$this->rateLimit = (int) $response->getHeader('x-ratelimit-limit');
$this->rateLimitRemaining = (int) $response->getHeader('x-ratelimit-remaining');
$this->lastRequestTimestamp = (int) strtotime($response->getHeader('date')[0]);
}
/**
* @return int
*/
protected function getRateLimitDelay()
{
$wait = 1.1 * 1000000;
if ($this->lastRequestTimestamp) {
$nextMinute = date('U', strtotime(date('Y-m-d H:i:', ((int) $this->lastRequestTimestamp + 60)).'00'));
$now = time();
$diff = $nextMinute - $now;
if ($diff > 0 && $this->rateLimitRemaining > 1) {
$wait = ($diff / ($this->rateLimitRemaining-1)) * 1100000;
}
}
return $wait;
}
/**
* @param string $resource
* @param string $action
* @param array $urlParams
*
* @return string
*/
protected function buildRequestUrl($resource, $action, array $urlParams = [])
{
if (!isset($urlParams['apiKey'])) {
$urlParams['api_key'] = $this->apiKey;
}
// Echonest accept a non standard querystring (ie, multiple "bucket" parameters)
// Build a querystring here, then strip out the index numbers
$encodedParams = preg_replace('/%5B[0-9]+%5D/simU', '', http_build_query($urlParams));
return sprintf(
'%s%s/%s?%s',
$this->apiUrl,
$resource,
$action,
$encodedParams
);
}
/**
* @param string $level
* @param string $message
*/
protected function writeLog($level, $message)
{
if ($this->logger) {
$this->logger->$level($message);
}
}
}