Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
<?php
namespace IQDEV\ElasticSearchTests\Filter;
use IQDEV\ElasticSearch\Criteria\Criteria;
use IQDEV\ElasticSearch\Criteria\Order\OrderDirection;
use IQDEV\ElasticSearch\Criteria\Order\OrderFactory;
use IQDEV\ElasticSearch\Criteria\Query\SearchQuery;
use IQDEV\ElasticSearch\Document\Property\Property;
use IQDEV\ElasticSearch\Document\Property\PropertyType;
use IQDEV\ElasticSearchTests\AbstractTestCase;
use IQDEV\ElasticSearchTests\Helpers\FormatData;
use IQDEV\ElasticSearchTests\Service\SearchClient;
class SortTest extends AbstractTestCase
{
/**
* Сортировка элементов по категории
*
* @return void
*/
public function testSortByCategory(): void
{
$criteria = new Criteria();
$criteria->getSorting()->add(
OrderFactory::createByProperty(new Property('category_id'), OrderDirection::ASC)
);
$q = new SearchQuery($criteria);
$handler = SearchClient::getInstance();
$result = $handler->handle($q)->result;
$expected = [
'hits' => [
'p1',
's1',
's2',
's3',
's4',
'h1',
'h2',
'h3',
]
];
$this->assertEquals($expected, FormatData::formatData($result));
}
/**
* Обратная сортировка элементов по категории
*
* @return void
*/
public function testSortByCategoryReverse(): void
{
$criteria = new Criteria();
$criteria->getSorting()->add(
OrderFactory::createByProperty(new Property('category_id'), OrderDirection::DESC)
);
$q = new SearchQuery($criteria);
$handler = SearchClient::getInstance();
$result = $handler->handle($q)->result;
$expected = [
'hits' => [
'h1',
'h2',
'h3',
's1',
's2',
's3',
's4',
'p1',
]
];
$this->assertEquals($expected, FormatData::formatData($result));
}
/**
* Сортировка элементов по свойству
*
* @return void
*/
public function testSortByKeyword(): void
{
$criteria = new Criteria();
$criteria->getSorting()->add(
OrderFactory::createByProperty(new Property('color', PropertyType::KEYWORD), OrderDirection::ASC)
);
$q = new SearchQuery($criteria);
$handler = SearchClient::getInstance();
$result = $handler->handle($q)->result;
$expected = [
'hits' => [
's3',
's1',
's4',
's2',
'h1',
'h2',
'h3',
'p1'
]
];
$this->assertEquals($expected, FormatData::formatData($result));
}
/**
* Сортировка элементов по свойству
*
* @return void
*/
public function testSortByKeywordReverse(): void
{
$criteria = new Criteria();
$criteria->getSorting()->add(
OrderFactory::createByProperty(new Property('color', PropertyType::KEYWORD), OrderDirection::DESC)
);
$q = new SearchQuery($criteria);
$handler = SearchClient::getInstance();
$result = $handler->handle($q)->result;
$expected = [
'hits' => [
'h2',
'h3',
'p1',
's2',
'h1',
's1',
's4',
's3',
]
];
$this->assertEquals($expected, FormatData::formatData($result));
}
/**
* Сортировка элементов по свойству
*
* @return void
*/
public function testSortByNumber(): void
{
$criteria = new Criteria();
$criteria->getSorting()->add(
OrderFactory::createByProperty(new Property('price', PropertyType::NUMBER), OrderDirection::ASC)
);
$q = new SearchQuery($criteria);
$handler = SearchClient::getInstance();
$result = $handler->handle($q)->result;
$expected = [
'hits' => [
's1',
's2',
's3',
's4',
'h1',
'h2',
'h3',
'p1'
]
];
$this->assertEquals($expected, FormatData::formatData($result));
}
/**
* Сортировка элементов по свойству
*
* @return void
*/
public function testSortByNumberReverse(): void
{
$criteria = new Criteria();
$criteria->getSorting()->add(
OrderFactory::createByProperty(new Property('price', PropertyType::NUMBER), OrderDirection::DESC)
);
$q = new SearchQuery($criteria);
$handler = SearchClient::getInstance();
$result = $handler->handle($q)->result;
$expected = [
'hits' => [
'p1',
'h3',
'h2',
'h1',
's4',
's3',
's2',
's1',
]
];
$this->assertEquals($expected, FormatData::formatData($result));
}
/**
* Сортировка элементов по свойству
*
* @return void
*/
public function testSortByCombined(): void
{
$criteria = new Criteria();
$criteria->getSorting()->add(
OrderFactory::createByProperty(new Property('size', PropertyType::KEYWORD), OrderDirection::ASC)
);
$q = new SearchQuery($criteria);
$handler = SearchClient::getInstance();
$result = $handler->handle($q)->result;
$expected = [
'hits' => [
's4',
's1',
's2',
's3',
'h1',
'h3',
'p1',
'h2',
]
];
$this->assertEquals($expected, FormatData::formatData($result));
}
/**
* Сортировка элементов по свойству
*
* @return void
*/
public function testSortByCombinedReverse(): void
{
$criteria = new Criteria();
$criteria->getSorting()->add(
OrderFactory::createByProperty(new Property('size', PropertyType::KEYWORD), OrderDirection::DESC)
);
$q = new SearchQuery($criteria);
$handler = SearchClient::getInstance();
$result = $handler->handle($q)->result;
$expected = [
'hits' => [
'h2',
'h1',
'h3',
'p1',
's2',
's3',
's1',
's4',
]
];
$this->assertEquals($expected, FormatData::formatData($result));
}
}
\ No newline at end of file
<?php
declare(strict_types=1);
namespace IQDEV\ElasticSearchTests\Filter;
use IQDEV\ElasticSearch\Criteria\Criteria;
use IQDEV\ElasticSearch\Criteria\Filter\Collection\FilterGroupCollection;
use IQDEV\ElasticSearch\Criteria\Filter\Field;
use IQDEV\ElasticSearch\Criteria\Filter\Filter;
use IQDEV\ElasticSearch\Criteria\Filter\FilterOperator;
use IQDEV\ElasticSearch\Criteria\Filter\LogicOperator;
use IQDEV\ElasticSearch\Criteria\Filter\Value\FilterKeyword;
use IQDEV\ElasticSearch\Criteria\Query\SearchQuery;
use IQDEV\ElasticSearch\Criteria\Search\Search;
use IQDEV\ElasticSearch\Document\Property\Property;
use IQDEV\ElasticSearchTests\AbstractTestCase;
use IQDEV\ElasticSearchTests\Helpers\FormatData;
use IQDEV\ElasticSearchTests\Service\SearchClient;
class UpdatedSearchTest extends AbstractTestCase
{
/**
* Поиск по свойству nested
*
* @return void
*/
public function testSearchByNestedProperty(): void
{
$criteria = new Criteria();
$criteria->getSearch()->add(
new Search(
new Property('brand'),
'rebook',
),
);
$q = new SearchQuery($criteria);
$handler = SearchClient::getInstance();
$result = $handler->handle($q)->result;
$expected = [
'hits' => [
's3',
'h3',
'p1'
]
];
$this->assertEqualsCanonicalizing($expected, FormatData::formatData($result));
}
/**
* Поиск по nested полю и обычному свойству одноврмененно
*
* @return void
*/
public function testSearchByNestedAndNonNestedProperty(): void
{
$criteria = new Criteria();
$criteria->getSearch()->add(
new Search(
new Property('brand'),
'rebook',
),
);
$criteria->getSearch()->add(
new Search(
new Property('category_id'),
'prices',
),
);
$q = new SearchQuery($criteria);
$handler = SearchClient::getInstance();
$result = $handler->handle($q)->result;
$expected = [
'hits' => [
'p1'
]
];
$this->assertEqualsCanonicalizing($expected, FormatData::formatData($result));
}
/**
* Поиск по нескольким nested полям
*
* @return void
*/
public function testSearchByDifferentNestedProperty(): void
{
$criteria = new Criteria();
$criteria->getSearch()->add(
new Search(
new Property('brand'),
'rebook',
),
);
$criteria->getSearch()->add(
new Search(
new Property('color'),
'white',
),
);
$q = new SearchQuery($criteria);
$handler = SearchClient::getInstance();
$result = $handler->handle($q)->result;
$expected = [
'hits' => [
'h3',
'p1'
]
];
$this->assertEqualsCanonicalizing($expected, FormatData::formatData($result));
}
public function testQueryNonNested(): void
{
$criteria = new Criteria();
$filterCollection = new FilterGroupCollection([
new Filter(
new Field('category_id'),
FilterOperator::CONTAINS,
new FilterKeyword('shoes'),
)
]);
$criteria->getFilters()->add($filterCollection);
$q = new SearchQuery($criteria);
$handler = SearchClient::getInstance();
$result = $handler->handle($q)->result;
$expected = [
'hits' => [
's1',
's2',
's3',
's4',
]
];
$this->assertEqualsCanonicalizing($expected, FormatData::formatData($result));
}
public function testQueryNonNestedMoreLogicOr(): void
{
$criteria = new Criteria();
$filterCollection = new FilterGroupCollection([
new Filter(
new Field('category_id'),
FilterOperator::CONTAINS,
new FilterKeyword('shoes'),
),
new Filter(
new Field('category_id'),
FilterOperator::CONTAINS,
new FilterKeyword('t-short'),
),
]);
$filterCollection->setLogicOperator(LogicOperator::OR);
$criteria->getFilters()->add($filterCollection);
$q = new SearchQuery($criteria);
$handler = SearchClient::getInstance();
$result = $handler->handle($q)->result;
$expected = [
'hits' => [
's1',
's2',
's3',
's4',
'h1',
'h2',
'h3',
]
];
$this->assertEqualsCanonicalizing($expected, FormatData::formatData($result));
}
public function testQueryNonNestedMoreLogicAnd(): void
{
$criteria = new Criteria();
$filterCollection = new FilterGroupCollection([
new Filter(
new Field('category_id'),
FilterOperator::CONTAINS,
new FilterKeyword('shoes'),
),
new Filter(
new Field('category_id'),
FilterOperator::CONTAINS,
new FilterKeyword('t-short'),
),
]);
$criteria->getFilters()->add($filterCollection);
$q = new SearchQuery($criteria);
$handler = SearchClient::getInstance();
$result = $handler->handle($q)->result;
$expected = [
'hits' => [
]
];
$this->assertEqualsCanonicalizing($expected, FormatData::formatData($result));
}
public function testMustNotNestedFilter(): void
{
$criteria = new Criteria();
$group = new FilterGroupCollection([
new Filter(
new Field('brand'),
FilterOperator::EQ,
new FilterKeyword('adidas'),
)
]);
$group->setLogicOperator(LogicOperator::NOT);
$criteria->getFilters()->add($group);
$q = new SearchQuery($criteria);
$handler = SearchClient::getInstance();
$result = $handler->handle($q)->result;
$expected = [
'hits' => [
's3',
's4',
'h1',
'h2',
'h3',
'p1',
]
];
$this->assertEqualsCanonicalizing($expected, FormatData::formatData($result));
}
public function testMustNotPropertyFilter(): void
{
$criteria = new Criteria();
$group = new FilterGroupCollection([
new Filter(
new Field('category_id'),
FilterOperator::EQ,
new FilterKeyword('prices'),
)
]);
$group->setLogicOperator(LogicOperator::NOT);
$criteria->getFilters()->add($group);
$q = new SearchQuery($criteria);
$handler = SearchClient::getInstance();
$result = $handler->handle($q)->result;
$expected = [
'hits' => [
's1',
's2',
's3',
's4',
'h1',
'h2',
'h3',
]
];
$this->assertEqualsCanonicalizing($expected, FormatData::formatData($result));
}
}
<?php
namespace IQDEV\ElasticSearchTests\Helpers;
class Arr
{
/**
* Flatten a multi-dimensional associative array with dots.
* @param array $array
* @param $prepend
* @return array
*/
public static function dot(array $array, $prepend = ''): array
{
$results = [];
foreach ($array as $key => $value) {
if (is_array($value) && ! empty($value)) {
$results = array_merge($results, static::dot($value, $prepend.$key.'.'));
} else {
$results[$prepend.$key] = $value;
}
}
return $results;
}
}
\ No newline at end of file
<?php
namespace IQDEV\ElasticSearchTests\Helpers;
use IQDEV\ElasticSearch\Document\Product;
use IQDEV\ElasticSearch\Facet\FacetResult;
use IQDEV\ElasticSearch\Facet\Item\FacetItemList;
use IQDEV\ElasticSearch\Facet\Item\FacetItemRange;
use IQDEV\ElasticSearch\Result;
class FormatData
{
public static function formatData(Result $result): array
{
$oProductCollection = $result->getProducts();
$aResult = ['hits' => []];
foreach ($oProductCollection as $oProduct) {
/** @var Product $oProduct */
$aResult['hits'][] = $oProduct->id;
}
return $aResult;
}
public static function formatDataWFacets(Result $result): array
{
$aResult = ['facets' => []];
$aResult['hits'] = static::formatData($result)['hits'];
foreach ($result->getFacets() as $facet) {
/** @var FacetResult $facet */
$dataFacet = [
'code' => $facet->getCode(),
'label' => null, // $facet->getLabel(),
'type' => $facet->getType()->value,
'items' => [
'list' => [],
'range' => []
],
];
$items = $facet->products->sort('getValue');
foreach ($items as $item) {
if ($item instanceof FacetItemList) {
/** @var FacetItemList $item */
$dataFacet['items']['list'][] = [
'label' => $item->getLabel(),
'value' => $item->getValue(),
'count' => $item->getCount(),
'active' => $item->isActive(),
];
}
if ($item instanceof FacetItemRange) {
/** @var FacetItemRange $item */
$aData = [
'label' => $item->getLabel(),
'count' => $item->getCount(),
'active' => $item->isActive(),
];
$aData['fullRange'] = $item->getFullRange();
$aData['activeRange'] = $item->getSelectedRange();
if ($result->getTotal() > 0 && empty(array_filter($aData['activeRange']))) {
$aData['activeRange'] = $aData['fullRange'];
}
$dataFacet['items']['range'][] = $aData;
}
}
$aResult['facets'][] = $dataFacet;
}
return $aResult;
}
public static function formatDataProducts(Result $result): array
{
$products = [];
/** @var Product $product */
foreach ($result->getProducts() as $product) {
$data = [
'id' => $product->id,
'category' => $product->info['category_id']
];
if ($product->title) {
$data['name'] = $product->title;
}
if (isset($product->info['search_data'])) {
$props = $product->info['search_data'];
if (!empty($props['keyword_facet'])) {
foreach ($props['keyword_facet'] as $prop) {
$data['properties'][$prop['facet_code']] = $prop['facet_value'];
}
}
if (!empty($props['number_facet'])) {
foreach ($props['number_facet'] as $prop) {
$data['properties'][$prop['facet_code']] = $prop['facet_value'];
}
}
}
$products[] = $data;
}
return ['products' => $products];
}
}
\ No newline at end of file
<?php
namespace IQDEV\ElasticSearchTests\Helpers;
use IQDEV\ElasticSearch\Configuration;
use IQDEV\ElasticSearch\Document\ProductDocument;
use IQDEV\ElasticSearch\Document\Property\Property;
use IQDEV\ElasticSearch\Document\Property\PropertyType;
use IQDEV\ElasticSearch\Facet\FacetFactory;
use IQDEV\ElasticSearch\Indexer\AddIndex;
use IQDEV\ElasticSearch\Indexer\DeleteIndex;
use IQDEV\ElasticSearch\Indexer\IndexProvider;
use IQDEV\ElasticSearch\Indexer\UpdateIndex;
class TestIndexProvider implements IndexProvider
{
private Configuration $configuration;
private array $products = [];
public function __construct(Configuration $configuration, array $products)
{
$this->configuration = $configuration;
$this->products = $products;
}
/**
* @inheritDoc
*/
public function get(): \Generator
{
foreach ($this->products as $product) {
$document = new ProductDocument(
FacetFactory::createFromProperty(new Property('category_id', PropertyType::BASE), $product['category'])
);
//todo по-хорошему нужны базовые классы, которые будут описывать свойства
// и формировать структуру для последующей обработки
$data = [
'id' => $product['id'],
];
if (isset($product['name'])) {
$document->setSearchContent($product['name']);
$data['title'] = $product['name'];
}
$document->setAdditionData($data);
if (isset($product['properties'])) {
foreach ($product['properties'] as $key => $value) {
if ($key === 'price') {
$document->getNumberFacets()->add(
FacetFactory::createFromProperty(new Property($key, PropertyType::NUMBER), $value)
);
} else {
$document->getKeywordFacets()->add(
FacetFactory::createFromProperty(new Property($key, PropertyType::KEYWORD), $value)
);
}
}
}
$document->setByConfiguration($this->configuration, 'new', $product['new']);
$document->setByConfiguration($this->configuration, 'rating', $product['rating']);
$product['type'] = $product['type'] ?? null;
switch ($product['type']) {
case 'update':
$document->skipEmpty(true);
$index = new UpdateIndex(
$this->configuration->getIndexName(),
$document,
$product['id']
);
break;
case 'delete':
$index = new DeleteIndex(
$this->configuration->getIndexName(),
$product['id']
);
break;
default:
$index = new AddIndex(
$this->configuration->getIndexName(),
$document,
$product['id']
);
break;
}
yield $index;
}
}
/**
* @inheritDoc
*/
public function setBatchSize(int $size): void
{
}
/**
* @inheritDoc
*/
public function getBatchSize(): ?int
{
return null;
}
/**
* @inheritDoc
*/
public function setLimit(int $limit): void
{
}
/**
* @inheritDoc
*/
public function getLimit(): ?int
{
return null;
}
}
<?php
namespace IQDEV\ElasticSearchTests\Seed;
use IQDEV\ElasticSearch\Config\BaseConfiguration;
use IQDEV\ElasticSearch\Configuration;
use IQDEV\ElasticSearch\Indexer\IndexRunner;
use IQDEV\ElasticSearchTests\Factory\ClientFactory;
use IQDEV\ElasticSearchTests\Helpers\TestIndexProvider;
use Psr\Log\NullLogger;
class DefaultSeed
{
private IndexRunner $indexRunner;
private Configuration $configuration;
public function __construct()
{
$this->configuration = new BaseConfiguration();
$this->indexRunner = new IndexRunner(
ClientFactory::create(),
$this->configuration,
new NullLogger()
);
}
public function start()
{
$provider = new TestIndexProvider($this->configuration, [
[
'id' => 's1',
'name' => 'Кроссовки NMD_R1 Boba Fett Spectoo',
'category' => 'shoes',
'properties' => ['brand' => 'adidas', 'color' => 'green', 'size' => 46,'price' => 100],
'year' => 2014,
'new' => false,
'rating' => 3,
],
[
'id' => 's2',
'name' => 'КРОССОВКИ ULTRABOOST 5.0 DNA',
'category' => 'shoes',
'properties' => ['brand' => 'adidas', 'color' => 'red', 'size' => 47,'price' => 101],
'year' => 2023,
'new' => true,
'rating' => 3,
],
[
'id' => 's3',
'name' => 'Кроссовки Reebok Royal Techque',
'category' => 'shoes',
'properties' => ['brand' => 'rebook', 'color' => 'blue', 'size' => 47,'price' => 102],
'year' => 1980,
'new' => false,
'rating' => 3,
],
[
'id' => 's4',
'name' => 'Nike Air Zoom Pegasus 39',
'category' => 'shoes',
'properties' => ['brand' => 'nike', 'color' => 'green', 'size' => 43,'price' => 103],
'year' => 2014,
'new' => true,
'rating' => 5,
],
[
'id' => 'h1',
'name' => 'Nike Dri-FIT Strike',
'category' => 't-short',
'properties' => ['brand' => 'nike', 'color' => 'red', 'size' => 'xl','price' => 104],
'year' => 2010,
'new' => true,
'rating' => 4,
],
[
'id' => 'h2',
'name' => 'Nike Dri-FIT Rise 365',
'category' => 't-short',
'properties' => ['brand' => 'nike', 'color' => 'white', 'size' => 'xxl','price' => 105],
'year' => 2000,
'new' => true,
'rating' => 3,
],
[
'id' => 'h3',
'name' => 'Компрессионная Футболка ACTIVCHILL Graphic Move',
'category' => 't-short',
'properties' => ['brand' => 'rebook', 'color' => 'white', 'size' => 'xl','price' => 106],
'year' => 1990,
'new' => true,
'rating' => 3,
],
[
'id' => 'p1',
'name' => 'Товар с ценой',
'category' => 'prices',
'properties' => ['brand' => 'rebook', 'color' => 'white', 'size' => 'xl','price' => 107],
'year' => 2015,
'new' => false,
'rating' => 3,
],
]);
$this->indexRunner->run($provider);
}
}
<?php
namespace IQDEV\ElasticSearchTests\Service;
use IQDEV\ElasticSearch\Config\BaseConfiguration as Configuration;
use IQDEV\ElasticSearch\Criteria\Query\SearchQueryHandler;
use IQDEV\ElasticSearch\SearchService;
use IQDEV\ElasticSearchTests\Factory\ClientFactory;
class SearchClient
{
private function __construct() { }
protected static SearchQueryHandler $oInstance;
public static function getInstance(): SearchQueryHandler
{
if (!isset(static::$oInstance)) {
static::$oInstance = new SearchQueryHandler(
new SearchService(
ClientFactory::create(),
new Configuration(),
)
);
}
return static::$oInstance;
}
}
\ No newline at end of file
<?php
use Dotenv\Dotenv;
require_once __DIR__ . '/../vendor/autoload.php';
$dotenv = Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->load();