WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit e083b65

Browse files
committed
Refactored location sorting logic in query builders and enhance filtering tests
1 parent 6fe6167 commit e083b65

File tree

7 files changed

+226
-15
lines changed

7 files changed

+226
-15
lines changed

src/lib/Persistence/Legacy/Filter/SortClauseQueryBuilder/Location/BaseLocationSortClauseQueryBuilder.php

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,76 @@
1111
use Ibexa\Contracts\Core\Persistence\Filter\Doctrine\FilteringQueryBuilder;
1212
use Ibexa\Contracts\Core\Repository\Values\Filter\FilteringSortClause;
1313
use Ibexa\Contracts\Core\Repository\Values\Filter\SortClauseQueryBuilder;
14+
use Ibexa\Core\Persistence\Legacy\Content\Location\Gateway as LocationGateway;
1415

1516
/**
1617
* @internal
1718
*/
1819
abstract class BaseLocationSortClauseQueryBuilder implements SortClauseQueryBuilder
1920
{
21+
private const CONTENT_SORT_LOCATION_ALIAS = 'ibexa_sort_location';
22+
23+
private string $locationAlias = self::CONTENT_SORT_LOCATION_ALIAS;
24+
25+
private bool $needsMainLocationJoin = true;
26+
2027
public function buildQuery(
2128
FilteringQueryBuilder $queryBuilder,
2229
FilteringSortClause $sortClause
2330
): void {
24-
$sort = $this->getSortingExpression();
25-
$queryBuilder
26-
->addSelect($this->getSortingExpression())
27-
->joinAllLocations();
31+
$this->prepareLocationAlias($queryBuilder);
32+
33+
$sort = $this->getSortingExpression($this->locationAlias);
34+
$queryBuilder->addSelect($sort);
35+
36+
if ($this->needsMainLocationJoin) {
37+
$this->joinMainLocationOnly($queryBuilder, $this->locationAlias);
38+
}
2839

2940
/** @var \Ibexa\Contracts\Core\Repository\Values\Content\Query\SortClause $sortClause */
3041
$queryBuilder->addOrderBy($sort, $sortClause->direction);
3142
}
3243

33-
abstract protected function getSortingExpression(): string;
44+
private function prepareLocationAlias(FilteringQueryBuilder $queryBuilder): void
45+
{
46+
if ($this->isLocationFilteringContext($queryBuilder)) {
47+
$queryBuilder->joinAllLocations();
48+
$this->locationAlias = 'location';
49+
$this->needsMainLocationJoin = false;
50+
51+
return;
52+
}
53+
54+
$this->locationAlias = self::CONTENT_SORT_LOCATION_ALIAS;
55+
$this->needsMainLocationJoin = true;
56+
}
57+
58+
private function isLocationFilteringContext(FilteringQueryBuilder $queryBuilder): bool
59+
{
60+
$fromParts = $queryBuilder->getQueryPart('from');
61+
foreach ($fromParts as $fromPart) {
62+
if (($fromPart['alias'] ?? null) === 'location') {
63+
return true;
64+
}
65+
}
66+
67+
return false;
68+
}
69+
70+
private function joinMainLocationOnly(FilteringQueryBuilder $queryBuilder, string $alias): void
71+
{
72+
$queryBuilder->joinOnce(
73+
'content',
74+
LocationGateway::CONTENT_TREE_TABLE,
75+
$alias,
76+
(string)$queryBuilder->expr()->andX(
77+
sprintf('content.id = %s.contentobject_id', $alias),
78+
sprintf('%s.node_id = %s.main_node_id', $alias, $alias)
79+
)
80+
);
81+
}
82+
83+
abstract protected function getSortingExpression(string $locationAlias): string;
3484
}
3585

3686
class_alias(BaseLocationSortClauseQueryBuilder::class, 'eZ\Publish\Core\Persistence\Legacy\Filter\SortClauseQueryBuilder\Location\BaseLocationSortClauseQueryBuilder');

src/lib/Persistence/Legacy/Filter/SortClauseQueryBuilder/Location/DepthQueryBuilder.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ public function accepts(FilteringSortClause $sortClause): bool
2121
return $sortClause instanceof Location\Depth;
2222
}
2323

24-
protected function getSortingExpression(): string
24+
protected function getSortingExpression(string $locationAlias): string
2525
{
26-
return 'location.depth';
26+
return sprintf('%s.depth', $locationAlias);
2727
}
2828
}
2929

src/lib/Persistence/Legacy/Filter/SortClauseQueryBuilder/Location/IdQueryBuilder.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ public function accepts(FilteringSortClause $sortClause): bool
2121
return $sortClause instanceof Location\Id;
2222
}
2323

