diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9408c239..01fcaea3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,7 +4,7 @@ on: [push, pull_request] jobs: test: - name: Run tests with PHP v8.3 + name: Run tests with PHP v8.4 runs-on: ubuntu-latest steps: - name: Start Typesense @@ -24,10 +24,10 @@ jobs: --enable-cors - uses: actions/checkout@v4 - - name: Setup PHP 8.3 + - name: Setup PHP 8.4 uses: shivammathur/setup-php@v2 with: - php-version: "8.3" + php-version: "8.4" coverage: xdebug - uses: php-actions/composer@v6 - name: Run tests diff --git a/src/CurationSet.php b/src/CurationSet.php index db1c7fec..ad466c8e 100644 --- a/src/CurationSet.php +++ b/src/CurationSet.php @@ -22,10 +22,7 @@ class CurationSet */ private ApiCall $apiCall; - /** - * @var CurationSetItems - */ - private CurationSetItems $items; + private array $typesenseCurationSetItems = []; /** * CurationSet constructor. @@ -37,7 +34,24 @@ public function __construct(string $curationSetName, ApiCall $apiCall) { $this->curationSetName = $curationSetName; $this->apiCall = $apiCall; - $this->items = new CurationSetItems($curationSetName, $apiCall); + } + + /** + * @param $id + * + * @return mixed + */ + public function __get($id) + { + if (isset($this->{$id})) { + return $this->{$id}; + } + + if (!isset($this->typesenseCurationSetItems[$id])) { + $this->typesenseCurationSetItems[$id] = new CurationSetItems($this->curationSetName, $this->apiCall); + } + + return $this->typesenseCurationSetItems[$id]; } /** @@ -86,6 +100,6 @@ public function delete(): array */ public function getItems(): CurationSetItems { - return $this->items; + return $this->__get('items'); } } diff --git a/src/SynonymSet.php b/src/SynonymSet.php index 95aa4775..e8d5c86a 100644 --- a/src/SynonymSet.php +++ b/src/SynonymSet.php @@ -21,6 +21,8 @@ class SynonymSet * @var ApiCall */ private ApiCall $apiCall; + + private array $typesenseSynonymSetItems = []; /** * SynonymSet constructor. @@ -34,6 +36,24 @@ public function __construct(string $synonymSetName, ApiCall $apiCall) $this->apiCall = $apiCall; } + /** + * @param $id + * + * @return mixed + */ + public function __get($id) + { + if (isset($this->{$id})) { + return $this->{$id}; + } + + if (!isset($this->typesenseSynonymSetItems[$id])) { + $this->typesenseSynonymSetItems[$id] = new SynonymSetItems($this->synonymSetName, $this->apiCall); + } + + return $this->typesenseSynonymSetItems[$id]; + } + /** * @return string */ @@ -74,4 +94,12 @@ public function delete(): array { return $this->apiCall->delete($this->endPointPath()); } -} \ No newline at end of file + + /** + * @return SynonymSetItems + */ + public function getItems(): SynonymSetItems + { + return $this->__get('items'); + } +} diff --git a/src/SynonymSetItem.php b/src/SynonymSetItem.php new file mode 100644 index 00000000..e3ac6971 --- /dev/null +++ b/src/SynonymSetItem.php @@ -0,0 +1,85 @@ +synonymSetName = $synonymSetName; + $this->itemId = $itemId; + $this->apiCall = $apiCall; + } + + /** + * @return string + */ + private function endPointPath(): string + { + return sprintf( + '%s/%s/items/%s', + SynonymSets::RESOURCE_PATH, + encodeURIComponent($this->synonymSetName), + encodeURIComponent($this->itemId) + ); + } + + /** + * @return array + * @throws TypesenseClientError|HttpClientException + */ + public function retrieve(): array + { + return $this->apiCall->get($this->endPointPath(), []); + } + + /** + * @param array $params + * + * @return array + * @throws TypesenseClientError|HttpClientException + */ + public function upsert(array $params): array + { + return $this->apiCall->put($this->endPointPath(), $params); + } + + /** + * @return array + * @throws TypesenseClientError|HttpClientException + */ + public function delete(): array + { + return $this->apiCall->delete($this->endPointPath()); + } +} diff --git a/src/SynonymSetItems.php b/src/SynonymSetItems.php new file mode 100644 index 00000000..ce7141ad --- /dev/null +++ b/src/SynonymSetItems.php @@ -0,0 +1,98 @@ +synonymSetName = $synonymSetName; + $this->apiCall = $apiCall; + } + + /** + * @return string + */ + private function endPointPath(): string + { + return sprintf( + '%s/%s/items', + SynonymSets::RESOURCE_PATH, + encodeURIComponent($this->synonymSetName) + ); + } + + /** + * @return array + * @throws TypesenseClientError|HttpClientException + */ + public function retrieve(): array + { + return $this->apiCall->get($this->endPointPath(), []); + } + + /** + * @inheritDoc + */ + public function offsetExists($itemId): bool + { + return isset($this->items[$itemId]); + } + + /** + * @inheritDoc + */ + public function offsetGet($itemId): SynonymSetItem + { + if (!isset($this->items[$itemId])) { + $this->items[$itemId] = new SynonymSetItem($this->synonymSetName, $itemId, $this->apiCall); + } + + return $this->items[$itemId]; + } + + /** + * @inheritDoc + */ + public function offsetSet($itemId, $value): void + { + $this->items[$itemId] = $value; + } + + /** + * @inheritDoc + */ + public function offsetUnset($itemId): void + { + unset($this->items[$itemId]); + } +} diff --git a/tests/Feature/SynonymSetItemsTest.php b/tests/Feature/SynonymSetItemsTest.php new file mode 100644 index 00000000..f30428a5 --- /dev/null +++ b/tests/Feature/SynonymSetItemsTest.php @@ -0,0 +1,70 @@ + [ + [ + 'id' => 'synonym-rule-1', + 'synonyms' => ['foo', 'bar', 'baz'], + 'root' => '', + ], + ], + ]; + + protected function setUp(): void + { + parent::setUp(); + + if (!$this->isV30OrAbove()) { + $this->markTestSkipped('SynonymSetItems is only supported in Typesense v30+'); + } + + $this->synonymSets = $this->client()->synonymSets; + $this->synonymSets->upsert('test-synonym-set-items', $this->synonymSetData); + } + + protected function tearDown(): void + { + try { + if ($this->synonymSets !== null) { + $this->synonymSets['test-synonym-set-items']->delete(); + } + } catch (Exception $e) { + // Ignore cleanup errors + } + parent::tearDown(); + } + + public function testCanListItemsInASynonymSet(): void + { + $items = $this->synonymSets['test-synonym-set-items']->getItems()->retrieve(); + + $this->assertIsArray($items); + $this->assertGreaterThan(0, count($items)); + $this->assertEquals('foo', $items[0]['synonyms'][0]); + } + + public function testCanUpsertRetrieveAndDeleteAnItem(): void + { + $upserted = $this->synonymSets['test-synonym-set-items']->getItems()['synonym-rule-1']->upsert([ + 'id' => 'synonym-rule-1', + 'synonyms' => ['red', 'crimson'], + 'root' => '', + ]); + + $this->assertEquals('synonym-rule-1', $upserted['id']); + + $fetched = $this->synonymSets['test-synonym-set-items']->getItems()['synonym-rule-1']->retrieve(); + $this->assertEquals('red', $fetched['synonyms'][0]); + + $deletion = $this->synonymSets['test-synonym-set-items']->getItems()['synonym-rule-1']->delete(); + $this->assertEquals('synonym-rule-1', $deletion['id']); + } +}