Passed
Push — trunk ( 74bc07...45b10d )
by Christian
11:29 queued 12s
created

ElasticsearchHelper::logAndThrowException()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 2
nop 1
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Elasticsearch\Framework;
4
5
use OpenSearch\Client;
6
use OpenSearchDSL\Query\Compound\BoolQuery;
7
use OpenSearchDSL\Query\FullText\MatchQuery;
8
use OpenSearchDSL\Search;
9
use Psr\Log\LoggerInterface;
10
use Shopware\Core\Framework\Context;
11
use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
12
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
13
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
14
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
15
use Shopware\Elasticsearch\Exception\ServerNotAvailableException;
16
use Shopware\Elasticsearch\Exception\UnsupportedElasticsearchDefinitionException;
17
use Shopware\Elasticsearch\Framework\DataAbstractionLayer\CriteriaParser;
18
19
/**
20
 * @package core
21
 */
22
class ElasticsearchHelper
23
{
24
    // max for default configuration
25
    public const MAX_SIZE_VALUE = 10000;
26
27
    private Client $client;
28
29
    private ElasticsearchRegistry $registry;
30
31
    private CriteriaParser $parser;
32
33
    private bool $searchEnabled;
34
35
    private bool $indexingEnabled;
36
37
    private string $environment;
38
39
    private LoggerInterface $logger;
40
41
    private string $prefix;
42
43
    private bool $throwException;
44
45
    /**
46
     * @internal
47
     */
48
    public function __construct(
49
        string $environment,
50
        bool $searchEnabled,
51
        bool $indexingEnabled,
52
        string $prefix,
53
        bool $throwException,
54
        Client $client,
55
        ElasticsearchRegistry $registry,
56
        CriteriaParser $parser,
57
        LoggerInterface $logger
58
    ) {
59
        $this->client = $client;
60
        $this->registry = $registry;
61
        $this->parser = $parser;
62
        $this->searchEnabled = $searchEnabled;
63
        $this->indexingEnabled = $indexingEnabled;
64
        $this->environment = $environment;
65
        $this->logger = $logger;
66
        $this->prefix = $prefix;
67
        $this->throwException = $throwException;
68
    }
69
70
    public function logAndThrowException(\Throwable $exception): bool
71
    {
72
        $this->logger->critical($exception->getMessage());
73
74
        if ($this->environment === 'test' || $this->throwException) {
75
            throw $exception;
76
        }
77
78
        return false;
79
    }
80
81
    /**
82
     * Created the index alias
83
     */
84
    public function getIndexName(EntityDefinition $definition, string $languageId): string
85
    {
86
        return $this->prefix . '_' . $definition->getEntityName() . '_' . $languageId;
87
    }
88
89
    public function allowIndexing(): bool
90
    {
91
        if (!$this->indexingEnabled) {
92
            return false;
93
        }
94
95
        if (!$this->client->ping()) {
96
            return $this->logAndThrowException(new ServerNotAvailableException());
97
        }
98
99
        return true;
100
    }
101
102
    /**
103
     * Validates if it is allowed do execute the search request over elasticsearch
104
     */
105
    public function allowSearch(EntityDefinition $definition, Context $context, Criteria $criteria): bool
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

105
    public function allowSearch(EntityDefinition $definition, /** @scrutinizer ignore-unused */ Context $context, Criteria $criteria): bool

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
106
    {
107
        if (!$this->searchEnabled) {
108
            return false;
109
        }
110
111
        if (!$this->isSupported($definition)) {
112
            return false;
113
        }
114
115
        return $criteria->hasState(Criteria::STATE_ELASTICSEARCH_AWARE);
116
    }
117
118
    public function handleIds(EntityDefinition $definition, Criteria $criteria, Search $search, Context $context): void
119
    {
120
        $ids = $criteria->getIds();
121
122
        if (empty($ids)) {
123
            return;
124
        }
125
126
        /** @var list<string> $ids */
127
        $ids = array_values($ids);
0 ignored issues
show
Bug introduced by
$ids of type Shopware\Elasticsearch\Framework\list is incompatible with the type array expected by parameter $array of array_values(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

127
        $ids = array_values(/** @scrutinizer ignore-type */ $ids);
Loading history...
128
129
        $query = $this->parser->parseFilter(
130
            new EqualsAnyFilter('id', $ids),
0 ignored issues
show
Bug introduced by
$ids of type Shopware\Elasticsearch\Framework\list is incompatible with the type array expected by parameter $value of Shopware\Core\Framework\...nyFilter::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

130
            new EqualsAnyFilter('id', /** @scrutinizer ignore-type */ $ids),
Loading history...
131
            $definition,
132
            $definition->getEntityName(),
133
            $context
134
        );
135
136
        $search->addQuery($query, BoolQuery::FILTER);
137
    }
138
139
    public function addFilters(EntityDefinition $definition, Criteria $criteria, Search $search, Context $context): void
140
    {
141
        $filters = $criteria->getFilters();
142
        if (empty($filters)) {
143
            return;
144
        }
145
146
        $query = $this->parser->parseFilter(
147
            new MultiFilter(MultiFilter::CONNECTION_AND, $filters),
148
            $definition,
149
            $definition->getEntityName(),
150
            $context
151
        );
152
153
        $search->addQuery($query, BoolQuery::FILTER);
154
    }
155
156
    public function addPostFilters(EntityDefinition $definition, Criteria $criteria, Search $search, Context $context): void
157
    {
158
        $postFilters = $criteria->getPostFilters();
159
        if (empty($postFilters)) {
160
            return;
161
        }
162
163
        $query = $this->parser->parseFilter(
164
            new MultiFilter(MultiFilter::CONNECTION_AND, $postFilters),
165
            $definition,
166
            $definition->getEntityName(),
167
            $context
168
        );
169
170
        $search->addPostFilter($query, BoolQuery::FILTER);
171
    }
172
173
    public function addTerm(Criteria $criteria, Search $search, Context $context, EntityDefinition $definition): void
174
    {
175
        if (!$criteria->getTerm()) {
176
            return;
177
        }
178
179
        $esDefinition = $this->registry->get($definition->getEntityName());
180
181
        if (!$esDefinition) {
182
            throw new UnsupportedElasticsearchDefinitionException($definition->getEntityName());
183
        }
184
185
        $query = $esDefinition->buildTermQuery($context, $criteria);
186
187
        $search->addQuery($query);
188
    }
189
190
    public function addQueries(EntityDefinition $definition, Criteria $criteria, Search $search, Context $context): void
191
    {
192
        $queries = $criteria->getQueries();
193
        if (empty($queries)) {
194
            return;
195
        }
196
197
        $bool = new BoolQuery();
198
199
        foreach ($queries as $query) {
200
            $parsed = $this->parser->parseFilter($query->getQuery(), $definition, $definition->getEntityName(), $context);
201
202
            if ($parsed instanceof MatchQuery) {
203
                $score = (string) $query->getScore();
204
205
                $parsed->addParameter('boost', $score);
206
                $parsed->addParameter('fuzziness', '2');
207
            }
208
209
            $bool->add($parsed, BoolQuery::SHOULD);
210
        }
211
212
        $bool->addParameter('minimum_should_match', '1');
213
        $search->addQuery($bool);
214
    }
215
216
    public function addSortings(EntityDefinition $definition, Criteria $criteria, Search $search, Context $context): void
217
    {
218
        foreach ($criteria->getSorting() as $sorting) {
219
            $search->addSort(
220
                $this->parser->parseSorting($sorting, $definition, $context)
221
            );
222
        }
223
    }
224
225
    public function addAggregations(EntityDefinition $definition, Criteria $criteria, Search $search, Context $context): void
226
    {
227
        $aggregations = $criteria->getAggregations();
228
        if (empty($aggregations)) {
229
            return;
230
        }
231
232
        foreach ($aggregations as $aggregation) {
233
            $agg = $this->parser->parseAggregation($aggregation, $definition, $context);
234
235
            if (!$agg) {
236
                continue;
237
            }
238
239
            $search->addAggregation($agg);
240
        }
241
    }
242
243
    /**
244
     * Only used for unit tests because the container parameter bag is frozen and can not be changed at runtime.
245
     * Therefore this function can be used to test different behaviours
246
     *
247
     * @internal
248
     */
249
    public function setEnabled(bool $enabled): self
250
    {
251
        $this->searchEnabled = $enabled;
252
        $this->indexingEnabled = $enabled;
253
254
        return $this;
255
    }
256
257
    public function isSupported(EntityDefinition $definition): bool
258
    {
259
        $entityName = $definition->getEntityName();
260
261
        return $this->registry->has($entityName);
262
    }
263
}
264