Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement TagId and TagKeyword visitors for elasticsearch #157

Merged
merged 9 commits into from
Jul 28, 2023
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we cannot obviously add Ibexa Elastic Search to requirements to silence PHPStan here, we need to ignore these errors in phpstan.neon

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just ignore every error containing the string Ibexa\Contracts\Elasticsearch or Ibexa\Elasticsearch

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in a238f85, 2d2e4ed and ebb0e60

{
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#'
Loading