Skip to content

Commit

Permalink
IBX-5111: Introduced mapper registry for customizing value object bui…
Browse files Browse the repository at this point in the history
…lding on search hits
  • Loading branch information
konradoboza committed Jun 5, 2023
1 parent 747765e commit e011a31
Show file tree
Hide file tree
Showing 18 changed files with 442 additions and 86 deletions.
2 changes: 2 additions & 0 deletions src/bundle/Core/ApiLoader/RepositoryFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public function buildRepository(
PersistenceHandler $persistenceHandler,
SearchHandler $searchHandler,
BackgroundIndexer $backgroundIndexer,
Mapper\SearchResultMapperRegistryInterface $searchResultMapperRegistry,
RelationProcessor $relationProcessor,
FieldTypeRegistry $fieldTypeRegistry,
PasswordHashService $passwordHashService,
Expand All @@ -99,6 +100,7 @@ public function buildRepository(
$persistenceHandler,
$searchHandler,
$backgroundIndexer,
$searchResultMapperRegistry,
$relationProcessor,
$fieldTypeRegistry,
$passwordHashService,
Expand Down
11 changes: 9 additions & 2 deletions src/contracts/Repository/Decorator/SearchServiceDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Ibexa\Contracts\Core\Repository\Values\Content\Query;
use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion;
use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult;
use Ibexa\Contracts\Core\Search\Subject\SearchSubjectInterface;

abstract class SearchServiceDecorator implements SearchService
{
Expand All @@ -28,9 +29,15 @@ public function __construct(SearchService $innerService)
public function findContent(
Query $query,
array $languageFilter = [],
bool $filterOnUserPermissions = true
bool $filterOnUserPermissions = true,
SearchSubjectInterface $searchSubject = null
): SearchResult {
return $this->innerService->findContent($query, $languageFilter, $filterOnUserPermissions);
return $this->innerService->findContent(
$query,
$languageFilter,
$filterOnUserPermissions,
$searchSubject
);
}

public function findContentInfo(
Expand Down
8 changes: 7 additions & 1 deletion src/contracts/Repository/SearchService.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Ibexa\Contracts\Core\Repository\Values\Content\Query;
use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion;
use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult;
use Ibexa\Contracts\Core\Search\Subject\SearchSubjectInterface;

/**
* Search service.
Expand Down Expand Up @@ -128,7 +129,12 @@ interface SearchService
*
* @return \Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult
*/
public function findContent(Query $query, array $languageFilter = [], bool $filterOnUserPermissions = true): SearchResult;
public function findContent(
Query $query,
array $languageFilter = [],
bool $filterOnUserPermissions = true,
SearchSubjectInterface $searchSubject = null
): SearchResult;

/**
* Finds contentInfo objects for the given query.
Expand Down
15 changes: 15 additions & 0 deletions src/contracts/Search/Subject/SearchSubjectInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
namespace Ibexa\Contracts\Core\Search\Subject;

/**
* Used to distinguish which implementation of Ibexa\Core\Repository\Mapper\SearchResultMapperInterface
* should be responsible for mapping search results to ValueObjects.
*/
interface SearchSubjectInterface
{
}
3 changes: 3 additions & 0 deletions src/lib/Base/Container/ApiLoader/RepositoryFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Ibexa\Core\FieldType\FieldTypeRegistry;
use Ibexa\Core\Repository\Helper\RelationProcessor;
use Ibexa\Core\Repository\Mapper;
use Ibexa\Core\Repository\Mapper\SearchResultMapperRegistryInterface;
use Ibexa\Core\Repository\Permission\LimitationService;
use Ibexa\Core\Repository\ProxyFactory\ProxyDomainMapperFactoryInterface;
use Ibexa\Core\Repository\User\PasswordValidatorInterface;
Expand Down Expand Up @@ -67,6 +68,7 @@ public function buildRepository(
PersistenceHandler $persistenceHandler,
SearchHandler $searchHandler,
BackgroundIndexer $backgroundIndexer,
SearchResultMapperRegistryInterface $searchResultMapperRegistry,
RelationProcessor $relationProcessor,
FieldTypeRegistry $fieldTypeRegistry,
PasswordHashService $passwordHashService,
Expand All @@ -89,6 +91,7 @@ public function buildRepository(
$persistenceHandler,
$searchHandler,
$backgroundIndexer,
$searchResultMapperRegistry,
$relationProcessor,
$fieldTypeRegistry,
$passwordHashService,
Expand Down
18 changes: 15 additions & 3 deletions src/lib/Repository/Mapper/ContentDomainMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult;
use Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo as APIVersionInfo;
use Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType;
use Ibexa\Contracts\Core\Search\Subject\SearchSubjectInterface;
use Ibexa\Core\Base\Exceptions\InvalidArgumentException;
use Ibexa\Core\Base\Exceptions\InvalidArgumentType;
use Ibexa\Core\Base\Exceptions\InvalidArgumentValue;
Expand All @@ -36,6 +37,7 @@
use Ibexa\Core\Repository\Values\Content\Location;
use Ibexa\Core\Repository\Values\Content\Relation;
use Ibexa\Core\Repository\Values\Content\VersionInfo;
use Ibexa\Core\Search\Common\Subject\Content as ContentSearchSubject;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
Expand All @@ -46,7 +48,7 @@
*
* @internal Meant for internal use by Repository.
*/
class ContentDomainMapper extends ProxyAwareDomainMapper implements LoggerAwareInterface
class ContentDomainMapper extends ProxyAwareDomainMapper implements SearchResultMapperInterface, LoggerAwareInterface
{
use LoggerAwareTrait;

Expand Down Expand Up @@ -610,8 +612,8 @@ public function buildContentDomainObjectsOnSearchResult(SearchResult $result, ar
$info = $hit->valueObject;
$contentIds[] = $info->id;
$contentTypeIds[] = $info->contentTypeId;
// Unless we are told to load all languages, we add main language to translations so they are loaded too
// Might in some case load more languages then intended, but prioritised handling will pick right one
// Unless we are told to load all languages, we add main language to translations, so they are loaded too
// Might in some case load more languages than intended, but prioritised handling will pick right one
if (!empty($languageFilter['languages']) && $useAlwaysAvailable && $info->alwaysAvailable) {
$translations[] = $info->mainLanguageCode;
}
Expand Down Expand Up @@ -893,6 +895,16 @@ private function isRootLocation(SPILocation $spiLocation): bool
{
return $spiLocation->id === $spiLocation->parentId;
}

public function buildObjectsOnSearchResult(SearchResult $result, array $languageFilter = []): array
{
return $this->buildContentDomainObjectsOnSearchResult($result, $languageFilter);
}

public function supports(SearchSubjectInterface $searchSubject): bool
{
return $searchSubject instanceof ContentSearchSubject;
}
}

class_alias(ContentDomainMapper::class, 'eZ\Publish\Core\Repository\Mapper\ContentDomainMapper');
22 changes: 22 additions & 0 deletions src/lib/Repository/Mapper/SearchResultMapperInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
namespace Ibexa\Core\Repository\Mapper;

use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult;
use Ibexa\Contracts\Core\Search\Subject\SearchSubjectInterface;

interface SearchResultMapperInterface
{
/**
* @param array<mixed> $languageFilter
*
* @return array<\Ibexa\Contracts\Core\Persistence\ValueObject>
*/
public function buildObjectsOnSearchResult(SearchResult $result, array $languageFilter = []): array;

public function supports(SearchSubjectInterface $searchSubject): bool;
}
58 changes: 58 additions & 0 deletions src/lib/Repository/Mapper/SearchResultMapperRegistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
namespace Ibexa\Core\Repository\Mapper;

use Ibexa\Contracts\Core\Search\Subject\SearchSubjectInterface;
use Ibexa\Core\Base\Exceptions\InvalidArgumentException;

final class SearchResultMapperRegistry implements SearchResultMapperRegistryInterface
{
/** @var iterable<\Ibexa\Core\Repository\Mapper\SearchResultMapperInterface> */
private iterable $mappers;

/**
* @param iterable<\Ibexa\Core\Repository\Mapper\SearchResultMapperInterface> $mappers
*/
public function __construct(iterable $mappers)
{
$this->mappers = $mappers;
}

public function hasMapper(SearchSubjectInterface $searchSubject): bool
{
return $this->findMappers($searchSubject) !== null;
}

public function getMapper(SearchSubjectInterface $searchSubject): SearchResultMapperInterface
{
$mapper = $this->findMappers($searchSubject);

if ($mapper === null) {
throw new InvalidArgumentException(
'$hitValueObject',
sprintf(
'undefined %s for search subject %s',
SearchResultMapperInterface::class,
get_debug_type($searchSubject)
)
);
}

return $mapper;
}

private function findMappers(SearchSubjectInterface $searchSubject): ?SearchResultMapperInterface
{
foreach ($this->mappers as $mapper) {
if ($mapper->supports($searchSubject)) {
return $mapper;
}
}

return null;
}
}
16 changes: 16 additions & 0 deletions src/lib/Repository/Mapper/SearchResultMapperRegistryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
namespace Ibexa\Core\Repository\Mapper;

use Ibexa\Contracts\Core\Search\Subject\SearchSubjectInterface;

interface SearchResultMapperRegistryInterface
{
public function hasMapper(SearchSubjectInterface $searchSubject): bool;

public function getMapper(SearchSubjectInterface $searchSubject): SearchResultMapperInterface;
}
6 changes: 6 additions & 0 deletions src/lib/Repository/Repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
use Ibexa\Core\FieldType\FieldTypeRegistry;
use Ibexa\Core\Repository\Helper\NameSchemaService;
use Ibexa\Core\Repository\Helper\RelationProcessor;
use Ibexa\Core\Repository\Mapper\SearchResultMapperRegistryInterface;
use Ibexa\Core\Repository\Permission\LimitationService;
use Ibexa\Core\Repository\ProxyFactory\ProxyDomainMapperFactoryInterface;
use Ibexa\Core\Repository\ProxyFactory\ProxyDomainMapperInterface;
Expand Down Expand Up @@ -228,6 +229,8 @@ class Repository implements RepositoryInterface
/** @var \Ibexa\Core\Search\Common\BackgroundIndexer|null */
protected $backgroundIndexer;

protected SearchResultMapperRegistryInterface $searchResultMapperRegistry;

/** @var \Psr\Log\LoggerInterface */
private $logger;

Expand Down Expand Up @@ -270,6 +273,7 @@ public function __construct(
PersistenceHandler $persistenceHandler,
SearchHandler $searchHandler,
BackgroundIndexer $backgroundIndexer,
SearchResultMapperRegistryInterface $searchResultMapperRegistry,
RelationProcessor $relationProcessor,
FieldTypeRegistry $fieldTypeRegistry,
PasswordHashService $passwordHashGenerator,
Expand All @@ -293,6 +297,7 @@ public function __construct(
$this->persistenceHandler = $persistenceHandler;
$this->searchHandler = $searchHandler;
$this->backgroundIndexer = $backgroundIndexer;
$this->searchResultMapperRegistry = $searchResultMapperRegistry;
$this->relationProcessor = $relationProcessor;
$this->fieldTypeRegistry = $fieldTypeRegistry;
$this->passwordHashService = $passwordHashGenerator;
Expand Down Expand Up @@ -694,6 +699,7 @@ public function getSearchService(): SearchServiceInterface
$this->contentDomainMapper,
$this->getPermissionCriterionResolver(),
$this->backgroundIndexer,
$this->searchResultMapperRegistry,
$this->serviceSettings['search']
);

Expand Down
32 changes: 29 additions & 3 deletions src/lib/Repository/SearchService.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

namespace Ibexa\Core\Repository;

use Ibexa\Contracts\Core\Persistence\Content\ContentInfo;
use Ibexa\Contracts\Core\Repository\PermissionCriterionResolver;
use Ibexa\Contracts\Core\Repository\Repository as RepositoryInterface;
use Ibexa\Contracts\Core\Repository\SearchService as SearchServiceInterface;
Expand All @@ -22,11 +23,14 @@
use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult;
use Ibexa\Contracts\Core\Search\Capable;
use Ibexa\Contracts\Core\Search\Handler;
use Ibexa\Contracts\Core\Search\Subject\SearchSubjectInterface;
use Ibexa\Core\Base\Exceptions\InvalidArgumentException;
use Ibexa\Core\Base\Exceptions\InvalidArgumentType;
use Ibexa\Core\Base\Exceptions\NotFoundException;
use Ibexa\Core\Repository\Mapper\ContentDomainMapper;
use Ibexa\Core\Repository\Mapper\SearchResultMapperRegistryInterface;
use Ibexa\Core\Search\Common\BackgroundIndexer;
use Ibexa\Core\Search\Common\Subject\Content as ContentSearchSubject;

/**
* Search service.
Expand All @@ -51,6 +55,8 @@ class SearchService implements SearchServiceInterface
/** @var \Ibexa\Core\Search\Common\BackgroundIndexer */
protected $backgroundIndexer;

protected SearchResultMapperRegistryInterface $searchResultMapperRegistry;

/**
* Setups service with reference to repository object that created it & corresponding handler.
*
Expand All @@ -67,6 +73,7 @@ public function __construct(
ContentDomainMapper $contentDomainMapper,
PermissionCriterionResolver $permissionCriterionResolver,
BackgroundIndexer $backgroundIndexer,
SearchResultMapperRegistryInterface $searchResultMapperRegistry,
array $settings = []
) {
$this->repository = $repository;
Expand All @@ -78,6 +85,7 @@ public function __construct(
];
$this->permissionCriterionResolver = $permissionCriterionResolver;
$this->backgroundIndexer = $backgroundIndexer;
$this->searchResultMapperRegistry = $searchResultMapperRegistry;
}

/**
Expand All @@ -93,11 +101,29 @@ public function __construct(
*
* @return \Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult
*/
public function findContent(Query $query, array $languageFilter = [], bool $filterOnUserPermissions = true): SearchResult
{
public function findContent(
Query $query,
array $languageFilter = [],
bool $filterOnUserPermissions = true,
SearchSubjectInterface $searchSubject = null
): SearchResult {
if ($searchSubject === null) {
$searchSubject = new ContentSearchSubject();
}

$missingContentList = [];
$result = $this->internalFindContentInfo($query, $languageFilter, $filterOnUserPermissions);
$missingContentList = $this->contentDomainMapper->buildContentDomainObjectsOnSearchResult($result, $languageFilter);

if ($this->searchResultMapperRegistry->hasMapper($searchSubject)) {
$mapper = $this->searchResultMapperRegistry->getMapper($searchSubject);
$missingContentList = $mapper->buildObjectsOnSearchResult($result, $languageFilter);
}

foreach ($missingContentList as $missingContent) {
if (!$missingContent instanceof ContentInfo) {
continue;
}

$this->backgroundIndexer->registerContent($missingContent);
}

Expand Down
Loading

0 comments on commit e011a31

Please sign in to comment.