Completed
Pull Request — master (#348)
by
unknown
10:14
created

Search   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 770
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
wmc 56
lcom 1
cbo 10
dl 0
loc 770
rs 5.35
c 0
b 0
f 0

How to fix   Complexity   

Complex Class

Complex classes like Search often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Search, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the ONGR package.
5
 *
6
 * (c) NFQ Technologies UAB <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace ONGR\ElasticsearchDSL;
15
16
use ONGR\ElasticsearchDSL\Aggregation\AbstractAggregation;
17
use ONGR\ElasticsearchDSL\Highlight\Highlight;
18
use ONGR\ElasticsearchDSL\InnerHit\NestedInnerHit;
19
use ONGR\ElasticsearchDSL\Query\Compound\BoolQuery;
20
use ONGR\ElasticsearchDSL\SearchEndpoint\AbstractSearchEndpoint;
21
use ONGR\ElasticsearchDSL\SearchEndpoint\AggregationsEndpoint;
22
use ONGR\ElasticsearchDSL\SearchEndpoint\HighlightEndpoint;
23
use ONGR\ElasticsearchDSL\SearchEndpoint\InnerHitsEndpoint;
24
use ONGR\ElasticsearchDSL\SearchEndpoint\PostFilterEndpoint;
25
use ONGR\ElasticsearchDSL\SearchEndpoint\QueryEndpoint;
26
use ONGR\ElasticsearchDSL\SearchEndpoint\SearchEndpointFactory;
27
use ONGR\ElasticsearchDSL\SearchEndpoint\SearchEndpointInterface;
28
use ONGR\ElasticsearchDSL\SearchEndpoint\SortEndpoint;
29
use ONGR\ElasticsearchDSL\Serializer\Normalizer\CustomReferencedNormalizer;
30
use ONGR\ElasticsearchDSL\Serializer\OrderedSerializer;
31
use Symfony\Component\Serializer\Normalizer\CustomNormalizer;
32
use ONGR\ElasticsearchDSL\SearchEndpoint\SuggestEndpoint;
33
34
/**
35
 * Search object that can be executed by a manager.
36
 */
37
class Search
38
{
39
    /**
40
     * If you don’t need to track the total number of hits at all you can improve
41
     * query times by setting this option to false. Defaults to true.
42
     */
43
    private ?bool $trackTotalHits = null;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected '?', expecting T_FUNCTION or T_CONST
Loading history...
44
45
    /**
46
     * To retrieve hits from a certain offset. Defaults to 0.
47
     *
48
     */
49
    private ?int $from = null;
50
51
    /**
52
     * The number of hits to return. Defaults to 10. If you do not care about getting some
53
     * hits back but only about the number of matches and/or aggregations, setting the value
54
     * to 0 will help performance.
55
     */
56
    private ?int $size = null;
57
58
    /**
59
     * Allows to control how the _source field is returned with every hit. By default
60
     * operations return the contents of the _source field unless you have used the
61
     * stored_fields parameter or if the _source field is disabled.
62
     */
63
    private ?bool $source = null;
64
65
    /**
66
     * Allows to selectively load specific stored fields for each document represented by a search hit.
67
     */
68
    private ?array $storedFields = null;
69
70
    /**
71
     * Allows to return a script evaluation (based on different fields) for each hit.
72
     * Script fields can work on fields that are not stored, and allow to return custom
73
     * values to be returned (the evaluated value of the script). Script fields can
74
     * also access the actual _source document indexed and extract specific elements
75
     * to be returned from it (can be an "object" type).
76
     */
77
    private ?array $scriptFields = null;
78
79
    /**
80
     * Allows to return the doc value representation of a field for each hit. Doc value
81
     * fields can work on fields that are not stored. Note that if the fields parameter
82
     * specifies fields without docvalues it will try to load the value from the fielddata
83
     * cache causing the terms for that field to be loaded to memory (cached), which will
84
     * result in more memory consumption.
85
     */
86
    private ?array $docValueFields = null;
87
88
    /**
89
     * Enables explanation for each hit on how its score was computed.
90
     */
91
    private ?bool $explain = null;
92
93
    /**
94
     * Returns a version for each search hit.
95
     */
96
    private ?bool $version = null;
97
98
    /**
99
     * Allows to configure different boost level per index when searching across more
100
     * than one indices. This is very handy when hits coming from one index matter more
101
     * than hits coming from another index (think social graph where each user has an index).
102
     */
103
    private ?array $indicesBoost = null;
104
105
    /**
106
     * Exclude documents which have a _score less than the minimum specified in min_score.
107
     */
108
    private ?int $minScore = null;
109
110
    /**
111
     * Pagination of results can be done by using the from and size but the cost becomes
112
     * prohibitive when the deep pagination is reached. The index.max_result_window which
113
     * defaults to 10,000 is a safeguard, search requests take heap memory and time
114
     * proportional to from + size. The Scroll api is recommended for efficient deep
115
     * scrolling but scroll contexts are costly and it is not recommended to use it for
116
     * real time user requests. The search_after parameter circumvents this problem by
117
     * providing a live cursor. The idea is to use the results from the previous page to
118
     * help the retrieval of the next page.
119
     */
120
    private ?array $searchAfter = null;
121
122
    /**
123
     * URI parameters alongside Request body search.
124
     *
125
     * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/search-uri-request.html
126
     */
127
    private array $uriParams = [];
128
129
    /**
130
     * While a search request returns a single “page” of results, the scroll API can be used to retrieve
131
     * large numbers of results (or even all results) from a single search request, in much the same way
132
     * as you would use a cursor on a traditional database. Scrolling is not intended for real time user
133
     * requests, but rather for processing large amounts of data, e.g. in order to reindex the contents
134
     * of one index into a new index with a different configuration.
135
     */
136
    private ?string $scroll = null;
137
138
    private static ?OrderedSerializer $serializer = null;
139
140
    private array $endpoints = [];
141
142
    public function __construct()
143
    {
144
        $this->initializeSerializer();
145
    }
146
147
    public function __wakeup()
148
    {
149
        $this->initializeSerializer();
150
    }
151
152
    private function initializeSerializer(): void
153
    {
154
        if (static::$serializer === null) {
155
            static::$serializer = new OrderedSerializer(
156
                [
157
                    new CustomReferencedNormalizer(),
158
                    new CustomNormalizer(),
159
                ]
160
            );
161
        }
162
    }
163
164
    public function destroyEndpoint(string $type): void
165
    {
166
        unset($this->endpoints[$type]);
167
    }
168
169
    public function addQuery(BuilderInterface $query, string $boolType = BoolQuery::MUST, ?string $key = null): static
170
    {
171
        $endpoint = $this->getEndpoint(QueryEndpoint::NAME);
172
        $endpoint->addToBool($query, $boolType, $key);
173
174
        return $this;
175
    }
176
177
    private function getEndpoint(string $type): SearchEndpointInterface
178
    {
179
        if (!array_key_exists($type, $this->endpoints)) {
180
            $this->endpoints[$type] = SearchEndpointFactory::get($type);
181
        }
182
183
        return $this->endpoints[$type];
184
    }
185
186
    public function getQueries(): BoolQuery
187
    {
188
        $endpoint = $this->getEndpoint(QueryEndpoint::NAME);
189
190
        return $endpoint->getBool();
191
    }
192
193
    public function setQueryParameters(array $parameters): static
194
    {
195
        $this->setEndpointParameters(QueryEndpoint::NAME, $parameters);
196
197
        return $this;
198
    }
199
200
    public function setEndpointParameters(string $endpointName, array $parameters): static
201
    {
202
        /** @var AbstractSearchEndpoint $endpoint */
203
        $endpoint = $this->getEndpoint($endpointName);
204
        $endpoint->setParameters($parameters);
205
206
        return $this;
207
    }
208
209
    public function addPostFilter(
210
        BuilderInterface $filter,
211
        string $boolType = BoolQuery::MUST,
212
        ?string $key = null
213
    ): static {
214
        $this
215
            ->getEndpoint(PostFilterEndpoint::NAME)
216
            ->addToBool($filter, $boolType, $key);
217
218
        return $this;
219
    }
220
221
    public function getPostFilters(): BoolQuery
222
    {
223
        $endpoint = $this->getEndpoint(PostFilterEndpoint::NAME);
224
225
        return $endpoint->getBool();
226
    }
227
228
    public function setPostFilterParameters(array $parameters): static
229
    {
230
        $this->setEndpointParameters(PostFilterEndpoint::NAME, $parameters);
231
232
        return $this;
233
    }
234
235
    public function addAggregation(AbstractAggregation $aggregation): static
236
    {
237
        $this->getEndpoint(AggregationsEndpoint::NAME)->add($aggregation, $aggregation->getName());
238
239
        return $this;
240
    }
241
242
    public function getAggregations(): array
243
    {
244
        return $this->getEndpoint(AggregationsEndpoint::NAME)->getAll();
245
    }
246
247
    public function addInnerHit(NestedInnerHit $innerHit): static
248
    {
249
        $this->getEndpoint(InnerHitsEndpoint::NAME)->add($innerHit, $innerHit->getName());
250
251
        return $this;
252
    }
253
254
    public function getInnerHits(): array
255
    {
256
        return $this->getEndpoint(InnerHitsEndpoint::NAME)->getAll();
257
    }
258
259
    public function addSort(BuilderInterface $sort): static
260
    {
261
        $this->getEndpoint(SortEndpoint::NAME)->add($sort);
262
263
        return $this;
264
    }
265
266
    public function getSorts(): array
267
    {
268
        return $this->getEndpoint(SortEndpoint::NAME)->getAll();
269
    }
270
271
    public function addHighlight(Highlight $highlight): static
272
    {
273
        $this->getEndpoint(HighlightEndpoint::NAME)->add($highlight);
274
275
        return $this;
276
    }
277
278
    public function getHighlights(): BuilderInterface
279
    {
280
        /** @var HighlightEndpoint $highlightEndpoint */
281
        $highlightEndpoint = $this->getEndpoint(HighlightEndpoint::NAME);
282
283
        return $highlightEndpoint->getHighlight();
284
    }
285
286
    public function addSuggest(NamedBuilderInterface $suggest): static
287
    {
288
        $this->getEndpoint(SuggestEndpoint::NAME)->add($suggest, $suggest->getName());
289
290
        return $this;
291
    }
292
293
    public function getSuggests(): array
294
    {
295
        return $this->getEndpoint(SuggestEndpoint::NAME)->getAll();
296
    }
297
298
    public function getFrom(): ?int
299
    {
300
        return $this->from;
301
    }
302
303
    public function setFrom(?int $from): static
304
    {
305
        $this->from = $from;
306
307
        return $this;
308
    }
309
310
    public function isTrackTotalHits(): bool
311
    {
312
        return $this->trackTotalHits;
313
    }
314
315
    public function setTrackTotalHits(bool $trackTotalHits): static
316
    {
317
        $this->trackTotalHits = $trackTotalHits;
318
319
        return $this;
320
    }
321
322
    public function getSize(): ?int
323
    {
324
        return $this->size;
325
    }
326
327
    public function setSize(?int $size): static
328
    {
329
        $this->size = $size;
330
331
        return $this;
332
    }
333
334
    public function isSource(): ?bool
335
    {
336
        return $this->source;
337
    }
338
339
    public function setSource(?bool $source): static
340
    {
341
        $this->source = $source;
342
343
        return $this;
344
    }
345
346
    public function getStoredFields(): ?array
347
    {
348
        return $this->storedFields;
349
    }
350
351
    public function setStoredFields(?array $storedFields): static
352
    {
353
        $this->storedFields = $storedFields;
354
355
        return $this;
356
    }
357
358
    public function getScriptFields(): ?array
359
    {
360
        return $this->scriptFields;
361
    }
362
363
    public function setScriptFields(?array $scriptFields): static
364
    {
365
        $this->scriptFields = $scriptFields;
366
367
        return $this;
368
    }
369
370
    public function getDocValueFields(): ?array
371
    {
372
        return $this->docValueFields;
373
    }
374
375
    public function setDocValueFields(?array $docValueFields): static
376
    {
377
        $this->docValueFields = $docValueFields;
378
379
        return $this;
380
    }
381
382
    public function isExplain(): ?bool
383
    {
384
        return $this->explain;
385
    }
386
387
    public function setExplain(?bool $explain): static
388
    {
389
        $this->explain = $explain;
390
391
        return $this;
392
    }
393
394
    public function isVersion(): ?bool
395
    {
396
        return $this->version;
397
    }
398
399
    public function setVersion(?bool $version): static
400
    {
401
        $this->version = $version;
402
403
        return $this;
404
    }
405
406
    public function getIndicesBoost(): ?array
407
    {
408
        return $this->indicesBoost;
409
    }
410
411
    public function setIndicesBoost(?array $indicesBoost): static
412
    {
413
        $this->indicesBoost = $indicesBoost;
414
415
        return $this;
416
    }
417
418
    public function getMinScore(): ?int
419
    {
420
        return $this->minScore;
421
    }
422
423
    public function setMinScore(?int $minScore): static
424
    {
425
        $this->minScore = $minScore;
426
427
        return $this;
428
    }
429
430
    public function getSearchAfter(): ?array
431
    {
432
        return $this->searchAfter;
433
    }
434
435
    public function setSearchAfter(?array $searchAfter): static
436
    {
437
        $this->searchAfter = $searchAfter;
438
439
        return $this;
440
    }
441
442
    public function getScroll(): ?string
443
    {
444
        return $this->scroll;
445
    }
446
447
    public function setScroll(?string $scroll = '5m'): static
448
    {
449
        $this->scroll = $scroll;
450
451
        $this->addUriParam('scroll', $this->scroll);
452
453
        return $this;
454
    }
455
456
    public function addUriParam(string $name, string|array|bool $value): static
457
    {
458
        if (in_array(
459
            $name,
460
            [
461
                'q',
462
                'df',
463
                'analyzer',
464
                'analyze_wildcard',
465
                'default_operator',
466
                'lenient',
467
                'explain',
468
                '_source',
469
                '_source_exclude',
470
                '_source_include',
471
                'stored_fields',
472
                'sort',
473
                'track_scores',
474
                'timeout',
475
                'terminate_after',
476
                'from',
477
                'size',
478
                'search_type',
479
                'scroll',
480
                'allow_no_indices',
481
                'ignore_unavailable',
482
                'typed_keys',
483
                'pre_filter_shard_size',
484
                'ignore_unavailable',
485
            ]
486
        )) {
487
            $this->uriParams[$name] = $value;
488
        } else {
489
            throw new \InvalidArgumentException(sprintf('Parameter %s is not supported.', $value));
490
        }
491
492
        return $this;
493
    }
494
495
    public function getUriParams(): array
496
    {
497
        return $this->uriParams;
498
    }
499
500
    public function toArray(): array
501
    {
502
        $output = array_filter(static::$serializer->normalize($this->endpoints));
503
504
        $params = [
505
            'from' => 'from',
506
            'size' => 'size',
507
            'source' => '_source',
508
            'storedFields' => 'stored_fields',
509
            'scriptFields' => 'script_fields',
510
            'docValueFields' => 'docvalue_fields',
511
            'explain' => 'explain',
512
            'version' => 'version',
513
            'indicesBoost' => 'indices_boost',
514
            'minScore' => 'min_score',
515
            'searchAfter' => 'search_after',
516
            'trackTotalHits' => 'track_total_hits',
517
        ];
518
519
        foreach ($params as $field => $param) {
520
            if ($this->$field !== null) {
521
                $output[$param] = $this->$field;
522
            }
523
        }
524
525
        return $output;
526
    }
527
}
528