24-
protected function getSortingExpression(): string
24+
protected function getSortingExpression(string $locationAlias): string
2525
{
26-
return 'location.node_id';
26+
return sprintf('%s.node_id', $locationAlias);
2727
}
2828
}
2929

src/lib/Persistence/Legacy/Filter/SortClauseQueryBuilder/Location/PathQueryBuilder.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ public function accepts(FilteringSortClause $sortClause): bool
1818
return $sortClause instanceof Location\Path;
1919
}
2020

21-
protected function getSortingExpression(): string
21+
protected function getSortingExpression(string $locationAlias): string
2222
{
23-
return 'location.path_string';
23+
return sprintf('%s.path_string', $locationAlias);
2424
}
2525
}
2626

src/lib/Persistence/Legacy/Filter/SortClauseQueryBuilder/Location/PriorityQueryBuilder.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ public function accepts(FilteringSortClause $sortClause): bool
1818
return $sortClause instanceof Location\Priority;
1919
}
2020

21-
protected function getSortingExpression(): string
21+
protected function getSortingExpression(string $locationAlias): string
2222
{
23-
return 'location.priority';
23+
return sprintf('%s.priority', $locationAlias);
2424
}
2525
}
2626

src/lib/Persistence/Legacy/Filter/SortClauseQueryBuilder/Location/VisibilityQueryBuilder.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ public function accepts(FilteringSortClause $sortClause): bool
1818
return $sortClause instanceof Location\Visibility;
1919
}
2020

21-
protected function getSortingExpression(): string
21+
protected function getSortingExpression(string $locationAlias): string
2222
{
23-
return 'location.is_invisible';
23+
return sprintf('%s.is_invisible', $locationAlias);
2424
}
2525
}
2626

tests/integration/Core/Repository/Filtering/ContentFilteringTest.php

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@
88

99
namespace Ibexa\Tests\Integration\Core\Repository\Filtering;
1010

11+
use function array_filter;
1112
use function array_map;
13+
use function array_values;
1214
use function count;
15+
use function iterator_to_array;
16+
use Ibexa\Contracts\Core\Repository\LocationService;
1317
use Ibexa\Contracts\Core\Repository\Values\Content\Content;
1418
use Ibexa\Contracts\Core\Repository\Values\Content\ContentList;
19+
use Ibexa\Contracts\Core\Repository\Values\Content\Location;
1520
use Ibexa\Contracts\Core\Repository\Values\Content\Query;
1621
use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion;
1722
use Ibexa\Contracts\Core\Repository\Values\Content\Query\SortClause;
@@ -81,6 +86,154 @@ public function testFindWithLocationSortClauses(): void
8186
);
8287
}
8388

