From 743634b9ae4c9096201b2ac6114d88270d62ce92 Mon Sep 17 00:00:00 2001 From: Holger Veltrup Date: Mon, 6 May 2024 13:58:05 +0200 Subject: [PATCH] refactor: move logic from dto to Services --- src/Console/Command/Suggest.php | 4 +- .../Query/Facet/AbsoluteDateRangeFacet.php | 3 + src/Dto/Search/Query/Facet/CategoryFacet.php | 17 +- .../Query/Facet/ContentSectionTypeFacet.php | 17 +- .../Facet/{FacetField.php => FieldFacet.php} | 2 +- src/Dto/Search/Query/Facet/GroupFacet.php | 17 +- ...acetMultiQuery.php => MultiQueryFacet.php} | 4 +- .../Search/Query/Facet/ObjectTypeFacet.php | 17 +- .../Facet/{FacetQuery.php => QueryFacet.php} | 2 +- .../Query/Facet/RelativeDateRangeFacet.php | 3 + src/Dto/Search/Query/Facet/SiteFacet.php | 17 +- .../Query/Filter/AbsoluteDateRangeFilter.php | 25 +- src/Dto/Search/Query/Filter/AndFilter.php | 15 +- src/Dto/Search/Query/Filter/ArchiveFilter.php | 8 +- .../Search/Query/Filter/CategoryFilter.php | 13 - .../Query/Filter/ContentSectionTypeFilter.php | 13 - src/Dto/Search/Query/Filter/FieldFilter.php | 30 +- src/Dto/Search/Query/Filter/Filter.php | 2 - src/Dto/Search/Query/Filter/GroupFilter.php | 13 - src/Dto/Search/Query/Filter/NotFilter.php | 10 +- .../Search/Query/Filter/ObjectTypeFilter.php | 13 - src/Dto/Search/Query/Filter/OrFilter.php | 15 +- src/Dto/Search/Query/Filter/QueryFilter.php | 10 +- .../Query/Filter/RelativeDateRangeFilter.php | 138 +------ src/Dto/Search/Query/Filter/SiteFilter.php | 13 - src/Service/ResourceChannelBasedIndexName.php | 6 +- src/Service/Search/Schema2xFieldMapper.php | 98 +++++ .../Search/SolrAbsoluteDateRangeFacet.php | 45 --- src/Service/Search/SolrDateMapper.php | 33 +- src/Service/Search/SolrMoreLikeThis.php | 28 +- src/Service/Search/SolrQueryFacetAppender.php | 161 ++++---- .../Search/SolrQueryFilterAppender.php | 138 +++++++ src/Service/Search/SolrRangeFacet.php | 13 - .../Search/SolrRelativeDateRangeFacet.php | 84 ---- src/Service/Search/SolrSearch.php | 53 +-- src/Service/Search/SolrSuggest.php | 26 +- .../Filter/AbsoluteDateRangeFilterTest.php | 32 +- .../Dto/Search/Query/Filter/AndFilterTest.php | 35 -- .../Search/Query/Filter/ArchiveFilterTest.php | 23 -- .../Search/Query/Filter/FieldFilterTest.php | 37 +- .../Dto/Search/Query/Filter/NotFilterTest.php | 28 -- test/Dto/Search/Query/Filter/OrFilterTest.php | 35 -- .../Search/Query/Filter/QueryFilterTest.php | 23 -- .../Filter/RelativeDateRangeFilterTest.php | 208 ---------- .../Search/Query/SearchQueryBuilderTest.php | 19 +- .../ResourceChannelBasedIndexNameTest.php | 10 + .../Search/Schema2xFieldMapperTest.php | 166 ++++++++ test/Service/Search/SolrDateMapperTest.php | 182 +++++++++ test/Service/Search/SolrMoreLikeThisTest.php | 13 +- .../Search/SolrQueryFacetAppenderTest.php | 289 ++++++++++++++ .../Search/SolrQueryFilterAppenderTest.php | 376 ++++++++++++++++++ test/Service/Search/SolrSearchTest.php | 246 +++++++++--- test/Service/Search/SolrSuggestTest.php | 17 +- test/Service/Search/TestSortCriteria.php | 11 + 54 files changed, 1711 insertions(+), 1145 deletions(-) rename src/Dto/Search/Query/Facet/{FacetField.php => FieldFacet.php} (92%) rename src/Dto/Search/Query/Facet/{FacetMultiQuery.php => MultiQueryFacet.php} (83%) rename src/Dto/Search/Query/Facet/{FacetQuery.php => QueryFacet.php} (92%) create mode 100644 src/Service/Search/Schema2xFieldMapper.php delete mode 100644 src/Service/Search/SolrAbsoluteDateRangeFacet.php create mode 100644 src/Service/Search/SolrQueryFilterAppender.php delete mode 100644 src/Service/Search/SolrRangeFacet.php delete mode 100644 src/Service/Search/SolrRelativeDateRangeFacet.php delete mode 100644 test/Dto/Search/Query/Filter/AndFilterTest.php delete mode 100644 test/Dto/Search/Query/Filter/ArchiveFilterTest.php delete mode 100644 test/Dto/Search/Query/Filter/NotFilterTest.php delete mode 100644 test/Dto/Search/Query/Filter/OrFilterTest.php delete mode 100644 test/Dto/Search/Query/Filter/QueryFilterTest.php delete mode 100644 test/Dto/Search/Query/Filter/RelativeDateRangeFilterTest.php create mode 100644 test/Service/Search/Schema2xFieldMapperTest.php create mode 100644 test/Service/Search/SolrDateMapperTest.php create mode 100644 test/Service/Search/SolrQueryFacetAppenderTest.php create mode 100644 test/Service/Search/SolrQueryFilterAppenderTest.php create mode 100644 test/Service/Search/TestSortCriteria.php diff --git a/src/Console/Command/Suggest.php b/src/Console/Command/Suggest.php index 4d81c34..bfdfb2d 100644 --- a/src/Console/Command/Suggest.php +++ b/src/Console/Command/Suggest.php @@ -7,6 +7,7 @@ use Atoolo\Resource\ResourceChannelFactory; use Atoolo\Search\Console\Command\Io\TypifiedInput; use Atoolo\Search\Dto\Search\Query\Filter\ArchiveFilter; +use Atoolo\Search\Dto\Search\Query\Filter\NotFilter; use Atoolo\Search\Dto\Search\Query\Filter\ObjectTypeFilter; use Atoolo\Search\Dto\Search\Query\SuggestQuery; use Atoolo\Search\Dto\Search\Result\SuggestResult; @@ -76,8 +77,7 @@ protected function execute( protected function buildQuery(string $terms, string $lang): SuggestQuery { - $excludeMedia = new ObjectTypeFilter(['media'], 'media'); - $excludeMedia = $excludeMedia->exclude(); + $excludeMedia = new NotFilter(new ObjectTypeFilter(['media'])); return new SuggestQuery( $terms, $lang, diff --git a/src/Dto/Search/Query/Facet/AbsoluteDateRangeFacet.php b/src/Dto/Search/Query/Facet/AbsoluteDateRangeFacet.php index c840dda..d7da3da 100644 --- a/src/Dto/Search/Query/Facet/AbsoluteDateRangeFacet.php +++ b/src/Dto/Search/Query/Facet/AbsoluteDateRangeFacet.php @@ -7,6 +7,9 @@ use DateInterval; use DateTime; +/** + * @codeCoverageIgnore + */ class AbsoluteDateRangeFacet extends Facet { /** diff --git a/src/Dto/Search/Query/Facet/CategoryFacet.php b/src/Dto/Search/Query/Facet/CategoryFacet.php index 81043a7..625818d 100644 --- a/src/Dto/Search/Query/Facet/CategoryFacet.php +++ b/src/Dto/Search/Query/Facet/CategoryFacet.php @@ -7,21 +7,6 @@ /** * @codeCoverageIgnore */ -class CategoryFacet extends FacetField +class CategoryFacet extends FieldFacet { - /** - * @param string[] $categories - * @param string[] $excludeFilter - */ - public function __construct( - string $key, - array $categories, - array $excludeFilter = [] - ) { - parent::__construct( - $key, - $categories, - $excludeFilter - ); - } } diff --git a/src/Dto/Search/Query/Facet/ContentSectionTypeFacet.php b/src/Dto/Search/Query/Facet/ContentSectionTypeFacet.php index 5315a44..67da84a 100644 --- a/src/Dto/Search/Query/Facet/ContentSectionTypeFacet.php +++ b/src/Dto/Search/Query/Facet/ContentSectionTypeFacet.php @@ -7,21 +7,6 @@ /** * @codeCoverageIgnore */ -class ContentSectionTypeFacet extends FacetField +class ContentSectionTypeFacet extends FieldFacet { - /** - * @param string[] $contentSectionTypes - * @param string[] $excludeFilter - */ - public function __construct( - string $key, - array $contentSectionTypes, - array $excludeFilter = [] - ) { - parent::__construct( - $key, - $contentSectionTypes, - $excludeFilter - ); - } } diff --git a/src/Dto/Search/Query/Facet/FacetField.php b/src/Dto/Search/Query/Facet/FieldFacet.php similarity index 92% rename from src/Dto/Search/Query/Facet/FacetField.php rename to src/Dto/Search/Query/Facet/FieldFacet.php index 3634165..7999882 100644 --- a/src/Dto/Search/Query/Facet/FacetField.php +++ b/src/Dto/Search/Query/Facet/FieldFacet.php @@ -7,7 +7,7 @@ /** * @codeCoverageIgnore */ -class FacetField extends Facet +class FieldFacet extends Facet { /** * @param string[] $terms diff --git a/src/Dto/Search/Query/Facet/GroupFacet.php b/src/Dto/Search/Query/Facet/GroupFacet.php index 8503ed6..c12beb9 100644 --- a/src/Dto/Search/Query/Facet/GroupFacet.php +++ b/src/Dto/Search/Query/Facet/GroupFacet.php @@ -7,21 +7,6 @@ /** * @codeCoverageIgnore */ -class GroupFacet extends FacetField +class GroupFacet extends FieldFacet { - /** - * @param string[] $groups - * @param string[] $excludeFilter - */ - public function __construct( - string $key, - public readonly array $groups, - array $excludeFilter = [] - ) { - parent::__construct( - $key, - $groups, - $excludeFilter - ); - } } diff --git a/src/Dto/Search/Query/Facet/FacetMultiQuery.php b/src/Dto/Search/Query/Facet/MultiQueryFacet.php similarity index 83% rename from src/Dto/Search/Query/Facet/FacetMultiQuery.php rename to src/Dto/Search/Query/Facet/MultiQueryFacet.php index 4d3b681..83aaf66 100644 --- a/src/Dto/Search/Query/Facet/FacetMultiQuery.php +++ b/src/Dto/Search/Query/Facet/MultiQueryFacet.php @@ -7,10 +7,10 @@ /** * @codeCoverageIgnore */ -class FacetMultiQuery extends Facet +class MultiQueryFacet extends Facet { /** - * @param FacetQuery[] $queries + * @param QueryFacet[] $queries * @param string[] $excludeFilter */ public function __construct( diff --git a/src/Dto/Search/Query/Facet/ObjectTypeFacet.php b/src/Dto/Search/Query/Facet/ObjectTypeFacet.php index bf10937..98db822 100644 --- a/src/Dto/Search/Query/Facet/ObjectTypeFacet.php +++ b/src/Dto/Search/Query/Facet/ObjectTypeFacet.php @@ -7,21 +7,6 @@ /** * @codeCoverageIgnore */ -class ObjectTypeFacet extends FacetField +class ObjectTypeFacet extends FieldFacet { - /** - * @param string[] $objectTypes - * @param string[] $excludeFilter - */ - public function __construct( - string $key, - public readonly array $objectTypes, - array $excludeFilter = [] - ) { - parent::__construct( - $key, - $objectTypes, - $excludeFilter - ); - } } diff --git a/src/Dto/Search/Query/Facet/FacetQuery.php b/src/Dto/Search/Query/Facet/QueryFacet.php similarity index 92% rename from src/Dto/Search/Query/Facet/FacetQuery.php rename to src/Dto/Search/Query/Facet/QueryFacet.php index f701e06..3cd6501 100644 --- a/src/Dto/Search/Query/Facet/FacetQuery.php +++ b/src/Dto/Search/Query/Facet/QueryFacet.php @@ -7,7 +7,7 @@ /** * @codeCoverageIgnore */ -class FacetQuery extends Facet +class QueryFacet extends Facet { /** * @param string[] $excludeFilter diff --git a/src/Dto/Search/Query/Facet/RelativeDateRangeFacet.php b/src/Dto/Search/Query/Facet/RelativeDateRangeFacet.php index 3f794d7..c664532 100644 --- a/src/Dto/Search/Query/Facet/RelativeDateRangeFacet.php +++ b/src/Dto/Search/Query/Facet/RelativeDateRangeFacet.php @@ -7,6 +7,9 @@ use Atoolo\Search\Dto\Search\Query\DateRangeRound; use DateInterval; +/** + * @codeCoverageIgnore + */ class RelativeDateRangeFacet extends Facet { /** diff --git a/src/Dto/Search/Query/Facet/SiteFacet.php b/src/Dto/Search/Query/Facet/SiteFacet.php index 225240e..e067dd5 100644 --- a/src/Dto/Search/Query/Facet/SiteFacet.php +++ b/src/Dto/Search/Query/Facet/SiteFacet.php @@ -7,21 +7,6 @@ /** * @codeCoverageIgnore */ -class SiteFacet extends FacetField +class SiteFacet extends FieldFacet { - /** - * @param string[] $sites - * @param string[] $excludeFilter - */ - public function __construct( - string $key, - public readonly array $sites, - array $excludeFilter = [] - ) { - parent::__construct( - $key, - $sites, - $excludeFilter - ); - } } diff --git a/src/Dto/Search/Query/Filter/AbsoluteDateRangeFilter.php b/src/Dto/Search/Query/Filter/AbsoluteDateRangeFilter.php index da27d7a..507dc16 100644 --- a/src/Dto/Search/Query/Filter/AbsoluteDateRangeFilter.php +++ b/src/Dto/Search/Query/Filter/AbsoluteDateRangeFilter.php @@ -10,8 +10,8 @@ class AbsoluteDateRangeFilter extends Filter { public function __construct( - private readonly ?DateTime $from, - private readonly ?DateTime $to, + public readonly ?DateTime $from, + public readonly ?DateTime $to, ?string $key = null ) { parent::__construct( @@ -24,25 +24,4 @@ public function __construct( ); } } - - public function getQuery(): string - { - return 'sp_date_list:' . - '[' . - $this->formatDate($this->from) . - ' TO ' . - $this->formatDate($this->to) . - ']'; - } - - private function formatDate(?DateTime $date): string - { - if ($date === null) { - return '*'; - } - - $formatter = clone $date; - $formatter->setTimezone(new \DateTimeZone('UTC')); - return $formatter->format('Y-m-d\TH:i:s\Z'); - } } diff --git a/src/Dto/Search/Query/Filter/AndFilter.php b/src/Dto/Search/Query/Filter/AndFilter.php index 253a492..4f1c87d 100644 --- a/src/Dto/Search/Query/Filter/AndFilter.php +++ b/src/Dto/Search/Query/Filter/AndFilter.php @@ -4,26 +4,19 @@ namespace Atoolo\Search\Dto\Search\Query\Filter; +/** + * @codeCoverageIgnore + */ class AndFilter extends Filter { /** * @param Filter[] $filter */ public function __construct( - private readonly array $filter, + public readonly array $filter, ?string $key = null, array $tags = [] ) { parent::__construct($key, $tags); } - - public function getQuery(): string - { - $query = []; - foreach ($this->filter as $filter) { - $query[] = $filter->getQuery(); - } - - return '(' . implode(' AND ', $query) . ')'; - } } diff --git a/src/Dto/Search/Query/Filter/ArchiveFilter.php b/src/Dto/Search/Query/Filter/ArchiveFilter.php index 2b12b39..11b7cca 100644 --- a/src/Dto/Search/Query/Filter/ArchiveFilter.php +++ b/src/Dto/Search/Query/Filter/ArchiveFilter.php @@ -4,6 +4,9 @@ namespace Atoolo\Search\Dto\Search\Query\Filter; +/** + * @codeCoverageIgnore + */ class ArchiveFilter extends Filter { public function __construct() @@ -12,9 +15,4 @@ public function __construct() 'archive' ); } - - public function getQuery(): string - { - return '-sp_archive:true'; - } } diff --git a/src/Dto/Search/Query/Filter/CategoryFilter.php b/src/Dto/Search/Query/Filter/CategoryFilter.php index f49ebb3..a16a687 100644 --- a/src/Dto/Search/Query/Filter/CategoryFilter.php +++ b/src/Dto/Search/Query/Filter/CategoryFilter.php @@ -9,17 +9,4 @@ */ class CategoryFilter extends FieldFilter { - /** - * @param string[] $category - */ - public function __construct( - array $category, - ?string $key = null - ) { - parent::__construct( - 'sp_category_path', - $category, - $key - ); - } } diff --git a/src/Dto/Search/Query/Filter/ContentSectionTypeFilter.php b/src/Dto/Search/Query/Filter/ContentSectionTypeFilter.php index 90b2ae0..f2427b0 100644 --- a/src/Dto/Search/Query/Filter/ContentSectionTypeFilter.php +++ b/src/Dto/Search/Query/Filter/ContentSectionTypeFilter.php @@ -9,17 +9,4 @@ */ class ContentSectionTypeFilter extends FieldFilter { - /** - * @param string[] $contentTypes - */ - public function __construct( - array $contentTypes, - ?string $key = null, - ) { - parent::__construct( - 'sp_contenttype', - $contentTypes, - $key - ); - } } diff --git a/src/Dto/Search/Query/Filter/FieldFilter.php b/src/Dto/Search/Query/Filter/FieldFilter.php index 3caa308..327c097 100644 --- a/src/Dto/Search/Query/Filter/FieldFilter.php +++ b/src/Dto/Search/Query/Filter/FieldFilter.php @@ -8,17 +8,11 @@ class FieldFilter extends Filter { - /** - * @var string[] - */ - private readonly array $values; - /** * @param string[] $values */ public function __construct( - private readonly string $field, - array $values, + public readonly array $values, ?string $key = null ) { if (count($values) === 0) { @@ -26,31 +20,9 @@ public function __construct( 'values is an empty array' ); } - $this->values = $values; parent::__construct( $key, $key !== null ? [$key] : [] ); } - - public function getQuery(): string - { - $filterValue = count($this->values) === 1 - ? $this->values[0] - : '(' . implode(' ', $this->values) . ')'; - return $this->field . ':' . $filterValue; - } - - public function exclude(): FieldFilter - { - $field = $this->field; - if (!str_starts_with($field, '-')) { - $field = '-' . $field; - } - return new FieldFilter( - $field, - $this->values, - $this->key - ); - } } diff --git a/src/Dto/Search/Query/Filter/Filter.php b/src/Dto/Search/Query/Filter/Filter.php index cb5edf1..f350f96 100644 --- a/src/Dto/Search/Query/Filter/Filter.php +++ b/src/Dto/Search/Query/Filter/Filter.php @@ -17,6 +17,4 @@ public function __construct( public readonly array $tags = [] ) { } - - abstract public function getQuery(): string; } diff --git a/src/Dto/Search/Query/Filter/GroupFilter.php b/src/Dto/Search/Query/Filter/GroupFilter.php index 5ec4333..62e718f 100644 --- a/src/Dto/Search/Query/Filter/GroupFilter.php +++ b/src/Dto/Search/Query/Filter/GroupFilter.php @@ -9,17 +9,4 @@ */ class GroupFilter extends FieldFilter { - /** - * @param string[] $group - */ - public function __construct( - array $group, - ?string $key = null, - ) { - parent::__construct( - 'sp_group_path', - $group, - $key - ); - } } diff --git a/src/Dto/Search/Query/Filter/NotFilter.php b/src/Dto/Search/Query/Filter/NotFilter.php index 22aef3e..06e71cd 100644 --- a/src/Dto/Search/Query/Filter/NotFilter.php +++ b/src/Dto/Search/Query/Filter/NotFilter.php @@ -4,18 +4,16 @@ namespace Atoolo\Search\Dto\Search\Query\Filter; +/** + * @codeCoverageIgnore + */ class NotFilter extends Filter { public function __construct( - private readonly Filter $filter, + public readonly Filter $filter, ?string $key = null, array $tags = [] ) { parent::__construct($key, $tags); } - - public function getQuery(): string - { - return 'NOT ' . $this->filter->getQuery(); - } } diff --git a/src/Dto/Search/Query/Filter/ObjectTypeFilter.php b/src/Dto/Search/Query/Filter/ObjectTypeFilter.php index f854b0e..2d61788 100644 --- a/src/Dto/Search/Query/Filter/ObjectTypeFilter.php +++ b/src/Dto/Search/Query/Filter/ObjectTypeFilter.php @@ -9,17 +9,4 @@ */ class ObjectTypeFilter extends FieldFilter { - /** - * @param string[] $objectTypes - */ - public function __construct( - array $objectTypes, - ?string $key = null, - ) { - parent::__construct( - 'sp_objecttype', - $objectTypes, - $key - ); - } } diff --git a/src/Dto/Search/Query/Filter/OrFilter.php b/src/Dto/Search/Query/Filter/OrFilter.php index 0781f07..23a2f0d 100644 --- a/src/Dto/Search/Query/Filter/OrFilter.php +++ b/src/Dto/Search/Query/Filter/OrFilter.php @@ -4,6 +4,9 @@ namespace Atoolo\Search\Dto\Search\Query\Filter; +/** + * @codeCoverageIgnore + */ class OrFilter extends Filter { /** @@ -11,20 +14,10 @@ class OrFilter extends Filter * @param string[] $tags */ public function __construct( - private readonly array $filter, + public readonly array $filter, ?string $key = null, array $tags = [] ) { parent::__construct($key, $tags); } - - public function getQuery(): string - { - $query = []; - foreach ($this->filter as $filter) { - $query[] = $filter->getQuery(); - } - - return '(' . implode(' OR ', $query) . ')'; - } } diff --git a/src/Dto/Search/Query/Filter/QueryFilter.php b/src/Dto/Search/Query/Filter/QueryFilter.php index 6a25853..6dcaecb 100644 --- a/src/Dto/Search/Query/Filter/QueryFilter.php +++ b/src/Dto/Search/Query/Filter/QueryFilter.php @@ -4,19 +4,17 @@ namespace Atoolo\Search\Dto\Search\Query\Filter; +/** + * @codeCoverageIgnore + */ class QueryFilter extends Filter { public function __construct( - private readonly string $query, + public readonly string $query, ?string $key = null, ) { parent::__construct( $key ); } - - public function getQuery(): string - { - return $this->query; - } } diff --git a/src/Dto/Search/Query/Filter/RelativeDateRangeFilter.php b/src/Dto/Search/Query/Filter/RelativeDateRangeFilter.php index ce0f576..493789c 100644 --- a/src/Dto/Search/Query/Filter/RelativeDateRangeFilter.php +++ b/src/Dto/Search/Query/Filter/RelativeDateRangeFilter.php @@ -7,16 +7,18 @@ use Atoolo\Search\Dto\Search\Query\DateRangeRound; use DateInterval; use DateTime; -use InvalidArgumentException; +/** + * @codeCoverageIgnore + */ class RelativeDateRangeFilter extends Filter { public function __construct( - private readonly ?DateTime $base, - private readonly ?DateInterval $before, - private readonly ?DateInterval $after, - private readonly ?DateRangeRound $roundStart, - private readonly ?DateRangeRound $roundEnd, + public readonly ?DateTime $base, + public readonly ?DateInterval $before, + public readonly ?DateInterval $after, + public readonly ?DateRangeRound $roundStart, + public readonly ?DateRangeRound $roundEnd, ?string $key = null ) { parent::__construct( @@ -24,128 +26,4 @@ public function __construct( $key !== null ? [$key] : [] ); } - - public function getQuery(): string - { - return 'sp_date_list:' . $this->toSolrDateRage(); - } - - private function toSolrDateRage(): string - { - if ($this->before === null) { - $from = $this->roundStart($this->getBaseInSolrSyntax()); - } else { - $from = $this->roundStart( - $this->toSolrIntervalSyntax($this->before, '-') - ); - } - - if ($this->after === null) { - $to = $this->roundEnd($this->getBaseInSolrSyntax()); - } else { - $to = $this->roundEnd( - $this->toSolrIntervalSyntax($this->after, '+') - ); - } - - return '[' . $from . ' TO ' . $to . ']'; - } - - private function roundStart(string $start): string - { - return $start . $this->toSolrRound( - $this->roundStart ?? DateRangeRound::START_OF_DAY - ); - } - - private function roundEnd(string $start): string - { - return $start . $this->toSolrRound( - $this->roundEnd ?? DateRangeRound::END_OF_DAY - ); - } - - private function getBaseInSolrSyntax(): string - { - if ($this->base === null) { - return 'NOW'; - } - - $formatter = clone $this->base; - $formatter->setTimezone(new \DateTimeZone('UTC')); - return $formatter->format('Y-m-d\TH:i:s\Z'); - } - - private function toSolrIntervalSyntax( - DateInterval $value, - string $operator - ): string { - $interval = $this->getBaseInSolrSyntax(); - if ($value->y > 0) { - $interval = $interval . $operator . $value->y . 'YEARS'; - } - if ($value->m > 0) { - $interval = $interval . $operator . $value->m . 'MONTHS'; - } - if ($value->d > 0) { - $interval = $interval . $operator . $value->d . 'DAYS'; - } - if ($value->h > 0) { - throw new InvalidArgumentException( - 'Hours are not supported for the RelativeDateRangeFilter' - ); - } - if ($value->i > 0) { - throw new InvalidArgumentException( - 'Minutes are not supported for the RelativeDateRangeFilter' - ); - } - if ($value->s > 0) { - throw new InvalidArgumentException( - 'Seconds are not supported for the RelativeDateRangeFilter' - ); - } - - return $interval; - } - - private function toSolrRound(DateRangeRound $round): string - { - if ($round === DateRangeRound::START_OF_DAY) { - return '/DAY'; - } - if ($round === DateRangeRound::START_OF_PREVIOUS_DAY) { - return '/DAY-1DAY'; - } - if ($round === DateRangeRound::END_OF_DAY) { - return '/DAY+1DAY-1SECOND'; - } - if ($round === DateRangeRound::END_OF_PREVIOUS_DAY) { - return '/DAY-1SECOND'; - } - if ($round === DateRangeRound::START_OF_MONTH) { - return '/MONTH'; - } - if ($round === DateRangeRound::START_OF_PREVIOUS_MONTH) { - return '/MONTH-1MONTH'; - } - if ($round === DateRangeRound::END_OF_MONTH) { - return '/MONTH+1MONTH-1SECOND'; - } - if ($round === DateRangeRound::END_OF_PREVIOUS_MONTH) { - return '/MONTH-1SECOND'; - } - if ($round === DateRangeRound::START_OF_YEAR) { - return '/YEAR'; - } - if ($round === DateRangeRound::START_OF_PREVIOUS_YEAR) { - return '/YEAR-1YEAR'; - } - if ($round === DateRangeRound::END_OF_YEAR) { - return '/YEAR+1YEAR-1SECOND'; - } - if ($round === DateRangeRound::END_OF_PREVIOUS_YEAR) { - return '/YEAR-1SECOND'; - } - } } diff --git a/src/Dto/Search/Query/Filter/SiteFilter.php b/src/Dto/Search/Query/Filter/SiteFilter.php index 37b786d..b84ad33 100644 --- a/src/Dto/Search/Query/Filter/SiteFilter.php +++ b/src/Dto/Search/Query/Filter/SiteFilter.php @@ -9,17 +9,4 @@ */ class SiteFilter extends FieldFilter { - /** - * @param string[] $site - */ - public function __construct( - array $site, - ?string $key = null, - ) { - parent::__construct( - 'sp_site', - $site, - $key - ); - } } diff --git a/src/Service/ResourceChannelBasedIndexName.php b/src/Service/ResourceChannelBasedIndexName.php index fa2bf08..f9f2541 100644 --- a/src/Service/ResourceChannelBasedIndexName.php +++ b/src/Service/ResourceChannelBasedIndexName.php @@ -20,10 +20,6 @@ public function __construct( */ public function name(ResourceLanguage $lang): string { - if ($lang === ResourceLanguage::default()) { - return $this->resourceChannel->searchIndex; - } - $locale = $this->langToAvailableLocale($this->resourceChannel, $lang); if (empty($locale)) { @@ -58,7 +54,7 @@ private function langToAvailableLocale( ResourceLanguage $lang ): string { - if ($lang === ResourceLanguage::default()) { + if ($lang->code === ResourceLanguage::default()->code) { return ''; } diff --git a/src/Service/Search/Schema2xFieldMapper.php b/src/Service/Search/Schema2xFieldMapper.php new file mode 100644 index 0000000..0498912 --- /dev/null +++ b/src/Service/Search/Schema2xFieldMapper.php @@ -0,0 +1,98 @@ +from); - } - - public function getEnd(): string - { - return SolrDateMapper::mapDateTime($this->to); - } - - public function getGap(): ?string - { - if ($this->gap === null) { - return null; - } - return SolrDateMapper::mapDateInterval($this->gap, '+'); - } -} diff --git a/src/Service/Search/SolrDateMapper.php b/src/Service/Search/SolrDateMapper.php index 82ca289..ed02821 100644 --- a/src/Service/Search/SolrDateMapper.php +++ b/src/Service/Search/SolrDateMapper.php @@ -12,8 +12,10 @@ class SolrDateMapper { - public static function mapDateInterval(?DateInterval $value, string $operator): string - { + public static function mapDateInterval( + ?DateInterval $value, + string $operator + ): string { if ($value === null) { return $operator . '1DAY'; } @@ -46,8 +48,13 @@ public static function mapDateInterval(?DateInterval $value, string $operator): return $interval; } - public static function mapDateTime(DateTime $date): string - { + public static function mapDateTime( + ?DateTime $date, + string $default = 'NOW' + ): string { + if ($date === null) { + return $default; + } $formatter = clone $date; $formatter->setTimezone(new DateTimeZone('UTC')); return $formatter->format('Y-m-d\TH:i:s\Z'); @@ -92,4 +99,22 @@ public static function mapDateRangeRound(DateRangeRound $round): string return '/YEAR-1SECOND'; } } + + public static function roundStart( + string $start, + ?DateRangeRound $round + ): string { + return $start . self::mapDateRangeRound( + $round ?? DateRangeRound::START_OF_DAY + ); + } + + public static function roundEnd( + string $end, + ?DateRangeRound $round + ): string { + return $end . self::mapDateRangeRound( + $round ?? DateRangeRound::END_OF_DAY + ); + } } diff --git a/src/Service/Search/SolrMoreLikeThis.php b/src/Service/Search/SolrMoreLikeThis.php index 9f7159e..9e2bd52 100644 --- a/src/Service/Search/SolrMoreLikeThis.php +++ b/src/Service/Search/SolrMoreLikeThis.php @@ -5,6 +5,7 @@ namespace Atoolo\Search\Service\Search; use Atoolo\Resource\ResourceLanguage; +use Atoolo\Search\Dto\Search\Query\Filter\Filter; use Atoolo\Search\Dto\Search\Query\MoreLikeThisQuery; use Atoolo\Search\Dto\Search\Result\SearchResult; use Atoolo\Search\MoreLikeThis; @@ -22,7 +23,8 @@ class SolrMoreLikeThis implements MoreLikeThis public function __construct( private readonly IndexName $index, private readonly SolrClientFactory $clientFactory, - private readonly SolrResultToResourceResolver $resultToResourceResolver + private readonly SolrResultToResourceResolver $resultToResourceResolver, + private readonly Schema2xFieldMapper $schemaFieldMapper ) { } @@ -48,19 +50,29 @@ private function buildSolrQuery( $solrQuery->setRows($query->limit); $solrQuery->setMinimumTermFrequency(2); $solrQuery->setMatchInclude(true); - $solrQuery->createFilterQuery('nomedia') - ->setQuery('-sp_objecttype:media'); // Filter - foreach ($query->filter as $filter) { - $filterQuery = $solrQuery->createFilterQuery($filter->key); - $filterQuery->setQuery($filter->getQuery()); - $filterQuery->setTags($filter->tags); - } + $this->addFilterQueriesToSolrQuery($solrQuery, $query->filter); return $solrQuery; } + /** + * @param Filter[] $filterList + */ + private function addFilterQueriesToSolrQuery( + SolrMoreLikeThisQuery $solrQuery, + array $filterList + ): void { + $filterAppender = new SolrQueryFilterAppender( + $solrQuery, + $this->schemaFieldMapper + ); + foreach ($filterList as $filter) { + $filterAppender->append($filter); + } + } + private function buildResult( SolrMoreLikeThisResult $result, ResourceLanguage $lang diff --git a/src/Service/Search/SolrQueryFacetAppender.php b/src/Service/Search/SolrQueryFacetAppender.php index e37c26d..bc312ee 100644 --- a/src/Service/Search/SolrQueryFacetAppender.php +++ b/src/Service/Search/SolrQueryFacetAppender.php @@ -5,16 +5,11 @@ namespace Atoolo\Search\Service\Search; use Atoolo\Search\Dto\Search\Query\Facet\AbsoluteDateRangeFacet; -use Atoolo\Search\Dto\Search\Query\Facet\CategoryFacet; -use Atoolo\Search\Dto\Search\Query\Facet\ContentSectionTypeFacet; use Atoolo\Search\Dto\Search\Query\Facet\Facet; -use Atoolo\Search\Dto\Search\Query\Facet\FacetField; -use Atoolo\Search\Dto\Search\Query\Facet\FacetMultiQuery; -use Atoolo\Search\Dto\Search\Query\Facet\FacetQuery; -use Atoolo\Search\Dto\Search\Query\Facet\GroupFacet; -use Atoolo\Search\Dto\Search\Query\Facet\ObjectTypeFacet; +use Atoolo\Search\Dto\Search\Query\Facet\FieldFacet; +use Atoolo\Search\Dto\Search\Query\Facet\MultiQueryFacet; +use Atoolo\Search\Dto\Search\Query\Facet\QueryFacet; use Atoolo\Search\Dto\Search\Query\Facet\RelativeDateRangeFacet; -use Atoolo\Search\Dto\Search\Query\Facet\SiteFacet; use InvalidArgumentException; use Solarium\Component\Facet\Field; use Solarium\QueryType\Select\Query\Query as SolrSelectQuery; @@ -22,22 +17,23 @@ class SolrQueryFacetAppender { public function __construct( - private readonly SolrSelectQuery $solrQuery + private readonly SolrSelectQuery $solrQuery, + private readonly Schema2xFieldMapper $fieldMapper ) { } public function append(Facet $facet): void { - $facet = $this->mapFacet($facet); - - if ($facet instanceof FacetField) { - $this->addFacetFieldToSolrQuery($facet); - } elseif ($facet instanceof FacetQuery) { - $this->addFacetQueryToSolrQuery($facet); - } elseif ($facet instanceof FacetMultiQuery) { - $this->addFacetMultiQueryToSolrQuery($facet); - } elseif ($facet instanceof SolrRangeFacet) { - $this->addFacetRangeToSolrQuery($facet); + if ($facet instanceof FieldFacet) { + $this->appendFacetField($facet); + } elseif ($facet instanceof QueryFacet) { + $this->appendFacetQuery($facet); + } elseif ($facet instanceof MultiQueryFacet) { + $this->appendFacetMultiQuery($facet); + } elseif ($facet instanceof AbsoluteDateRangeFacet) { + $this->appendAbsoluteDateRangeFacet($facet); + } elseif ($facet instanceof RelativeDateRangeFacet) { + $this->appendRelativeDateRangeFacet($facet); } else { throw new InvalidArgumentException( 'Unsupported facet-class ' . get_class($facet) @@ -45,38 +41,11 @@ public function append(Facet $facet): void } } - private function mapFacet(Facet $facet): Facet - { - switch (true) { - case $facet instanceof AbsoluteDateRangeFacet: - return new SolrAbsoluteDateRangeFacet( - $facet->key, - $facet->from, - $facet->to, - $facet->gap, - $facet->excludeFilter - ); - case $facet instanceof RelativeDateRangeFacet: - return new SolrRelativeDateRangeFacet( - $facet->key, - $facet->base, - $facet->before, - $facet->after, - $facet->gap, - $facet->roundStart, - $facet->roundEnd, - $facet->excludeFilter - ); - default: - return $facet; - } - } - /** * https://solarium.readthedocs.io/en/stable/queries/select-query/building-a-select-query/components/facetset-component/facet-field/ */ - private function addFacetFieldToSolrQuery( - FacetField $facet + private function appendFacetField( + FieldFacet $facet ): void { $facetSet = $this->solrQuery->getFacetSet(); /** @var Field $solrFacet */ @@ -89,33 +58,14 @@ private function addFacetFieldToSolrQuery( private function getFacetField(Facet $facet): string { - switch (true) { - case $facet instanceof CategoryFacet: - return 'sp_category_path'; - case $facet instanceof ContentSectionTypeFacet: - return 'sp_contenttype'; - case $facet instanceof GroupFacet: - return 'sp_group_path'; - case $facet instanceof ObjectTypeFacet: - return 'sp_objecttype'; - case $facet instanceof SiteFacet: - return 'sp_site'; - case $facet instanceof SolrAbsoluteDateRangeFacet: - return 'sp_date_list'; - case $facet instanceof SolrRelativeDateRangeFacet: - return 'sp_date_list'; - default: - throw new InvalidArgumentException( - 'Unsupported facet-field-class ' . get_class($facet) - ); - } + return $this->fieldMapper->getFacetField($facet); } /** * https://solarium.readthedocs.io/en/stable/queries/select-query/building-a-select-query/components/facetset-component/facet-query/ */ - private function addFacetQueryToSolrQuery( - FacetQuery $facet + private function appendFacetQuery( + QueryFacet $facet ): void { $facetSet = $this->solrQuery->getFacetSet(); $facetSet->createFacetQuery($facet->key) @@ -126,8 +76,8 @@ private function addFacetQueryToSolrQuery( /** * https://solarium.readthedocs.io/en/stable/queries/select-query/building-a-select-query/components/facetset-component/facet-multiquery/ */ - private function addFacetMultiQueryToSolrQuery( - FacetMultiQuery $facet + private function appendFacetMultiQuery( + MultiQueryFacet $facet ): void { $facetSet = $this->solrQuery->getFacetSet(); $solrFacet = $facetSet->createFacetMultiQuery($facet->key); @@ -140,23 +90,64 @@ private function addFacetMultiQueryToSolrQuery( } } - private function addFacetRangeToSolrQuery( - SolrRangeFacet $facet + private function appendAbsoluteDateRangeFacet( + AbsoluteDateRangeFacet $facet + ): void { + $start = SolrDateMapper::mapDateTime($facet->from); + $end = SolrDateMapper::mapDateTime($facet->to); + $gap = $facet->gap !== null + ? SolrDateMapper::mapDateInterval($facet->gap, '+') + : null; + $this->appendFacetRange($facet, $start, $end, $gap); + } + + private function appendRelativeDateRangeFacet( + RelativeDateRangeFacet $facet ): void { - if ($facet->getGap() === null) { + $start = $facet->before === null + ? SolrDateMapper::roundStart( + SolrDateMapper::mapDateTime($facet->base), + $facet->roundStart + ) + : SolrDateMapper::roundStart( + SolrDateMapper::mapDateTime($facet->base) . + SolrDateMapper::mapDateInterval($facet->before, '-'), + $facet->roundStart + ); + + $end = $facet->after === null + ? SolrDateMapper::roundEnd( + SolrDateMapper::mapDateTime($facet->base), + $facet->roundStart + ) + : SolrDateMapper::roundEnd( + SolrDateMapper::mapDateTime($facet->base) . + SolrDateMapper::mapDateInterval($facet->after, '+'), + $facet->roundEnd + ); + + $gap = $facet->gap !== null + ? SolrDateMapper::mapDateInterval($facet->gap, '+') + : null; + $this->appendFacetRange($facet, $start, $end, $gap); + } + + private function appendFacetRange( + Facet $facet, + string $start, + string $end, + ?string $gap + ): void { + if ($gap === null) { // without `gap` it is a simple facet query - $facetQuery = new FacetQuery( + $facetQuery = new QueryFacet( $facet->key, $this->getFacetField($facet) . ':' . - '[' . - $facet->getStart() . - ' TO ' . - $facet->getEnd() . - ']', + '[' . $start . ' TO ' . $end . ']', $facet->excludeFilter ); - $this->addFacetQueryToSolrQuery($facetQuery); + $this->appendFacetQuery($facetQuery); return; } @@ -166,8 +157,8 @@ private function addFacetRangeToSolrQuery( $solrFacet->setExcludes($facet->excludeFilter); $solrFacet ->setField($this->getFacetField($facet)) - ->setStart($facet->getStart()) - ->setEnd($facet->getEnd()) - ->setGap($facet->getGap()); + ->setStart($start) + ->setEnd($end) + ->setGap($gap); } } diff --git a/src/Service/Search/SolrQueryFilterAppender.php b/src/Service/Search/SolrQueryFilterAppender.php new file mode 100644 index 0000000..b605b8c --- /dev/null +++ b/src/Service/Search/SolrQueryFilterAppender.php @@ -0,0 +1,138 @@ +key ?? uniqid('', true); + $filterQuery = $this->solrQuery->createFilterQuery($key); + $filterQuery->setQuery($this->getQuery($filter)); + $filterQuery->setTags($filter->tags); + } + + private function getQuery(Filter $filter): string + { + switch (true) { + case $filter instanceof ArchiveFilter: + return '-' . $this->getFilterField($filter) . ':true'; + case $filter instanceof FieldFilter: + return $this->getFieldQuery($filter); + case $filter instanceof AndFilter: + return $this->getAndQuery($filter); + case $filter instanceof OrFilter: + return $this->getOrQuery($filter); + case $filter instanceof NotFilter: + return 'NOT ' . $this->getQuery($filter->filter); + case $filter instanceof QueryFilter: + return $filter->query; + case $filter instanceof AbsoluteDateRangeFilter: + return $this->getAbsoluteDateRangeQuery($filter); + case $filter instanceof RelativeDateRangeFilter: + return $this->getRelativeDateRangeQuery($filter); + default: + throw new InvalidArgumentException( + 'unsupported filter ' . get_class($filter) + ); + } + } + + private function getAndQuery(AndFilter $andFilter): string + { + $query = []; + foreach ($andFilter->filter as $filter) { + $query[] = $this->getQuery($filter); + } + + return '(' . implode(' AND ', $query) . ')'; + } + + private function getOrQuery(OrFilter $orFilter): string + { + $query = []; + foreach ($orFilter->filter as $filter) { + $query[] = $this->getQuery($filter); + } + + return '(' . implode(' OR ', $query) . ')'; + } + + private function getFieldQuery(FieldFilter $filter): string + { + $field = $this->getFilterField($filter); + $filterValue = count($filter->values) === 1 + ? $filter->values[0] + : '(' . implode(' ', $filter->values) . ')'; + return $field . ':' . $filterValue; + } + + private function getFilterField(Filter $filter): string + { + return $this->fieldMapper->getFilterField($filter); + } + + private function getAbsoluteDateRangeQuery( + AbsoluteDateRangeFilter $filter + ): string { + $field = $this->getFilterField($filter); + $from = SolrDateMapper::mapDateTime($filter->from, '*'); + $to = SolrDateMapper::mapDateTime($filter->to, '*'); + return $field . ':' . '[' . $from . ' TO ' . $to . ']'; + } + + private function getRelativeDateRangeQuery( + RelativeDateRangeFilter $filter + ): string { + + if ($filter->before === null) { + $from = SolrDateMapper::roundStart( + SolrDateMapper::mapDateTime($filter->base), + $filter->roundStart + ); + } else { + $from = SolrDateMapper::roundStart( + SolrDateMapper::mapDateTime($filter->base) . + SolrDateMapper::mapDateInterval($filter->before, '-'), + $filter->roundStart + ); + } + + if ($filter->after === null) { + $to = SolrDateMapper::roundEnd( + SolrDateMapper::mapDateTime($filter->base), + $filter->roundEnd + ); + } else { + $to = SolrDateMapper::roundEnd( + SolrDateMapper::mapDateTime($filter->base) . + SolrDateMapper::mapDateInterval($filter->after, '+'), + $filter->roundEnd + ); + } + + $field = $this->getFilterField($filter); + + return $field . ':[' . $from . ' TO ' . $to . ']'; + } +} diff --git a/src/Service/Search/SolrRangeFacet.php b/src/Service/Search/SolrRangeFacet.php deleted file mode 100644 index 5e6d580..0000000 --- a/src/Service/Search/SolrRangeFacet.php +++ /dev/null @@ -1,13 +0,0 @@ -before === null) { - return $this->roundStart($this->getBaseInSolrSyntax()); - } - - return $this->roundStart( - $this->getBaseInSolrSyntax() . - SolrDateMapper::mapDateInterval($this->before, '-') - ); - } - - public function getEnd(): string - { - if ($this->after === null) { - return $this->roundEnd($this->getBaseInSolrSyntax()); - } - return $this->roundEnd( - $this->getBaseInSolrSyntax() . - SolrDateMapper::mapDateInterval($this->after, '+') - ); - } - - private function roundStart(string $start): string - { - return $start . SolrDateMapper::mapDateRangeRound( - $this->roundStart ?? DateRangeRound::START_OF_DAY - ); - } - - private function roundEnd(string $start): string - { - return $start . SolrDateMapper::mapDateRangeRound( - $this->roundEnd ?? DateRangeRound::END_OF_DAY - ); - } - - public function getGap(): ?string - { - if ($this->gap === null) { - return null; - } - return SolrDateMapper::mapDateInterval($this->gap, '+'); - } - - private function getBaseInSolrSyntax(): string - { - if ($this->base === null) { - return 'NOW'; - } - - return SolrDateMapper::mapDateTime($this->base); - } -} diff --git a/src/Service/Search/SolrSearch.php b/src/Service/Search/SolrSearch.php index 88fa192..343b100 100644 --- a/src/Service/Search/SolrSearch.php +++ b/src/Service/Search/SolrSearch.php @@ -9,11 +9,6 @@ use Atoolo\Search\Dto\Search\Query\QueryOperator; use Atoolo\Search\Dto\Search\Query\SearchQuery; use Atoolo\Search\Dto\Search\Query\Sort\Criteria; -use Atoolo\Search\Dto\Search\Query\Sort\Date; -use Atoolo\Search\Dto\Search\Query\Sort\Headline; -use Atoolo\Search\Dto\Search\Query\Sort\Name; -use Atoolo\Search\Dto\Search\Query\Sort\Natural; -use Atoolo\Search\Dto\Search\Query\Sort\Score; use Atoolo\Search\Dto\Search\Result\Facet; use Atoolo\Search\Dto\Search\Result\FacetGroup; use Atoolo\Search\Dto\Search\Result\SearchResult; @@ -21,6 +16,8 @@ use Atoolo\Search\Service\IndexName; use Atoolo\Search\Service\SolrClientFactory; use InvalidArgumentException; +use Solarium\Component\Result\Facet\Field as SolrFacetField; +use Solarium\Component\Result\Facet\Query as SolrFacetQuery; use Solarium\Core\Client\Client; use Solarium\QueryType\Select\Query\Query as SolrSelectQuery; use Solarium\QueryType\Select\Result\Result as SelectResult; @@ -37,6 +34,7 @@ public function __construct( private readonly IndexName $index, private readonly SolrClientFactory $clientFactory, private readonly SolrResultToResourceResolver $resultToResourceResolver, + private readonly Schema2xFieldMapper $schemaFieldMapper, private readonly iterable $solrQueryModifierList = [] ) { } @@ -103,26 +101,11 @@ private function addSortToSolrQuery( SolrSelectQuery $solrQuery, array $criteriaList ): void { + $sorts = []; foreach ($criteriaList as $criteria) { - if ($criteria instanceof Name) { - $field = 'sp_name'; - } elseif ($criteria instanceof Headline) { - $field = 'sp_title'; - } elseif ($criteria instanceof Date) { - $field = 'sp_date'; - } elseif ($criteria instanceof Natural) { - $field = 'sp_sortvalue'; - } elseif ($criteria instanceof Score) { - $field = 'score'; - } else { - throw new InvalidArgumentException( - 'unsupported sort criteria: ' . get_class($criteria) - ); - } - + $field = $this->schemaFieldMapper->getSortField($criteria); $direction = strtolower($criteria->direction->name); - $sorts[$field] = $direction; } $solrQuery->setSorts($sorts); @@ -143,7 +126,8 @@ private function addTextFilterToSolrQuery( } $terms = explode(' ', $text); $terms = array_map( - fn ($term) => $solrQuery->getHelper()->escapeTerm(trim($term)), + static fn ($term) => + $solrQuery->getHelper()->escapeTerm(trim($term)), $terms ); $text = implode(' ', $terms); @@ -168,12 +152,12 @@ private function addFilterQueriesToSolrQuery( SolrSelectQuery $solrQuery, array $filterList ): void { - + $filterAppender = new SolrQueryFilterAppender( + $solrQuery, + $this->schemaFieldMapper + ); foreach ($filterList as $filter) { - $key = $filter->key ?? uniqid('', true); - $filterQuery = $solrQuery->createFilterQuery($key); - $filterQuery->setQuery($filter->getQuery()); - $filterQuery->setTags($filter->tags); + $filterAppender->append($filter); } } @@ -184,7 +168,10 @@ private function addFacetListToSolrQuery( SolrSelectQuery $solrQuery, array $facetList ): void { - $facetAppender = new SolrQueryFacetAppender($solrQuery); + $facetAppender = new SolrQueryFacetAppender( + $solrQuery, + $this->schemaFieldMapper + ); foreach ($facetList as $facet) { $facetAppender->append($facet); } @@ -230,7 +217,7 @@ private function buildFacetGroupList( continue; } if ( - $resultFacet instanceof \Solarium\Component\Result\Facet\Field + $resultFacet instanceof SolrFacetField ) { $facetGroupList[] = $this->buildFacetGroupByField( $facet->key, @@ -239,7 +226,7 @@ private function buildFacetGroupList( } if ( - $resultFacet instanceof \Solarium\Component\Result\Facet\Query + $resultFacet instanceof SolrFacetQuery ) { $facetGroupList[] = $this->buildFacetGroupByQuery( $facet->key, @@ -252,7 +239,7 @@ private function buildFacetGroupList( private function buildFacetGroupByField( string $key, - \Solarium\Component\Result\Facet\Field $solrFacet + SolrFacetField $solrFacet ): FacetGroup { $facetList = []; foreach ($solrFacet as $value => $count) { @@ -268,7 +255,7 @@ private function buildFacetGroupByField( private function buildFacetGroupByQuery( string $key, - \Solarium\Component\Result\Facet\Query $solrFacet + SolrFacetQuery $solrFacet ): FacetGroup { $facetList = []; diff --git a/src/Service/Search/SolrSuggest.php b/src/Service/Search/SolrSuggest.php index 1557590..bdd5c0e 100644 --- a/src/Service/Search/SolrSuggest.php +++ b/src/Service/Search/SolrSuggest.php @@ -5,6 +5,7 @@ namespace Atoolo\Search\Service\Search; use Atoolo\Resource\ResourceLanguage; +use Atoolo\Search\Dto\Search\Query\Filter\Filter; use Atoolo\Search\Dto\Search\Query\SuggestQuery; use Atoolo\Search\Dto\Search\Result\Suggestion; use Atoolo\Search\Dto\Search\Result\SuggestResult; @@ -32,7 +33,8 @@ class SolrSuggest implements Suggest public function __construct( private readonly IndexName $index, - private readonly SolrClientFactory $clientFactory + private readonly SolrClientFactory $clientFactory, + private readonly Schema2xFieldMapper $schemaFieldMapper ) { } @@ -79,15 +81,27 @@ private function buildSolrQuery( $solrQuery->setRows(0); // Filter - foreach ($query->filter as $filter) { - $filterQuery = $solrQuery->createFilterQuery($filter->key); - $filterQuery->setQuery($filter->getQuery()); - $filterQuery->setTags($filter->tags); - } + $this->addFilterQueriesToSolrQuery($solrQuery, $query->filter); return $solrQuery; } + /** + * @param Filter[] $filterList + */ + private function addFilterQueriesToSolrQuery( + SolrSelectQuery $solrQuery, + array $filterList + ): void { + $filterAppender = new SolrQueryFilterAppender( + $solrQuery, + $this->schemaFieldMapper + ); + foreach ($filterList as $filter) { + $filterAppender->append($filter); + } + } + private function buildResult( SolrSelectResult $solrResult ): SuggestResult { diff --git a/test/Dto/Search/Query/Filter/AbsoluteDateRangeFilterTest.php b/test/Dto/Search/Query/Filter/AbsoluteDateRangeFilterTest.php index f90547c..67b1b27 100644 --- a/test/Dto/Search/Query/Filter/AbsoluteDateRangeFilterTest.php +++ b/test/Dto/Search/Query/Filter/AbsoluteDateRangeFilterTest.php @@ -5,40 +5,16 @@ namespace Atoolo\Search\Test\Dto\Search\Query\Filter; use Atoolo\Search\Dto\Search\Query\Filter\AbsoluteDateRangeFilter; +use InvalidArgumentException; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; #[CoversClass(AbsoluteDateRangeFilter::class)] class AbsoluteDateRangeFilterTest extends TestCase { - public function testGetQueryWithFromAndTo(): void + public function testConstructorWithoutFromAndTo(): void { - $from = new \DateTime('2021-01-01 00:00:00'); - $to = new \DateTime('2021-01-02 00:00:00'); - $filter = new AbsoluteDateRangeFilter($from, $to, 'sp_date_list'); - $this->assertEquals( - 'sp_date_list:[2021-01-01T00:00:00Z TO 2021-01-02T00:00:00Z]', - $filter->getQuery() - ); - } - - public function testGetQueryWithFrom(): void - { - $from = new \DateTime('2021-01-01 00:00:00'); - $filter = new AbsoluteDateRangeFilter($from, null, 'sp_date_list'); - $this->assertEquals( - 'sp_date_list:[2021-01-01T00:00:00Z TO *]', - $filter->getQuery() - ); - } - - public function testGetQueryWithTo(): void - { - $to = new \DateTime('2021-01-02 00:00:00'); - $filter = new AbsoluteDateRangeFilter(null, $to, 'sp_date_list'); - $this->assertEquals( - 'sp_date_list:[* TO 2021-01-02T00:00:00Z]', - $filter->getQuery() - ); + $this->expectException(InvalidArgumentException::class); + new AbsoluteDateRangeFilter(null, null); } } diff --git a/test/Dto/Search/Query/Filter/AndFilterTest.php b/test/Dto/Search/Query/Filter/AndFilterTest.php deleted file mode 100644 index dea1659..0000000 --- a/test/Dto/Search/Query/Filter/AndFilterTest.php +++ /dev/null @@ -1,35 +0,0 @@ -createStub(Filter::class); - $a->method('getQuery') - ->willReturn('a'); - - $b = $this->createStub(Filter::class); - $b->method('getQuery') - ->willReturn('b'); - - $and = new AndFilter([$a, $b]); - - assertEquals( - '(a AND b)', - $and->getQuery(), - 'unexpected query' - ); - } -} diff --git a/test/Dto/Search/Query/Filter/ArchiveFilterTest.php b/test/Dto/Search/Query/Filter/ArchiveFilterTest.php deleted file mode 100644 index 022d96d..0000000 --- a/test/Dto/Search/Query/Filter/ArchiveFilterTest.php +++ /dev/null @@ -1,23 +0,0 @@ -assertEquals( - '-sp_archive:true', - $filter->getQuery(), - 'unexpected query' - ); - } -} diff --git a/test/Dto/Search/Query/Filter/FieldFilterTest.php b/test/Dto/Search/Query/Filter/FieldFilterTest.php index 13d45ae..55b5953 100644 --- a/test/Dto/Search/Query/Filter/FieldFilterTest.php +++ b/test/Dto/Search/Query/Filter/FieldFilterTest.php @@ -12,40 +12,15 @@ #[CoversClass(FieldFilter::class)] class FieldFilterTest extends TestCase { - public function testEmptyValues(): void + public function testConstructor(): void { - $this->expectException(InvalidArgumentException::class); - new FieldFilter('test', []); - } - - public function testGetQueryWithOneField(): void - { - $field = new FieldFilter('test', ['a']); - $this->assertEquals( - 'test:a', - $field->getQuery(), - 'unexpected query' - ); + $filter = new FieldFilter(['a']); + $this->assertEquals(['a'], $filter->values, 'Unexpected values'); } - public function testGetQueryWithTwoFields(): void + public function testConstructorWithEmptyValues(): void { - $field = new FieldFilter('test', ['a', 'b']); - $this->assertEquals( - 'test:(a b)', - $field->getQuery(), - 'unexpected query' - ); - } - - public function testExclude(): void - { - $field = new FieldFilter('test', ['a']); - $exclude = $field->exclude(); - $this->assertEquals( - '-test:a', - $exclude->getQuery(), - 'unexpected exclude query' - ); + $this->expectException(InvalidArgumentException::class); + new FieldFilter([]); } } diff --git a/test/Dto/Search/Query/Filter/NotFilterTest.php b/test/Dto/Search/Query/Filter/NotFilterTest.php deleted file mode 100644 index a4bfecb..0000000 --- a/test/Dto/Search/Query/Filter/NotFilterTest.php +++ /dev/null @@ -1,28 +0,0 @@ -createStub(Filter::class); - $filter->method('getQuery') - ->willReturn('a:b'); - $notFilter = new NotFilter($filter); - - $this->assertEquals( - 'NOT a:b', - $notFilter->getQuery(), - 'unexpected query' - ); - } -} diff --git a/test/Dto/Search/Query/Filter/OrFilterTest.php b/test/Dto/Search/Query/Filter/OrFilterTest.php deleted file mode 100644 index 5d29ae5..0000000 --- a/test/Dto/Search/Query/Filter/OrFilterTest.php +++ /dev/null @@ -1,35 +0,0 @@ -createStub(Filter::class); - $a->method('getQuery') - ->willReturn('a'); - - $b = $this->createStub(Filter::class); - $b->method('getQuery') - ->willReturn('b'); - - $and = new OrFilter([$a, $b]); - - assertEquals( - '(a OR b)', - $and->getQuery(), - 'unexpected query' - ); - } -} diff --git a/test/Dto/Search/Query/Filter/QueryFilterTest.php b/test/Dto/Search/Query/Filter/QueryFilterTest.php deleted file mode 100644 index d0a1315..0000000 --- a/test/Dto/Search/Query/Filter/QueryFilterTest.php +++ /dev/null @@ -1,23 +0,0 @@ -assertEquals( - 'a:b', - $filter->getQuery(), - 'unexpected query' - ); - } -} diff --git a/test/Dto/Search/Query/Filter/RelativeDateRangeFilterTest.php b/test/Dto/Search/Query/Filter/RelativeDateRangeFilterTest.php deleted file mode 100644 index f43ae0d..0000000 --- a/test/Dto/Search/Query/Filter/RelativeDateRangeFilterTest.php +++ /dev/null @@ -1,208 +0,0 @@ - - */ - public static function additionProviderForBeforeIntervals(): array - { - return [ - ['P1D', 'sp_date_list:[NOW-1DAYS/DAY TO NOW/DAY+1DAY-1SECOND]'], - ['P1W', 'sp_date_list:[NOW-7DAYS/DAY TO NOW/DAY+1DAY-1SECOND]'], - ['P2M', 'sp_date_list:[NOW-2MONTHS/DAY TO NOW/DAY+1DAY-1SECOND]'], - ['P3Y', 'sp_date_list:[NOW-3YEARS/DAY TO NOW/DAY+1DAY-1SECOND]'], - ]; - } - - /** - * @return array - */ - public static function additionProviderForAfterIntervals(): array - { - return [ - ['P1D', 'sp_date_list:[NOW/DAY TO NOW+1DAYS/DAY+1DAY-1SECOND]'], - ['P1W', 'sp_date_list:[NOW/DAY TO NOW+7DAYS/DAY+1DAY-1SECOND]'], - ['P2M', 'sp_date_list:[NOW/DAY TO NOW+2MONTHS/DAY+1DAY-1SECOND]'], - ['P3Y', 'sp_date_list:[NOW/DAY TO NOW+3YEARS/DAY+1DAY-1SECOND]'], - ]; - } - - /** - * @return array - */ - public static function additionProviderForBeforeAndAfterIntervals(): array - { - return [ - [ - 'P1D', - 'P1D', - 'sp_date_list:[NOW-1DAYS/DAY TO NOW+1DAYS/DAY+1DAY-1SECOND]' - ], - [ - 'P1W', - 'P2M', - 'sp_date_list:[NOW-7DAYS/DAY TO NOW+2MONTHS/DAY+1DAY-1SECOND]' - ], - ]; - } - - /** - * @return array - */ - public static function additionProviderWithBase(): array - { - return [ - [ - new DateTime('2021-01-01 00:00:00'), - 'P1D', - null, - 'sp_date_list:[2021-01-01T00:00:00Z-1DAYS/DAY' . - ' TO 2021-01-01T00:00:00Z/DAY+1DAY-1SECOND]' - ], - [ - new DateTime('2021-01-01 00:00:00'), - null, - 'P2M', - 'sp_date_list:[2021-01-01T00:00:00Z/DAY' . - ' TO 2021-01-01T00:00:00Z+2MONTHS/DAY+1DAY-1SECOND]' - ], - [ - new DateTime('2021-01-01 00:00:00'), - 'P1W', - 'P2M', - 'sp_date_list:[2021-01-01T00:00:00Z-7DAYS/DAY' . - ' TO 2021-01-01T00:00:00Z+2MONTHS/DAY+1DAY-1SECOND]' - ], - ]; - } - - /** - * @return array - */ - public static function additionProviderForInvalidIntervals(): array - { - return [ - ['PT1H'], - ['PT1M'], - ['PT1S'], - ]; - } - - /** - * @throws Exception - */ - #[DataProvider('additionProviderForBeforeIntervals')] - public function testGetQueryWithFrom( - string $before, - string $expected - ): void { - $filter = new RelativeDateRangeFilter( - null, - new DateInterval($before), - null, - ); - - $this->assertEquals( - $expected, - $filter->getQuery(), - 'unexpected query' - ); - } - - /** - * @throws Exception - */ - #[DataProvider('additionProviderForAfterIntervals')] - public function testGetQueryWithTo( - string $after, - string $expected - ): void { - $filter = new RelativeDateRangeFilter( - null, - null, - new DateInterval($after), - ); - - $this->assertEquals( - $expected, - $filter->getQuery(), - 'unexpected query' - ); - } - - /** - * @throws Exception - */ - #[DataProvider('additionProviderForBeforeAndAfterIntervals')] - public function testGetQueryWithFromAndTo( - string $before, - string $after, - string $expected - ): void { - $filter = new RelativeDateRangeFilter( - null, - new DateInterval($before), - new DateInterval($after), - ); - - $this->assertEquals( - $expected, - $filter->getQuery(), - 'unexpected query' - ); - } - - /** - * @throws Exception - */ - #[DataProvider('additionProviderWithBase')] - public function testGetQueryWithBase( - DateTime $base, - ?string $before, - ?string $after, - string $expected - ): void { - $filter = new RelativeDateRangeFilter( - $base, - $before === null ? null : new DateInterval($before), - $after === null ? null : new DateInterval($after), - ); - - $this->assertEquals( - $expected, - $filter->getQuery(), - 'unexpected query' - ); - } - - /** - * @throws Exception - */ - #[DataProvider('additionProviderForInvalidIntervals')] - public function testGetQueryWithInvalidIntervals( - string $interval - ): void { - $filter = new RelativeDateRangeFilter( - null, - null, - new DateInterval($interval), - ); - $this->expectException(InvalidArgumentException::class); - $filter->getQuery(); - } -} diff --git a/test/Dto/Search/Query/SearchQueryBuilderTest.php b/test/Dto/Search/Query/SearchQueryBuilderTest.php index 40f2e5e..15eea98 100644 --- a/test/Dto/Search/Query/SearchQueryBuilderTest.php +++ b/test/Dto/Search/Query/SearchQueryBuilderTest.php @@ -9,6 +9,7 @@ use Atoolo\Search\Dto\Search\Query\QueryOperator; use Atoolo\Search\Dto\Search\Query\SearchQueryBuilder; use Atoolo\Search\Dto\Search\Query\Sort\Criteria; +use DateTimeZone; use InvalidArgumentException; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\MockObject\Exception; @@ -102,7 +103,7 @@ public function testSetTwoFilterWithSameKey(): void public function testSetFacet(): void { $facet = $this->getMockBuilder(Facet::class) - ->setConstructorArgs(['test', null]) + ->setConstructorArgs(['test']) ->getMock(); $this->builder->facet($facet); $query = $this->builder->build(); @@ -112,10 +113,10 @@ public function testSetFacet(): void public function testSetTwoFacetSWithSameKey(): void { $facetA = $this->getMockBuilder(Facet::class) - ->setConstructorArgs(['test', null]) + ->setConstructorArgs(['test']) ->getMock(); $facetB = $this->getMockBuilder(Facet::class) - ->setConstructorArgs(['test', null]) + ->setConstructorArgs(['test']) ->getMock(); $this->expectException(InvalidArgumentException::class); @@ -132,4 +133,16 @@ public function testSetQueryDefaultOperator(): void 'unexpected queryDefaultOperator' ); } + + public function testSetTimeZone(): void + { + $timeZone = new DateTimeZone('UTC'); + $this->builder->timeZone($timeZone); + $query = $this->builder->build(); + $this->assertEquals( + $timeZone, + $query->timeZone, + 'unexpected timeZone' + ); + } } diff --git a/test/Service/ResourceChannelBasedIndexNameTest.php b/test/Service/ResourceChannelBasedIndexNameTest.php index 9f5b1dd..c033d8f 100644 --- a/test/Service/ResourceChannelBasedIndexNameTest.php +++ b/test/Service/ResourceChannelBasedIndexNameTest.php @@ -57,6 +57,16 @@ public function testNameWithLang(): void ); } + public function testNameWithEmptyLang(): void + { + $this->assertEquals( + 'test', + $this->indexName->name(ResourceLanguage::of('')), + 'The default index name should be returned ' . + 'if the default language is given' + ); + } + public function testNameWithUnsupportedLang(): void { $this->expectException(UnsupportedIndexLanguageException::class); diff --git a/test/Service/Search/Schema2xFieldMapperTest.php b/test/Service/Search/Schema2xFieldMapperTest.php new file mode 100644 index 0000000..98ab08e --- /dev/null +++ b/test/Service/Search/Schema2xFieldMapperTest.php @@ -0,0 +1,166 @@ +mapper = new Schema2xFieldMapper(); + } + + /** + * @return array + */ + public static function getFacets(): array + { + return [ + [ CategoryFacet::class, 'sp_category_path' ], + [ ContentSectionTypeFacet::class, 'sp_contenttype' ], + [ GroupFacet::class, 'sp_group_path' ], + [ ObjectTypeFacet::class, 'sp_objecttype' ], + [ SiteFacet::class, 'sp_site' ], + [ RelativeDateRangeFacet::class, 'sp_date_list' ], + [ AbsoluteDateRangeFacet::class, 'sp_date_list' ], + ]; + } + + /** + * @return array + */ + public static function getFilter(): array + { + return [ + [ CategoryFilter::class, 'sp_category_path' ], + [ ContentSectionTypeFilter::class, 'sp_contenttype' ], + [ GroupFilter::class, 'sp_group_path' ], + [ ObjectTypeFilter::class, 'sp_objecttype' ], + [ SiteFilter::class, 'sp_site' ], + [ RelativeDateRangeFilter::class, 'sp_date_list' ], + [ AbsoluteDateRangeFilter::class, 'sp_date_list' ], + ]; + } + + /** + * @return array + */ + public static function getSortCriteria(): array + { + return [ + [ Name::class, 'sp_name' ], + [ Headline::class, 'sp_title' ], + [ Date::class, 'sp_date' ], + [ Natural::class, 'sp_sortvalue' ], + [ Score::class, 'score' ], + ]; + } + + /** + * @param class-string $facetClass + * @throws Exception + */ + #[DataProvider('getFacets')] + public function testGetFacetField( + string $facetClass, + string $expected + ): void { + /** @var Facet $facet */ + $facet = $this->createStub($facetClass); + $this->assertEquals( + $expected, + $this->mapper->getFacetField($facet) + ); + } + + public function testUnsupportedFacetField(): void + { + $this->expectException(InvalidArgumentException::class); + $this->mapper->getFacetField( + $this->createStub(Facet::class) + ); + } + + /** + * @param class-string $filterClass + * @throws Exception + */ + #[DataProvider('getFilter')] + public function testGetFilterField( + string $filterClass, + string $expected + ): void { + /** @var Filter $filter */ + $filter = $this->createStub($filterClass); + $this->assertEquals( + $expected, + $this->mapper->getFilterField($filter) + ); + } + + public function testUnsupportedFilterField(): void + { + $this->expectException(InvalidArgumentException::class); + $this->mapper->getFilterField( + $this->createStub(Filter::class) + ); + } + + /** + * @param class-string $sortCriteriaClass + * @throws Exception + */ + #[DataProvider('getSortCriteria')] + public function testGetSortField( + string $sortCriteriaClass, + string $expected + ): void { + /** @var Criteria $criteria */ + $criteria = $this->createStub($sortCriteriaClass); + $this->assertEquals( + $expected, + $this->mapper->getSortField($criteria) + ); + } + + public function testUnsupportedSortField(): void + { + $this->expectException(InvalidArgumentException::class); + $this->mapper->getSortField( + $this->createStub(Criteria::class) + ); + } +} diff --git a/test/Service/Search/SolrDateMapperTest.php b/test/Service/Search/SolrDateMapperTest.php new file mode 100644 index 0000000..fc5150c --- /dev/null +++ b/test/Service/Search/SolrDateMapperTest.php @@ -0,0 +1,182 @@ + + */ + public static function getDateIntervals() + { + return [ + [ 'P1D', '+', '+1DAYS' ], + [ 'P2W', '+', '+14DAYS' ], + [ 'P3M', '+', '+3MONTHS' ], + [ 'P2Y', '+', '+2YEARS' ], + [ 'P1D', '-', '-1DAYS' ], + [ 'P2W', '-', '-14DAYS' ], + [ 'P3M', '-', '-3MONTHS' ], + [ 'P2Y', '-', '-2YEARS' ], + [ 'PT1H', '+', 'error' ], + [ 'PT1M', '+', 'error' ], + [ 'PT1S', '+', 'error' ], + ]; + } + + /** + * @return array + */ + public static function getDateRangeRounds() + { + return [ + [ DateRangeRound::START_OF_DAY, '/DAY' ], + [ DateRangeRound::START_OF_PREVIOUS_DAY, '/DAY-1DAY' ], + [ DateRangeRound::END_OF_DAY, '/DAY+1DAY-1SECOND' ], + [ DateRangeRound::END_OF_PREVIOUS_DAY, '/DAY-1SECOND' ], + [ DateRangeRound::START_OF_MONTH, '/MONTH' ], + [ DateRangeRound::START_OF_PREVIOUS_MONTH, '/MONTH-1MONTH' ], + [ DateRangeRound::END_OF_MONTH, '/MONTH+1MONTH-1SECOND' ], + [ DateRangeRound::END_OF_PREVIOUS_MONTH, '/MONTH-1SECOND' ], + [ DateRangeRound::START_OF_YEAR, '/YEAR' ], + [ DateRangeRound::START_OF_PREVIOUS_YEAR, '/YEAR-1YEAR' ], + [ DateRangeRound::END_OF_YEAR, '/YEAR+1YEAR-1SECOND' ], + [ DateRangeRound::END_OF_PREVIOUS_YEAR, '/YEAR-1SECOND' ], + ]; + } + + #[DataProvider('getDateIntervals')] + public function testMapDateInterval( + string $interval, + string $operator, + string $expected + ): void { + + if ($expected === 'error') { + $this->expectException(InvalidArgumentException::class); + } + + $result = SolrDateMapper::mapDateInterval( + new DateInterval($interval), + $operator + ); + + if ($expected !== 'error') { + $this->assertEquals( + $expected, + $result, + 'Unexpected date interval mapping' + ); + } + } + + public function testMapDateIntervalWithDefault(): void + { + $this->assertEquals( + '+1DAY', + SolrDateMapper::mapDateInterval(null, '+'), + 'unexpected default date interval' + ); + } + + #[DataProvider('getDateRangeRounds')] + public function testMapDateRangeRound( + DateRangeRound $round, + string $expected + ): void { + $this->assertEquals( + $expected, + SolrDateMapper::mapDateRangeRound($round), + 'Unexpected date range round mapping' + ); + } + + public function testMapDateTime(): void + { + $dateTime = new \DateTime('2021-01-01T00:00:00Z'); + $this->assertEquals( + '2021-01-01T00:00:00Z', + SolrDateMapper::mapDateTime($dateTime), + 'Unexpected date time mapping' + ); + } + + public function testMapDateTimeWithNull(): void + { + $this->assertEquals( + 'NOW', + SolrDateMapper::mapDateTime(null), + 'NOW should be returned for null date time' + ); + } + + public function testMapDateTimeWithDefault(): void + { + $default = new \DateTime('2021-01-01T00:00:00Z'); + + $this->assertEquals( + '2021-01-01T00:00:00Z', + SolrDateMapper::mapDateTime($default), + 'default date time should be returned' + ); + } + + public function testRoundStart(): void + { + $this->assertEquals( + '2021-01-01T00:00:00Z/DAY-1DAY', + SolrDateMapper::roundStart( + '2021-01-01T00:00:00Z', + DateRangeRound::START_OF_PREVIOUS_DAY + ), + 'Unexpected round date' + ); + } + + public function testRoundStartWithDefault(): void + { + $this->assertEquals( + '2021-01-01T00:00:00Z/DAY', + SolrDateMapper::roundStart( + '2021-01-01T00:00:00Z', + null + ), + 'Unexpected round date' + ); + } + + public function testRoundEnd(): void + { + $this->assertEquals( + '2021-01-01T00:00:00Z/MONTH-1SECOND', + SolrDateMapper::roundEnd( + '2021-01-01T00:00:00Z', + DateRangeRound::END_OF_PREVIOUS_MONTH + ), + 'Unexpected round date' + ); + } + + public function testRoundEndWithDefault(): void + { + $this->assertEquals( + '2021-01-01T00:00:00Z/DAY+1DAY-1SECOND', + SolrDateMapper::roundEnd( + '2021-01-01T00:00:00Z', + null + ), + 'Unexpected round date' + ); + } +} diff --git a/test/Service/Search/SolrMoreLikeThisTest.php b/test/Service/Search/SolrMoreLikeThisTest.php index 4195f9d..6b4891a 100644 --- a/test/Service/Search/SolrMoreLikeThisTest.php +++ b/test/Service/Search/SolrMoreLikeThisTest.php @@ -6,9 +6,10 @@ use Atoolo\Resource\Resource; use Atoolo\Resource\ResourceLocation; -use Atoolo\Search\Dto\Search\Query\Filter\Filter; +use Atoolo\Search\Dto\Search\Query\Filter\ObjectTypeFilter; use Atoolo\Search\Dto\Search\Query\MoreLikeThisQuery; use Atoolo\Search\Service\IndexName; +use Atoolo\Search\Service\Search\Schema2xFieldMapper; use Atoolo\Search\Service\Search\SolrMoreLikeThis; use Atoolo\Search\Service\Search\SolrResultToResourceResolver; use Atoolo\Search\Service\SolrClientFactory; @@ -54,19 +55,21 @@ protected function setUp(): void $resultToResourceResolver ->method('loadResourceList') ->willReturn([$this->resource]); + $schemaFieldMapper = $this->createStub( + Schema2xFieldMapper::class + ); $this->searcher = new SolrMoreLikeThis( $indexName, $clientFactory, - $resultToResourceResolver + $resultToResourceResolver, + $schemaFieldMapper ); } public function testMoreLikeThis(): void { - $filter = $this->getMockBuilder(Filter::class) - ->setConstructorArgs(['test', []]) - ->getMock(); + $filter = new ObjectTypeFilter(['test']); $query = new MoreLikeThisQuery( ResourceLocation::of('/test.php'), diff --git a/test/Service/Search/SolrQueryFacetAppenderTest.php b/test/Service/Search/SolrQueryFacetAppenderTest.php new file mode 100644 index 0000000..a2fdfe9 --- /dev/null +++ b/test/Service/Search/SolrQueryFacetAppenderTest.php @@ -0,0 +1,289 @@ +facetField = $this->createMock(Field::class); + $this->facetQuery = $this->createMock(Query::class); + $this->facetQuery->method('setQuery') + ->willReturn($this->facetQuery); + $this->facetMultiQuery = $this->createMock(MultiQuery::class); + $this->facetRage = $this->createMock(Range::class); + $this->facetRage->method('setField') + ->willReturn($this->facetRage); + $this->facetRage->method('setStart') + ->willReturn($this->facetRage); + $this->facetRage->method('setEnd') + ->willReturn($this->facetRage); + $this->facetRage->method('setGap') + ->willReturn($this->facetRage); + + $facetSet = $this->createStub(FacetSet::class); + $facetSet->method('createFacetField') + ->willReturn($this->facetField); + $facetSet->method('createFacetQuery') + ->willReturn($this->facetQuery); + $facetSet->method('createFacetMultiQuery') + ->willReturn($this->facetMultiQuery); + $facetSet->method('createFacetRange') + ->willReturn($this->facetRage); + + $solrQuery = $this->createMock(SolrSelectQuery::class); + $solrQuery->method('getFacetSet') + ->willReturn($facetSet); + + $fieldMapper = $this->createMock(Schema2xFieldMapper::class); + $fieldMapper->method('getFacetField') + ->willReturn('test'); + $this->appender = new SolrQueryFacetAppender($solrQuery, $fieldMapper); + } + + public function testAppendFieldFacet(): void + { + $this->facetField->expects($this->once()) + ->method('setField') + ->with('test'); + $this->facetField->expects($this->once()) + ->method('setTerms') + ->with(['a']); + $this->facetField->expects($this->once()) + ->method('setExcludes') + ->with(['exclude']); + $this->appender->append(new ObjectTypeFacet('key', ['a'], ['exclude'])); + } + + public function testAppendQueryFacet(): void + { + $this->facetQuery->expects($this->once()) + ->method('setQuery') + ->with('test:a'); + $this->facetQuery->expects($this->once()) + ->method('setExcludes') + ->with(['exclude']); + $this->appender->append(new QueryFacet('key', 'test:a', ['exclude'])); + } + + public function testAppendMultiQueryFacet(): void + { + $this->facetMultiQuery->expects($this->once()) + ->method('createQuery') + ->with('key', 'test:a'); + $this->facetMultiQuery->expects($this->once()) + ->method('setExcludes') + ->with(['exclude']); + $this->appender->append( + new MultiQueryFacet( + 'key', + [ new QueryFacet('key', 'test:a', ['exclude']) ], + ['exclude'] + ) + ); + } + + public function testAppendAbsoluteDateRangeFacetWithOutGap(): void + { + $this->facetQuery->expects($this->once()) + ->method('setQuery') + ->with('test:[2021-01-02T00:00:00Z TO 2021-01-03T00:00:00Z]'); + $this->facetQuery->expects($this->once()) + ->method('setExcludes') + ->with(['exclude']); + $this->appender->append( + new AbsoluteDateRangeFacet( + 'key', + new DateTime('2021-01-02T00:00:00Z'), + new DateTime('2021-01-03T00:00:00Z'), + null, + ['exclude'] + ) + ); + } + + public function testAppendAbsoluteDateRangeFacetWithGap(): void + { + $this->facetRage->expects($this->once()) + ->method('setField') + ->with('test'); + $this->facetRage->expects($this->once()) + ->method('setStart') + ->with('2021-01-01T00:00:00Z'); + $this->facetRage->expects($this->once()) + ->method('setEnd') + ->with('2021-01-03T00:00:00Z'); + $this->facetRage->expects($this->once()) + ->method('setGap') + ->with('+1DAYS'); + $this->facetRage->expects($this->once()) + ->method('setExcludes') + ->with(['exclude']); + $this->appender->append( + new AbsoluteDateRangeFacet( + 'key', + new DateTime('2021-01-01T00:00:00Z'), + new DateTime('2021-01-03T00:00:00Z'), + new DateInterval('P1D'), + ['exclude'] + ) + ); + } + + public function testAppendRelativeDateRangeFacetWithoutGap(): void + { + $this->facetQuery->expects($this->once()) + ->method('setQuery') + ->with( + 'test:[' . + '2021-01-01T00:00:00Z-2DAYS/DAY' . + ' TO ' . + '2021-01-01T00:00:00Z+3DAYS/DAY+1DAY-1SECOND' . + ']' + ); + $this->facetQuery->expects($this->once()) + ->method('setExcludes') + ->with(['exclude']); + $this->appender->append( + new RelativeDateRangeFacet( + 'key', + new DateTime('2021-01-01T00:00:00Z'), + new DateInterval('P2D'), + new DateInterval('P3D'), + null, + null, + null, + ['exclude'] + ) + ); + } + + public function testAppendRelativeDateRangeFacetWithoutBeforeAndGap(): void + { + $this->facetQuery->expects($this->once()) + ->method('setQuery') + ->with( + 'test:[' . + '2021-01-01T00:00:00Z/DAY' . + ' TO ' . + '2021-01-01T00:00:00Z+3DAYS/DAY+1DAY-1SECOND' . + ']' + ); + $this->facetQuery->expects($this->once()) + ->method('setExcludes') + ->with(['exclude']); + $this->appender->append( + new RelativeDateRangeFacet( + 'key', + new DateTime('2021-01-01T00:00:00Z'), + null, + new DateInterval('P3D'), + null, + null, + null, + ['exclude'] + ) + ); + } + + public function testAppendRelativeDateRangeFacetWithoutAfterAndGap(): void + { + $this->facetQuery->expects($this->once()) + ->method('setQuery') + ->with( + 'test:[' . + '2021-01-01T00:00:00Z-2DAYS/DAY' . + ' TO ' . + '2021-01-01T00:00:00Z/DAY+1DAY-1SECOND' . + ']' + ); + $this->facetQuery->expects($this->once()) + ->method('setExcludes') + ->with(['exclude']); + $this->appender->append( + new RelativeDateRangeFacet( + 'key', + new DateTime('2021-01-01T00:00:00Z'), + new DateInterval('P2D'), + null, + null, + null, + null, + ['exclude'] + ) + ); + } + + public function testAppendRelativeDateRangeFacetWithGap(): void + { + $this->facetRage->expects($this->once()) + ->method('setField') + ->with('test'); + $this->facetRage->expects($this->once()) + ->method('setStart') + ->with('2021-01-01T00:00:00Z-2DAYS/DAY'); + $this->facetRage->expects($this->once()) + ->method('setEnd') + ->with('2021-01-01T00:00:00Z+3DAYS/DAY+1DAY-1SECOND'); + $this->facetRage->expects($this->once()) + ->method('setGap') + ->with('+1DAYS'); + $this->facetRage->expects($this->once()) + ->method('setExcludes') + ->with(['exclude']); + $this->appender->append( + new RelativeDateRangeFacet( + 'key', + new DateTime('2021-01-01T00:00:00Z'), + new DateInterval('P2D'), + new DateInterval('P3D'), + new DateInterval('P1D'), + null, + null, + ['exclude'] + ) + ); + } + + public function testUnsupportedFacet(): void + { + $this->expectException(InvalidArgumentException::class); + $this->appender->append( + $this->createStub(Facet::class) + ); + } +} diff --git a/test/Service/Search/SolrQueryFilterAppenderTest.php b/test/Service/Search/SolrQueryFilterAppenderTest.php new file mode 100644 index 0000000..8be3531 --- /dev/null +++ b/test/Service/Search/SolrQueryFilterAppenderTest.php @@ -0,0 +1,376 @@ +filterQuery = $this->createMock(FilterQuery::class); + $solrQuery = $this->createMock(SolrSelectQuery::class); + $solrQuery->method('createFilterQuery') + ->willReturn($this->filterQuery); + $fieldMapper = $this->createMock(Schema2xFieldMapper::class); + $fieldMapper->method('getFilterField') + ->willReturn('test'); + $this->appender = new SolrQueryFilterAppender($solrQuery, $fieldMapper); + } + + public function testFieldFilterWithOneField(): void + { + $field = new FieldFilter(['a']); + + $this->filterQuery->expects($this->once()) + ->method('setQuery') + ->with('test:a'); + + $this->appender->append($field); + } + + public function testFieldFilterWithTwoFields(): void + { + $field = new FieldFilter(['a', 'b']); + + $this->filterQuery->expects($this->once()) + ->method('setQuery') + ->with('test:(a b)'); + + $this->appender->append($field); + } + + public function testAndFilter(): void + { + $a = new FieldFilter(['a']); + $b = new FieldFilter(['b']); + + $filter = new AndFilter([$a, $b]); + + $this->filterQuery->expects($this->once()) + ->method('setQuery') + ->with('(test:a AND test:b)'); + + $this->appender->append($filter); + } + + public function testOrFilter(): void + { + $a = new FieldFilter(['a']); + $b = new FieldFilter(['b']); + + $filter = new OrFilter([$a, $b]); + + $this->filterQuery->expects($this->once()) + ->method('setQuery') + ->with('(test:a OR test:b)'); + + $this->appender->append($filter); + } + + public function testNotFilter(): void + { + $a = new FieldFilter(['a']); + + $filter = new NotFilter($a); + + $this->filterQuery->expects($this->once()) + ->method('setQuery') + ->with('NOT test:a'); + + $this->appender->append($filter); + } + + public function testArchiveFilter(): void + { + $filter = new ArchiveFilter(); + + $this->filterQuery->expects($this->once()) + ->method('setQuery') + ->with('-test:true'); + + $this->appender->append($filter); + } + + public function testQueryFilter(): void + { + $filter = new QueryFilter('a:b'); + + $this->filterQuery->expects($this->once()) + ->method('setQuery') + ->with('a:b'); + + $this->appender->append($filter); + } + + public function testAbsoluteDateRangeFilterWithFromAndTo(): void + { + $from = new \DateTime('2021-01-01 00:00:00'); + $to = new \DateTime('2021-01-02 00:00:00'); + $filter = new AbsoluteDateRangeFilter($from, $to, 'sp_date_list'); + + $this->filterQuery->expects($this->once()) + ->method('setQuery') + ->with('test:[2021-01-01T00:00:00Z TO 2021-01-02T00:00:00Z]'); + + $this->appender->append($filter); + } + + public function testAbsoluteDateRangeFilterWithFrom(): void + { + $from = new \DateTime('2021-01-01 00:00:00'); + $filter = new AbsoluteDateRangeFilter($from, null, 'sp_date_list'); + + $this->filterQuery->expects($this->once()) + ->method('setQuery') + ->with('test:[2021-01-01T00:00:00Z TO *]'); + + $this->appender->append($filter); + } + + public function testAbsoluteDateRangeFilterWithTo(): void + { + $to = new \DateTime('2021-01-02 00:00:00'); + $filter = new AbsoluteDateRangeFilter(null, $to, 'sp_date_list'); + + $this->filterQuery->expects($this->once()) + ->method('setQuery') + ->with('test:[* TO 2021-01-02T00:00:00Z]'); + + $this->appender->append($filter); + } + + /** + * @return array + */ + public static function additionProviderForBeforeIntervals(): array + { + return [ + ['P1D', 'test:[NOW-1DAYS/DAY TO NOW/DAY+1DAY-1SECOND]'], + ['P1W', 'test:[NOW-7DAYS/DAY TO NOW/DAY+1DAY-1SECOND]'], + ['P2M', 'test:[NOW-2MONTHS/DAY TO NOW/DAY+1DAY-1SECOND]'], + ['P3Y', 'test:[NOW-3YEARS/DAY TO NOW/DAY+1DAY-1SECOND]'], + ]; + } + + /** + * @return array + */ + public static function additionProviderForAfterIntervals(): array + { + return [ + ['P1D', 'test:[NOW/DAY TO NOW+1DAYS/DAY+1DAY-1SECOND]'], + ['P1W', 'test:[NOW/DAY TO NOW+7DAYS/DAY+1DAY-1SECOND]'], + ['P2M', 'test:[NOW/DAY TO NOW+2MONTHS/DAY+1DAY-1SECOND]'], + ['P3Y', 'test:[NOW/DAY TO NOW+3YEARS/DAY+1DAY-1SECOND]'], + ]; + } + + /** + * @return array + */ + public static function additionProviderForBeforeAndAfterIntervals(): array + { + return [ + [ + 'P1D', + 'P1D', + 'test:[NOW-1DAYS/DAY TO NOW+1DAYS/DAY+1DAY-1SECOND]' + ], + [ + 'P1W', + 'P2M', + 'test:[NOW-7DAYS/DAY TO NOW+2MONTHS/DAY+1DAY-1SECOND]' + ], + ]; + } + + /** + * @return array + */ + public static function additionProviderWithBase(): array + { + return [ + [ + new DateTime('2021-01-01 00:00:00'), + 'P1D', + null, + 'test:[2021-01-01T00:00:00Z-1DAYS/DAY' . + ' TO 2021-01-01T00:00:00Z/DAY+1DAY-1SECOND]' + ], + [ + new DateTime('2021-01-01 00:00:00'), + null, + 'P2M', + 'test:[2021-01-01T00:00:00Z/DAY' . + ' TO 2021-01-01T00:00:00Z+2MONTHS/DAY+1DAY-1SECOND]' + ], + [ + new DateTime('2021-01-01 00:00:00'), + 'P1W', + 'P2M', + 'test:[2021-01-01T00:00:00Z-7DAYS/DAY' . + ' TO 2021-01-01T00:00:00Z+2MONTHS/DAY+1DAY-1SECOND]' + ], + ]; + } + + /** + * @return array + */ + public static function additionProviderForInvalidIntervals(): array + { + return [ + ['PT1H'], + ['PT1M'], + ['PT1S'], + ]; + } + + /** + * @throws Exception + */ + #[DataProvider('additionProviderForBeforeIntervals')] + public function testGetQueryWithFrom( + string $before, + string $expected + ): void { + $filter = new RelativeDateRangeFilter( + null, + new DateInterval($before), + null, + null, + null + ); + + $this->filterQuery->expects($this->once()) + ->method('setQuery') + ->with($expected); + + $this->appender->append($filter); + } + + /** + * @throws Exception + */ + #[DataProvider('additionProviderForAfterIntervals')] + public function testGetQueryWithTo( + string $after, + string $expected + ): void { + $filter = new RelativeDateRangeFilter( + null, + null, + new DateInterval($after), + null, + null + ); + + $this->filterQuery->expects($this->once()) + ->method('setQuery') + ->with($expected); + + $this->appender->append($filter); + } + + /** + * @throws Exception + */ + #[DataProvider('additionProviderForBeforeAndAfterIntervals')] + public function testGetQueryWithFromAndTo( + string $before, + string $after, + string $expected + ): void { + $filter = new RelativeDateRangeFilter( + null, + new DateInterval($before), + new DateInterval($after), + null, + null + ); + + $this->filterQuery->expects($this->once()) + ->method('setQuery') + ->with($expected); + + $this->appender->append($filter); + } + + /** + * @throws Exception + */ + #[DataProvider('additionProviderWithBase')] + public function testGetQueryWithBase( + DateTime $base, + ?string $before, + ?string $after, + string $expected + ): void { + $filter = new RelativeDateRangeFilter( + $base, + $before === null ? null : new DateInterval($before), + $after === null ? null : new DateInterval($after), + null, + null + ); + + $this->filterQuery->expects($this->once()) + ->method('setQuery') + ->with($expected); + + $this->appender->append($filter); + } + + /** + * @throws Exception + */ + #[DataProvider('additionProviderForInvalidIntervals')] + public function testGetQueryWithInvalidIntervals( + string $interval + ): void { + $filter = new RelativeDateRangeFilter( + null, + null, + new DateInterval($interval), + null, + null + ); + $this->expectException(InvalidArgumentException::class); + $this->appender->append($filter); + } + + public function testUnsupportedFilter(): void + { + $filter = $this->createStub(Filter::class); + $this->expectException(InvalidArgumentException::class); + $this->appender->append($filter); + } +} diff --git a/test/Service/Search/SolrSearchTest.php b/test/Service/Search/SolrSearchTest.php index 94d6ac1..1cfe7e8 100644 --- a/test/Service/Search/SolrSearchTest.php +++ b/test/Service/Search/SolrSearchTest.php @@ -6,30 +6,34 @@ use Atoolo\Resource\Resource; use Atoolo\Search\Dto\Search\Query\Facet\Facet; -use Atoolo\Search\Dto\Search\Query\Facet\FacetMultiQuery; -use Atoolo\Search\Dto\Search\Query\Facet\FacetQuery; +use Atoolo\Search\Dto\Search\Query\Facet\MultiQueryFacet; use Atoolo\Search\Dto\Search\Query\Facet\ObjectTypeFacet; -use Atoolo\Search\Dto\Search\Query\Filter\Filter; +use Atoolo\Search\Dto\Search\Query\Facet\QueryFacet; +use Atoolo\Search\Dto\Search\Query\Filter\ObjectTypeFilter; use Atoolo\Search\Dto\Search\Query\QueryOperator; use Atoolo\Search\Dto\Search\Query\SearchQuery; -use Atoolo\Search\Dto\Search\Query\Sort\Criteria; use Atoolo\Search\Dto\Search\Query\Sort\Date; use Atoolo\Search\Dto\Search\Query\Sort\Headline; use Atoolo\Search\Dto\Search\Query\Sort\Name; use Atoolo\Search\Dto\Search\Query\Sort\Natural; use Atoolo\Search\Dto\Search\Query\Sort\Score; +use Atoolo\Search\Dto\Search\Result\FacetGroup; use Atoolo\Search\Service\IndexName; +use Atoolo\Search\Service\Search\Schema2xFieldMapper; use Atoolo\Search\Service\Search\SolrQueryModifier; use Atoolo\Search\Service\Search\SolrResultToResourceResolver; use Atoolo\Search\Service\Search\SolrSearch; use Atoolo\Search\Service\SolrClientFactory; +use DateTimeZone; use InvalidArgumentException; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\Stub\Stub; use PHPUnit\Framework\TestCase; use Solarium\Client; use Solarium\Component\FacetSet; -use Solarium\Component\Result\Facet\Field; +use Solarium\Component\Result\Facet\Field as SolrFacetField; +use Solarium\Component\Result\Facet\Query as SolrFacetQuery; use Solarium\QueryType\Select\Query\FilterQuery; use Solarium\QueryType\Select\Query\Query as SolrSelectQuery; use Solarium\QueryType\Select\Result\Result as SelectResult; @@ -41,6 +45,8 @@ class SolrSearchTest extends TestCase private SelectResult|Stub $result; + private SolrSelectQuery&MockObject $solrQuery; + private SolrSearch $searcher; protected function setUp(): void @@ -52,14 +58,14 @@ protected function setUp(): void $client = $this->createStub(Client::class); $clientFactory->method('create')->willReturn($client); - $query = $this->createStub(SolrSelectQuery::class); + $this->solrQuery = $this->createMock(SolrSelectQuery::class); - $query->method('createFilterQuery') + $this->solrQuery->method('createFilterQuery') ->willReturn(new FilterQuery()); - $query->method('getFacetSet') + $this->solrQuery->method('getFacetSet') ->willReturn(new FacetSet()); - $client->method('createSelect')->willReturn($query); + $client->method('createSelect')->willReturn($this->solrQuery); $this->result = $this->createStub(SelectResult::class); $client->method('execute')->willReturn($this->result); @@ -71,16 +77,21 @@ protected function setUp(): void ); $solrQueryModifier = $this->createStub(SolrQueryModifier::class); - $solrQueryModifier->method('modify')->willReturn($query); + $solrQueryModifier->method('modify')->willReturn($this->solrQuery); $resultToResourceResolver ->method('loadResourceList') ->willReturn([$this->resource]); + $schemaFieldMapper = $this->createStub( + Schema2xFieldMapper::class + ); + $this->searcher = new SolrSearch( $indexName, $clientFactory, $resultToResourceResolver, + $schemaFieldMapper, [$solrQueryModifier], ); } @@ -96,7 +107,8 @@ public function testSelectEmpty(): void ], [], [], - QueryOperator::OR + QueryOperator::OR, + null ); $searchResult = $this->searcher->search($query); @@ -119,7 +131,8 @@ public function testSelectWithText(): void ], [], [], - QueryOperator::OR + QueryOperator::OR, + null ); $searchResult = $this->searcher->search($query); @@ -147,7 +160,8 @@ public function testSelectWithSort(): void ], [], [], - QueryOperator::OR + QueryOperator::OR, + null ); $searchResult = $this->searcher->search($query); @@ -159,25 +173,6 @@ public function testSelectWithSort(): void ); } - public function testSelectWithInvalidSort(): void - { - $sort = $this->createStub(Criteria::class); - - $query = new SearchQuery( - '', - '', - 0, - 10, - [$sort], - [], - [], - QueryOperator::OR - ); - - $this->expectException(InvalidArgumentException::class); - $this->searcher->search($query); - } - public function testSelectWithAndDefaultOperator(): void { $query = new SearchQuery( @@ -188,7 +183,8 @@ public function testSelectWithAndDefaultOperator(): void [], [], [], - QueryOperator::AND + QueryOperator::AND, + null ); $searchResult = $this->searcher->search($query); @@ -202,9 +198,7 @@ public function testSelectWithAndDefaultOperator(): void public function testSelectWithFilter(): void { - $filter = $this->getMockBuilder(Filter::class) - ->setConstructorArgs(['test', []]) - ->getMock(); + $filter = new ObjectTypeFilter(['test']); $query = new SearchQuery( '', @@ -214,7 +208,8 @@ public function testSelectWithFilter(): void [], [$filter], [], - QueryOperator::OR + QueryOperator::OR, + null ); $searchResult = $this->searcher->search($query); @@ -230,12 +225,12 @@ public function testSelectWithFacets(): void { $facets = [ - new ObjectTypeFacet('objectType', ['content'], 'ob'), - new FacetQuery('query', 'sp_id:123', 'ob'), - new FacetMultiQuery( + new ObjectTypeFacet('objectType', ['content'], ['ob']), + new QueryFacet('query', 'sp_id:123', ['ob']), + new MultiQueryFacet( 'multiquery', - [new FacetQuery('query', 'sp_id:123', null)], - 'ob' + [new QueryFacet('query', 'sp_id:123')], + ['ob'] ) ]; @@ -247,7 +242,8 @@ public function testSelectWithFacets(): void [], [], $facets, - QueryOperator::OR + QueryOperator::OR, + null ); $searchResult = $this->searcher->search($query); @@ -274,17 +270,18 @@ public function testSelectWithInvalidFacets(): void [], [], $facets, - QueryOperator::OR + QueryOperator::OR, + null ); $this->expectException(InvalidArgumentException::class); $this->searcher->search($query); } - public function testResultFacets(): void + public function testResulWithFacetField(): void { - $facet = new Field([ + $facet = new SolrFacetField([ 'content' => 10, 'media' => 5 ]); @@ -296,13 +293,7 @@ public function testResultFacets(): void ->willReturn($facetSet); $facets = [ - new ObjectTypeFacet('objectType', ['content'], 'ob'), - new FacetQuery('query', 'sp_id:123', 'ob'), - new FacetMultiQuery( - 'multiquery', - [new FacetQuery('query', 'sp_id:123', null)], - 'ob' - ) + new ObjectTypeFacet('objectType', ['content'], ['ob']), ]; $query = new SearchQuery( @@ -313,22 +304,73 @@ public function testResultFacets(): void [], [], $facets, - QueryOperator::OR + QueryOperator::OR, + null ); $searchResult = $this->searcher->search($query); - $this->assertEquals( + $expected = new FacetGroup( 'objectType', - $searchResult->facetGroups[0]->key, - 'unexpected results' + [ + new \Atoolo\Search\Dto\Search\Result\Facet('content', 10), + new \Atoolo\Search\Dto\Search\Result\Facet('media', 5) + ] + ); + + $this->assertEquals( + $expected, + $searchResult->facetGroups[0], + 'unexpected facet results' ); } + public function testResultWithFacetQuery(): void + { + + $facet = new SolrFacetQuery(5); + $facetSet = new \Solarium\Component\Result\FacetSet([ + 'aquery' => $facet + ]); + + $this->result->method('getFacetSet') + ->willReturn($facetSet); + + $facets = [ + new QueryFacet('aquery', 'sp_id:123'), + ]; + + $query = new SearchQuery( + '', + '', + 0, + 10, + [], + [], + $facets, + QueryOperator::OR, + null + ); + + $searchResult = $this->searcher->search($query); + + $expected = new FacetGroup( + 'aquery', + [ + new \Atoolo\Search\Dto\Search\Result\Facet('aquery', 5) + ] + ); + + $this->assertEquals( + $expected, + $searchResult->facetGroups[0], + 'unexpected facet results' + ); + } public function testInvalidResultFacets(): void { - $facet = new Field([ + $facet = new SolrFacetField([ 'content' => 'nonint', ]); $facetSet = new \Solarium\Component\Result\FacetSet([ @@ -339,12 +381,12 @@ public function testInvalidResultFacets(): void ->willReturn($facetSet); $facets = [ - new ObjectTypeFacet('objectType', ['content'], 'ob'), - new FacetQuery('query', 'sp_id:123', 'ob'), - new FacetMultiQuery( + new ObjectTypeFacet('objectType', ['content'], ['ob']), + new QueryFacet('query', 'sp_id:123', ['ob']), + new MultiQueryFacet( 'multiquery', - [new FacetQuery('query', 'sp_id:123', null)], - 'ob' + [new QueryFacet('query', 'sp_id:123')], + ['ob'] ) ]; @@ -356,10 +398,86 @@ public function testInvalidResultFacets(): void [], [], $facets, - QueryOperator::OR + QueryOperator::OR, + null ); $this->expectException(InvalidArgumentException::class); $this->searcher->search($query); } + + public function testResultWithoutFacets(): void + { + + $facetSet = new \Solarium\Component\Result\FacetSet([ + ]); + + $this->result->method('getFacetSet') + ->willReturn($facetSet); + + $facets = [ + new ObjectTypeFacet('objectType', ['content'], ['ob']), + ]; + + $query = new SearchQuery( + '', + '', + 0, + 10, + [], + [], + $facets, + QueryOperator::OR, + null + ); + + $searchResult = $this->searcher->search($query); + + $this->assertEmpty( + $searchResult->facetGroups, + 'facets should be empty' + ); + } + + public function testSetTimeZone(): void + { + $query = new SearchQuery( + '', + '', + 0, + 10, + [], + [], + [], + QueryOperator::OR, + new DateTimeZone("UTC") + ); + + $this->solrQuery->expects($this->once()) + ->method('setTimezone') + ->with(new DateTimeZone('UTC')); + + $this->searcher->search($query); + } + + public function testSetDefaultTimeZone(): void + { + $query = new SearchQuery( + '', + '', + 0, + 10, + [], + [], + [], + QueryOperator::OR, + null + ); + + $this->solrQuery->expects($this->once()) + ->method('setTimezone') + ->with(date_default_timezone_get()); + + $this->searcher->search($query); + } } diff --git a/test/Service/Search/SolrSuggestTest.php b/test/Service/Search/SolrSuggestTest.php index 734420d..e15c629 100644 --- a/test/Service/Search/SolrSuggestTest.php +++ b/test/Service/Search/SolrSuggestTest.php @@ -4,11 +4,12 @@ namespace Atoolo\Search\Test\Service\Search; -use Atoolo\Search\Dto\Search\Query\Filter\Filter; +use Atoolo\Search\Dto\Search\Query\Filter\ObjectTypeFilter; use Atoolo\Search\Dto\Search\Query\SuggestQuery; use Atoolo\Search\Dto\Search\Result\Suggestion; use Atoolo\Search\Exception\UnexpectedResultException; use Atoolo\Search\Service\IndexName; +use Atoolo\Search\Service\Search\Schema2xFieldMapper; use Atoolo\Search\Service\Search\SolrSuggest; use Atoolo\Search\Service\SolrClientFactory; use PHPUnit\Framework\Attributes\CoversClass; @@ -46,14 +47,20 @@ protected function setUp(): void $this->result = $this->createStub(SelectResult::class); $client->method('select')->willReturn($this->result); - $this->searcher = new SolrSuggest($indexName, $clientFactory); + $schemaFieldMapper = $this->createStub( + Schema2xFieldMapper::class + ); + + $this->searcher = new SolrSuggest( + $indexName, + $clientFactory, + $schemaFieldMapper + ); } public function testSuggest(): void { - $filter = $this->getMockBuilder(Filter::class) - ->setConstructorArgs(['test', []]) - ->getMock(); + $filter = new ObjectTypeFilter(['test']); $query = new SuggestQuery( 'myindex', diff --git a/test/Service/Search/TestSortCriteria.php b/test/Service/Search/TestSortCriteria.php new file mode 100644 index 0000000..4ce088e --- /dev/null +++ b/test/Service/Search/TestSortCriteria.php @@ -0,0 +1,11 @@ +