<?php
namespace App\Services;
use Elasticsearch\ClientBuilder;
final class ElasticSearchService {
public const MIN_STOCK_REQUIREMENT = 5;
public const MAX_SEARCH_RESULTS = 40;
public const MAX_SEARCH_RESULTS_AJAX = 1000;
public const MAX_WINDOW_RESULTS = 100000; // 100k
public const MAX_FILTER_FACET_VALUES = 1000; // 1000 valeurs max, ce qui est large
public const PREFIX_INDEX_NAME = 'ppmcdev_';
public const PREFIX_FACET_FIELD_NAME = 'facet_';
public const FACET_PRICE_MIN = 'min_price';
public const FACET_PRICE_MAX = 'max_price';
public const AVAILABLE_SORT_COLUMNS = [
'name', 'created_at', 'price', 'badge_sort'
];
public function getIndexName(string $localeCode, bool $changeIndex = false): string
{
$projectRoot = __DIR__ . '/../../';
$currentIndex = json_decode(file_get_contents($projectRoot.'current_elastic_index.json'));
if($changeIndex) {
if($currentIndex->index == 1) {
$currentIndex->index = 2;
}
else {
$currentIndex->index = 1;
}
}
// dump($currentIndex->index);
// $projectRoot = $this->get('kernel')->getProjectDir();
return self::PREFIX_INDEX_NAME. $currentIndex->index . '_' . strtolower($localeCode);
}
public function rewriteCurrentIndex()
{
$projectRoot = __DIR__ . '/../../';
$currentIndex = json_decode(file_get_contents($projectRoot.'current_elastic_index.json'));
if($currentIndex->index == 1) {
$currentIndex->index = 2;
}
else {
$currentIndex->index = 1;
}
file_put_contents($projectRoot.'current_elastic_index.json', json_encode($currentIndex));
}
/**
* Effectue une recherche
*/
public function search(string $localeCode, string $search, int $offset, array $facetFilters = [], $sortBy = null, $sortOrder = 'desc')
{
// REQUETE DE BASE
$params = [
'index' => $this->getIndexName($localeCode),
'from' => $offset,
'size' => self::MAX_SEARCH_RESULTS,
'track_total_hits' => true,
'body' => [
'min_score' => 0,
'query' => [
'bool' => [
'must' => [
[
'range' => [
'stock' => [
'gte' => self::MIN_STOCK_REQUIREMENT
]
]
],
[
'term' => [
'blocked' => 0
]
]
]
]
]
],
'_source' => false
];
// SEARCH QUERY
if (empty($search) == true) {
$params['body']['query']['bool']['must'][] = [
'match_all' => (object) []
];
} else {
$params['body']['query']['bool']['must'][] = [
'match' => [
'global_single_field' => [
'query' => $search,
'fuzziness' => '1'
]
]
];
}
// FILTER FACET
if (count($facetFilters) > 0) {
foreach ($facetFilters as $key => $value) {
if ($key == self::FACET_PRICE_MIN || $key == self::FACET_PRICE_MAX) {
$value = intval($value);
}
$fieldKey = $key;
if (is_numeric($key) == true) {
$fieldKey = self::PREFIX_FACET_FIELD_NAME . $key;
}
if ($key == self::FACET_PRICE_MIN || $key == self::FACET_PRICE_MAX) {
$params['body']['query']['bool']['must'][] = [
'range' => [
'price' => [
($key == self::FACET_PRICE_MIN ? 'gte' : 'lte') => $value * 100
]
]
];
} else {
$params['body']['query']['bool']['must'][] = [
'terms' => [
$fieldKey => is_array($value) ? $value : [$value]
]
];
}
}
}
// SORT BY
if (in_array($sortBy, self::AVAILABLE_SORT_COLUMNS) == false) {
$sortBy = null;
}
if (empty($sortBy) == false) {
$params['body']['sort'] = [
[
$sortBy => $sortOrder
]
];
}
$client = ClientBuilder::create()->build();
$results = $client->search($params);
return $results;
}
/**
* Récupère les filtres à facettes disponible dans ElasticSearch
* pour cette recherche
*
* @param int $searchTxt La recherche
* @param array $facetFilters les filtres à facettes à appliquer sur cette requête de recherche
* @param array $siteFilterFacetIds Tous les ids de tous les filtres à facettes
*/
public function getFilterFacetsSearch(string $localeCode, string $search, array $facetFilters = [], array $siteFilterFacetIds)
{
// REQUETE DE BASE
$params = [
'index' => $this->getIndexName($localeCode),
'size' => 0,
'body' => [
'min_score' => 1,
'query' => [
'bool' => [
'must' => [
[
'range' => [
'stock' => [
'gte' => self::MIN_STOCK_REQUIREMENT
]
]
]
]
]
],
'aggs' => [
'shape' => [
'terms' => [
'field' => 'shape',
'size' => self::MAX_FILTER_FACET_VALUES
]
],
'color' => [
'terms' => [
'field' => 'color',
'size' => self::MAX_FILTER_FACET_VALUES
]
],
'badge' => [
'terms' => [
'field' => 'badge',
'size' => self::MAX_FILTER_FACET_VALUES
]
],
self::FACET_PRICE_MAX => [
'max' => [
'field' => 'price'
]
],
self::FACET_PRICE_MIN => [
'min' => [
'field' => 'price'
]
]
]
],
'_source' => false
];
// SEARCH QUERY
if (empty($search) == true) {
$params['body']['query']['bool']['must'][] = [
'match_all' => (object) []
];
} else {
$params['body']['query']['bool']['must'][] = [
'match' => [
'global_single_field' => [
'query' => $search,
'fuzziness' => '1'
]
]
];
}
// FILTER FACET DE BASE
if (count($facetFilters) > 0) {
foreach ($facetFilters as $key => $value) {
$fieldKey = $key;
if (is_numeric($key) == true) {
$fieldKey = self::PREFIX_FACET_FIELD_NAME . $key;
}
if ($key == self::FACET_PRICE_MIN || $key == self::FACET_PRICE_MAX) {
$params['body']['query']['bool']['must'][] = [
'range' => [
'price' => [
($key == self::FACET_PRICE_MIN ? 'gte' : 'lte') => $value * 100
]
]
];
} else {
$params['body']['query']['bool']['must'][] = [
'terms' => [
$fieldKey => is_array($value) ? $value : [$value]
]
];
}
}
}
// AGREGATION
foreach ($siteFilterFacetIds as $rowId) {
$params['body']['aggs'][$rowId] = [
'terms' => [
'field' => self::PREFIX_FACET_FIELD_NAME . $rowId,
'size' => self::MAX_FILTER_FACET_VALUES
]
];
}
$client = ClientBuilder::create()->build();
$results = $client->search($params);
// exit();
return $results['aggregations'];
}
/**
* Récupère des éléments dans ElasticSearch à partir d'un taxon
*/
public function getElementsByTaxon(string $localeCode, int $taxonId, int $offset, array $facetFilters = [], $sortBy = null, $sortOrder = 'desc', $ajax = null)
{
// REQUETE DE BASE
$size = self::MAX_SEARCH_RESULTS;
if($ajax){
$size = self::MAX_SEARCH_RESULTS_AJAX;
$offset = 0;
}
$params = [
'index' => $this->getIndexName($localeCode),
'from' => $offset,
'size' => $size,
'track_total_hits' => true,
'body' => [
'min_score' => 0,
'query' => [
'bool' => [
'must' => [
[
'term' => [
'taxons' => $taxonId
],
],
[
'range' => [
'stock' => [
'gte' => self::MIN_STOCK_REQUIREMENT
]
]
],
[
'term' => [
'blocked' => 0
]
]
]
]
]
],
'_source' => false
];
// FILTER FACET
if (count($facetFilters) > 0) {
foreach ($facetFilters as $key => $value) {
if ($key == self::FACET_PRICE_MIN || $key == self::FACET_PRICE_MAX) {
$value = intval($value);
}
$fieldKey = $key;
if (is_numeric($key) == true) {
$fieldKey = self::PREFIX_FACET_FIELD_NAME . $key;
}
if ($key == self::FACET_PRICE_MIN || $key == self::FACET_PRICE_MAX) {
$params['body']['query']['bool']['must'][] = [
'range' => [
'price' => [
($key == self::FACET_PRICE_MIN ? 'gte' : 'lte') => $value * 100
]
]
];
} else {
$params['body']['query']['bool']['must'][] = [
'terms' => [
$fieldKey => is_array($value) ? $value : [$value]
]
];
}
}
}
// SORT BY
if (in_array($sortBy, self::AVAILABLE_SORT_COLUMNS) == false) {
$sortBy = null;
}
if (empty($sortBy) == true) {
$params['body']['sort'] = [
[
'badge_sort' => 'asc'
],
[
'stock' => 'desc'
],
[
'_score' => 'desc'
]
];
} else {
$params['body']['sort'] = [
[
$sortBy => $sortOrder
]
];
}
$client = ClientBuilder::create()->build();
$results = $client->search($params);
dump( $results);
return $results;
}
/**
* Récupère les filtres à facettes disponible dans ElasticSearch
* pour les produits qui sont dans ce taxon.
*
* @param int $taxonId L'ID de la catégorie
* @param array $siteFilterFacetIds Tous les ids de tous les filtres à facettes
*/
public function getFilterFacetsByTaxon(string $localeCode, int $taxonId, array $siteFilterFacetIds)
{
// REQUETE DE BASE
$params = [
'index' => $this->getIndexName($localeCode),
'size' => 0,
'body' => [
'min_score' => 0,
'query' => [
'bool' => [
'must' => [
[
'term' => [
'taxons' => $taxonId
],
],
[
'range' => [
'stock' => [
'gte' => self::MIN_STOCK_REQUIREMENT
]
]
]
]
]
],
'aggs' => [
'shape' => [
'terms' => [
'field' => 'shape',
'size' => self::MAX_FILTER_FACET_VALUES
]
],
'color' => [
'terms' => [
'field' => 'color',
'size' => self::MAX_FILTER_FACET_VALUES
]
],
'badge' => [
'terms' => [
'field' => 'badge',
'size' => self::MAX_FILTER_FACET_VALUES
]
],
self::FACET_PRICE_MAX => [
'max' => [
'field' => 'price'
]
],
self::FACET_PRICE_MIN => [
'min' => [
'field' => 'price'
]
]
]
],
'_source' => false
];
// AGREGATION
foreach ($siteFilterFacetIds as $rowId) {
$params['body']['aggs'][$rowId] = [
'terms' => [
'field' => self::PREFIX_FACET_FIELD_NAME . $rowId,
'size' => self::MAX_FILTER_FACET_VALUES
]
];
}
// dump($params);
// exit();
$client = ClientBuilder::create()->build();
// dump($client->indices());
// exit();
$results = $client->search($params);
// dump( $results);
return $results['aggregations'];
}
}