89+
public function testLocationSortClausesUseMainLocationDuringContentFiltering(): void
90+
{
91+
$repository = $this->getRepository(false);
92+
$locationService = $repository->getLocationService();
93+
$contentService = $repository->getContentService();
94+
95+
$shallowParent = $this->createFolder(
96+
['eng-GB' => 'Shallow Parent'],
97+
2
98+
);
99+
$referenceContent = $this->createFolder(
100+
['eng-GB' => 'Reference folder'],
101+
$shallowParent->contentInfo->mainLocationId
102+
);
103+
$deepParent = $this->createFolder(
104+
['eng-GB' => 'Deep Parent'],
105+
$referenceContent->contentInfo->mainLocationId
106+
);
107+
$contentWithAdditionalLocation = $this->createFolder(
108+
['eng-GB' => 'Folder with extra location'],
109+
$deepParent->contentInfo->mainLocationId
110+
);
111+
$locationService->createLocation(
112+
$contentWithAdditionalLocation->contentInfo,
113+
$locationService->newLocationCreateStruct(2)
114+
);
115+
116+
$mainLocation = $this->loadMainLocation($locationService, $contentWithAdditionalLocation);
117+
$nonMainLocations = array_values(
118+
array_filter(
119+
iterator_to_array($locationService->loadLocations($contentWithAdditionalLocation->contentInfo)),
120+
static function ($location) use ($contentWithAdditionalLocation) {
121+
return $location->id !== $contentWithAdditionalLocation->contentInfo->mainLocationId;
122+
}
123+
)
124+
);
125+
self::assertNotEmpty($nonMainLocations);
126+
$nonMainLocation = $nonMainLocations[0];
127+
$referenceLocation = $this->loadMainLocation($locationService, $referenceContent);
128+
129+
self::assertLessThan($referenceLocation->depth, $nonMainLocation->depth);
130+
self::assertLessThan($mainLocation->depth, $referenceLocation->depth);
131+
132+
$filter = (new Filter())
133+
->withCriterion(
134+
new Criterion\ContentId(
135+
[
136+
$contentWithAdditionalLocation->id,
137+
$referenceContent->id,
138+
]
139+
)
140+
)
141+
->withSortClause(new SortClause\Location\Depth(Query::SORT_ASC));
142+
143+
$contentList = $contentService->find($filter);
144+
145+
self::assertSame(
146+
[$referenceContent->id, $contentWithAdditionalLocation->id],
147+
array_map(
148+
static function (Content $content): int {
149+
return $content->id;
150+
},
151+
iterator_to_array($contentList)
152+
)
153+
);
154+
}
155+
156+
public function testLocationSortClausesStayDeterministicWithComplexCriteria(): void
157+
{
158+
$repository = $this->getRepository(false);
159+
$locationService = $repository->getLocationService();
160+
$contentService = $repository->getContentService();
161+
162+
$shallowParent = $this->createFolder(
163+
['eng-GB' => 'Complex Root'],
164+
2
165+
);
166+
$referenceContent = $this->createFolder(
167+
['eng-GB' => 'Ref folder'],
168+
$shallowParent->contentInfo->mainLocationId
169+
);
170+
$middleContent = $this->createFolder(
171+
['eng-GB' => 'Middle folder'],
172+
$referenceContent->contentInfo->mainLocationId
173+
);
174+
$deepParent = $this->createFolder(
175+
['eng-GB' => 'Deep intermediate'],
176+
$middleContent->contentInfo->mainLocationId
177+
);
178+
$contentWithAdditionalLocation = $this->createFolder(
179+
['eng-GB' => 'Folder with randomizing location'],
180+
$deepParent->contentInfo->mainLocationId
181+
);
182+
$locationService->createLocation(
183+
$contentWithAdditionalLocation->contentInfo,
184+
$locationService->newLocationCreateStruct(2)
185+
);
186+
187+
$referenceLocation = $this->loadMainLocation($locationService, $referenceContent);
188+
$middleLocation = $this->loadMainLocation($locationService, $middleContent);
189+
$mainLocation = $this->loadMainLocation($locationService, $contentWithAdditionalLocation);
190+
$nonMainLocations = array_values(
191+
array_filter(
192+
iterator_to_array($locationService->loadLocations($contentWithAdditionalLocation->contentInfo)),
193+
static function ($location) use ($contentWithAdditionalLocation) {
194+
return $location->id !== $contentWithAdditionalLocation->contentInfo->mainLocationId;
195+
}
196+
)
197+
);
198+
self::assertNotEmpty($nonMainLocations);
199+
$nonMainLocation = $nonMainLocations[0];
200+
self::assertNotEquals($mainLocation->depth, $nonMainLocation->depth);
201+
202+
$shallowParentLocation = $this->loadMainLocation($locationService, $shallowParent);
203+
204+
$filter = (new Filter())
205+
->withCriterion(new Criterion\Subtree($shallowParentLocation->pathString))
206+
->andWithCriterion(new Criterion\ContentTypeIdentifier('folder'))
207+
->andWithCriterion(
208+
new Criterion\ContentId(
209+
[
210+
$referenceContent->id,
211+
$middleContent->id,
212+
$contentWithAdditionalLocation->id,
213+
]
214+
)
215+
)
216+
->withSortClause(new SortClause\Location\Depth(Query::SORT_ASC))
217+
->withSortClause(new SortClause\ContentId(Query::SORT_ASC))
218+
->withLimit(10);
219+
220+
$contentList = $contentService->find($filter);
221+
222+
self::assertSame(
223+
[
224+
$referenceContent->id,
225+
$middleContent->id,
226+
$contentWithAdditionalLocation->id,
227+
],
228+
array_map(
229+
static function (Content $content): int {
230+
return $content->id;
231+
},
232+
iterator_to_array($contentList)
233+
)
234+
);
235+
}
236+
84237
/**
85238
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException
86239
* @throws \Exception
@@ -447,6 +600,14 @@ private function buildSearchQueryFromFilter(Filter $filter): Query
447600
]
448601
);
449602
}
603+
604+
private function loadMainLocation(LocationService $locationService, Content $content): Location
605+
{
606+
$mainLocationId = $content->contentInfo->mainLocationId;
607+
self::assertNotNull($mainLocationId);
608+
609+
return $locationService->loadLocation($mainLocationId);
610+
}
450611
}
451612

452613
class_alias(ContentFilteringTest::class, 'eZ\Publish\API\Repository\Tests\Filtering\ContentFilteringTest');

0 commit comments

Comments
 (0)