Passed
Push — sheepy/introspection ( 17b395...901708 )
by Simon
08:33 queued 05:46
created

SearchResult   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 270
Duplicated Lines 0 %

Test Coverage

Coverage 96.94%

Importance

Changes 11
Bugs 1 Features 0
Metric Value
wmc 33
eloc 87
c 11
b 1
f 0
dl 0
loc 270
ccs 95
cts 98
cp 0.9694
rs 9.76

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 2
A getHighlightByID() 0 11 4
A createExcerpt() 0 8 1
A setSpellcheck() 0 13 4
A getMatches() 0 17 3
A isDataObject() 0 9 4
A createFacet() 0 15 2
A setCustomisedMatches() 0 5 1
A getPaginatedMatches() 0 16 1
A setFacets() 0 5 1
A setMatches() 0 12 2
A buildFacets() 0 13 3
A setCollatedSpellcheck() 0 8 3
A getClassFacets() 0 10 2
1
<?php
2
3
4
namespace Firesphere\SolrSearch\Results;
5
6
use Firesphere\SolrSearch\Indexes\BaseIndex;
7
use Firesphere\SolrSearch\Queries\BaseQuery;
8
use Firesphere\SolrSearch\Services\SolrCoreService;
9
use Firesphere\SolrSearch\Traits\SearchResultGetTrait;
10
use Firesphere\SolrSearch\Traits\SearchResultSetTrait;
11
use SilverStripe\Control\HTTPRequest;
12
use SilverStripe\ORM\ArrayList;
13
use SilverStripe\ORM\DataObject;
14
use SilverStripe\ORM\FieldType\DBField;
15
use SilverStripe\ORM\PaginatedList;
16
use SilverStripe\View\ArrayData;
17
use Solarium\Component\Result\Facet\Field;
18
use Solarium\Component\Result\FacetSet;
19
use Solarium\Component\Result\Spellcheck\Collation;
20
use Solarium\Component\Result\Spellcheck\Result as SpellcheckResult;
21
use Solarium\QueryType\Select\Result\Document;
22
use Solarium\QueryType\Select\Result\Result;
23
24
class SearchResult
25
{
26
    use SearchResultGetTrait;
27
    use SearchResultSetTrait;
28
    /**
29
     * @var BaseQuery
30
     */
31
    protected $query;
32
33
    /**
34
     * @var BaseIndex
35
     */
36
    protected $index;
37
38
    /**
39
     * @var ArrayList
40
     */
41
    protected $matches;
42
43
    /**
44
     * SearchResult constructor.
45
     * Funnily enough, the $result contains the actual results, and has methods for the other things.
46
     * See Solarium docs for this.
47
     *
48
     * @param Result $result
49
     * @param BaseQuery $query
50
     * @param BaseIndex $index
51
     */
52 4
    public function __construct(Result $result, BaseQuery $query, BaseIndex $index)
53
    {
54 4
        $this->index = $index;
55 4
        $this->query = $query;
56 4
        $this->setMatches($result->getDocuments())
57 4
            ->setFacets($result->getFacetSet())
58 4
            ->setHighlight($result->getHighlighting())
59 4
            ->setTotalItems($result->getNumFound());
60 4
        if ($query->hasSpellcheck()) {
61 4
            $this->setSpellcheck($result->getSpellcheck())
62 4
                ->setCollatedSpellcheck($result->getSpellcheck());
63
        }
64 4
    }
65
66
    /**
67
     * @param FacetSet|null $facets
68
     * @return $this
69
     */
70 4
    protected function setFacets($facets): self
71
    {
72 4
        $this->facets = $this->buildFacets($facets);
73
74 4
        return $this;
75
    }
76
77
    /**
78
     * Build the given list of key-value pairs in to a SilverStripe useable array
79
     * @param FacetSet|null $facets
80
     * @return ArrayData
81
     */
82 4
    protected function buildFacets($facets): ArrayData
83
    {
84 4
        $facetArray = [];
85 4
        if ($facets) {
86 4
            $facetTypes = $this->index->getFacetFields();
87
            // Loop all available facet fields by type
88 4
            foreach ($facetTypes as $class => $options) {
89 1
                $facetArray = $this->createFacet($facets, $options, $class, $facetArray);
90
            }
91
        }
92
93
        // Return an ArrayList of the results
94 4
        return ArrayData::create($facetArray);
95
    }
96
97
    /**
98
     * @param FacetSet $facets
99
     * @param array $options
100
     * @param string $class
101
     * @param array $facetArray
102
     * @return array
103
     */
104 1
    protected function createFacet($facets, $options, $class, array $facetArray): array
105
    {
106
        // Get the facets by its title
107
        /** @var Field $typeFacets */
108 1
        $typeFacets = $facets->getFacet('facet-' . $options['Title']);
109 1
        $values = $typeFacets->getValues();
110 1
        $results = ArrayList::create();
111
        // If there are values, get the items one by one and push them in to the list
112 1
        if (count($values)) {
113 1
            $this->getClassFacets($class, $values, $results);
114
        }
115
        // Put the results in to the array
116 1
        $facetArray[$options['Title']] = $results;
117
118 1
        return $facetArray;
119
    }
120
121
    /**
122
     * @param $class
123
     * @param array $values
124
     * @param ArrayList $results
125
     */
126 1
    protected function getClassFacets($class, array $values, &$results): void
127
    {
128 1
        $items = $class::get()->byIds(array_keys($values));
129 1
        foreach ($items as $item) {
130
            // Set the FacetCount value to be sorted on later
131 1
            $item->FacetCount = $values[$item->ID];
132 1
            $results->push($item);
133
        }
134
        // Sort the results by FacetCount
135 1
        $results = $results->sort(['FacetCount' => 'DESC', 'Title' => 'ASC',]);
136 1
    }
137
138
    /**
139
     * @param mixed $collatedSpellcheck
140
     * @return $this
141
     */
142 4
    public function setCollatedSpellcheck($collatedSpellcheck): self
143
    {
144
        /** @var Collation $collated */
145 4
        if ($collatedSpellcheck && ($collated = $collatedSpellcheck->getCollations())) {
146
            $this->collatedSpellcheck = $collated[0]->getQuery();
147
        }
148
149 4
        return $this;
150
    }
151
152
    /**
153
     * @param SpellcheckResult|null $spellcheck
154
     * @return SearchResult
155
     */
156 4
    public function setSpellcheck($spellcheck): self
157
    {
158 4
        $spellcheckList = [];
159
160 4
        if ($spellcheck && ($suggestions = $spellcheck->getSuggestion(0))) {
161
            foreach ($suggestions->getWords() as $suggestion) {
162
                $spellcheckList[] = ArrayData::create($suggestion);
163
            }
164
        }
165
166 4
        $this->spellcheck = ArrayList::create($spellcheckList);
167
168 4
        return $this;
169
    }
170
171
    /**
172
     * @param HTTPRequest $request
173
     * @return PaginatedList
174
     */
175 1
    public function getPaginatedMatches(HTTPRequest $request): PaginatedList
176
    {
177
        // Get all the items in the set and push them in to the list
178 1
        $items = $this->getMatches();
179
        /** @var PaginatedList $paginated */
180 1
        $paginated = PaginatedList::create($items, $request);
181
        // Do not limit the pagination, it's done at Solr level
182 1
        $paginated->setLimitItems(false);
183
        // Override the count that's set from the item count
184 1
        $paginated->setTotalItems($this->getTotalItems());
185
        // Set the start to the current page from start.
186 1
        $paginated->setPageStart($this->query->getStart());
187
        // The amount of items per page to display
188 1
        $paginated->setPageLength($this->query->getRows());
189
190 1
        return $paginated;
191
    }
192
193
    /**
194
     * @return ArrayList
195
     */
196 1
    public function getMatches(): ArrayList
197
    {
198 1
        $matches = $this->matches;
199 1
        $items = [];
200 1
        $idField = SolrCoreService::ID_FIELD;
201 1
        $classIDField = SolrCoreService::CLASS_ID_FIELD;
202 1
        foreach ($matches as $match) {
203 1
            $item = $this->isDataObject($match, $classIDField);
204 1
            if ($item !== false) {
205 1
                $this->createExcerpt($idField, $match, $item);
206 1
                $items[] = $item;
207 1
                $item->destroy();
208
            }
209 1
            unset($match);
210
        }
211
212 1
        return ArrayList::create($items);
213
    }
214
215
    /**
216
     * @param array $result
217
     * @return $this
218
     */
219 4
    protected function setMatches($result): self
220
    {
221 4
        $data = [];
222
        /** @var Document $item */
223 4
        foreach ($result as $item) {
224 2
            $data[] = ArrayData::create($item->getFields());
225
        }
226
227 4
        $docs = ArrayList::create($data);
228 4
        $this->matches = $docs;
229
230 4
        return $this;
231
    }
232
233
    /**
234
     * @param $match
235
     * @param string $classIDField
236
     * @return DataObject|bool
237
     */
238 1
    protected function isDataObject($match, string $classIDField): DataObject
239
    {
240 1
        if (!$match instanceof DataObject) {
241 1
            $class = $match->ClassName;
242
            /** @var DataObject $match */
243 1
            $match = $class::get()->byID($match->{$classIDField});
244
        }
245
246 1
        return ($match && $match->exists()) ? $match : false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $match && $match-...ists() ? $match : false could return the type false which is incompatible with the type-hinted return SilverStripe\ORM\DataObject. Consider adding an additional type-check to rule them out.
Loading history...
247
    }
248
249
    /**
250
     * @param string $idField
251
     * @param $match
252
     * @param DataObject $item
253
     */
254 1
    protected function createExcerpt(string $idField, $match, DataObject $item): void
255
    {
256 1
        $item->Excerpt = DBField::create_field(
257 1
            'HTMLText',
258 1
            str_replace(
259 1
                '&#65533;',
260 1
                '',
261 1
                $this->getHighlightByID($match->{$idField})
262
            )
263
        );
264 1
    }
265
266
    /**
267
     * @param $docID
268
     * @return string|null
269
     */
270 1
    public function getHighlightByID($docID): ?string
271
    {
272 1
        $highlights = [];
273 1
        if ($this->highlight && $docID) {
274 1
            $highlights = [];
275 1
            foreach ($this->highlight->getResult($docID) as $field => $highlight) {
276 1
                $highlights[] = implode(' (...) ', $highlight);
277
            }
278
        }
279
280 1
        return implode(' (...) ', $highlights);
281
    }
282
283
    /**
284
     * Allow overriding of matches with a custom result
285
     *
286
     * @param $matches
287
     * @return mixed
288
     */
289 1
    public function setCustomisedMatches($matches)
290
    {
291 1
        $this->matches = $matches;
292
293 1
        return $matches;
294
    }
295
}
296