Skip to content

Commit

Permalink
Merge pull request #157 from netgen/FINAWEB-53
Browse files Browse the repository at this point in the history
Implement TagId and TagKeyword visitors for elasticsearch
  • Loading branch information
emodric authored Jul 28, 2023
2 parents 544fbeb + a238f85 commit d453fa1
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

namespace Netgen\TagsBundle\Core\Search\Elasticsearch\Query\Common\CriterionVisitor;

use Ibexa\Contracts\Core\Persistence\Content\Type\Handler;
use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion;
use Ibexa\Contracts\Elasticsearch\Query\CriterionVisitor;
use Ibexa\Core\Search\Common\FieldNameResolver;

use function array_merge;

abstract class Tags implements CriterionVisitor
{
public function __construct(
private readonly FieldNameResolver $fieldNameResolver,
private readonly Handler $contentTypeHandler,
private readonly string $fieldTypeIdentifier,
private readonly string $fieldName,
) {
}

protected function getSearchFields(Criterion $criterion): array
{
if ($criterion->target !== null) {
return $this->fieldNameResolver->getFieldTypes(
$criterion,
$criterion->target,
$this->fieldTypeIdentifier,
$this->fieldName,
);
}

$targetFieldTypes = [];
foreach ($this->contentTypeHandler->getSearchableFieldMap() as $fieldDefinitions) {
foreach ($fieldDefinitions as $fieldIdentifier => $fieldDefinition) {
if (!isset($fieldDefinition['field_type_identifier'])) {
continue;
}

if ($fieldDefinition['field_type_identifier'] !== $this->fieldTypeIdentifier) {
continue;
}

$fieldTypes = $this->fieldNameResolver->getFieldTypes(
$criterion,
$fieldIdentifier,
$this->fieldTypeIdentifier,
$this->fieldName,
);

$targetFieldTypes[] = $fieldTypes;
}
}

return array_merge(...$targetFieldTypes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace Netgen\TagsBundle\Core\Search\Elasticsearch\Query\Common\CriterionVisitor\Tags;

use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion;
use Ibexa\Contracts\Elasticsearch\Query\CriterionVisitor;
use Ibexa\Contracts\Elasticsearch\Query\LanguageFilter;
use Ibexa\Core\Base\Exceptions\InvalidArgumentException;
use Ibexa\Elasticsearch\ElasticSearch\QueryDSL\BoolQuery;
use Ibexa\Elasticsearch\ElasticSearch\QueryDSL\TermQuery;
use Netgen\TagsBundle\API\Repository\Values\Content\Query\Criterion\TagId as APITagId;
use Netgen\TagsBundle\Core\Search\Elasticsearch\Query\Common\CriterionVisitor\Tags;

use function count;

final class TagId extends Tags
{
public function supports(Criterion $criterion, LanguageFilter $languageFilter): bool
{
return $criterion instanceof APITagId;
}

public function visit(CriterionVisitor $dispatcher, Criterion $criterion, LanguageFilter $languageFilter): array
{
$criterion->value = (array) $criterion->value;
$searchFields = $this->getSearchFields($criterion);

if (count($searchFields) === 0) {
throw new InvalidArgumentException(
'$criterion->target',
"No searchable fields found for the given criterion target '{$criterion->target}'.",
);
}

$query = new BoolQuery();
foreach ($searchFields as $name => $fieldType) {
foreach ($criterion->value as $value) {
$query->addShould(new TermQuery($name, $value));
}
}

return $query->toArray();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace Netgen\TagsBundle\Core\Search\Elasticsearch\Query\Common\CriterionVisitor\Tags;

use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion;
use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Operator;
use Ibexa\Contracts\Elasticsearch\Query\CriterionVisitor;
use Ibexa\Contracts\Elasticsearch\Query\LanguageFilter;
use Ibexa\Core\Base\Exceptions\InvalidArgumentException;
use Ibexa\Elasticsearch\ElasticSearch\QueryDSL\BoolQuery;
use Ibexa\Elasticsearch\ElasticSearch\QueryDSL\TermQuery;
use Netgen\TagsBundle\API\Repository\Values\Content\Query\Criterion\TagKeyword as APITagKeyword;
use Netgen\TagsBundle\Core\Search\Elasticsearch\Query\Common\CriterionVisitor\Tags;
use Netgen\TagsBundle\Core\Search\Elasticsearch\QueryDSL\PrefixQuery;

use function count;

final class TagKeyword extends Tags
{
public function supports(Criterion $criterion, LanguageFilter $languageFilter): bool
{
return $criterion instanceof APITagKeyword;
}

public function visit(CriterionVisitor $dispatcher, Criterion $criterion, LanguageFilter $languageFilter): array
{
$criterion->value = (array) $criterion->value;
$searchFields = $this->getSearchFields($criterion);
$isLikeOperator = $criterion->operator === Operator::LIKE;

if (count($searchFields) === 0) {
throw new InvalidArgumentException(
'$criterion->target',
"No searchable fields found for the given criterion target '{$criterion->target}'.",
);
}

$query = new BoolQuery();
foreach ($searchFields as $name => $fieldType) {
/**
* @var string $value
*/
foreach ($criterion->value as $value) {
if ($isLikeOperator) {
$query->addShould(new PrefixQuery($name, $value));
} else {
$query->addShould(new TermQuery($name, $value));
}
}
}

return $query->toArray();
}
}
39 changes: 39 additions & 0 deletions bundle/Core/Search/Elasticsearch/QueryDSL/PrefixQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Netgen\TagsBundle\Core\Search\Elasticsearch\QueryDSL;

use Ibexa\Elasticsearch\ElasticSearch\QueryDSL\Query;

final class PrefixQuery implements Query
{
public function __construct(
private ?string $field = null,
private ?string $value = null,
) {
}

public function withField(string $field): self
{
$this->field = $field;

return $this;
}

public function withValue(string $value): self
{
$this->value = $value;

return $this;
}

public function toArray(): array
{
return [
'prefix' => [
$this->field => $this->value,
],
];
}
}
4 changes: 4 additions & 0 deletions bundle/DependencyInjection/NetgenTagsExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ public function load(array $configs, ContainerBuilder $container): void
$loader->load('search/legacy.yaml');
}

if (array_key_exists('IbexaElasticsearchBundle', $activatedBundles)) {
$loader->load('search/elasticsearch.yaml');
}

$this->processSemanticConfig($container, $config);
}

Expand Down
22 changes: 22 additions & 0 deletions bundle/Resources/config/search/elasticsearch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
services:
netgen_tags.search.elasticsearch.query.common.criterion_visitor.tag_id:
class: Netgen\TagsBundle\Core\Search\Elasticsearch\Query\Common\CriterionVisitor\Tags\TagId
arguments:
- "@Ibexa\\Core\\Search\\Common\\FieldNameResolver"
- "@Ibexa\\Contracts\\Core\\Persistence\\Content\\Type\\Handler"
- "eztags"
- "tag_ids"
tags:
- { name: ibexa.search.elasticsearch.query.content.criterion.visitor }
- { name: ibexa.search.elasticsearch.query.location.criterion.visitor }

netgen_tags.search.elasticsearch.query.common.criterion_visitor.tag_keyword:
class: Netgen\TagsBundle\Core\Search\Elasticsearch\Query\Common\CriterionVisitor\Tags\TagKeyword
arguments:
- "@Ibexa\\Core\\Search\\Common\\FieldNameResolver"
- "@Ibexa\\Contracts\\Core\\Persistence\\Content\\Type\\Handler"
- "eztags"
- "tag_keywords"
tags:
- { name: ibexa.search.elasticsearch.query.content.criterion.visitor }
- { name: ibexa.search.elasticsearch.query.location.criterion.visitor }
10 changes: 10 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ parameters:
checkMissingIterableValueType: false
checkGenericClassInNonGenericObjectType: false

excludePaths:
# The following two paths are excluded since PHPStan crashes with "unknown interface" error
# which cannot be excluded with ignoreErrors config
- bundle/Core/Search/Elasticsearch/Query/Common/CriterionVisitor/Tags.php
- bundle/Core/Search/Elasticsearch/QueryDSL/PrefixQuery.php

ignoreErrors:
-
message: '#Else branch is unreachable because ternary operator condition is always true.#'
Expand All @@ -30,3 +36,7 @@ parameters:
- "#Casting to int something that's already int.#"
- '#should be contravariant with parameter#'
- '#should be covariant with return type#'

# Errors caused by Ibexa Elasticsearch
- '#Ibexa\\Contracts\\Elasticsearch#'
- '#Ibexa\\Elasticsearch#'

0 comments on commit d453fa1

Please sign in to comment.