Skip to content

Commit 540faaf

Browse files
committed
Add new modifier to sort results by tree index
1 parent e48ef72 commit 540faaf

File tree

9 files changed

+144
-48
lines changed

9 files changed

+144
-48
lines changed

config/pimcore/config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ pimcore_generic_data_index:
209209
normalizer: generic_data_index_sort_normalizer
210210
classDefinitionIcon:
211211
type: keyword
212+
index:
213+
type: integer
212214
asset:
213215
mimetype:
214216
type: keyword

doc/04_Searching_For_Data_In_Index/05_Search_Modifiers/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ If multiple sort modifiers are added to the search, the order of the modifiers i
5959
| [OrderByFullPath](https://github.com/pimcore/generic-data-index-bundle/blob/1.x/src/Model/Search/Modifier/Sort/Tree/OrderByFullPath.php) | Tree related sorting | Order by full path (including element key) |
6060
| [OrderByField](https://github.com/pimcore/generic-data-index-bundle/blob/1.x/src/Model/Search/Modifier/Sort/OrderByField.php) | Field based sorting | Order by given field name.<br/>If `$enablePqlFieldNameResolution` is set to true (default) [Pimcore Query Language](https://github.com/pimcore/generic-data-index-bundle/blob/1.x/src/Model/Search/Modifier/Sort/OrderByField.php) field name resolution logic is enabled. Therefore it's possible to use short field names then instead of specifying the full path in OpenSearch. |
6161
| [OrderByPageNumber](https://github.com/pimcore/generic-data-index-bundle/blob/1.x/src/Model/Search/Modifier/Sort/Tree/OrderByPageNumber.php) | Search related sorting | Use inverted search for large amounts of data (this modifier is added to the search when there are at least 1000 results by default, and page number is above the half of total pages. Furthermore, existing sorting has to be already applied.) |
62+
| [OrderByIndexField](https://github.com/pimcore/generic-data-index-bundle/blob/1.x/src/Model/Search/Modifier/Sort/Tree/OrderByIndexField.php) | Search related sorting | Order by object tree index for custom tree sorting. This modifier is currently applied only for data objects! |
6263

6364
### Aggregations
6465

src/Enum/SearchIndex/FieldCategory/SystemField.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ enum SystemField: string
3333
case PUBLISHED = 'published';
3434
case TYPE = 'type';
3535
case KEY = 'key';
36+
case INDEX = 'index';
3637
case PATH = 'path';
3738
case FULL_PATH = 'fullPath';
3839
case PATH_LEVELS = 'pathLevels';

src/Model/Search/DataObject/SearchResult/DataObjectSearchResultItem.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class DataObjectSearchResultItem implements ElementSearchResultItemInterface
3131

3232
private string $key;
3333

34+
private int $index;
35+
3436
private bool $published;
3537

3638
private string $path;
@@ -116,6 +118,18 @@ public function setKey(string $key): DataObjectSearchResultItem
116118
return $this;
117119
}
118120

121+
public function getIndex(): int
122+
{
123+
return $this->index;
124+
}
125+
126+
public function setIndex(int $index): DataObjectSearchResultItem
127+
{
128+
$this->index = $index;
129+
130+
return $this;
131+
}
132+
119133
public function isPublished(): bool
120134
{
121135
return $this->published;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* Pimcore
6+
*
7+
* This source file is available under two different licenses:
8+
* - GNU General Public License version 3 (GPLv3)
9+
* - Pimcore Commercial License (PCL)
10+
* Full copyright and license information is available in
11+
* LICENSE.md which is distributed with this source code.
12+
*
13+
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
14+
* @license http://www.pimcore.org/license GPLv3 and PCL
15+
*/
16+
17+
namespace Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Sort\Tree;
18+
19+
use Pimcore\Bundle\GenericDataIndexBundle\Enum\Search\SortDirection;
20+
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\SearchModifierInterface;
21+
22+
final readonly class OrderByIndexField implements SearchModifierInterface
23+
{
24+
public function getDirection(): SortDirection
25+
{
26+
return SortDirection::ASC;
27+
}
28+
}

src/SearchIndexAdapter/OpenSearch/Search/Modifier/Sort/TreeSortHandlers.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222
use Pimcore\Bundle\GenericDataIndexBundle\Model\OpenSearch\Modifier\SearchModifierContextInterface;
2323
use Pimcore\Bundle\GenericDataIndexBundle\Model\OpenSearch\Sort\FieldSort;
2424
use Pimcore\Bundle\GenericDataIndexBundle\Model\OpenSearch\Sort\FieldSortList;
25+
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\DataObject\DataObjectSearch;
2526
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Sort\OrderByPageNumber;
2627
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Sort\Tree\OrderByFullPath;
28+
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Sort\Tree\OrderByIndexField;
2729
use Pimcore\Bundle\GenericDataIndexBundle\SearchIndexAdapter\SearchIndexServiceInterface;
2830

2931
/**
@@ -93,6 +95,24 @@ public function handleSortByPageNumber(
9395
}
9496
}
9597

98+
#[AsSearchModifierHandler]
99+
public function handleIndexSort(
100+
OrderByIndexField $indexSort,
101+
SearchModifierContextInterface $context
102+
): void {
103+
if (!$context->getOriginalSearch() instanceof DataObjectSearch) {
104+
return;
105+
}
106+
107+
$context->getSearch()
108+
->addSort(
109+
new FieldSort(
110+
SystemField::INDEX->getPath(),
111+
$indexSort->getDirection()->value
112+
)
113+
);
114+
}
115+
96116
private function getInvertedSortList(array $sortListItems): array
97117
{
98118
$invertedSortList = [];

src/Service/Serializer/Denormalizer/Search/DataObjectSearchResultDenormalizer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public function denormalize(
5959
->setType(SystemField::TYPE->getData($data))
6060
->setPublished($published)
6161
->setKey(SystemField::KEY->getData($data))
62+
->setIndex(SystemField::INDEX->getData($data))
6263
->setPath(SystemField::PATH->getData($data))
6364
->setFullPath(SystemField::FULL_PATH->getData($data))
6465
->setUserOwner(SystemField::USER_OWNER->getData($data) ?? 0)

src/Service/Serializer/Normalizer/DataObjectNormalizer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ private function normalizeSystemFields(AbstractObject $dataObject, bool $skipLaz
107107
SystemField::MODIFICATION_DATE->value => $this->formatTimestamp($dataObject->getModificationDate()),
108108
SystemField::TYPE->value => $dataObject->getType(),
109109
SystemField::KEY->value => $dataObject->getKey(),
110+
SystemField::INDEX->value => $dataObject->getIndex(),
110111
SystemField::PATH->value => $dataObject->getPath(),
111112
SystemField::FULL_PATH->value => $dataObject->getRealFullPath(),
112113
SystemField::USER_OWNER->value => $dataObject->getUserOwner(),

tests/Functional/Search/Modifier/Sort/SortModifierTest.php

Lines changed: 76 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -15,32 +15,46 @@
1515

1616
namespace Pimcore\Bundle\GenericDataIndexBundle\Tests\Functional\Search\Modifier\Sort;
1717

18+
use Codeception\Test\Unit;
1819
use Pimcore\Bundle\GenericDataIndexBundle\Enum\Search\SortDirection;
1920
use Pimcore\Bundle\GenericDataIndexBundle\Exception\OpenSearch\ResultWindowTooLargeException;
2021
use Pimcore\Bundle\GenericDataIndexBundle\Exception\OpenSearch\SearchFailedException;
2122
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Sort\OrderByField;
2223
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Sort\Tree\OrderByFullPath;
24+
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Sort\Tree\OrderByIndexField;
2325
use Pimcore\Bundle\GenericDataIndexBundle\SearchIndexAdapter\OpenSearch\Search\Modifier\Sort\TreeSortHandlers;
2426
use Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\Asset\AssetSearchServiceInterface;
27+
use Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\DataObject\DataObjectSearchServiceInterface;
2528
use Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\SearchProviderInterface;
2629
use Pimcore\Bundle\GenericDataIndexBundle\Service\SearchIndex\SearchIndexConfigServiceInterface;
30+
use Pimcore\Bundle\GenericDataIndexBundle\Tests\IndexTester;
2731
use Pimcore\Tests\Support\Util\TestHelper;
2832

29-
final class SortModifierTest extends \Codeception\Test\Unit
33+
final class SortModifierTest extends Unit
3034
{
31-
/**
32-
* @var \Pimcore\Bundle\GenericDataIndexBundle\Tests\IndexTester
33-
*/
34-
protected $tester;
35+
protected IndexTester $tester;
36+
37+
private AssetSearchServiceInterface $assetSearchService;
38+
39+
private DataObjectSearchServiceInterface $dataObjectSearchService;
40+
41+
private SearchProviderInterface $searchProvider;
3542

3643
private bool $indexSettingsChanged = false;
3744

38-
protected function _before()
45+
protected function _before(): void
3946
{
47+
$this->searchProvider = $this->tester->grabService(SearchProviderInterface::class);
48+
$this->assetSearchService = $this->tester->grabService(
49+
'generic-data-index.test.service.asset-search-service'
50+
);
51+
$this->dataObjectSearchService = $this->tester->grabService(
52+
'generic-data-index.test.service.data-object-search-service'
53+
);
4054
$this->tester->enableSynchronousProcessing();
4155
}
4256

43-
protected function _after()
57+
protected function _after(): void
4458
{
4559
if ($this->indexSettingsChanged) {
4660
$this->indexSettingsChanged = false;
@@ -53,7 +67,7 @@ protected function _after()
5367
$this->tester->flushIndex();
5468
}
5569

56-
public function testHandleSortByPageNumber()
70+
public function testHandleSortByPageNumber(): void
5771
{
5872
$assets = $this->createAssets();
5973
$fullPaths = [];
@@ -62,11 +76,7 @@ public function testHandleSortByPageNumber()
6276
}
6377
/** @var SearchIndexConfigServiceInterface $searchIndexConfigService */
6478
$searchIndexConfigService = $this->tester->grabService(SearchIndexConfigServiceInterface::class);
65-
/** @var AssetSearchServiceInterface $searchService */
66-
$searchService = $this->tester->grabService('generic-data-index.test.service.asset-search-service');
67-
/** @var SearchProviderInterface $searchProvider */
68-
$searchProvider = $this->tester->grabService(SearchProviderInterface::class);
69-
/** @var TreeSortHandlers $searchProvider */
79+
/** @var TreeSortHandlers $treeSort */
7080
$treeSort = $this->tester->grabService(TreeSortHandlers::class);
7181

7282
// Make sure that sorting by page number is executed
@@ -77,43 +87,43 @@ public function testHandleSortByPageNumber()
7787

7888
// Sort direction of the result should remain same even though the search order was reversed
7989
rsort($fullPaths);
80-
$assetSearch = $searchProvider
90+
$assetSearch = $this->searchProvider
8191
->createAssetSearch()
8292
->setPageSize(2)
8393
->setPage(5)
8494
->addModifier(new OrderByFullPath(SortDirection::DESC))
8595
;
86-
$searchResult = $searchService->search($assetSearch);
96+
$searchResult = $this->assetSearchService->search($assetSearch);
8797

8898
$this->assertCount(2, $searchResult->getItems());
8999
$this->assertSame($fullPaths[8], $searchResult->getItems()[0]->getFullPath());
90100
$this->assertSame($fullPaths[9], $searchResult->getItems()[1]->getFullPath());
91101

92102
sort($fullPaths);
93-
$assetSearch = $searchProvider
103+
$assetSearch = $this->searchProvider
94104
->createAssetSearch()
95105
->setPageSize(2)
96106
->setPage(5)
97107
->addModifier(new OrderByFullPath())
98108
;
99-
$searchResult = $searchService->search($assetSearch);
109+
$searchResult = $this->assetSearchService->search($assetSearch);
100110
$this->assertCount(2, $searchResult->getItems());
101111
$this->assertSame($fullPaths[8], $searchResult->getItems()[0]->getFullPath());
102112
$this->assertSame($fullPaths[9], $searchResult->getItems()[1]->getFullPath());
103113

104114
//Without initial sort is page sort not executed and because of the result window we expect an exception
105-
$assetSearch = $searchProvider
115+
$assetSearch = $this->searchProvider
106116
->createAssetSearch()
107117
->setPageSize(2)
108118
->setPage(5)
109119
;
110120

111121
$this->expectException(ResultWindowTooLargeException::class);
112-
$searchService->search($assetSearch);
122+
$this->assetSearchService->search($assetSearch);
113123
}
114124

115125
// tests
116-
public function testOrderByFullPath()
126+
public function testOrderByFullPath(): void
117127
{
118128
$asset = TestHelper::createImageAsset();
119129
$asset2 = TestHelper::createImageAsset();
@@ -126,41 +136,64 @@ public function testOrderByFullPath()
126136
];
127137
sort($fullPaths);
128138

129-
/** @var AssetSearchServiceInterface $searchService */
130-
$searchService = $this->tester->grabService('generic-data-index.test.service.asset-search-service');
131-
/** @var SearchProviderInterface $searchProvider */
132-
$searchProvider = $this->tester->grabService(SearchProviderInterface::class);
133-
134-
$assetSearch = $searchProvider
139+
$assetSearch = $this->searchProvider
135140
->createAssetSearch()
136141
->addModifier(new OrderByFullPath())
137142
;
138-
$searchResult = $searchService->search($assetSearch);
143+
$searchResult = $this->assetSearchService->search($assetSearch);
139144

140-
$resultFullPaths = array_map(function ($asset) {
145+
$resultFullPaths = array_map(static function ($asset) {
141146
return $asset->getFullPath();
142147
}, $searchResult->getItems());
143148

144149
$this->assertEquals($fullPaths, $resultFullPaths);
145150

146151
rsort($fullPaths);
147152

148-
$assetSearch = $searchProvider
153+
$assetSearch = $this->searchProvider
149154
->createAssetSearch()
150155
->addModifier(new OrderByFullPath(SortDirection::DESC))
151156
;
152-
$searchResult = $searchService->search($assetSearch);
157+
$searchResult = $this->assetSearchService->search($assetSearch);
153158

154-
$resultFullPaths = array_map(function ($asset) {
159+
$resultFullPaths = array_map(static function ($asset) {
155160
return $asset->getFullPath();
156161
}, $searchResult->getItems());
157162

158163
$this->assertEquals($fullPaths, $resultFullPaths);
159164

160165
}
161166

167+
public function testOrderByIndex(): void
168+
{
169+
$object = TestHelper::createEmptyObject()->setIndex(1)->save();
170+
$object2 = TestHelper::createEmptyObject()->setIndex(2)->save();
171+
$object3 = TestHelper::createEmptyObject()->setIndex(0)->save();
172+
$sortedIds = [$object3->getId(), $object->getId(), $object2->getId()];
173+
174+
$dataObjectSearch = $this->searchProvider
175+
->createDataObjectSearch()
176+
->addModifier(new OrderByIndexField())
177+
;
178+
179+
$results = $this->dataObjectSearchService->search($dataObjectSearch);
180+
$resultIds = array_map(static function ($object) {
181+
return $object->getId();
182+
}, $results->getItems());
183+
184+
$this->assertEquals($sortedIds, $resultIds);
185+
186+
$object->setIndex(3)->save();
187+
$results = $this->dataObjectSearchService->search($dataObjectSearch);
188+
$resultIds = array_map(static function ($object) {
189+
return $object->getId();
190+
}, $results->getItems());
191+
192+
$this->assertEquals($object->getId(), $resultIds[2]);
193+
}
194+
162195
// tests
163-
public function testOrderByField()
196+
public function testOrderByField(): void
164197
{
165198
$asset = TestHelper::createImageAsset();
166199
$asset2 = TestHelper::createImageAsset();
@@ -173,49 +206,44 @@ public function testOrderByField()
173206
];
174207
sort($keys);
175208

176-
/** @var AssetSearchServiceInterface $searchService */
177-
$searchService = $this->tester->grabService('generic-data-index.test.service.asset-search-service');
178-
/** @var SearchProviderInterface $searchProvider */
179-
$searchProvider = $this->tester->grabService(SearchProviderInterface::class);
180-
181-
$assetSearch = $searchProvider
209+
$assetSearch = $this->searchProvider
182210
->createAssetSearch()
183211
->addModifier(new OrderByField('key'))
184212
;
185-
$searchResult = $searchService->search($assetSearch);
186-
$resultKeys = array_map(function ($asset) {
213+
$searchResult = $this->assetSearchService->search($assetSearch);
214+
$resultKeys = array_map(static function ($asset) {
187215
return $asset->getKey();
188216
}, $searchResult->getItems());
189217
$this->assertEquals($keys, $resultKeys);
190218

191219
rsort($keys);
192-
$assetSearch = $searchProvider
220+
$assetSearch = $this->searchProvider
193221
->createAssetSearch()
194222
->addModifier(new OrderByField('key', SortDirection::DESC))
195223
;
196-
$searchResult = $searchService->search($assetSearch);
197-
$resultKeys = array_map(function ($asset) {
224+
$searchResult = $this->assetSearchService->search($assetSearch);
225+
$resultKeys = array_map(static function ($asset) {
198226
return $asset->getKey();
199227
}, $searchResult->getItems());
200228
$this->assertEquals($keys, $resultKeys);
201229

202-
$assetSearch = $searchProvider
230+
$assetSearch = $this->searchProvider
203231
->createAssetSearch()
204232
->addModifier(new OrderByField('system_fields.key.sort', SortDirection::DESC, false))
205233
;
206234

207-
$searchResult = $searchService->search($assetSearch);
208-
$resultKeys = array_map(function ($asset) {
235+
$searchResult = $this->assetSearchService->search($assetSearch);
236+
$resultKeys = array_map(static function ($asset) {
209237
return $asset->getKey();
210238
}, $searchResult->getItems());
211239
$this->assertEquals($keys, $resultKeys);
212240

213-
$assetSearch = $searchProvider
241+
$assetSearch = $this->searchProvider
214242
->createAssetSearch()
215243
->addModifier(new OrderByField('key', SortDirection::ASC, false))
216244
;
217245
$this->expectException(SearchFailedException::class);
218-
$searchService->search($assetSearch);
246+
$this->assetSearchService->search($assetSearch);
219247

220248
}
221249

0 commit comments

Comments
 (0)