diff --git a/src/Client.php b/src/Client.php index 350fbc8..d6e0222 100644 --- a/src/Client.php +++ b/src/Client.php @@ -3,6 +3,10 @@ namespace OramaCloud; use GuzzleHttp\Client as HttpClient; +use OramaCloud\Client\Cache; +use OramaCloud\Client\Query; +use OramaCloud\Manager\Endpoints; +use OramaCloud\Telemetry\Collector; class Client { @@ -10,27 +14,107 @@ class Client private $apiKey; private $endpoint; private $http; + private $id; private $collector; + private $cache = true; - public function __construct($endpoint, $apiKey, $answersApiBaseURL = null) + public function __construct(array $params) { - $this->apiKey = $apiKey; - $this->endpoint = $endpoint; - $this->answersApiBaseURL = $answersApiBaseURL; + $params = $this->validate($params); + $this->id = uniqid('p', true); $this->http = new HttpClient(); + $this->apiKey = $params['api_key']; + $this->endpoint = $params['endpoint']; + $this->answersApiBaseURL = $params['answersApiBaseURL']; + $this->cache = $params['cache']; + + // Telemetry is enabled by default + if ($params['telemetry'] !== false) { + $this->collector = Collector::create([ + 'id' => $this->id, + 'api_key' => $this->apiKey, + 'flushInterval' => $params['telemetry']['flushInterval'] ?? 5000, + 'flushSize' => $params['telemetry']['flushSize'] ?? 25 + ]); + } + + // Cache is enabled by default + if ($this->cache) { + $this->cache = new Cache(); + } + + $this->init(); } public function search(Query $query) { - $endpoint = "{$this->endpoint}/search?api-key={$this->apiKey}"; + $cacheKey = "search-" . $query->toJson(); + + if ($this->cache && $this->cache->has($cacheKey)) { + $roundTripTime = 0; + $searchResults = $this->cache->get($cacheKey); + $cached = true; + } else { + $startTime = microtime(true); + $endpoint = "{$this->endpoint}/search?api-key={$this->apiKey}"; + $response = $this->http->request('POST', $endpoint, [ + 'form_params' => [ + 'q' => $query->toJson() + ] + ]); + + $searchResults = $response->getBody(); - $response = $this->http->request('POST', $endpoint, [ - 'form_params' => [ - 'q' => $query->toJson() - ] - ]); + $endTime = microtime(true); + $roundTripTime = ($endTime - $startTime) * 1000; + $cached = false; - return json_decode($response->getBody(), true); + $this->cache->set($cacheKey, $searchResults); + } + + if ($this->collector !== null) { + $this->collector->add([ + 'rawSearchString' => $query->toArray()['term'], + 'resultsCount' => $searchResults->hits ?? 0, + 'roundTripTime' => $roundTripTime, + 'query' => $query->toJson(), + 'cached' => $cached, + 'searchedAt' => time() + ]); + } + + return json_decode($searchResults, true); + } + + private function validate($params) + { + if (empty($params['api_key'])) { + throw new \InvalidArgumentException('API key is required'); + } + + if (empty($params['endpoint'])) { + throw new \InvalidArgumentException('Endpoint is required'); + } + + if (isset($params['telemetry']) && $params['telemetry'] !== false && !is_array($params['telemetry'])) { + throw new \InvalidArgumentException('Telemetry must be an array'); + } + + $params['telemetry'] = isset($params['telemetry']) ? $params['telemetry'] : [ + 'flushInterval' => 5000, + 'flushSize' => 25 + ]; + + $params['answersApiBaseURL'] = isset($params['answersApiBaseURL']) ? $params['answersApiBaseURL'] : Endpoints::ORAMA_ANSWER_ENDPOINT; + + $params['cache'] = isset($params['cache']) ? $params['cache'] : true; + + return $params; + } + + private function init() + { + // } } diff --git a/src/Client/Cache.php b/src/Client/Cache.php new file mode 100644 index 0000000..c684a2e --- /dev/null +++ b/src/Client/Cache.php @@ -0,0 +1,77 @@ +cache = []; + } + + /** + * Set a value in the cache. + * @param string $key The key under which to store the value. + * @param mixed $value The value to store. + */ + public function set(string $key, $value): void + { + $this->cache[$key] = $value; + } + + /** + * Get a value from the cache. + * @param string $key The key of the value to retrieve. + * @return mixed|null The value or null if not found. + */ + public function get(string $key) + { + return $this->cache[$key] ?? null; + } + + /** + * Check if the cache has a key. + * @param string $key The key to check. + * @return bool True if the cache has the key, false otherwise. + */ + public function has(string $key): bool + { + return isset($this->cache[$key]); + } + + /** + * Delete a value from the cache. + * @param string $key The key of the value to delete. + * @return bool True if the value was successfully deleted, false otherwise. + */ + public function delete(string $key): bool + { + if ($this->has($key)) { + unset($this->cache[$key]); + return true; + } + return false; + } + + /** + * Clear the cache. + */ + public function clear(): void + { + $this->cache = []; + } + + /** + * Get the size of the cache. + * @return int The number of items in the cache. + */ + public function size(): int + { + return count($this->cache); + } +} diff --git a/src/Query.php b/src/Client/Query.php similarity index 79% rename from src/Query.php rename to src/Client/Query.php index 87dfb28..c03ee6d 100644 --- a/src/Query.php +++ b/src/Client/Query.php @@ -1,48 +1,55 @@ term = $term; $this->mode = $mode; } - public function term($term) { + public function term($term) + { $this->term = $term; return $this; } - public function mode($mode) { + public function mode($mode) + { $this->mode = $mode; return $this; } - public function where($property, $operator, $value) { + public function where($property, $operator, $value) + { $this->where[] = new Where($property, $operator, $value); return $this; } - public function limit($limit) { + public function limit($limit) + { $this->limit = $limit; return $this; } - public function offset($offset) { + public function offset($offset) + { $this->offset = $offset; return $this; } - public function toArray() { + public function toArray() + { $array = []; if (!is_null($this->term)) { @@ -72,16 +79,18 @@ public function toArray() { return $array; } - public function toJson() { + public function toJson() + { return json_encode($this->toArray()); } - public static function fromArray($array) { + public static function fromArray($array) + { $query = new Query(); - + $query->term(isset($array['term']) ? $array['term'] : ''); $query->mode(isset($array['mode']) ? $array['mode'] : 'fulltext'); - + if (isset($array['where']) && !is_null($array['where'])) { foreach ($array['where'] as $property => $value) { $query->where($property, key($value), $value[key($value)]); diff --git a/src/QueryParams/Where.php b/src/Client/QueryParams/Where.php similarity index 66% rename from src/QueryParams/Where.php rename to src/Client/QueryParams/Where.php index f5aad11..a7a4bd1 100644 --- a/src/QueryParams/Where.php +++ b/src/Client/QueryParams/Where.php @@ -1,20 +1,23 @@ property = $property; $this->operator = $operator; $this->value = $value; } - public function toArray() { + public function toArray() + { return [ $this->property => [ $this->operator => $this->value diff --git a/src/Manager/Endpoints.php b/src/Manager/Endpoints.php index 651471f..d28e421 100644 --- a/src/Manager/Endpoints.php +++ b/src/Manager/Endpoints.php @@ -15,4 +15,6 @@ class Endpoints const NOTIFY = self::WEBHOOKS_BASE_URL . '/[indexID]/notify'; const SNAPSHOT = self::WEBHOOKS_BASE_URL . '/[indexID]/snapshot'; + + const ORAMA_ANSWER_ENDPOINT = 'https://answer.api.orama.com'; } diff --git a/src/Telemetry/Collector.php b/src/Telemetry/Collector.php new file mode 100644 index 0000000..32d9658 --- /dev/null +++ b/src/Telemetry/Collector.php @@ -0,0 +1,84 @@ +client = new Client(); + $this->data = []; + $this->config = [ + 'id' => $id, + 'api_key' => $apiKey, + 'flushInterval' => $flushInterval, + 'flushSize' => $flushSize + ]; + } + + public function setParams($params) + { + $this->params = $params; + } + + public static function create($config) + { + $collector = new Collector($config['id'], $config['api_key']); + $collector->start(); + + return $collector; + } + + public function add($data) + { + $this->data[] = $data; + + if ($this->params != null && count($this->data) >= $this->config['flushSize']) { + $this->flush(); + } + } + + public function flush() + { + if ($this->params == null || count($this->data) === 0) { + return; + } + + $data = $this->data; + $this->data = []; + + $body = [ + 'source' => 'be', + 'deploymentID' => $this->params['deploymentID'], + 'index' => $this->params['index'], + 'oramaId' => $this->config['id'], + 'oramaVersion' => 'php-sdk-1.0.0', + 'userAgent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null, + 'events' => $data + ]; + + $this->sendBeacon($body); + } + + private function start() + { + $this->flush(); + } + + private function sendBeacon($body) + { + $url = $this->params['endpoint'] . '?api-key=' . $this->config['api_key']; + + $this->client->requestAsync('POST', $url, [ + 'json' => $body + ]); + } +} diff --git a/tests/Feature/ClientTest.php b/tests/Feature/ClientTest.php index 837bc6f..44dc4e1 100644 --- a/tests/Feature/ClientTest.php +++ b/tests/Feature/ClientTest.php @@ -1,13 +1,16 @@ PUBLIC_API_KEY, + 'endpoint' => API_ENDPOINT + ]); $result = $client->search( (new Query()) diff --git a/tests/Unit/QueryTest.php b/tests/Unit/QueryTest.php index 2f3caff..13fcc31 100644 --- a/tests/Unit/QueryTest.php +++ b/tests/Unit/QueryTest.php @@ -1,7 +1,7 @@ where('category', 'eq', 'shoes'); $result = $query->toArray(); - + $this->assertEquals($result['term'], 'red shoes'); $this->assertEquals($result['mode'], 'fulltext'); $this->assertEquals($result['where'], [ @@ -63,6 +63,6 @@ ]; $query = Query::fromArray($params); - + $this->assertEquals($params, json_decode($query->toJson(), true)); -}); \ No newline at end of file +});