Last active
April 17, 2022 17:34
-
-
Save Nemo64/b73b8fdba39b2b17e38f3da701f94213 to your computer and use it in GitHub Desktop.
Elasticsearch API Platform -> FOS Elastica configuration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Search; | |
use ApiPlatform\Core\DataProvider; | |
use FOS\ElasticaBundle\Index\IndexManager; | |
use Symfony\Component\Serializer\Serializer; | |
/** | |
* This class is based on API Platforms elasticsearch implemenation. | |
* However, It uses the FOS Elastica Bundle connection. | |
* | |
* @see \ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\CollectionDataProvider | |
* @see \ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\ItemDataProvider | |
*/ | |
class ElasticsearchProductProvider implements DataProvider\ContextAwareCollectionDataProviderInterface, DataProvider\ItemDataProviderInterface, DataProvider\RestrictedDataProviderInterface | |
{ | |
private const INDEX_NAME = 'product'; | |
private IndexManager $indexManager; | |
private DataProvider\Pagination $pagination; | |
private Serializer $serializer; | |
public function __construct(IndexManager $indexManager, DataProvider\Pagination $pagination, Serializer $serializer) | |
{ | |
$this->indexManager = $indexManager; | |
$this->pagination = $pagination; | |
$this->serializer = $serializer; | |
} | |
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool | |
{ | |
return $resourceClass === \App\Entity\Product::class; | |
} | |
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) | |
{ | |
$query = [ | |
'query' => [['term' => ['id' => $id]]], | |
]; | |
$index = $this->indexManager->getIndex(self::INDEX_NAME); | |
$response = $index->request('_search', 'GET', $query)->getData(); | |
if (count($response['hits']['hits']) < 1) { | |
return null; | |
} | |
return $this->serializer->denormalize( | |
$response['hits']['hits'][0]['_source'], | |
$resourceClass | |
); | |
} | |
public function getCollection(string $resourceClass, string $operationName = null, array $context = []) | |
{ | |
$query = [ | |
'from' => $this->pagination->getOffset($resourceClass, $operationName, $context), | |
'size' => $this->pagination->getLimit($resourceClass, $operationName, $context), | |
]; | |
// $context['filters'] has the query string, you can use that to build the query | |
// you can also build filter classes with \ApiPlatform\Core\Api\FilterInterface | |
// look at existing filters to figure out how api platform intents this to be used | |
$index = $this->indexManager->getIndex(self::INDEX_NAME); | |
$response = $index->request('_search', 'GET', $query)->getData(); | |
return new \ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Paginator( | |
$this->serializer, | |
$response, | |
$resourceClass, | |
$query['size'], | |
$query['from'] | |
); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
parameters: | |
env(ELASTICSEARCH_VERIFYPEER): yes | |
env(ELASTICSEARCH_VERIFYHOST): 2 | |
fos_elastica: | |
# the database connection | |
clients: | |
default: | |
url: '%env(ELASTICSEARCH_URL)%' | |
curl: | |
# with this, you can disable ssl host check, which is useful in dev environments | |
!php/const CURLOPT_SSL_VERIFYPEER: '%env(bool:ELASTICSEARCH_VERIFYPEER)%' | |
!php/const CURLOPT_SSL_VERIFYHOST: '%env(int:ELASTICSEARCH_VERIFYHOST)%' | |
indexes: | |
product: | |
persistence: | |
driver: orm | |
model: App\Entity\Product | |
provider: ~ # you can set a provider to build your model yourself | |
finder: ~ | |
# this defines how the domain model is serialized before being commited to elasticsearch | |
# https://symfony.com/doc/5.4/serializer.html#using-serialization-groups-annotations | |
serializer: | |
groups: [search] | |
# these properties are 1:1 the properties for elasticsearchs explicit mapping | |
# https://www.elastic.co/guide/en/elasticsearch/reference/7.17/explicit-mapping.html | |
# this, unintuitively, has nothing to do with serialization | |
properties: | |
title: { type: text, boost: 2.0 } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\ApiPlatform\Filter; | |
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\ContextAwareFilterInterface; | |
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; | |
use Doctrine\ORM\QueryBuilder; | |
use FOS\ElasticaBundle\Index\IndexManager; | |
class FulltextFilter implements ContextAwareFilterInterface | |
{ | |
private const INDEX_NAME = 'product'; | |
private IndexManager $indexManager; | |
public function __construct(IndexManager $indexManager) | |
{ | |
$this->indexManager = $indexManager; | |
} | |
public function getDescription(string $resourceClass): array | |
{ | |
return [ | |
'q' => [ | |
'property' => 'q', | |
'type' => 'string', | |
'required' => false, | |
], | |
'order[relevance]' => [ | |
'property' => 'order[relevance]', | |
'type' => 'string', | |
'required' => false, | |
], | |
]; | |
} | |
public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = []) | |
{ | |
$query = [ | |
'_source' => false, // you don't need the source document, just the ids | |
'size' => 10000, // elasticsearch limit per page | |
'query' => [ | |
'multi_match' => [ | |
'query' => $context['filters']['query'], | |
'fields' => array_keys($this->properties), | |
] | |
], | |
]; | |
$response = $this->indexManager->getIndex(self::INDEX_NAME)->request('/_search', 'GET', $query); | |
$ids = array_column($response->getData()['hits']['hits'] ?? [], '_id'); | |
if (empty($ids)) { | |
$queryBuilder->andWhere('1 = 0'); // enforce empty result | |
return; | |
} | |
// search for the ids in the query | |
$rootAlias = $queryBuilder->getRootAliases()[0]; | |
$queryBuilder->andWhere("$rootAlias.id IN (:fulltext_search_filter_ids)"); | |
$queryBuilder->setParameter('fulltext_search_filter_ids', $ids); | |
// sort by id list aka relevance | |
if (in_array($context['filters']['order']['relevance'] ?? null, ['asc', 'desc'], true)) { | |
$queryBuilder->addSelect("FIELD($rootAlias.id, :fulltext_search_filter_ids) AS HIDDEN __order"); | |
$queryBuilder->addOrderBy("__order", $context['filters']['order']['relevance']); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment