Skip to content

Commit aae2588

Browse files
authored
Merge pull request #3 from typesense/document-updates-and-upserts
Added support for document updates and upserts
2 parents 314fd10 + be04735 commit aae2588

7 files changed

Lines changed: 212 additions & 30 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Here are some examples that walk you through how to use the client: [doc/example
2020

2121
| Typesense Server | typesense-php |
2222
|------------------|----------------|
23+
| \>= v0.16.0 | \>= v4.1.0 |
2324
| \>= v0.15.0 | \>= v4.0.0 |
2425

2526
## Contributing

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
"require": {
3333
"php": ">=7.4",
3434
"guzzlehttp/guzzle": "^7.0",
35-
"ext-json": "*"
35+
"ext-json": "*",
36+
"monolog/monolog": "^2.1"
3637
},
3738
"require-dev": {
3839
"squizlabs/php_codesniffer": "3.*"

examples/collection_operations.php

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,42 @@
106106
);
107107
echo "--------Create Document-------\n";
108108
echo "\n";
109+
110+
echo "--------Upsert Document-------\n";
111+
print_r(
112+
$client->collections['books']->documents->upsert(
113+
[
114+
'id' => '1',
115+
'original_publication_year' => 2008,
116+
'authors' => [
117+
'Suzanne Collins',
118+
],
119+
'average_rating' => 4.6,
120+
'publication_year' => 2008,
121+
'publication_year_facet' => '2008',
122+
'authors_facet' => [
123+
'Suzanne Collins',
124+
],
125+
'title' => 'The Hunger Games',
126+
'image_url' => 'https://images.gr-assets.com/books/1447303603m/2767052.jpg',
127+
'ratings_count' => 4780653,
128+
]
129+
)
130+
);
131+
echo "--------Upsert Document-------\n";
132+
echo "\n";
133+
109134
echo "--------Export Documents-------\n";
110135
$exportedDocStrs = $client->collections['books']->documents->export();
111136
print_r($exportedDocStrs);
112137
echo "--------Export Documents-------\n";
113138
echo "\n";
139+
echo "--------Update Single Document-------\n";
140+
print_r($client->collections['books']->documents['1']->update([
141+
'average_rating' => 4.5,
142+
]));
143+
echo "--------Update Single Document-------\n";
144+
echo "\n";
114145
echo "--------Fetch Single Document-------\n";
115146
print_r($client->collections['books']->documents['1']->retrieve());
116147
echo "--------Fetch Single Document-------\n";
@@ -132,16 +163,36 @@
132163
echo "--------Delete Document-------\n";
133164
echo "\n";
134165
echo "--------Import Documents-------\n";
135-
$docsToImport = [];
166+
$docsToImport = [];
136167
$exportedDocStrsArray = explode('\n', $exportedDocStrs);
137168
foreach ($exportedDocStrsArray as $exportedDocStr) {
138169
$docsToImport[] = json_decode($exportedDocStr, true);
139170
}
140171
$importRes =
141-
$client->collections['books']->documents->createMany($docsToImport);
172+
$client->collections['books']->documents->import($docsToImport);
142173
print_r($importRes);
174+
175+
// Or if you have documents in JSONL format, and want to save the overhead of parsing JSON,
176+
// you can also pass in a JSONL string of documents
177+
// $client->collections['books']->documents->import($exportedDocStrsArray);
143178
echo "--------Import Documents-------\n";
144179
echo "\n";
180+
echo "--------Upsert Documents-------\n";
181+
$upsertRes =
182+
$client->collections['books']->documents->import($docsToImport, [
183+
'action' => 'upsert'
184+
]);
185+
print_r($upsertRes);
186+
echo "--------Upsert Documents-------\n";
187+
echo "\n";
188+
echo "--------Update Documents-------\n";
189+
$upsertRes =
190+
$client->collections['books']->documents->import($docsToImport, [
191+
'action' => 'update'
192+
]);
193+
print_r($upsertRes);
194+
echo "--------Upsert Documents-------\n";
195+
echo "\n";
145196
echo "--------Delete Collection-------\n";
146197
print_r($client->collections['books']->delete());
147198
echo "--------Delete Collection-------\n";

src/ApiCall.php

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Exception;
66
use GuzzleHttp\Exception\GuzzleException;
7+
use Psr\Log\LoggerInterface;
78
use Typesense\Lib\Node;
89
use Typesense\Lib\Configuration;
910
use GuzzleHttp\Exception\ClientException;
@@ -55,6 +56,11 @@ class ApiCall
5556
*/
5657
private int $nodeIndex;
5758

59+
/**
60+
* @var LoggerInterface
61+
*/
62+
public LoggerInterface $logger;
63+
5864
/**
5965
* ApiCall constructor.
6066
*
@@ -63,6 +69,7 @@ class ApiCall
6369
public function __construct(Configuration $config)
6470
{
6571
$this->config = $config;
72+
$this->logger = $config->getLogger();
6673
$this->client = new \GuzzleHttp\Client();
6774
self::$nodes = $this->config->getNodes();
6875
self::$nearestNode = $this->config->getNearestNode();
@@ -96,7 +103,7 @@ private function initializeNodes(): void
96103
public function get(string $endPoint, array $params, bool $asJson = true)
97104
{
98105
return $this->makeRequest('get', $endPoint, $asJson, [
99-
'data' => $params ?? [],
106+
'query' => $params ?? [],
100107
]);
101108
}
102109

@@ -105,41 +112,70 @@ public function get(string $endPoint, array $params, bool $asJson = true)
105112
* @param mixed $body
106113
*
107114
* @param bool $asJson
115+
* @param array $queryParameters
108116
*
109117
* @return array|string
110118
* @throws TypesenseClientError
111119
* @throws GuzzleException
112120
*/
113-
public function post(string $endPoint, $body, bool $asJson = true)
121+
public function post(string $endPoint, $body, bool $asJson = true, array $queryParameters = [])
114122
{
115123
return $this->makeRequest('post', $endPoint, $asJson, [
116124
'data' => $body ?? [],
125+
'query' => $queryParameters ?? []
117126
]);
118127
}
119128

120129
/**
121130
* @param string $endPoint
122131
* @param array $body
123132
*
133+
* @param bool $asJson
134+
* @param array $queryParameters
135+
*
124136
* @return array
125137
* @throws TypesenseClientError|GuzzleException
126138
*/
127-
public function put(string $endPoint, array $body): array
139+
public function put(string $endPoint, array $body, bool $asJson = true, array $queryParameters = []): array
128140
{
129-
return $this->makeRequest('put', $endPoint, true, [
141+
return $this->makeRequest('put', $endPoint, $asJson, [
130142
'data' => $body ?? [],
143+
'query' => $queryParameters ?? []
131144
]);
132145
}
133146

134147
/**
135148
* @param string $endPoint
149+
* @param array $body
150+
*
151+
* @param bool $asJson
152+
* @param array $queryParameters
136153
*
137154
* @return array
138155
* @throws TypesenseClientError|GuzzleException
139156
*/
140-
public function delete(string $endPoint): array
157+
public function patch(string $endPoint, array $body, bool $asJson = true, array $queryParameters = []): array
141158
{
142-
return $this->makeRequest('delete', $endPoint, true, []);
159+
return $this->makeRequest('patch', $endPoint, $asJson, [
160+
'data' => $body ?? [],
161+
'query' => $queryParameters ?? []
162+
]);
163+
}
164+
165+
/**
166+
* @param string $endPoint
167+
*
168+
* @param bool $asJson
169+
* @param array $queryParameters
170+
*
171+
* @return array
172+
* @throws TypesenseClientError|GuzzleException
173+
*/
174+
public function delete(string $endPoint, bool $asJson = true, array $queryParameters = []): array
175+
{
176+
return $this->makeRequest('delete', $endPoint, $asJson, [
177+
'query' => $queryParameters ?? []
178+
]);
143179
}
144180

145181
/**
@@ -166,15 +202,22 @@ private function makeRequest(string $method, string $endPoint, bool $asJson, arr
166202
$url = $node->url() . $endPoint;
167203
$reqOp = $this->getRequestOptions();
168204
if (isset($options['data'])) {
169-
if ($method === 'get') {
170-
$reqOp['query'] = http_build_query($options['data']);
171-
} elseif (is_string($options['data'])) {
205+
if (is_string($options['data'])) {
172206
$reqOp['body'] = $options['data'];
173207
} else {
174208
$reqOp['json'] = $options['data'];
175209
}
176210
}
177211

212+
if (isset($options['query'])) {
213+
foreach ($options['query'] as $key => $value) :
214+
if (is_bool($value)) {
215+
$options['query'][$key] = ($value) ? 'true' : 'false';
216+
}
217+
endforeach;
218+
$reqOp['query'] = http_build_query($options['query']);
219+
}
220+
178221
$response = $this->client->request($method, $url, $reqOp);
179222

180223
$statusCode = $response->getStatusCode();
@@ -315,4 +358,12 @@ public function getException(int $httpCode): TypesenseClientError
315358
return new TypesenseClientError();
316359
}
317360
}
361+
362+
/**
363+
* @return LoggerInterface
364+
*/
365+
public function getLogger()
366+
{
367+
return $this->logger;
368+
}
318369
}

src/Document.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,17 @@ public function retrieve(): array
6767
return $this->apiCall->get($this->endpointPath(), []);
6868
}
6969

70+
/**
71+
* @param array $partialDocument
72+
*
73+
* @return array
74+
* @throws TypesenseClientError|GuzzleException
75+
*/
76+
public function update(array $partialDocument): array
77+
{
78+
return $this->apiCall->patch($this->endpointPath(), $partialDocument);
79+
}
80+
7081
/**
7182
* @return array
7283
* @throws TypesenseClientError|GuzzleException

src/Documents.php

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class Documents implements \ArrayAccess
4141
public function __construct(string $collectionName, ApiCall $apiCall)
4242
{
4343
$this->collectionName = $collectionName;
44-
$this->apiCall = $apiCall;
44+
$this->apiCall = $apiCall;
4545
}
4646

4747
/**
@@ -62,41 +62,83 @@ private function endPointPath(string $action = ''): string
6262
*/
6363
public function create(array $document): array
6464
{
65-
return $this->apiCall->post($this->endPointPath(''), $document);
65+
return $this->apiCall->post($this->endPointPath(''), $document, true);
66+
}
67+
68+
/**
69+
* @param array $document
70+
*
71+
* @return array
72+
* @throws TypesenseClientError|GuzzleException
73+
*/
74+
public function upsert(array $document): array
75+
{
76+
return $this->apiCall->post($this->endPointPath(''), $document, true, ['action' => 'upsert']);
77+
}
78+
79+
/**
80+
* @param array $document
81+
*
82+
* @return array
83+
* @throws TypesenseClientError|GuzzleException
84+
*/
85+
public function update(array $document): array
86+
{
87+
return $this->apiCall->post($this->endPointPath(''), $document, true, ['action' => 'update']);
6688
}
6789

6890
/**
6991
* @param array $documents
92+
* @param array $options
7093
*
7194
* @return array
7295
* @throws TypesenseClientError|GuzzleException|\JsonException
7396
*/
74-
public function createMany(array $documents): array
97+
public function createMany(array $documents, array $options = []): array
7598
{
76-
$res = $this->import(
77-
implode(
78-
"\n",
79-
array_map(
80-
static fn(array $document) => json_encode($document, JSON_THROW_ON_ERROR),
81-
$documents
82-
)
83-
)
99+
$this->apiCall->getLogger()->warning(
100+
"createMany is deprecated and will be removed in a future version. " .
101+
"Use import instead, which now takes both an array of documents or a JSONL string of documents"
84102
);
85-
return array_map(static function ($item) {
86-
return json_decode($item, true, 512, JSON_THROW_ON_ERROR);
87-
}, explode("\n", $res));
103+
return $this->import($documents, $options);
88104
}
89105

90106
/**
91-
* @param string $documents
107+
* @param string|array $documents
108+
* @param array $options
92109
*
93-
* @return string
110+
* @return string|array
94111
* @throws TypesenseClientError
95112
* @throws GuzzleException
113+
* @throws \JsonException
96114
*/
97-
public function import(string $documents): string
115+
public function import($documents, array $options = [])
98116
{
99-
return $this->apiCall->post($this->endPointPath('import'), $documents, false);
117+
if (is_array($documents)) {
118+
$documentsInJSONLFormat = implode(
119+
"\n",
120+
array_map(
121+
static fn(array $document) => json_encode($document, JSON_THROW_ON_ERROR),
122+
$documents
123+
)
124+
);
125+
} else {
126+
$documentsInJSONLFormat = $documents;
127+
}
128+
$resultsInJSONLFormat = $this->apiCall->post(
129+
$this->endPointPath('import'),
130+
$documentsInJSONLFormat,
131+
false,
132+
$options
133+
);
134+
135+
if (is_array($documents)) {
136+
return array_map(static function ($item) {
137+
return json_decode($item, true, 512, JSON_THROW_ON_ERROR);
138+
}, explode("\n", $resultsInJSONLFormat));
139+
} else {
140+
return $resultsInJSONLFormat;
141+
}
100142
}
101143

102144
/**

0 commit comments

Comments
 (0)