Passed
Push — main ( 5a4aff...d40937 )
by Simon
01:12
created

QueryBuilder::suggestList()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 13
nc 4
nop 0
dl 0
loc 20
rs 9.8333
c 0
b 0
f 0
1
<?php
2
3
namespace Firesphere\ElasticSearch\Queries\Builders;
4
5
use Firesphere\ElasticSearch\Indexes\ElasticIndex;
6
use Firesphere\ElasticSearch\Queries\ElasticQuery;
7
use Firesphere\SearchBackend\Indexes\CoreIndex;
8
use Firesphere\SearchBackend\Interfaces\QueryBuilderInterface;
9
use Firesphere\SearchBackend\Queries\BaseQuery;
10
11
class QueryBuilder implements QueryBuilderInterface
12
{
13
    /**
14
     * @var ElasticQuery
15
     */
16
    protected $query;
17
18
    /**
19
     * @var ElasticIndex
20
     */
21
    protected $index;
22
23
    /**
24
     * @param ElasticQuery $query
25
     * @param ElasticIndex $index
26
     * @return array
27
     */
28
    public static function buildQuery(BaseQuery $query, CoreIndex $index): array
29
    {
30
        $self = self::init($query, $index);
31
        $filters = $self->getFilters($index, $query);
32
        $terms = $self->getUserQuery($query); // There's always a term
33
        $terms = array_merge($terms, $filters);
34
35
        return [
36
            'index' => $index->getIndexName(),
37
            'from'  => $query->getStart(),
38
            'size'  => $query->getRows(),
39
            'body'  => [
40
                'query'     => [
41
                    'bool' => $terms,
42
                ],
43
                'highlight' => $self->getHighlighter(),
44
                'suggest'   => $self->suggestList()
45
            ]
46
        ];
47
    }
48
49
    /**
50
     * @param ElasticQuery $query
51
     * @param ElasticIndex $index
52
     * @return self
53
     */
54
    protected static function init(ElasticQuery $query, ElasticIndex $index): self
55
    {
56
        $self = new self();
57
        $self->setIndex($index);
58
        $self->setQuery($query);
59
60
        return $self;
61
    }
62
63
    /**
64
     * @param mixed $index
65
     */
66
    public function setIndex($index): void
67
    {
68
        $this->index = $index;
69
    }
70
71
    /**
72
     * @param mixed $query
73
     */
74
    public function setQuery($query): void
75
    {
76
        $this->query = $query;
77
    }
78
79
    /**
80
     * Build the `OR` and `AND` filters
81
     * @param ElasticIndex $index
82
     * @param ElasticQuery $query
83
     * @return array[]
84
     */
85
    private function getFilters(ElasticIndex $index, ElasticQuery $query): array
86
    {
87
        return [
88
            'filter' => [
89
                'bool' => [
90
                    'must'   => $this->getAndFilters($index, $query),
91
                    'should' => $this->getOrFilters($query)
92
                ],
93
            ]
94
        ];
95
    }
96
97
    /**
98
     * Required must-be filters if they're here.
99
     * @param ElasticIndex $index
100
     * @param ElasticQuery $query
101
     * @return array[]
102
     */
103
    private function getAndFilters(ElasticIndex $index, ElasticQuery $query): array
104
    {
105
        // Default,
106
        $filters = [
107
            [
108
                'terms' => [
109
                    'ViewStatus' => $index->getViewStatusFilter(),
110
                ]
111
            ]
112
        ];
113
        if (count($query->getFilters())) {
114
            foreach ($query->getFilters() as $key => $value) {
115
                $value = is_array($value) ?: [$value];
116
                $filters[] = ['terms' => [$key => $value]];
117
            }
118
        }
119
120
        return $filters;
121
    }
122
123
    /**
124
     * Create the "should" filter, that is OR instead of AND
125
     * @param ElasticQuery $query
126
     * @return array
127
     */
128
    private function getOrFilters(ElasticQuery $query): array
129
    {
130
        $filters = [];
131
        if (count($query->getOrFilters())) {
132
            foreach ($query->getOrFilters() as $key => $value) {
133
                $value = is_array($value) ?: [$value];
134
                $filters[] = ['terms' => [$key => $value]];
135
            }
136
        }
137
138
        return $filters;
139
    }
140
141
    /**
142
     * this allows for multiple search terms to be entered
143
     * @param ElasticQuery|BaseQuery $query
144
     * @return array
145
     */
146
    private function getUserQuery(ElasticQuery|BaseQuery $query): array
147
    {
148
        $q = [];
149
        $terms = $query->getTerms();
150
        $type = 'match';
151
        if (!count($terms)) {
152
            $type = 'wildcard';
153
            $terms = ['*'];
154
        }
155
        foreach ($terms as $term) {
156
            $q['must'][] = ['match' => ['_text' => $term['text']]];
157
            if ($type !== 'wildcard') {
158
                $q = $this->getFieldBoosting($term, $type, $q);
159
            }
160
        }
161
162
        return $q;
163
    }
164
165
    /**
166
     * @param mixed $term
167
     * @param string $type
168
     * @param array $q
169
     * @return array
170
     */
171
    private function getFieldBoosting(mixed $term, string $type, array $q): array
172
    {
173
        $shoulds = [];
174
        $queryBoosts = $this->query->getBoostedFields();
175
        if ($term['boost'] > 1 && count($term['fields'])) {
176
            foreach ($term['fields'] as $field) {
177
                $shoulds[] = $this->addShould($type, $field, $term['text'], $term['boost']);
178
            }
179
        }
180
        foreach ($queryBoosts as $field => $boost) {
181
            $shoulds[] = $this->addShould($type, $field, $term['text'], $boost);
182
        }
183
        if (count($shoulds)) {
184
            $q['should'] = $shoulds;
185
        }
186
187
        return $q;
188
    }
189
190
    /**
191
     * @param string $type
192
     * @param string $field
193
     * @param $text
194
     * @param int $boost
195
     * @return array
196
     */
197
    private function addShould(string $type, string $field, $text, int $boost): array
198
    {
199
        $should = [
200
            $type => [
201
                $field => [
202
                    'query' => $text,
203
                    'boost' => $boost
204
                ]
205
            ]
206
        ];
207
208
        return $should;
209
    }
210
211
    private function getHighlighter(): array
212
    {
213
        if ($this->query->isHighlight()) {
214
            $highlights = [];
215
            foreach ($this->index->getFulltextFields() as $field) {
216
                $highlights[$field] = ['type' => 'unified'];
217
            }
218
219
            return ['fields' => $highlights];
220
        }
221
222
        return [];
223
    }
224
225
    private function suggestList()
226
    {
227
        $terms = $this->query->getTerms();
228
        $suggest = [];
229
        $base = [
230
            'term' => ['field' => '_text']
231
        ];
232
        foreach ($terms as $j => $term) {
233
            $base['text'] = $term['text'];
234
            $suggest[$j . '-fullterm'] = $base;
235
            if (str_contains($term['text'], ' ')) {
236
                $termArray = explode(' ', $term['text']);
237
                foreach ($termArray as $i => $word) {
238
                    $base['text'] = $word;
239
                    $suggest[$i . '-partterm'] = $base;
240
                }
241
            }
242
        }
243
244
        return $suggest;
245
    }
246
}
247