From 655f8fa6606e90ec2725632db7749d5fdbd2db1b Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Tue, 19 Aug 2025 18:23:03 +0300 Subject: [PATCH 1/5] feat: add `SynonymSet` and `SynonymSets` classes for global synonym management --- src/SynonymSet.php | 77 +++++++++++++++++++++++++++++++++++++ src/SynonymSets.php | 93 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 src/SynonymSet.php create mode 100644 src/SynonymSets.php diff --git a/src/SynonymSet.php b/src/SynonymSet.php new file mode 100644 index 00000000..95aa4775 --- /dev/null +++ b/src/SynonymSet.php @@ -0,0 +1,77 @@ +synonymSetName = $synonymSetName; + $this->apiCall = $apiCall; + } + + /** + * @return string + */ + private function endPointPath(): string + { + return sprintf( + '%s/%s', + SynonymSets::RESOURCE_PATH, + encodeURIComponent($this->synonymSetName) + ); + } + + /** + * @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 retrieve(): array + { + return $this->apiCall->get($this->endPointPath(), []); + } + + /** + * @return array + * @throws TypesenseClientError|HttpClientException + */ + public function delete(): array + { + return $this->apiCall->delete($this->endPointPath()); + } +} \ No newline at end of file diff --git a/src/SynonymSets.php b/src/SynonymSets.php new file mode 100644 index 00000000..e99f1200 --- /dev/null +++ b/src/SynonymSets.php @@ -0,0 +1,93 @@ +apiCall = $apiCall; + } + + /** + * @param string $synonymSetName + * @param array $config + * + * @return array + * @throws TypesenseClientError|HttpClientException + */ + public function upsert(string $synonymSetName, array $config): array + { + return $this->apiCall->put(sprintf('%s/%s', static::RESOURCE_PATH, encodeURIComponent($synonymSetName)), $config); + } + + /** + * @return array + * @throws TypesenseClientError|HttpClientException + */ + public function retrieve(): array + { + return $this->apiCall->get(static::RESOURCE_PATH, []); + } + + /** + * @inheritDoc + */ + public function offsetExists($synonymSetName): bool + { + return isset($this->synonymSets[$synonymSetName]); + } + + /** + * @inheritDoc + */ + public function offsetGet($synonymSetName): SynonymSet + { + if (!isset($this->synonymSets[$synonymSetName])) { + $this->synonymSets[$synonymSetName] = new SynonymSet($synonymSetName, $this->apiCall); + } + + return $this->synonymSets[$synonymSetName]; + } + + /** + * @inheritDoc + */ + public function offsetSet($synonymSetName, $value): void + { + $this->synonymSets[$synonymSetName] = $value; + } + + /** + * @inheritDoc + */ + public function offsetUnset($synonymSetName): void + { + unset($this->synonymSets[$synonymSetName]); + } +} \ No newline at end of file From 2e837e7e1109bfa40c5a4ef8939172e48e584808 Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Tue, 19 Aug 2025 18:23:29 +0300 Subject: [PATCH 2/5] feat(client): register synonym set classes in client --- src/Client.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Client.php b/src/Client.php index a4f8a084..070ab24d 100644 --- a/src/Client.php +++ b/src/Client.php @@ -90,6 +90,11 @@ class Client */ public NLSearchModels $nlSearchModels; + /** + * @var SynonymSets + */ + public SynonymSets $synonymSets; + /** * @var ApiCall */ @@ -121,6 +126,7 @@ public function __construct(array $config) $this->stemming = new Stemming($this->apiCall); $this->conversations = new Conversations($this->apiCall); $this->nlSearchModels = new NLSearchModels($this->apiCall); + $this->synonymSets = new SynonymSets($this->apiCall); } /** @@ -234,4 +240,12 @@ public function getNLSearchModels(): NLSearchModels { return $this->nlSearchModels; } + + /** + * @return SynonymSets + */ + public function getSynonymSets(): SynonymSets + { + return $this->synonymSets; + } } From 9f3feb0ae07d59bc5955ff44dd9ce849c78d90dc Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Tue, 19 Aug 2025 18:24:26 +0300 Subject: [PATCH 3/5] feat(test): add utility for checking to skip old synonym tests --- tests/TestCase.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/TestCase.php b/tests/TestCase.php index 6a26447b..b9852b11 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -6,6 +6,7 @@ use Typesense\Client; use Mockery; use Typesense\ApiCall; +use Exception; abstract class TestCase extends BaseTestCase { @@ -98,4 +99,25 @@ protected function tearDownTypesense(): void $this->typesenseClient->collections[$collection['name']]->delete(); } } + + protected function isV30OrAbove(): bool + { + try { + $debug = $this->typesenseClient->debug->retrieve(); + $version = $debug['version']; + + if ($version === 'nightly') { + return true; + } + + if (preg_match('/^v(\d+)/', $version, $matches)) { + $majorVersion = (int) $matches[1]; + return $majorVersion >= 30; + } + + return false; + } catch (Exception $e) { + return false; + } + } } From 13bf6b51929cbb2790db5096e944c115f35db9f5 Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Tue, 19 Aug 2025 18:24:58 +0300 Subject: [PATCH 4/5] feat(test): skip deprecated API tests for Synonyms and Analytics --- tests/Feature/AnalyticsEventsTest.php | 14 +++++++++++++- tests/Feature/AnalyticsRulesTest.php | 19 ++++++++++++++++--- tests/Feature/SynonymsTest.php | 5 +++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/tests/Feature/AnalyticsEventsTest.php b/tests/Feature/AnalyticsEventsTest.php index c1fa70d6..6b9a433c 100644 --- a/tests/Feature/AnalyticsEventsTest.php +++ b/tests/Feature/AnalyticsEventsTest.php @@ -3,6 +3,7 @@ namespace Feature; use Tests\TestCase; +use Exception; class AnalyticsEventsTest extends TestCase { @@ -11,6 +12,11 @@ class AnalyticsEventsTest extends TestCase protected function setUp(): void { parent::setUp(); + + if ($this->isV30OrAbove()) { + $this->markTestSkipped('Analytics is deprecated in Typesense v30+'); + } + $this->client()->collections->create([ "name" => "products", "fields" => [ @@ -52,7 +58,13 @@ protected function setUp(): void protected function tearDown(): void { parent::tearDown(); - $this->client()->analytics->rules()->{'product_queries_aggregation'}->delete(); + + if (!$this->isV30OrAbove()) { + try { + $this->client()->analytics->rules()->{'product_queries_aggregation'}->delete(); + } catch (Exception $e) { + } + } } public function testCanCreateAnEvent(): void diff --git a/tests/Feature/AnalyticsRulesTest.php b/tests/Feature/AnalyticsRulesTest.php index 882ef896..e5d84ead 100644 --- a/tests/Feature/AnalyticsRulesTest.php +++ b/tests/Feature/AnalyticsRulesTest.php @@ -4,6 +4,7 @@ use Tests\TestCase; use Typesense\Exceptions\ObjectNotFound; +use Exception; class AnalyticsRulesTest extends TestCase { @@ -26,14 +27,26 @@ class AnalyticsRulesTest extends TestCase protected function setUp(): void { parent::setUp(); + + if ($this->isV30OrAbove()) { + $this->markTestSkipped('Analytics is deprecated in Typesense v30+'); + } + $this->ruleUpsertResponse = $this->client()->analytics->rules()->upsert($this->ruleName, $this->ruleConfiguration); } protected function tearDown(): void { - $rules = $this->client()->analytics->rules()->retrieve(); - foreach ($rules['rules'] as $rule) { - $this->client()->analytics->rules()->{$rule['name']}->delete(); + if (!$this->isV30OrAbove()) { + try { + $rules = $this->client()->analytics->rules()->retrieve(); + if (is_array($rules) && isset($rules['rules'])) { + foreach ($rules['rules'] as $rule) { + $this->client()->analytics->rules()->{$rule['name']}->delete(); + } + } + } catch (Exception $e) { + } } } diff --git a/tests/Feature/SynonymsTest.php b/tests/Feature/SynonymsTest.php index 6cd5db05..5a61b871 100644 --- a/tests/Feature/SynonymsTest.php +++ b/tests/Feature/SynonymsTest.php @@ -17,6 +17,11 @@ class SynonymsTest extends TestCase protected function setUp(): void { parent::setUp(); + + if ($this->isV30OrAbove()) { + $this->markTestSkipped('Synonyms is deprecated in Typesense v30+, use SynonymSets instead'); + } + $this->setUpCollection('books'); $this->synonyms = $this->client()->collections['books']->synonyms; From 9ebff7d84357a0ada27a7dc6dffd941db27c1546 Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Tue, 19 Aug 2025 18:25:09 +0300 Subject: [PATCH 5/5] test: add test suite for synonym sets --- tests/Feature/SynonymSetsTest.php | 61 +++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 tests/Feature/SynonymSetsTest.php diff --git a/tests/Feature/SynonymSetsTest.php b/tests/Feature/SynonymSetsTest.php new file mode 100644 index 00000000..8a6ff9ea --- /dev/null +++ b/tests/Feature/SynonymSetsTest.php @@ -0,0 +1,61 @@ + [ + [ + 'id' => 'dummy', + 'synonyms' => ['foo', 'bar', 'baz'], + 'root' => '', + ], + ], + ]; + + protected function setUp(): void + { + parent::setUp(); + + if (!$this->isV30OrAbove()) { + $this->markTestSkipped('SynonymSets is only supported in Typesense v30+'); + } + + $this->synonymSets = $this->client()->synonymSets; + $this->upsertResponse = $this->synonymSets->upsert('test-synonym-set', $this->synonymSetData); + } + + + public function testCanUpsertASynonymSet(): void + { + $this->assertEquals($this->synonymSetData['synonyms'], $this->upsertResponse['synonyms']); + } + + public function testCanRetrieveAllSynonymSets(): void + { + $returnData = $this->synonymSets->retrieve(); + $this->assertCount(1, $returnData); + } + + public function testCanRetrieveASpecificSynonymSet(): void + { + $returnData = $this->synonymSets['test-synonym-set']->retrieve(); + $this->assertEquals($this->synonymSetData['synonyms'], $returnData['synonyms']); + } + + public function testCanDeleteASynonymSet(): void + { + $returnData = $this->synonymSets['test-synonym-set']->delete(); + $this->assertEquals('test-synonym-set', $returnData['name']); + + $this->expectException(ObjectNotFound::class); + $this->synonymSets['test-synonym-set']->retrieve(); + } +} \ No newline at end of file