QueryComponentFactory   A
last analyzed

Complexity

Total Complexity 27

Size/Duplication

Total Lines 298
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 85
c 0
b 0
f 0
dl 0
loc 298
ccs 80
cts 80
cp 1
rs 10
wmc 27

16 Methods

Rating   Name   Duplication   Size   Complexity  
A getHelper() 0 3 1
A setClientQuery() 0 5 1
A getClientQuery() 0 3 1
A setQuery() 0 5 1
A setIndex() 0 5 1
A getQueryArray() 0 3 1
A getIndex() 0 3 1
A setQueryArray() 0 5 1
A getQuery() 0 3 1
A setHelper() 0 5 1
A getBuildTerm() 0 9 2
A buildSpellcheck() 0 13 2
A buildTerms() 0 20 4
A isFuzzy() 0 8 3
A buildQuery() 0 24 3
A escapeSearch() 0 20 3
1
<?php
2
/**
3
 * class QueryComponentFactory|Firesphere\SolrSearch\Factories\QueryComponentFactory Build a Query component
4
 *
5
 * @package Firesphere\Solr\Search
6
 * @author Simon `Firesphere` Erkelens; Marco `Sheepy` Hermo
7
 * @copyright Copyright (c) 2018 - now() Firesphere & Sheepy
8
 */
9
10
namespace Firesphere\SolrSearch\Factories;
11
12
use Firesphere\SolrSearch\Indexes\BaseIndex;
13
use Firesphere\SolrSearch\Queries\BaseQuery;
14
use Firesphere\SolrSearch\Services\SolrCoreService;
15
use Firesphere\SolrSearch\Traits\QueryComponentBoostTrait;
16
use Firesphere\SolrSearch\Traits\QueryComponentFacetTrait;
17
use Firesphere\SolrSearch\Traits\QueryComponentFilterTrait;
18
use Solarium\Core\Query\Helper;
19
use Solarium\QueryType\Select\Query\Query;
20
21
/**
22
 * Class QueryComponentFactory
23
 *
24
 * Build a query component for each available build part
25
 *
26
 * @package Firesphere\Solr\Search
27
 */
