Skip to content

Commit a2d319c

Browse files
added update and delete on documents
1 parent 54d9590 commit a2d319c

5 files changed

Lines changed: 509 additions & 15 deletions

File tree

src/ApiCallv4.php

Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,368 @@
1+
<?php
2+
3+
namespace Devloops\Typesence;
4+
5+
use Exception;
6+
use Http\Client\Exception as HttpClientException;
7+
use Http\Client\Exception\HttpException;
8+
use Http\Client\HttpClient;
9+
use Psr\Http\Message\StreamInterface;
10+
use Psr\Log\LoggerInterface;
11+
use Typesense\Exceptions\HTTPStatus0Error;
12+
use Devloops\Typesence\Lib\Configuration;
13+
use Devloops\Typesence\Lib\Node;
14+
use Devloops\Typesence\Exceptions\ServerError;
15+
use Devloops\Typesence\Exceptions\ObjectNotFound;
16+
use Devloops\Typesence\Exceptions\RequestMalformed;
17+
use Devloops\Typesence\Exceptions\ServiceUnavailable;
18+
use Devloops\Typesence\Exceptions\RequestUnauthorized;
19+
use Devloops\Typesence\Exceptions\ObjectAlreadyExists;
20+
use Devloops\Typesence\Exceptions\ObjectUnprocessable;
21+
use Devloops\Typesence\Exceptions\TypesenseClientError;
22+
23+
/**
24+
* Class ApiCall
25+
*
26+
* @package \Typesense
27+
* @date 4/5/20
28+
* @author Abdullah Al-Faqeir <abdullah@devloops.net>
29+
*/
30+
class ApiCallv4
31+
{
32+
33+
private const API_KEY_HEADER_NAME = 'X-TYPESENSE-API-KEY';
34+
35+
/**
36+
* @var \GuzzleHttp\Client
37+
*/
38+
private $client;
39+
40+
/**
41+
* @var \Devloops\Typesence\Lib\Configuration
42+
*/
43+
private Configuration $config;
44+
45+
/**
46+
* @var array|Node[]
47+
*/
48+
private static array $nodes;
49+
50+
/**
51+
* @var Node|null
52+
*/
53+
private static ?Node $nearestNode;
54+
55+
/**
56+
* @var int
57+
*/
58+
private int $nodeIndex;
59+
60+
/**
61+
* @var LoggerInterface|null
62+
*/
63+
public ?LoggerInterface $logger;
64+
65+
/**
66+
* ApiCall constructor.
67+
*
68+
* @param \Devloops\Typesence\Lib\Configuration $config
69+
*/
70+
public function __construct(Configuration $config)
71+
{
72+
$this->config = $config;
73+
$this->logger = null;
74+
$this->client = new \GuzzleHttp\Client();
75+
static::$nodes = $this->config->getNodes();
76+
static::$nearestNode = $this->config->getNearestNode();
77+
$this->nodeIndex = 0;
78+
$this->initializeNodes();
79+
}
80+
81+
/**
82+
* Initialize Nodes
83+
*/
84+
private function initializeNodes(): void
85+
{
86+
if (static::$nearestNode !== null) {
87+
$this->setNodeHealthCheck(static::$nearestNode, true);
88+
}
89+
90+
foreach (static::$nodes as &$node) {
91+
$this->setNodeHealthCheck($node, true);
92+
}
93+
}
94+
95+
/**
96+
* @param string $endPoint
97+
* @param array $params
98+
* @param bool $asJson
99+
*
100+
* @return string|array
101+
* @throws TypesenseClientError
102+
* @throws Exception|HttpClientException
103+
*/
104+
public function get(string $endPoint, array $params, bool $asJson = true)
105+
{
106+
return $this->makeRequest('get', $endPoint, $asJson, [
107+
'query' => $params ?? [],
108+
]);
109+
}
110+
111+
/**
112+
* @param string $endPoint
113+
* @param mixed $body
114+
*
115+
* @param bool $asJson
116+
* @param array $queryParameters
117+
*
118+
* @return array|string
119+
* @throws TypesenseClientError
120+
* @throws HttpClientException
121+
*/
122+
public function post(string $endPoint, $body, bool $asJson = true, array $queryParameters = [])
123+
{
124+
return $this->makeRequest('post', $endPoint, $asJson, [
125+
'data' => $body ?? [],
126+
'query' => $queryParameters ?? []
127+
]);
128+
}
129+
130+
/**
131+
* @param string $endPoint
132+
* @param array $body
133+
*
134+
* @param bool $asJson
135+
* @param array $queryParameters
136+
*
137+
* @return array
138+
* @throws TypesenseClientError|HttpClientException
139+
*/
140+
public function put(string $endPoint, array $body, bool $asJson = true, array $queryParameters = []): array
141+
{
142+
return $this->makeRequest('put', $endPoint, $asJson, [
143+
'data' => $body ?? [],
144+
'query' => $queryParameters ?? []
145+
]);
146+
}
147+
148+
/**
149+
* @param string $endPoint
150+
* @param array $body
151+
*
152+
* @param bool $asJson
153+
* @param array $queryParameters
154+
*
155+
* @return array
156+
* @throws TypesenseClientError|HttpClientException
157+
*/
158+
public function patch(string $endPoint, array $body, bool $asJson = true, array $queryParameters = []): array
159+
{
160+
return $this->makeRequest('patch', $endPoint, $asJson, [
161+
'data' => $body ?? [],
162+
'query' => $queryParameters ?? []
163+
]);
164+
}
165+
166+
/**
167+
* @param string $endPoint
168+
*
169+
* @param bool $asJson
170+
* @param array $queryParameters
171+
*
172+
* @return array
173+
* @throws TypesenseClientError|HttpClientException
174+
*/
175+
public function delete(string $endPoint, bool $asJson = true, array $queryParameters = []): array
176+
{
177+
return $this->makeRequest('delete', $endPoint, $asJson, [
178+
'query' => $queryParameters ?? []
179+
]);
180+
}
181+
182+
/**
183+
* Makes the actual http request, along with retries
184+
*
185+
* @param string $method
186+
* @param string $endPoint
187+
* @param bool $asJson
188+
* @param array $options
189+
*
190+
* @return string|array
191+
* @throws TypesenseClientError|HttpClientException
192+
* @throws Exception
193+
*/
194+
private function makeRequest(string $method, string $endPoint, bool $asJson, array $options)
195+
{
196+
$numRetries = 0;
197+
$lastException = null;
198+
while ($numRetries < $this->config->getNumRetries() + 1) {
199+
$numRetries++;
200+
$node = $this->getNode();
201+
202+
try {
203+
$url = $node->url() . $endPoint;
204+
$reqOp = $this->getRequestOptions();
205+
if (isset($options['data'])) {
206+
if (is_string($options['data']) || $options['data'] instanceof StreamInterface) {
207+
$reqOp['body'] = $options['data'];
208+
} else {
209+
$reqOp['body'] = \json_encode($options['data']);
210+
}
211+
}
212+
213+
if (isset($options['query'])) {
214+
foreach ($options['query'] as $key => $value) :
215+
if (is_bool($value)) {
216+
$options['query'][$key] = ($value) ? 'true' : 'false';
217+
}
218+
endforeach;
219+
$reqOp['query'] = http_build_query($options['query']);
220+
}
221+
222+
$response = $this->client->request(
223+
\strtoupper($method),
224+
$url . '?' . ($reqOp['query'] ?? ''),
225+
$reqOp
226+
);
227+
228+
$statusCode = $response->getStatusCode();
229+
if (0 < $statusCode && $statusCode < 500) {
230+
$this->setNodeHealthCheck($node, true);
231+
}
232+
233+
if (!(200 <= $statusCode && $statusCode < 300)) {
234+
$errorMessage = json_decode($response->getBody()
235+
->getContents(), true, 512, JSON_THROW_ON_ERROR)['message'] ?? 'API error.';
236+
throw $this->getException($statusCode)
237+
->setMessage($errorMessage);
238+
}
239+
240+
return $asJson ? json_decode($response->getBody()
241+
->getContents(), true, 512, JSON_THROW_ON_ERROR) : $response->getBody()
242+
->getContents();
243+
} catch (HttpException $exception) {
244+
if (
245+
$exception->getResponse()
246+
->getStatusCode() === 408
247+
) {
248+
continue;
249+
}
250+
$this->setNodeHealthCheck($node, false);
251+
throw $this->getException($exception->getResponse()
252+
->getStatusCode())
253+
->setMessage($exception->getMessage());
254+
} catch (TypesenseClientError | HttpClientException $exception) {
255+
$this->setNodeHealthCheck($node, false);
256+
throw $exception;
257+
} catch (Exception $exception) {
258+
$this->setNodeHealthCheck($node, false);
259+
$lastException = $exception;
260+
sleep($this->config->getRetryIntervalSeconds());
261+
}
262+
}
263+
264+
if ($lastException) {
265+
throw $lastException;
266+
}
267+
}
268+
269+
/**
270+
* @return array
271+
*/
272+
private function getRequestOptions(): array
273+
{
274+
return [
275+
'headers' => [
276+
static::API_KEY_HEADER_NAME => $this->config->getApiKey(),
277+
]
278+
];
279+
}
280+
281+
/**
282+
* @param Node $node
283+
*
284+
* @return bool
285+
*/
286+
private function nodeDueForHealthCheck(Node $node): bool
287+
{
288+
$currentTimestamp = time();
289+
return ($currentTimestamp - $node->getLastAccessTs()) > $this->config->getHealthCheckIntervalSeconds();
290+
}
291+
292+
/**
293+
* @param Node $node
294+
* @param bool $isHealthy
295+
*/
296+
public function setNodeHealthCheck(Node $node, bool $isHealthy): void
297+
{
298+
$node->setHealthy($isHealthy);
299+
$node->setLastAccessTs(time());
300+
}
301+
302+
/**
303+
* Returns a healthy host from the pool in a round-robin fashion
304+
* Might return an unhealthy host periodically to check for recovery.
305+
*
306+
* @return Node
307+
*/
308+
public function getNode(): Lib\Node
309+
{
310+
if (static::$nearestNode !== null) {
311+
if (static::$nearestNode->isHealthy() || $this->nodeDueForHealthCheck(static::$nearestNode)) {
312+
return static::$nearestNode;
313+
}
314+
}
315+
$i = 0;
316+
while ($i < count(static::$nodes)) {
317+
$i++;
318+
$node = static::$nodes[$this->nodeIndex];
319+
$this->nodeIndex = ($this->nodeIndex + 1) % count(static::$nodes);
320+
if ($node->isHealthy() || $this->nodeDueForHealthCheck($node)) {
321+
return $node;
322+
}
323+
}
324+
325+
/**
326+
* None of the nodes are marked healthy, but some of them could have become healthy since last health check.
327+
* So we will just return the next node.
328+
*/
329+
return static::$nodes[$this->nodeIndex];
330+
}
331+
332+
/**
333+
* @param int $httpCode
334+
*
335+
* @return TypesenseClientError
336+
*/
337+
public function getException(int $httpCode): TypesenseClientError
338+
{
339+
switch ($httpCode) {
340+
case 0:
341+
return new HTTPStatus0Error();
342+
case 400:
343+
return new RequestMalformed();
344+
case 401:
345+
return new RequestUnauthorized();
346+
case 404:
347+
return new ObjectNotFound();
348+
case 409:
349+
return new ObjectAlreadyExists();
350+
case 422:
351+
return new ObjectUnprocessable();
352+
case 500:
353+
return new ServerError();
354+
case 503:
355+
return new ServiceUnavailable();
356+
default:
357+
return new TypesenseClientError();
358+
}
359+
}
360+
361+
/**
362+
* @return LoggerInterface
363+
*/
364+
public function getLogger()
365+
{
366+
return $this->logger;
367+
}
368+
}

src/Client.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ class Client
2929
*/
3030
public $aliases;
3131

32+
/**
33+
* @var MultiSearch
34+
*/
35+
public MultiSearch $multiSearch;
36+
3237
/**
3338
* Client constructor.
3439
*
@@ -41,6 +46,7 @@ public function __construct(array $config)
4146
$this->config = new Configuration($config);
4247
$this->collections = new Collections($this->config);
4348
$this->aliases = new Aliases($this->config);
49+
$this->multiSearch = new MultiSearch($this->config);
4450
}
4551

4652
/**

0 commit comments

Comments
 (0)