Skip to content

Commit 54e8996

Browse files
authored
Add support for synonym management (#16)
* Add support for synonym management * Peer review feedback * Update response for create/update synonym * Remove explicit forager dependency * Update forager minimum dependency
1 parent f656b5d commit 54e8996

13 files changed

+394
-2
lines changed

_config/synonyms.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
Name: forager-bifrost-synonyms
3+
Only:
4+
envvarset: 'BIFROST_MANAGEMENT_API_KEY'
5+
After:
6+
- 'silverstripe-forager-elastic-enterprise-synonyms'
7+
- 'silverstripe-forager-synonyms'
8+
---
9+
SilverStripe\Core\Injector\Injector:
10+
# Adaptors provided by this module
11+
SilverStripe\Forager\Interfaces\Requests\CreateSynonymRuleAdaptor:
12+
class: SilverStripe\ForagerBifrost\Adaptors\Requests\CreateSynonymRuleAdaptor
13+
SilverStripe\Forager\Interfaces\Requests\GetSynonymRuleAdaptor:
14+
class: SilverStripe\ForagerBifrost\Adaptors\Requests\GetSynonymRuleAdaptor
15+
SilverStripe\Forager\Interfaces\Requests\GetSynonymRulesAdaptor:
16+
class: SilverStripe\ForagerBifrost\Adaptors\Requests\GetSynonymRulesAdaptor
17+
SilverStripe\Forager\Interfaces\Requests\UpdateSynonymRuleAdaptor:
18+
class: SilverStripe\ForagerBifrost\Adaptors\Requests\UpdateSynonymRuleAdaptor
19+
SilverStripe\Forager\Interfaces\Requests\DeleteSynonymRuleAdaptor:
20+
class: SilverStripe\ForagerBifrost\Adaptors\Requests\DeleteSynonymRuleAdaptor
21+
# Adaptors provided by the ElasticEnterprise dependency
22+
SilverStripe\Forager\Interfaces\Requests\GetSynonymCollectionsAdaptor:
23+
class: SilverStripe\ForagerElasticEnterprise\Adaptors\Requests\GetSynonymCollectionsAdaptor
24+
# Request overrides to work with the Bifröst API
25+
Elastic\EnterpriseSearch\AppSearch\Request\DeleteSynonymSet:
26+
class: SilverStripe\ForagerBifrost\Service\Requests\DeleteSynonymRule
27+
Elastic\EnterpriseSearch\AppSearch\Request\GetSynonymSet:
28+
class: SilverStripe\ForagerBifrost\Service\Requests\GetSynonymRule
29+
Elastic\EnterpriseSearch\AppSearch\Request\ListSynonymSets:
30+
class: SilverStripe\ForagerBifrost\Service\Requests\GetSynonymRules
31+
Elastic\EnterpriseSearch\AppSearch\Request\CreateSynonymSet:
32+
class: SilverStripe\ForagerBifrost\Service\Requests\CreateSynonymRule
33+
Elastic\EnterpriseSearch\AppSearch\Request\PutSynonymSet:
34+
class: SilverStripe\ForagerBifrost\Service\Requests\UpdateSynonymRule

composer.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
"php": "^8.1",
2121
"silverstripe/framework": "^5",
2222
"silverstripe/reports": "^5",
23-
"silverstripe/silverstripe-forager-elastic-enterprise": "^1",
24-
"silverstripe/silverstripe-forager": "^1.1.0",
23+
"silverstripe/silverstripe-forager-elastic-enterprise": "^1.1",
2524
"guzzlehttp/guzzle": "^7"
2625
},
2726
"require-dev": {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace SilverStripe\ForagerBifrost\Adaptors\Requests;
4+
5+
use Elastic\EnterpriseSearch\Client;
6+
use SilverStripe\Core\Injector\Injector;
7+
use SilverStripe\Forager\Interfaces\Requests\CreateSynonymRuleAdaptor as PostSynonymRuleAdaptorInterface;
8+
use SilverStripe\Forager\Service\Query\SynonymRule as SynonymRuleQuery;
9+
use SilverStripe\Forager\Service\Results\SynonymRule as SynonymRuleResult;
10+
use SilverStripe\ForagerBifrost\Processors\SynonymRuleProcessor;
11+
use SilverStripe\ForagerBifrost\Service\Requests\CreateSynonymRule;
12+
13+
class CreateSynonymRuleAdaptor implements PostSynonymRuleAdaptorInterface
14+
{
15+
16+
private ?Client $client = null;
17+
18+
private static array $dependencies = [
19+
'client' => '%$' . Client::class . '.managementClient',
20+
];
21+
22+
public function setClient(?Client $client): void
23+
{
24+
$this->client = $client;
25+
}
26+
27+
public function process(int|string $synonymCollectionId, SynonymRuleQuery $synonymRule): SynonymRuleResult
28+
{
29+
$request = Injector::inst()->create(CreateSynonymRule::class, $synonymCollectionId, $synonymRule);
30+
31+
// Should either be successful or throw an exception, which we'll let fly
32+
$body = $this->client->appSearch()->createSynonymSet($request)->asString();
33+
$body = json_decode($body, true);
34+
35+
$synonymRuleResult = SynonymRuleResult::create($body['id']);
36+
SynonymRuleProcessor::applyStringToResult($synonymRuleResult, $body['synonyms']);
37+
38+
return $synonymRuleResult;
39+
}
40+
41+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace SilverStripe\ForagerBifrost\Adaptors\Requests;
4+
5+
use Elastic\EnterpriseSearch\AppSearch\Request\DeleteSynonymSet;
6+
use Elastic\EnterpriseSearch\Client;
7+
use SilverStripe\Core\Injector\Injector;
8+
use SilverStripe\Forager\Interfaces\Requests\DeleteSynonymRuleAdaptor as DeleteSynonymRuleAdaptorInterface;
9+
10+
class DeleteSynonymRuleAdaptor implements DeleteSynonymRuleAdaptorInterface
11+
{
12+
13+
private ?Client $client = null;
14+
15+
private static array $dependencies = [
16+
'client' => '%$' . Client::class . '.managementClient',
17+
];
18+
19+
public function setClient(?Client $client): void
20+
{
21+
$this->client = $client;
22+
}
23+
24+
public function process(int|string $synonymCollectionId, int|string $synonymRuleId): bool
25+
{
26+
$request = Injector::inst()->create(DeleteSynonymSet::class, $synonymCollectionId, $synonymRuleId);
27+
28+
// Should either be successful or throw an exception, which we'll let fly
29+
$body = $this->client->appSearch()->deleteSynonymSet($request)->asString();
30+
$body = json_decode($body, true);
31+
32+
return $body['success'];
33+
}
34+
35+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace SilverStripe\ForagerBifrost\Adaptors\Requests;
4+
5+
use Elastic\EnterpriseSearch\AppSearch\Request\GetSynonymSet;
6+
use Elastic\EnterpriseSearch\Client;
7+
use SilverStripe\Core\Injector\Injector;
8+
use SilverStripe\Forager\Interfaces\Requests\GetSynonymRuleAdaptor as GetSynonymRuleAdaptorInterface;
9+
use SilverStripe\Forager\Service\Results\SynonymRule;
10+
use SilverStripe\ForagerBifrost\Processors\SynonymRuleProcessor;
11+
12+
class GetSynonymRuleAdaptor implements GetSynonymRuleAdaptorInterface
13+
{
14+
15+
private ?Client $client = null;
16+
17+
private static array $dependencies = [
18+
'client' => '%$' . Client::class . '.managementClient',
19+
];
20+
21+
public function setClient(?Client $client): void
22+
{
23+
$this->client = $client;
24+
}
25+
26+
public function process(int|string $synonymCollectionId, int|string $synonymRuleId): SynonymRule
27+
{
28+
$request = Injector::inst()->create(GetSynonymSet::class, $synonymCollectionId, $synonymRuleId);
29+
30+
// Should either be successful or throw an exception, which we'll let fly
31+
$body = $this->client->appSearch()->getSynonymSet($request)->asString();
32+
$body = json_decode($body, true);
33+
34+
$synonymRule = SynonymRule::create($body['id']);
35+
SynonymRuleProcessor::applyStringToResult($synonymRule, $body['synonyms']);
36+
37+
return $synonymRule;
38+
}
39+
40+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace SilverStripe\ForagerBifrost\Adaptors\Requests;
4+
5+
use Elastic\EnterpriseSearch\AppSearch\Request\ListSynonymSets;
6+
use Elastic\EnterpriseSearch\Client;
7+
use SilverStripe\Core\Injector\Injector;
8+
use SilverStripe\Forager\Interfaces\Requests\GetSynonymRulesAdaptor as GetSynonymRulesAdaptorInterface;
9+
use SilverStripe\Forager\Service\Results\SynonymRule;
10+
use SilverStripe\Forager\Service\Results\SynonymRules;
11+
use SilverStripe\ForagerBifrost\Processors\SynonymRuleProcessor;
12+
13+
class GetSynonymRulesAdaptor implements GetSynonymRulesAdaptorInterface
14+
{
15+
16+
private ?Client $client = null;
17+
18+
private static array $dependencies = [
19+
'client' => '%$' . Client::class . '.managementClient',
20+
];
21+
22+
public function setClient(?Client $client): void
23+
{
24+
$this->client = $client;
25+
}
26+
27+
public function process(int|string $synonymCollectionId): SynonymRules
28+
{
29+
$request = Injector::inst()->create(ListSynonymSets::class, $synonymCollectionId);
30+
31+
// Should either be successful or throw an exception, which we'll let fly
32+
$body = $this->client->appSearch()->listSynonymSets($request)->asString();
33+
$body = json_decode($body, true);
34+
35+
$synonymRules = SynonymRules::create();
36+
37+
foreach ($body as $result) {
38+
$synonymRule = SynonymRule::create($result['id']);
39+
SynonymRuleProcessor::applyStringToResult($synonymRule, $result['synonyms']);
40+
41+
$synonymRules->add($synonymRule);
42+
}
43+
44+
return $synonymRules;
45+
}
46+
47+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace SilverStripe\ForagerBifrost\Adaptors\Requests;
4+
5+
use Elastic\EnterpriseSearch\Client;
6+
use SilverStripe\Core\Injector\Injector;
7+
use SilverStripe\Forager\Interfaces\Requests\UpdateSynonymRuleAdaptor as PatchSynonymRuleAdaptorInterface;
8+
use SilverStripe\Forager\Service\Query\SynonymRule as SynonymRuleQuery;
9+
use SilverStripe\Forager\Service\Results\SynonymRule as SynonymRuleResult;
10+
use SilverStripe\ForagerBifrost\Processors\SynonymRuleProcessor;
11+
use SilverStripe\ForagerBifrost\Service\Requests\UpdateSynonymRule;
12+
13+
class UpdateSynonymRuleAdaptor implements PatchSynonymRuleAdaptorInterface
14+
{
15+
16+
private ?Client $client = null;
17+
18+
private static array $dependencies = [
19+
'client' => '%$' . Client::class . '.managementClient',
20+
];
21+
22+
public function setClient(?Client $client): void
23+
{
24+
$this->client = $client;
25+
}
26+
27+
public function process(
28+
int|string $synonymCollectionId,
29+
int|string $synonymRuleId,
30+
SynonymRuleQuery $synonymRule
31+
): SynonymRuleResult {
32+
$request = Injector::inst()->create(
33+
UpdateSynonymRule::class,
34+
$synonymCollectionId,
35+
$synonymRuleId,
36+
$synonymRule
37+
);
38+
39+
// Should either be successful or throw an exception, which we'll let fly
40+
$body = $this->client->appSearch()->createSynonymSet($request)->asString();
41+
$body = json_decode($body, true);
42+
43+
$synonymRuleResult = SynonymRuleResult::create($body['id']);
44+
SynonymRuleProcessor::applyStringToResult($synonymRuleResult, $body['synonyms']);
45+
46+
return $synonymRuleResult;
47+
}
48+
49+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace SilverStripe\ForagerBifrost\Processors;
4+
5+
use SilverStripe\Core\Injector\Injectable;
6+
use SilverStripe\Forager\Service\Query\SynonymRule as SynonymRuleQuery;
7+
use SilverStripe\Forager\Service\Results\SynonymRule as SynonymRuleResult;
8+
9+
class SynonymRuleProcessor
10+
{
11+
12+
use Injectable;
13+
14+
public static function getStringFromQuery(SynonymRuleQuery $synonymRule): string
15+
{
16+
if ($synonymRule->getType() === SynonymRuleResult::TYPE_EQUIVALENT) {
17+
return implode(', ', $synonymRule->getSynonyms());
18+
}
19+
20+
return sprintf(
21+
'%s => %s',
22+
implode(', ', $synonymRule->getRoot()),
23+
implode(', ', $synonymRule->getSynonyms())
24+
);
25+
}
26+
27+
public static function applyStringToResult(SynonymRuleResult $synonymRule, string $synonymsString): void
28+
{
29+
$type = str_contains($synonymsString, '=>')
30+
? SynonymRuleResult::TYPE_DIRECTIONAL
31+
: SynonymRuleResult::TYPE_EQUIVALENT;
32+
33+
$synonymRule->setType($type);
34+
35+
if ($synonymRule->getType() === SynonymRuleResult::TYPE_DIRECTIONAL) {
36+
$split = explode('=>', $synonymsString);
37+
// We'd expect there to always be 2 items
38+
$root = trim($split[0]);
39+
$synonyms = trim($split[1]);
40+
41+
$synonymRule->setRoot(array_map('trim', explode(',', $root)));
42+
$synonymRule->setSynonyms(array_map('trim', explode(',', $synonyms)));
43+
} else {
44+
$synonymRule->setSynonyms(array_map('trim', explode(',', $synonymsString)));
45+
}
46+
}
47+
48+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace SilverStripe\ForagerBifrost\Service\Requests;
4+
5+
use Elastic\EnterpriseSearch\AppSearch\Request\CreateSynonymSet;
6+
use SilverStripe\Forager\Service\Query\SynonymRule as SynonymRuleQuery;
7+
use SilverStripe\ForagerBifrost\Processors\SynonymRuleProcessor;
8+
use stdClass;
9+
10+
class CreateSynonymRule extends CreateSynonymSet
11+
{
12+
13+
public function __construct(string $synonymCollectionId, SynonymRuleQuery $synonymRule)
14+
{
15+
$body = new stdClass();
16+
$body->synonyms = SynonymRuleProcessor::singleton()->getStringFromQuery($synonymRule);
17+
18+
$this->method = 'POST';
19+
$this->headers['Content-Type'] = 'application/json';
20+
$this->path = sprintf('/api/v1/%s/synonyms', $synonymCollectionId);
21+
$this->body = $body;
22+
}
23+
24+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace SilverStripe\ForagerBifrost\Service\Requests;
4+
5+
use Elastic\EnterpriseSearch\AppSearch\Request\DeleteSynonymSet;
6+
7+
class DeleteSynonymRule extends DeleteSynonymSet
8+
{
9+
10+
public function __construct(string $synonymCollectionId, string $synonymRuleId)
11+
{
12+
parent::__construct($synonymCollectionId, $synonymRuleId);
13+
14+
$this->path = sprintf('/api/v1/%s/synonyms/%s', $synonymCollectionId, $synonymRuleId);
15+
}
16+
17+
}

0 commit comments

Comments
 (0)