28
class QueryComponentFactory
29
{
30
    use QueryComponentFilterTrait;
31
    use QueryComponentBoostTrait;
32
    use QueryComponentFacetTrait;
33
34
    /**
35
     * Default fields that should always be added
36
     *
37
     * @var array
38
     */
39
    const DEFAULT_FIELDS = [
40
        SolrCoreService::ID_FIELD,
41
        SolrCoreService::CLASS_ID_FIELD,
42
        SolrCoreService::CLASSNAME,
43
    ];
44
45
    /**
46
     * @var array Build methods to run
47
     */
48
    protected static $builds = [
49
        'Terms',
50
        'ViewFilter',
51
        'ClassFilter',
52
        'Filters',
53
        'Excludes',
54
        'QueryFacets',
55
        'AndFacetFilterQuery',
56
        'OrFacetFilterQuery',
57
        'Spellcheck',
58
    ];
59
    /**
60
     * @var BaseQuery BaseQuery that needs to be executed
61
     */
62
    protected $query;
63
    /**
64
     * @var Helper Helper to escape the query terms properly
65
     */
66
    protected $helper;
67
    /**
68
     * @var array Resulting query parts as an array
69
     */
70
    protected $queryArray = [];
71
    /**
72
     * @var BaseIndex Index to query
73
     */
74
    protected $index;
75
76
    /**
77
     * Build the full query
78
     *
79
     * @return Query
80
     */
81 9
    public function buildQuery(): Query
82
    {
83 9
        foreach (static::$builds as $build) {
84 9
            $method = sprintf('build%s', $build);
85 9
            $this->$method();
86
        }
87
        // Set the start
88 9
        $this->clientQuery->setStart($this->query->getStart());
89
        // Double the rows in case something has been deleted, but not from Solr
90 9
        $this->clientQuery->setRows($this->query->getRows() * 2);
91
        // Add highlighting before adding boosting
92 9
        $this->clientQuery->getHighlighting()->setFields($this->query->getHighlight());
93
        // Add boosting
94 9
        $this->buildBoosts();
95
96
        // Filter out the fields we want to see if they're set
97 9
        $fields = $this->query->getFields();
98 9
        if (count($fields)) {
99
            // We _ALWAYS_ need the ClassName for getting the DataObjects back
100 1
            $fields = array_merge(static::DEFAULT_FIELDS, $fields);
101 1
            $this->clientQuery->setFields($fields);
102
        }
103
104 9
        return $this->clientQuery;
105
    }
106
107
    /**
108
     * Get the base query
109
     *
110
     * @return BaseQuery
111
     */
112 1
    public function getQuery(): BaseQuery
113
    {
114 1
        return $this->query;
115
    }
116
117
    /**
118
     * Set the base query
119
     *
120
     * @param BaseQuery $query
121
     * @return self
122
     */
123 9
    public function setQuery(BaseQuery $query): self
124
    {
125 9
        $this->query = $query;
126
127 9
        return $this;
128
    }
129
130
    /**
131
     * Get the array of terms used to query Solr
132
     *
133
     * @return array
134
     */
135 9
    public function getQueryArray(): array
136
    {
137 9
        return array_merge($this->queryArray, $this->boostTerms);
138
    }
139
140
    /**
141
     * Set the array of queries that are sent to Solr
142
     *
143
     * @param array $queryArray
144
     * @return self
145
     */
146 1
    public function setQueryArray(array $queryArray): self
147
    {
148 1
        $this->queryArray = $queryArray;
149
150 1
        return $this;
151
    }
152
153
    /**
154
     * Get the client Query components
155
     *
156
     * @return Query
157
     */
158 1
    public function getClientQuery(): Query
159
    {
160 1
        return $this->clientQuery;
161
    }
162
163
    /**
164
     * Set a custom Client Query object
165
     *
166
     * @param Query $clientQuery
167
     * @return self
168
     */
169 9
    public function setClientQuery(Query $clientQuery): self
170
    {
171 9
        $this->clientQuery = $clientQuery;
172
173 9
        return $this;
174
    }
175
176
    /**
177
     * Get the query helper
178
     *
179
     * @return Helper
180
     */
181 1
    public function getHelper(): Helper
182
    {
183 1
        return $this->helper;
184
    }
185
186
    /**
187
     * Set the Helper
188
     *
189
     * @param Helper $helper
190
     * @return self
191
     */
192 8
    public function setHelper(Helper $helper): self
193
    {
194 8
        $this->helper = $helper;
195
196 8
        return $this;
197
    }
198
199
    /**
200
     * Get the BaseIndex
201
     *
202
     * @return BaseIndex
203
     */
204 2
    public function getIndex(): BaseIndex
205
    {
206 2
        return $this->index;
207
    }
208
209
    /**
210
     * Set a BaseIndex
211
     *
212
     * @param BaseIndex $index
213
     * @return self
214
     */
215 11
    public function setIndex(BaseIndex $index): self
216
    {
217 11
        $this->index = $index;
218
219 11
        return $this;
220
    }
221
222
    /**
223
     * Build the terms and boost terms
224
     *
225
     * @return void
226
     */
227 9
    protected function buildTerms(): void
228
    {
229 9
        $terms = $this->query->getTerms();
230 9
        $boostTerms = $this->getBoostTerms();
231
232 9
        foreach ($terms as $search) {
233 6
            $term = $this->getBuildTerm($search);
234 6
            $postfix = $this->isFuzzy($search);
235
            // We can add the same term multiple times with different boosts
236
            // Not ideal, but it might happen, so let's add the term itself only once
237 6
            if (!in_array($term, $this->queryArray, true)) {
238 5
                $this->queryArray[] = $term . $postfix;
239
            }
240
            // If boosting is set, add the fields to boost
241 6
            if ($search['boost'] > 1) {
242 6
                $boostTerms = $this->buildQueryBoost($search, $term, $boostTerms);
243
            }
244
        }
245
        // Clean up the boost terms, remove doubles
246 9
        $this->setBoostTerms(array_values(array_unique($boostTerms)));
247 9
    }
248
249
    /**
250
     * Get the escaped search string, or, if empty, a global search
251
     *
252
     * @param array $search
253
     * @return string
254
     */
255 6
    protected function getBuildTerm($search)
256
    {
257 6
        $term = $search['text'];
258 6
        $term = $this->escapeSearch($term);
259 6
        if ($term === '') {
260 1
            $term = '*:*';
261
        }
262
263 6
        return $term;
264
    }
265
266
    /**
267
     * Escape the search query
268
     *
269
     * @param string $searchTerm
270
     * @return string
271
     */
272 7
    public function escapeSearch($searchTerm): string
273
    {
274 7
        $term = [];
275
        // Escape special characters where needed. Except for quoted parts, those should be phrased
276 7
        preg_match_all('/"[^"]*"|\S+/', $searchTerm, $parts);
277 7
        foreach ($parts[0] as $part) {
278 7
            $escaped = $this->helper->escapeTerm($part);
279
            // As we split the parts, everything with two quotes is a phrase
280
            // We need however, to strip out double quoting
281 7
            if (substr_count($part, '"') === 2) {
282
                // Strip all double quotes out for the phrase.
283
                // @todo make this less clunky
284
                // @todo add useful tests for this
285 1
                $part = str_replace('"', '', $part);
286 1
                $escaped = $this->helper->escapePhrase($part);
287
            }
288 7
            $term[] = $escaped;
289
        }
290
291 7
        return implode(' ', $term);
292
    }
293
294
    /**
295
     * If the search is fuzzy, add fuzzyness
296
     *
297
     * @param $search
298
     * @return string
299
     */
300 6
    protected function isFuzzy($search): string
301
    {
302
        // When doing fuzzy search, postfix, otherwise, don't
303 6
        if ($search['fuzzy']) {
304 1
            return '~' . (is_numeric($search['fuzzy']) ? $search['fuzzy'] : '');
305
        }
306
307 6
        return '';
308
    }
309
310
    /**
311
     * Add spellcheck elements
312
     */
313 9
    protected function buildSpellcheck(): void
314
    {
315
        // Assuming the first term is the term entered
316 9
        $queryString = implode(' ', $this->queryArray);
317
        // Arbitrarily limit to 5 if the config isn't set
318 9
        $count = BaseIndex::config()->get('spellcheckCount') ?: 5;
319 9
        $spellcheck = $this->clientQuery->getSpellcheck();
320 9
        $spellcheck->setQuery($queryString);
321 9
        $spellcheck->setCount($count);
322 9
        $spellcheck->setBuild(true);
323 9
        $spellcheck->setCollate(true);
324 9
        $spellcheck->setExtendedResults(true);
325 9
        $spellcheck->setCollateExtendedResults(true);
326 9
    }
327
}
328