diff --git a/lib/AlgoliaSearch/Client.php b/lib/AlgoliaSearch/Client.php
index 496d21b6..53f74293 100644
--- a/lib/AlgoliaSearch/Client.php
+++ b/lib/AlgoliaSearch/Client.php
@@ -37,6 +37,7 @@ class Client
const CAINFO = 'cainfo';
const CURLOPT = 'curloptions';
const PLACES_ENABLED = 'placesEnabled';
+ const FAILING_HOSTS_CACHE = 'failingHostsCache';
/**
* @var ClientContext
@@ -94,12 +95,18 @@ public function __construct($applicationID, $apiKey, $hostsArray = null, $option
case self::PLACES_ENABLED:
$this->placesEnabled = (bool) $value;
break;
+ case self::FAILING_HOSTS_CACHE:
+ if (! $value instanceof FailingHostsCache) {
+ throw new \InvalidArgumentException('failingHostsCache must be an instance of \AlgoliaSearch\FailingHostsCache.');
+ }
+ break;
default:
throw new \Exception('Unknown option: '.$option);
}
}
- $this->context = new ClientContext($applicationID, $apiKey, $hostsArray, $this->placesEnabled);
+ $failingHostsCache = isset($options[self::FAILING_HOSTS_CACHE]) ? $options[self::FAILING_HOSTS_CACHE] : null;
+ $this->context = new ClientContext($applicationID, $apiKey, $hostsArray, $this->placesEnabled, $failingHostsCache);
}
/**
@@ -408,13 +415,13 @@ public function initIndex($indexName)
}
/**
- * List all existing user keys with their associated ACLs.
+ * List all existing API keys with their associated ACLs.
*
* @return mixed
*
* @throws AlgoliaException
*/
- public function listUserKeys()
+ public function listApiKeys()
{
return $this->request(
$this->context,
@@ -429,13 +436,22 @@ public function listUserKeys()
}
/**
- * Get ACL of a user key.
+ * @return mixed
+ * @deprecated use listApiKeys instead
+ */
+ public function listUserKeys()
+ {
+ return $this->listApiKeys();
+ }
+
+ /**
+ * Get ACL of a API key.
*
* @param string $key
*
* @return mixed
*/
- public function getUserKeyACL($key)
+ public function getApiKey($key)
{
return $this->request(
$this->context,
@@ -450,13 +466,23 @@ public function getUserKeyACL($key)
}
/**
- * Delete an existing user key.
+ * @param $key
+ * @return mixed
+ * @deprecated use getApiKey instead
+ */
+ public function getUserKeyACL($key)
+ {
+ return $this->getApiKey($key);
+ }
+
+ /**
+ * Delete an existing API key.
*
* @param string $key
*
* @return mixed
*/
- public function deleteUserKey($key)
+ public function deleteApiKey($key)
{
return $this->request(
$this->context,
@@ -471,7 +497,17 @@ public function deleteUserKey($key)
}
/**
- * Create a new user key.
+ * @param $key
+ * @return mixed
+ * @deprecated use deleteApiKey instead
+ */
+ public function deleteUserKey($key)
+ {
+ return $this->deleteApiKey($key);
+ }
+
+ /**
+ * Create a new API key.
*
* @param array $obj can be two different parameters:
* The list of parameters for this key. Defined by an array that
@@ -504,7 +540,7 @@ public function deleteUserKey($key)
*
* @throws AlgoliaException
*/
- public function addUserKey($obj, $validity = 0, $maxQueriesPerIPPerHour = 0, $maxHitsPerQuery = 0, $indexes = null)
+ public function addApiKey($obj, $validity = 0, $maxQueriesPerIPPerHour = 0, $maxHitsPerQuery = 0, $indexes = null)
{
if ($obj !== array_values($obj)) { // is dict of value
$params = $obj;
@@ -537,7 +573,21 @@ public function addUserKey($obj, $validity = 0, $maxQueriesPerIPPerHour = 0, $ma
}
/**
- * Update a user key.
+ * @param $obj
+ * @param int $validity
+ * @param int $maxQueriesPerIPPerHour
+ * @param int $maxHitsPerQuery
+ * @param null $indexes
+ * @return mixed
+ * @deprecated use addApiKey instead
+ */
+ public function addUserKey($obj, $validity = 0, $maxQueriesPerIPPerHour = 0, $maxHitsPerQuery = 0, $indexes = null)
+ {
+ return $this->addApiKey($obj, $validity, $maxQueriesPerIPPerHour, $maxHitsPerQuery, $indexes);
+ }
+
+ /**
+ * Update an API key.
*
* @param string $key
* @param array $obj can be two different parameters:
@@ -571,7 +621,7 @@ public function addUserKey($obj, $validity = 0, $maxQueriesPerIPPerHour = 0, $ma
*
* @throws AlgoliaException
*/
- public function updateUserKey(
+ public function updateApiKey(
$key,
$obj,
$validity = 0,
@@ -608,6 +658,27 @@ public function updateUserKey(
);
}
+ /**
+ * @param $key
+ * @param $obj
+ * @param int $validity
+ * @param int $maxQueriesPerIPPerHour
+ * @param int $maxHitsPerQuery
+ * @param null $indexes
+ * @return mixed
+ * @deprecated use updateApiKey instead
+ */
+ public function updateUserKey(
+ $key,
+ $obj,
+ $validity = 0,
+ $maxQueriesPerIPPerHour = 0,
+ $maxHitsPerQuery = 0,
+ $indexes = null
+ ) {
+ return $this->updateApiKey($key, $obj, $validity, $maxQueriesPerIPPerHour, $maxHitsPerQuery, $indexes);
+ }
+
/**
* Send a batch request targeting multiple indices.
*
@@ -918,7 +989,7 @@ public function doRequest(
curl_close($curlHandle);
if (intval($http_status / 100) == 4) {
- throw new AlgoliaException(isset($answer['message']) ? $answer['message'] : $http_status . ' error');
+ throw new AlgoliaException(isset($answer['message']) ? $answer['message'] : $http_status.' error');
} elseif (intval($http_status / 100) != 2) {
throw new \Exception($http_status.': '.$response);
}
diff --git a/lib/AlgoliaSearch/ClientContext.php b/lib/AlgoliaSearch/ClientContext.php
index ec86dd13..55adeac6 100644
--- a/lib/AlgoliaSearch/ClientContext.php
+++ b/lib/AlgoliaSearch/ClientContext.php
@@ -77,21 +77,20 @@ class ClientContext
public $connectTimeout;
/**
- * @var array
+ * @var FailingHostsCache
*/
- private static $failingHosts = array();
+ private $failingHostsCache;
/**
- * ClientContext constructor.
- *
- * @param string $applicationID
- * @param string $apiKey
- * @param array $hostsArray
- * @param bool $placesEnabled
+ * @param string $applicationID
+ * @param string $apiKey
+ * @param array $hostsArray
+ * @param bool $placesEnabled
+ * @param FailingHostsCache $failingHostsCache
*
* @throws Exception
*/
- public function __construct($applicationID, $apiKey, $hostsArray, $placesEnabled = false)
+ public function __construct($applicationID, $apiKey, $hostsArray, $placesEnabled = false, FailingHostsCache $failingHostsCache = null)
{
// connect timeout of 1s by default
$this->connectTimeout = 1;
@@ -113,8 +112,6 @@ public function __construct($applicationID, $apiKey, $hostsArray, $placesEnabled
$this->writeHostsArray = $this->getDefaultWriteHosts();
}
- $this->rotateHosts();
-
if ($this->applicationID == null || mb_strlen($this->applicationID) == 0) {
throw new Exception('AlgoliaSearch requires an applicationID.');
}
@@ -129,6 +126,14 @@ public function __construct($applicationID, $apiKey, $hostsArray, $placesEnabled
$this->algoliaUserToken = null;
$this->rateLimitAPIKey = null;
$this->headers = array();
+
+ if ($failingHostsCache === null) {
+ $this->failingHostsCache = new InMemoryFailingHostsCache();
+ } else {
+ $this->failingHostsCache = $failingHostsCache;
+ }
+
+ $this->rotateHosts();
}
/**
@@ -182,7 +187,7 @@ private function getDefaultWriteHosts()
*/
public function __destruct()
{
- if ($this->curlMHandle != null) {
+ if (is_resource($this->curlMHandle)) {
curl_multi_close($this->curlMHandle);
}
}
@@ -194,7 +199,7 @@ public function __destruct()
*/
public function getMHandle($curlHandle)
{
- if ($this->curlMHandle == null) {
+ if (!is_resource($this->curlMHandle)) {
$this->curlMHandle = curl_multi_init();
}
curl_multi_add_handle($this->curlMHandle, $curlHandle);
@@ -258,15 +263,20 @@ public function setExtraHeader($key, $value)
}
/**
- * @param $host
+ * @param string $host
*/
- public static function addFailingHost($host)
+ public function addFailingHost($host)
{
- if (! in_array($host, self::$failingHosts)) {
- self::$failingHosts[] = $host;
- }
+ $this->failingHostsCache->addFailingHost($host);
}
+ /**
+ * @return FailingHostsCache
+ */
+ public function getFailingHostsCache()
+ {
+ return $this->failingHostsCache;
+ }
/**
* This method is called to pass on failing hosts.
* If the host is first either in the failingHosts array, we
@@ -276,14 +286,15 @@ public static function addFailingHost($host)
*/
public function rotateHosts()
{
+ $failingHosts = $this->failingHostsCache->getFailingHosts();
$i = 0;
- while ($i <= count($this->readHostsArray) && in_array($this->readHostsArray[0], self::$failingHosts)) {
+ while ($i <= count($this->readHostsArray) && in_array($this->readHostsArray[0], $failingHosts)) {
$i++;
$this->readHostsArray[] = array_shift($this->readHostsArray);
}
$i = 0;
- while ($i <= count($this->writeHostsArray) && in_array($this->writeHostsArray[0], self::$failingHosts)) {
+ while ($i <= count($this->writeHostsArray) && in_array($this->writeHostsArray[0], $failingHosts)) {
$i++;
$this->writeHostsArray[] = array_shift($this->writeHostsArray);
}
diff --git a/lib/AlgoliaSearch/FailingHostsCache.php b/lib/AlgoliaSearch/FailingHostsCache.php
new file mode 100644
index 00000000..17ab336f
--- /dev/null
+++ b/lib/AlgoliaSearch/FailingHostsCache.php
@@ -0,0 +1,23 @@
+failingHostsCacheFile = $this->getDefaultCacheFile();
+ } else {
+ $this->failingHostsCacheFile = (string) $file;
+ }
+
+ $this->assertCacheFileIsValid($this->failingHostsCacheFile);
+
+ if ($ttl === null) {
+ $ttl = 60 * 5; // 5 minutes
+ }
+
+ $this->ttl = (int) $ttl;
+ }
+
+ /**
+ * @return int
+ */
+ public function getTtl()
+ {
+ return $this->ttl;
+ }
+
+ /**
+ * @param $file
+ */
+ private function assertCacheFileIsValid($file)
+ {
+ $fileDirectory = dirname($file);
+
+ if (! is_writable($fileDirectory)) {
+ throw new \RuntimeException(sprintf('Cache file directory "%s" is not writable.', $fileDirectory));
+ }
+
+ if (! file_exists($file)) {
+ // The dir being writable, the file will be created when needed.
+ return;
+ }
+
+ if (! is_readable($file)) {
+ throw new \RuntimeException(sprintf('Cache file "%s" is not readable.', $file));
+ }
+
+ if (! is_writable($file)) {
+ throw new \RuntimeException(sprintf('Cache file "%s" is not writable.', $file));
+ }
+ }
+
+ /**
+ * @return string
+ */
+ private function getDefaultCacheFile()
+ {
+ return sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'algolia-failing-hosts';
+ }
+
+ /**
+ * @param string $host
+ */
+ public function addFailingHost($host)
+ {
+ $cache = $this->loadFailingHostsCacheFromDisk();
+
+ if (isset($cache[self::TIMESTAMP]) && isset($cache[self::FAILING_HOSTS])) {
+ // Update failing hosts cache.
+ // Here we don't take care of invalidating. We do that on retrieval.
+ if (!in_array($host, $cache[self::FAILING_HOSTS])) {
+ $cache[self::FAILING_HOSTS][] = $host;
+ $this->writeFailingHostsCacheFile($cache);
+ }
+ } else {
+ $cache[self::TIMESTAMP] = time();
+ $cache[self::FAILING_HOSTS] = array($host);
+ $this->writeFailingHostsCacheFile($cache);
+ }
+ }
+
+ /**
+ * Get failing hosts from cache. This method should also handle cache invalidation if required.
+ * The TTL of the failed hosts cache should be 5mins.
+ *
+ * @return array
+ */
+ public function getFailingHosts()
+ {
+ $cache = $this->loadFailingHostsCacheFromDisk();
+
+ return isset($cache[self::FAILING_HOSTS]) ? $cache[self::FAILING_HOSTS] : array();
+ }
+
+ /**
+ * Removes the file storing the failing hosts.
+ */
+ public function flushFailingHostsCache()
+ {
+ if (file_exists($this->failingHostsCacheFile)) {
+ unlink($this->failingHostsCacheFile);
+ }
+ }
+
+ /**
+ * @return array
+ */
+ private function loadFailingHostsCacheFromDisk()
+ {
+ if (! file_exists($this->failingHostsCacheFile)) {
+ return array();
+ }
+
+ $json = file_get_contents($this->failingHostsCacheFile);
+ if ($json === false) {
+ return array();
+ }
+
+ $data = json_decode($json, true);
+
+ if (json_last_error() !== JSON_ERROR_NONE) {
+ return array();
+ }
+
+ // Some basic checks.
+ if (
+ !isset($data[self::TIMESTAMP])
+ || !isset($data[self::FAILING_HOSTS])
+ || !is_int($data[self::TIMESTAMP])
+ || !is_array($data[self::FAILING_HOSTS])
+ ) {
+ return array();
+ }
+
+ // Validate the hosts array.
+ foreach ($data[self::FAILING_HOSTS] as $host) {
+ if (!is_string($host)) {
+ return array();
+ }
+ }
+
+ $elapsed = time() - $data[self::TIMESTAMP]; // Number of seconds elapsed.
+
+ if ($elapsed > $this->ttl) {
+ $this->flushFailingHostsCache();
+
+ return array();
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param array $data
+ */
+ private function writeFailingHostsCacheFile(array $data)
+ {
+ $json = json_encode($data);
+ if ($json !== false) {
+ file_put_contents($this->failingHostsCacheFile, $json);
+ }
+ }
+}
diff --git a/lib/AlgoliaSearch/InMemoryFailingHostsCache.php b/lib/AlgoliaSearch/InMemoryFailingHostsCache.php
new file mode 100644
index 00000000..a4cea21c
--- /dev/null
+++ b/lib/AlgoliaSearch/InMemoryFailingHostsCache.php
@@ -0,0 +1,76 @@
+ttl = (int) $ttl;
+ }
+
+
+ /**
+ * @param string $host
+ */
+ public function addFailingHost($host)
+ {
+ if (! in_array($host, self::$failingHosts)) {
+ // Keep a local cache of failed hosts in case the file based strategy doesn't work out.
+ self::$failingHosts[] = $host;
+
+ if (self::$timestamp === null) {
+ self::$timestamp = time();
+ }
+ }
+ }
+
+ /**
+ * Get failing hosts from cache. This method should also handle cache invalidation if required.
+ * The TTL of the failed hosts cache should be 5mins.
+ *
+ * @return array
+ */
+ public function getFailingHosts()
+ {
+ if (self::$timestamp === null) {
+ return self::$failingHosts;
+ }
+
+ $elapsed = time() - self::$timestamp;
+ if ($elapsed > $this->ttl) {
+ $this->flushFailingHostsCache();
+ }
+
+ return self::$failingHosts;
+ }
+
+ public function flushFailingHostsCache()
+ {
+ self::$failingHosts = array();
+ self::$timestamp = null;
+ }
+}
diff --git a/lib/AlgoliaSearch/Index.php b/lib/AlgoliaSearch/Index.php
index 3be615e4..82d76612 100644
--- a/lib/AlgoliaSearch/Index.php
+++ b/lib/AlgoliaSearch/Index.php
@@ -172,9 +172,8 @@ public function addObjects($objects, $objectIDKey = 'objectID')
/**
* Get an object from this index.
*
- * @param $objectID the unique identifier of the object to retrieve
- * @param $attributesToRetrieve (optional) if set, contains the list of attributes to retrieve as a string
- * separated by ","
+ * @param string $objectID the unique identifier of the object to retrieve
+ * @param string[] $attributesToRetrieve (optional) if set, contains the list of attributes to retrieve
*
* @return mixed
*/
@@ -194,6 +193,10 @@ public function getObject($objectID, $attributesToRetrieve = null)
);
}
+ if (is_array($attributesToRetrieve)) {
+ $attributesToRetrieve = implode(',', $attributesToRetrieve);
+ }
+
return $this->client->request(
$this->context,
'GET',
@@ -209,13 +212,14 @@ public function getObject($objectID, $attributesToRetrieve = null)
/**
* Get several objects from this index.
*
- * @param array $objectIDs the array of unique identifier of objects to retrieve
+ * @param array $objectIDs the array of unique identifier of objects to retrieve
+ * @param string[] $attributesToRetrieve (optional) if set, contains the list of attributes to retrieve
*
* @return mixed
*
* @throws \Exception
*/
- public function getObjects($objectIDs)
+ public function getObjects($objectIDs, $attributesToRetrieve = null)
{
if ($objectIDs == null) {
throw new \Exception('No list of objectID provided');
@@ -224,6 +228,15 @@ public function getObjects($objectIDs)
$requests = array();
foreach ($objectIDs as $object) {
$req = array('indexName' => $this->indexName, 'objectID' => $object);
+
+ if ($attributesToRetrieve) {
+ if (is_array($attributesToRetrieve)) {
+ $attributesToRetrieve = implode(',', $attributesToRetrieve);
+ }
+
+ $req['attributesToRetrieve'] = $attributesToRetrieve;
+ }
+
array_push($requests, $req);
}
@@ -412,7 +425,7 @@ public function deleteByQuery($query, $args = array(), $waitLastCall = true)
* Search inside the index.
*
* @param string $query the full text query
- * @param mixed $args (optional) if set, contains an associative array with query parameters:
+ * @param mixed $args (optional) if set, contains an associative array with query parameters:
* - page: (integer) Pagination parameter used to select the page to retrieve.
* Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9
* - hitsPerPage: (integer) Pagination parameter used to select the number of hits per page.
@@ -488,8 +501,8 @@ public function deleteByQuery($query, $args = array(), $waitLastCall = true)
* duplicate value for the attributeForDistinct attribute are removed from results. For example,
* if the chosen attribute is show_name and several hits have the same value for show_name, then
* only the best one is kept and others are removed.
- *
* @return mixed
+ * @throws AlgoliaException
*/
public function search($query, $args = null)
{
@@ -498,6 +511,10 @@ public function search($query, $args = null)
}
$args['query'] = $query;
+ if (isset($args['disjunctiveFacets'])) {
+ return $this->searchWithDisjunctiveFaceting($query, $args);
+ }
+
return $this->client->request(
$this->context,
'POST',
@@ -510,15 +527,148 @@ public function search($query, $args = null)
);
}
+ /**
+ * @param $query
+ * @param $args
+ * @return mixed
+ * @throws AlgoliaException
+ */
+ private function searchWithDisjunctiveFaceting($query, $args)
+ {
+ if (! is_array($args['disjunctiveFacets']) || count($args['disjunctiveFacets']) <= 0) {
+ throw new \InvalidArgumentException('disjunctiveFacets needs to be an non empty array');
+ }
+
+ if (isset($args['filters'])) {
+ throw new \InvalidArgumentException('You can not use disjunctive faceting and the filters parameter');
+ }
+
+ /**
+ * Prepare queries
+ */
+ // Get the list of disjunctive queries to do: 1 per disjunctive facet
+ $disjunctiveQueries = $this->getDisjunctiveQueries($args);
+
+ // Format disjunctive queries for multipleQueries call
+ foreach ($disjunctiveQueries as &$disjunctiveQuery) {
+ $disjunctiveQuery['indexName'] = $this->indexName;
+ $disjunctiveQuery['query'] = $query;
+ unset($disjunctiveQuery['disjunctiveFacets']);
+ }
+
+ // Merge facets and disjunctiveFacets for the hits query
+ $facets = isset($args['facets']) ? $args['facets'] : array();
+ $facets = array_merge($facets, $args['disjunctiveFacets']);
+ unset($args['disjunctiveFacets']);
+
+ // format the hits query for multipleQueries call
+ $args['query'] = $query;
+ $args['indexName'] = $this->indexName;
+ $args['facets'] = $facets;
+
+ // Put the hit query first
+ array_unshift($disjunctiveQueries, $args);
+
+ /**
+ * Do all queries in one call
+ */
+ $results = $this->client->multipleQueries(array_values($disjunctiveQueries));
+ $results = $results['results'];
+
+ /**
+ * Merge facets from disjunctive queries with facets from the hits query
+ */
+
+ // The first query is the hits query that the one we'll return to the user
+ $queryResults = array_shift($results);
+
+ // To be able to add facets from disjunctive query we create 'facets' key in case we only have disjunctive facets
+ if (false === isset($queryResults['facets'])) {
+ $queryResults['facets'] = array();
+ }
+
+ foreach ($results as $disjunctiveResults) {
+ if (isset($disjunctiveResults['facets'])) {
+ foreach ($disjunctiveResults['facets'] as $facetName => $facetValues) {
+ $queryResults['facets'][$facetName] = $facetValues;
+ }
+ }
+ }
+
+ return $queryResults;
+ }
+
+ /**
+ * @param $queryParams
+ * @return array
+ */
+ private function getDisjunctiveQueries($queryParams)
+ {
+ $queriesParams = array();
+
+ foreach ($queryParams['disjunctiveFacets'] as $facetName) {
+ $params = $queryParams;
+ $params['facets'] = array($facetName);
+ $facetFilters = isset($params['facetFilters']) ? $params['facetFilters']: array();
+ $numericFilters = isset($params['numericFilters']) ? $params['numericFilters']: array();
+
+ $additionalParams = array(
+ 'hitsPerPage' => 1,
+ 'page' => 0,
+ 'attributesToRetrieve' => array(),
+ 'attributesToHighlight' => array(),
+ 'attributesToSnippet' => array()
+ );
+
+ $additionalParams['facetFilters'] = $this->getAlgoliaFiltersArrayWithoutCurrentRefinement($facetFilters, $facetName . ':');
+ $additionalParams['numericFilters'] = $this->getAlgoliaFiltersArrayWithoutCurrentRefinement($numericFilters, $facetName);
+
+ $queriesParams[$facetName] = array_merge($params, $additionalParams);
+ }
+
+ return $queriesParams;
+ }
+
+ /**
+ * @param $filters
+ * @param $needle
+ * @return array
+ */
+ private function getAlgoliaFiltersArrayWithoutCurrentRefinement($filters, $needle)
+ {
+ // iterate on each filters which can be string or array and filter out every refinement matching the needle
+ for ($i = 0; $i < count($filters); $i++) {
+ if (is_array($filters[$i])) {
+ foreach ($filters[$i] as $filter) {
+ if (mb_substr($filter, 0, mb_strlen($needle)) === $needle) {
+ unset($filters[$i]);
+ $filters = array_values($filters);
+ $i--;
+ break;
+ }
+ }
+ } else {
+ if (mb_substr($filters[$i], 0, mb_strlen($needle)) === $needle) {
+ unset($filters[$i]);
+ $filters = array_values($filters);
+ $i--;
+ }
+ }
+ }
+
+ return $filters;
+ }
+
/**
* Perform a search inside facets.
*
* @param $facetName
* @param $facetQuery
* @param array $query
+ *
* @return mixed
*/
- public function searchFacet($facetName, $facetQuery, $query = array())
+ public function searchForFacetValues($facetName, $facetQuery, $query = array())
{
$query['facetQuery'] = $facetQuery;
@@ -547,6 +697,7 @@ public function searchFacet($facetName, $facetQuery, $query = array())
*
* @throws AlgoliaException
* @throws \Exception
+ * @deprecated you should use $index->search($query, ['disjunctiveFacets' => $disjunctive_facets]]); instead
*/
public function searchDisjunctiveFaceting($query, $disjunctive_facets, $params = array(), $refinements = array())
{
@@ -752,69 +903,70 @@ public function clearIndex()
/**
* Set settings for this index.
*
- * @param mixed $settings the settings object that can contains :
- * - minWordSizefor1Typo: (integer) the minimum number of characters to accept one typo (default =
- * 3).
- * - minWordSizefor2Typos: (integer) the minimum number of characters to accept two typos (default
- * = 7).
- * - hitsPerPage: (integer) the number of hits per page (default = 10).
- * - attributesToRetrieve: (array of strings) default list of attributes to retrieve in objects.
- * If set to null, all attributes are retrieved.
- * - attributesToHighlight: (array of strings) default list of attributes to highlight.
- * If set to null, all indexed attributes are highlighted.
- * - attributesToSnippet**: (array of strings) default list of attributes to snippet alongside the
- * number of words to return (syntax is attributeName:nbWords). By default no snippet is computed.
- * If set to null, no snippet is computed.
- * - searchableAttributes (formerly named attributesToIndex): (array of strings) the list of fields you want to index.
- * If set to null, all textual and numerical attributes of your objects are indexed, but you
- * should update it to get optimal results. This parameter has two important uses:
- * - Limit the attributes to index: For example if you store a binary image in base64, you want to
- * store it and be able to retrieve it but you don't want to search in the base64 string.
- * - Control part of the ranking*: (see the ranking parameter for full explanation) Matches in
- * attributes at the beginning of the list will be considered more important than matches in
- * attributes further down the list. In one attribute, matching text at the beginning of the
- * attribute will be considered more important than text after, you can disable this behavior if
- * you add your attribute inside `unordered(AttributeName)`, for example searchableAttributes:
- * ["title", "unordered(text)"].
- * - attributesForFaceting: (array of strings) The list of fields you want to use for faceting.
- * All strings in the attribute selected for faceting are extracted and added as a facet. If set
- * to null, no attribute is used for faceting.
- * - attributeForDistinct: (string) The attribute name used for the Distinct feature. This feature
- * is similar to the SQL "distinct" keyword: when enabled in query with the distinct=1 parameter,
- * all hits containing a duplicate value for this attribute are removed from results. For example,
- * if the chosen attribute is show_name and several hits have the same value for show_name, then
- * only the best one is kept and others are removed.
- * - ranking: (array of strings) controls the way results are sorted.
- * We have six available criteria:
- * - typo: sort according to number of typos,
- * - geo: sort according to decreassing distance when performing a geo-location based search,
- * - proximity: sort according to the proximity of query words in hits,
- * - attribute: sort according to the order of attributes defined by searchableAttributes,
- * - exact:
- * - if the user query contains one word: sort objects having an attribute that is exactly the
- * query word before others. For example if you search for the "V" TV show, you want to find it
- * with the "V" query and avoid to have all popular TV show starting by the v letter before it.
- * - if the user query contains multiple words: sort according to the number of words that matched
- * exactly (and not as a prefix).
- * - custom: sort according to a user defined formula set in **customRanking** attribute.
- * The standard order is ["typo", "geo", "proximity", "attribute", "exact", "custom"]
- * - customRanking: (array of strings) lets you specify part of the ranking.
- * The syntax of this condition is an array of strings containing attributes prefixed by asc
- * (ascending order) or desc (descending order) operator. For example `"customRanking" =>
- * ["desc(population)", "asc(name)"]`
- * - queryType: Select how the query words are interpreted, it can be one of the following value:
- * - prefixAll: all query words are interpreted as prefixes,
- * - prefixLast: only the last word is interpreted as a prefix (default behavior),
- * - prefixNone: no query word is interpreted as a prefix. This option is not recommended.
- * - highlightPreTag: (string) Specify the string that is inserted before the highlighted parts in
- * the query result (default to "").
- * - highlightPostTag: (string) Specify the string that is inserted after the highlighted parts in
- * the query result (default to "").
- * - optionalWords: (array of strings) Specify a list of words that should be considered as
- * optional when found in the query.
- *
- * @param bool $forwardToReplicas
+ * @param mixed $settings the settings object that can contains :
+ * - minWordSizefor1Typo: (integer) the minimum number of characters to accept one typo (default =
+ * 3).
+ * - minWordSizefor2Typos: (integer) the minimum number of characters to accept two typos (default
+ * = 7).
+ * - hitsPerPage: (integer) the number of hits per page (default = 10).
+ * - attributesToRetrieve: (array of strings) default list of attributes to retrieve in objects.
+ * If set to null, all attributes are retrieved.
+ * - attributesToHighlight: (array of strings) default list of attributes to highlight.
+ * If set to null, all indexed attributes are highlighted.
+ * - attributesToSnippet**: (array of strings) default list of attributes to snippet alongside the
+ * number of words to return (syntax is attributeName:nbWords). By default no snippet is computed.
+ * If set to null, no snippet is computed.
+ * - searchableAttributes (formerly named attributesToIndex): (array of strings) the list of fields you want to index.
+ * If set to null, all textual and numerical attributes of your objects are indexed, but you
+ * should update it to get optimal results. This parameter has two important uses:
+ * - Limit the attributes to index: For example if you store a binary image in base64, you want to
+ * store it and be able to retrieve it but you don't want to search in the base64 string.
+ * - Control part of the ranking*: (see the ranking parameter for full explanation) Matches in
+ * attributes at the beginning of the list will be considered more important than matches in
+ * attributes further down the list. In one attribute, matching text at the beginning of the
+ * attribute will be considered more important than text after, you can disable this behavior if
+ * you add your attribute inside `unordered(AttributeName)`, for example searchableAttributes:
+ * ["title", "unordered(text)"].
+ * - attributesForFaceting: (array of strings) The list of fields you want to use for faceting.
+ * All strings in the attribute selected for faceting are extracted and added as a facet. If set
+ * to null, no attribute is used for faceting.
+ * - attributeForDistinct: (string) The attribute name used for the Distinct feature. This feature
+ * is similar to the SQL "distinct" keyword: when enabled in query with the distinct=1 parameter,
+ * all hits containing a duplicate value for this attribute are removed from results. For example,
+ * if the chosen attribute is show_name and several hits have the same value for show_name, then
+ * only the best one is kept and others are removed.
+ * - ranking: (array of strings) controls the way results are sorted.
+ * We have six available criteria:
+ * - typo: sort according to number of typos,
+ * - geo: sort according to decreasing distance when performing a geo-location based search,
+ * - proximity: sort according to the proximity of query words in hits,
+ * - attribute: sort according to the order of attributes defined by searchableAttributes,
+ * - exact:
+ * - if the user query contains one word: sort objects having an attribute that is exactly the
+ * query word before others. For example if you search for the "V" TV show, you want to find it
+ * with the "V" query and avoid to have all popular TV show starting by the v letter before it.
+ * - if the user query contains multiple words: sort according to the number of words that matched
+ * exactly (and not as a prefix).
+ * - custom: sort according to a user defined formula set in **customRanking** attribute.
+ * The standard order is ["typo", "geo", "proximity", "attribute", "exact", "custom"]
+ * - customRanking: (array of strings) lets you specify part of the ranking.
+ * The syntax of this condition is an array of strings containing attributes prefixed by asc
+ * (ascending order) or desc (descending order) operator. For example `"customRanking" =>
+ * ["desc(population)", "asc(name)"]`
+ * - queryType: Select how the query words are interpreted, it can be one of the following value:
+ * - prefixAll: all query words are interpreted as prefixes,
+ * - prefixLast: only the last word is interpreted as a prefix (default behavior),
+ * - prefixNone: no query word is interpreted as a prefix. This option is not recommended.
+ * - highlightPreTag: (string) Specify the string that is inserted before the highlighted parts in
+ * the query result (default to "").
+ * - highlightPostTag: (string) Specify the string that is inserted after the highlighted parts in
+ * the query result (default to "").
+ * - optionalWords: (array of strings) Specify a list of words that should be considered as
+ * optional when found in the query.
+ * @param bool $forwardToReplicas
+ *
* @return mixed
+ *
* @throws AlgoliaException
*/
public function setSettings($settings, $forwardToReplicas = false)
@@ -838,13 +990,13 @@ public function setSettings($settings, $forwardToReplicas = false)
}
/**
- * List all existing user keys associated to this index with their associated ACLs.
+ * List all existing API keys associated to this index with their associated ACLs.
*
* @return mixed
*
* @throws AlgoliaException
*/
- public function listUserKeys()
+ public function listApiKeys()
{
return $this->client->request(
$this->context,
@@ -859,7 +1011,26 @@ public function listUserKeys()
}
/**
- * Get ACL of a user key associated to this index.
+ * @deprecated use listApiKeys instead
+ * @return mixed
+ */
+ public function listUserKeys()
+ {
+ return $this->listApiKeys();
+ }
+
+ /**
+ * @deprecated use getApiKey in
+ * @param $key
+ * @return mixed
+ */
+ public function getUserKeyACL($key)
+ {
+ return $this->getApiKey($key);
+ }
+
+ /**
+ * Get ACL of a API key associated to this index.
*
* @param string $key
*
@@ -867,7 +1038,7 @@ public function listUserKeys()
*
* @throws AlgoliaException
*/
- public function getUserKeyACL($key)
+ public function getApiKey($key)
{
return $this->client->request(
$this->context,
@@ -881,8 +1052,9 @@ public function getUserKeyACL($key)
);
}
+
/**
- * Delete an existing user key associated to this index.
+ * Delete an existing API key associated to this index.
*
* @param string $key
*
@@ -890,7 +1062,7 @@ public function getUserKeyACL($key)
*
* @throws AlgoliaException
*/
- public function deleteUserKey($key)
+ public function deleteApiKey($key)
{
return $this->client->request(
$this->context,
@@ -905,7 +1077,17 @@ public function deleteUserKey($key)
}
/**
- * Create a new user key associated to this index.
+ * @param $key
+ * @return mixed
+ * @deprecated use deleteApiKey instead
+ */
+ public function deleteUserKey($key)
+ {
+ return $this->deleteApiKey($key);
+ }
+
+ /**
+ * Create a new API key associated to this index.
*
* @param array $obj can be two different parameters:
* The list of parameters for this key. Defined by a array that
@@ -937,7 +1119,7 @@ public function deleteUserKey($key)
*
* @throws AlgoliaException
*/
- public function addUserKey($obj, $validity = 0, $maxQueriesPerIPPerHour = 0, $maxHitsPerQuery = 0)
+ public function addApiKey($obj, $validity = 0, $maxQueriesPerIPPerHour = 0, $maxHitsPerQuery = 0)
{
// is dict of value
if ($obj !== array_values($obj)) {
@@ -967,7 +1149,21 @@ public function addUserKey($obj, $validity = 0, $maxQueriesPerIPPerHour = 0, $ma
}
/**
- * Update a user key associated to this index.
+ * @param $obj
+ * @param int $validity
+ * @param int $maxQueriesPerIPPerHour
+ * @param int $maxHitsPerQuery
+ * @return mixed
+ * @deprecated use addApiKey instead
+ */
+ public function addUserKey($obj, $validity = 0, $maxQueriesPerIPPerHour = 0, $maxHitsPerQuery = 0)
+ {
+ return $this->addApiKey($obj, $validity, $maxQueriesPerIPPerHour, $maxHitsPerQuery);
+ }
+
+
+ /**
+ * Update an API key associated to this index.
*
* @param string $key
* @param array $obj can be two different parameters:
@@ -1000,7 +1196,7 @@ public function addUserKey($obj, $validity = 0, $maxQueriesPerIPPerHour = 0, $ma
*
* @throws AlgoliaException
*/
- public function updateUserKey($key, $obj, $validity = 0, $maxQueriesPerIPPerHour = 0, $maxHitsPerQuery = 0)
+ public function updateApiKey($key, $obj, $validity = 0, $maxQueriesPerIPPerHour = 0, $maxHitsPerQuery = 0)
{
// is dict of value
if ($obj !== array_values($obj)) {
@@ -1029,6 +1225,20 @@ public function updateUserKey($key, $obj, $validity = 0, $maxQueriesPerIPPerHour
);
}
+ /**
+ * @param $key
+ * @param $obj
+ * @param int $validity
+ * @param int $maxQueriesPerIPPerHour
+ * @param int $maxHitsPerQuery
+ * @return mixed
+ * @deprecated use updateApiKey instead
+ */
+ public function updateUserKey($key, $obj, $validity = 0, $maxQueriesPerIPPerHour = 0, $maxHitsPerQuery = 0)
+ {
+ return $this->updateApiKey($key, $obj, $validity, $maxQueriesPerIPPerHour, $maxHitsPerQuery);
+ }
+
/**
* Send a batch request.
*
@@ -1283,6 +1493,18 @@ public function saveSynonym($objectID, $content, $forwardToReplicas = false)
);
}
+ /**
+ * @deprecated Please use searchForFacetValues instead
+ * @param $facetName
+ * @param $facetQuery
+ * @param array $query
+ * @return mixed
+ */
+ public function searchFacet($facetName, $facetQuery, $query = array())
+ {
+ return $this->searchForFacetValues($facetName, $facetQuery, $query);
+ }
+
/**
* @param string $name
* @param array $arguments
@@ -1291,14 +1513,14 @@ public function saveSynonym($objectID, $content, $forwardToReplicas = false)
*/
public function __call($name, $arguments)
{
- if ($name !== 'browse') {
- return;
- }
+ if ($name === 'browse') {
+ if (count($arguments) >= 1 && is_string($arguments[0])) {
+ return call_user_func_array(array($this, 'doBrowse'), $arguments);
+ }
- if (count($arguments) >= 1 && is_string($arguments[0])) {
- return call_user_func_array(array($this, 'doBrowse'), $arguments);
+ return call_user_func_array(array($this, 'doBcBrowse'), $arguments);
}
- return call_user_func_array(array($this, 'doBcBrowse'), $arguments);
+ return;
}
}
diff --git a/lib/AlgoliaSearch/Json.php b/lib/AlgoliaSearch/Json.php
index 5b02d500..f86819e3 100644
--- a/lib/AlgoliaSearch/Json.php
+++ b/lib/AlgoliaSearch/Json.php
@@ -3,11 +3,7 @@
namespace AlgoliaSearch;
/**
- * Class Json
- * @package AlgoliaSearch
- *
- * Helper class to simplify work with JSON
- *
+ * Class Json.
*/
class Json
{
diff --git a/lib/AlgoliaSearch/Version.php b/lib/AlgoliaSearch/Version.php
index a8e1c592..18b9c605 100644
--- a/lib/AlgoliaSearch/Version.php
+++ b/lib/AlgoliaSearch/Version.php
@@ -29,7 +29,7 @@
class Version
{
- const VALUE = '1.12.1';
+ const VALUE = '1.17.0';
public static $custom_value = '';
@@ -39,12 +39,12 @@ class Version
// Method untouched to keep backward compatibility
public static function get()
{
- return self::VALUE . static::$custom_value;
+ return self::VALUE.static::$custom_value;
}
public static function getUserAgent()
{
- $userAgent = self::$prefixUserAgentSegments . 'Algolia for PHP ('.self::VALUE.')' . static::$suffixUserAgentSegments;
+ $userAgent = self::$prefixUserAgentSegments.'Algolia for PHP ('.self::VALUE.')'.static::$suffixUserAgentSegments;
// Keep backward compatibility
$userAgent .= static::$custom_value;
@@ -54,11 +54,25 @@ public static function getUserAgent()
public static function addPrefixUserAgentSegment($segment, $version)
{
- self::$prefixUserAgentSegments = $segment.' ('.$version.'); '.self::$prefixUserAgentSegments;
+ $prefix = $segment.' ('.$version.'); ';
+
+ if (false === mb_strpos(self::getUserAgent(), $prefix)) {
+ self::$prefixUserAgentSegments = $prefix . self::$prefixUserAgentSegments;
+ }
}
public static function addSuffixUserAgentSegment($segment, $version)
{
- self::$suffixUserAgentSegments .= '; '.$segment.' ('.$version.')';
+ $suffix = '; '.$segment.' ('.$version.')';
+
+ if (false === mb_strpos(self::getUserAgent(), $suffix)) {
+ self::$suffixUserAgentSegments .= $suffix;
+ }
+ }
+
+ public static function clearUserAgentSuffixesAndPrefixes()
+ {
+ self::$suffixUserAgentSegments = '';
+ self::$prefixUserAgentSegments = '';
}
}
diff --git a/lib/AlgoliaSearch/loader.php b/lib/AlgoliaSearch/loader.php
index 0fecfc7f..994f95a0 100644
--- a/lib/AlgoliaSearch/loader.php
+++ b/lib/AlgoliaSearch/loader.php
@@ -33,3 +33,6 @@
require_once __DIR__.'/SynonymType.php';
require_once __DIR__.'/Version.php';
require_once __DIR__.'/Json.php';
+require_once __DIR__.'/FailingHostsCache.php';
+require_once __DIR__.'/FileFailingHostsCache.php';
+require_once __DIR__.'/InMemoryFailingHostsCache.php';