Complex classes like SearchResultSetService 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 SearchResultSetService, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
53 | class SearchResultSetService |
||
54 | { |
||
55 | /** |
||
56 | * Additional filters, which will be added to the query, as well as to |
||
57 | * suggest queries. |
||
58 | * |
||
59 | * @var array |
||
60 | */ |
||
61 | protected $additionalFilters = []; |
||
62 | |||
63 | /** |
||
64 | * Track, if the number of results per page has been changed by the current request |
||
65 | * |
||
66 | * @var bool |
||
67 | */ |
||
68 | protected $resultsPerPageChanged = false; |
||
69 | |||
70 | /** |
||
71 | * @var Search |
||
72 | */ |
||
73 | protected $search; |
||
74 | |||
75 | /** |
||
76 | * @var SearchResultSet |
||
77 | */ |
||
78 | protected $lastResultSet = null; |
||
79 | |||
80 | /** |
||
81 | * @var boolean |
||
82 | */ |
||
83 | protected $isSolrAvailable = false; |
||
84 | |||
85 | /** |
||
86 | * @var TypoScriptConfiguration |
||
87 | */ |
||
88 | protected $typoScriptConfiguration; |
||
89 | |||
90 | /** |
||
91 | * @var SolrLogManager; |
||
92 | */ |
||
93 | protected $logger = null; |
||
94 | |||
95 | /** |
||
96 | * @var SearchResultBuilder |
||
97 | */ |
||
98 | protected $searchResultBuilder; |
||
99 | |||
100 | /** |
||
101 | * @param TypoScriptConfiguration $configuration |
||
102 | * @param Search $search |
||
103 | * @param SolrLogManager $solrLogManager |
||
104 | * @param SearchResultBuilder $resultBuilder |
||
105 | */ |
||
106 | 47 | public function __construct(TypoScriptConfiguration $configuration, Search $search, SolrLogManager $solrLogManager = null, SearchResultBuilder $resultBuilder = null) |
|
107 | { |
||
108 | 47 | $this->search = $search; |
|
109 | 47 | $this->typoScriptConfiguration = $configuration; |
|
110 | 47 | $this->logger = is_null($solrLogManager) ? GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__) : $solrLogManager; |
|
111 | 47 | $this->searchResultBuilder = is_null($resultBuilder) ? GeneralUtility::makeInstance(SearchResultBuilder::class) : $resultBuilder; |
|
112 | 47 | } |
|
113 | |||
114 | /** |
||
115 | * @param bool $useCache |
||
116 | * @return bool |
||
117 | */ |
||
118 | public function getIsSolrAvailable($useCache = true) |
||
119 | { |
||
120 | $this->isSolrAvailable = $this->search->ping($useCache); |
||
121 | return $this->isSolrAvailable; |
||
122 | } |
||
123 | |||
124 | /** |
||
125 | * @return bool |
||
126 | */ |
||
127 | 30 | public function getHasSearched() |
|
128 | { |
||
129 | 30 | return $this->search->hasSearched(); |
|
130 | } |
||
131 | |||
132 | /** |
||
133 | * Retrieves the used search instance. |
||
134 | * |
||
135 | * @return Search |
||
136 | */ |
||
137 | 2 | public function getSearch() |
|
138 | { |
||
139 | 2 | return $this->search; |
|
140 | } |
||
141 | |||
142 | /** |
||
143 | * Initializes the Query object and SearchComponents and returns |
||
144 | * the initialized query object, when a search should be executed. |
||
145 | * |
||
146 | * @param string|null $rawQuery |
||
147 | * @param int $resultsPerPage |
||
148 | * @return Query |
||
149 | */ |
||
150 | 38 | protected function getPreparedQuery($rawQuery, $resultsPerPage) |
|
151 | { |
||
152 | /* @var $query Query */ |
||
153 | 38 | $query = $this->getQueryInstance($rawQuery); |
|
154 | |||
155 | 38 | $this->applyPageSectionsRootLineFilter($query); |
|
156 | |||
157 | 38 | if ($this->typoScriptConfiguration->getLoggingQuerySearchWords()) { |
|
158 | $this->logger->log( |
||
159 | SolrLogManager::INFO, |
||
160 | 'Received search query', |
||
161 | [ |
||
162 | $rawQuery |
||
163 | ] |
||
164 | ); |
||
165 | } |
||
166 | |||
167 | 38 | $query->setResultsPerPage($resultsPerPage); |
|
168 | |||
169 | 38 | if ($this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchQueryAllowEmptyQuery()) { |
|
170 | // empty main query, but using a "return everything" |
||
171 | // alternative query in q.alt |
||
172 | 32 | $query->setAlternativeQuery('*:*'); |
|
173 | } |
||
174 | |||
175 | 38 | if ($this->typoScriptConfiguration->getSearchInitializeWithQuery()) { |
|
176 | 3 | $query->setAlternativeQuery($this->typoScriptConfiguration->getSearchInitializeWithQuery()); |
|
177 | } |
||
178 | |||
179 | 38 | foreach ($this->getAdditionalFilters() as $additionalFilter) { |
|
180 | 2 | $query->getFilters()->add($additionalFilter); |
|
181 | } |
||
182 | |||
183 | 38 | return $query; |
|
184 | } |
||
185 | |||
186 | /** |
||
187 | * @param Query $query |
||
188 | * @param SearchRequest $searchRequest |
||
189 | */ |
||
190 | 38 | protected function initializeRegisteredSearchComponents(Query $query, SearchRequest $searchRequest) |
|
191 | { |
||
192 | 38 | $searchComponents = $this->getRegisteredSearchComponents(); |
|
193 | |||
194 | 38 | foreach ($searchComponents as $searchComponent) { |
|
195 | /** @var Search\SearchComponent $searchComponent */ |
||
196 | 33 | $searchComponent->setSearchConfiguration($this->typoScriptConfiguration->getSearchConfiguration()); |
|
197 | |||
198 | 33 | if ($searchComponent instanceof QueryAware) { |
|
199 | 33 | $searchComponent->setQuery($query); |
|
200 | } |
||
201 | |||
202 | 33 | if ($searchComponent instanceof SearchRequestAware) { |
|
203 | 32 | $searchComponent->setSearchRequest($searchRequest); |
|
204 | } |
||
205 | |||
206 | 33 | $searchComponent->initializeSearchComponent(); |
|
207 | } |
||
208 | 38 | } |
|
209 | |||
210 | /** |
||
211 | * @return string |
||
212 | */ |
||
213 | 40 | protected function getResultSetClassName() |
|
214 | { |
||
215 | 40 | return isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultSetClassName ']) ? |
|
216 | 40 | $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultSetClassName '] : SearchResultSet::class; |
|
217 | } |
||
218 | |||
219 | /** |
||
220 | * Initializes additional filters configured through TypoScript and |
||
221 | * Flexforms for use in regular queries and suggest queries. |
||
222 | * |
||
223 | * @param Query $query |
||
224 | * @return void |
||
225 | */ |
||
226 | 38 | protected function applyPageSectionsRootLineFilter(Query $query) |
|
227 | { |
||
228 | 38 | $searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration(); |
|
229 | 38 | if (count($searchQueryFilters) <= 0) { |
|
230 | 36 | return; |
|
231 | } |
||
232 | |||
233 | // special filter to limit search to specific page tree branches |
||
234 | 2 | if (array_key_exists('__pageSections', $searchQueryFilters)) { |
|
235 | $query->setRootlineFilter($searchQueryFilters['__pageSections']); |
||
236 | $this->typoScriptConfiguration->removeSearchQueryFilterForPageSections(); |
||
237 | } |
||
238 | 2 | } |
|
239 | |||
240 | /** |
||
241 | * Retrieves the configuration filters from the TypoScript configuration, except the __pageSections filter. |
||
242 | * |
||
243 | * @return array |
||
244 | */ |
||
245 | 43 | public function getAdditionalFilters() |
|
246 | { |
||
247 | // when we've build the additionalFilter once, we could return them |
||
248 | 43 | if (count($this->additionalFilters) > 0) { |
|
249 | 2 | return $this->additionalFilters; |
|
250 | } |
||
251 | |||
252 | 43 | $searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration(); |
|
253 | 43 | if (count($searchQueryFilters) <= 0) { |
|
254 | 41 | return []; |
|
255 | } |
||
256 | |||
257 | 2 | $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class); |
|
258 | |||
259 | // all other regular filters |
||
260 | 2 | foreach ($searchQueryFilters as $filterKey => $filter) { |
|
261 | // the __pageSections filter should not be handled as additional filter |
||
262 | 2 | if ($filterKey === '__pageSections') { |
|
263 | continue; |
||
264 | } |
||
265 | |||
266 | 2 | $filterIsArray = is_array($searchQueryFilters[$filterKey]); |
|
267 | 2 | if ($filterIsArray) { |
|
268 | continue; |
||
269 | } |
||
270 | |||
271 | 2 | $hasSubConfiguration = is_array($searchQueryFilters[$filterKey . '.']); |
|
272 | 2 | if ($hasSubConfiguration) { |
|
273 | $filter = $cObj->stdWrap($searchQueryFilters[$filterKey], $searchQueryFilters[$filterKey . '.']); |
||
274 | } |
||
275 | |||
276 | 2 | $this->additionalFilters[$filterKey] = $filter; |
|
277 | } |
||
278 | |||
279 | 2 | return $this->additionalFilters; |
|
280 | } |
||
281 | |||
282 | /** |
||
283 | * Performs a search and returns a SearchResultSet. |
||
284 | * |
||
285 | * @param SearchRequest $searchRequest |
||
286 | * @return SearchResultSet |
||
287 | */ |
||
288 | 40 | public function search(SearchRequest $searchRequest) |
|
289 | { |
||
290 | /** @var $resultSet SearchResultSet */ |
||
291 | 40 | $resultSetClass = $this->getResultSetClassName(); |
|
292 | 40 | $resultSet = GeneralUtility::makeInstance($resultSetClass); |
|
293 | 40 | $resultSet->setUsedSearchRequest($searchRequest); |
|
294 | 40 | $this->lastResultSet = $resultSet; |
|
295 | |||
296 | 40 | $resultSet = $this->handleSearchHook('beforeSearch', $resultSet); |
|
297 | |||
298 | 40 | if ($searchRequest->getRawUserQueryIsNull() && !$this->getInitialSearchIsConfigured()) { |
|
299 | // when no rawQuery was passed or no initialSearch is configured, we pass an empty result set |
||
300 | 2 | return $resultSet; |
|
301 | } |
||
302 | |||
303 | 38 | if ($searchRequest->getRawUserQueryIsEmptyString() && !$this->typoScriptConfiguration->getSearchQueryAllowEmptyQuery()) { |
|
304 | // the user entered an empty query string "" or " " and empty querystring is not allowed |
||
305 | return $resultSet; |
||
306 | } |
||
307 | |||
308 | 38 | $rawQuery = $searchRequest->getRawUserQuery(); |
|
309 | 38 | $resultsPerPage = (int)$searchRequest->getResultsPerPage(); |
|
310 | 38 | $query = $this->getPreparedQuery($rawQuery, $resultsPerPage); |
|
311 | 38 | $this->initializeRegisteredSearchComponents($query, $searchRequest); |
|
312 | 38 | $resultSet->setUsedQuery($query); |
|
313 | |||
314 | // the offset mulitplier is page - 1 but not less then zero |
||
315 | 38 | $offsetMultiplier = max(0, $searchRequest->getPage() - 1); |
|
316 | 38 | $offSet = $offsetMultiplier * $resultsPerPage; |
|
317 | |||
318 | // performing the actual search, sending the query to the Solr server |
||
319 | 38 | $query = $this->modifyQuery($query, $searchRequest, $this->search); |
|
320 | 38 | $response = $this->doASearch($query, $offSet); |
|
321 | |||
322 | 37 | if ($resultsPerPage === 0) { |
|
323 | // when resultPerPage was forced to 0 we also set the numFound to 0 to hide results, e.g. |
||
324 | // when results for the initial search should not be shown. |
||
325 | 2 | $response->response->numFound = 0; |
|
|
|||
326 | } |
||
327 | |||
328 | 37 | $resultSet->setUsedSearch($this->search); |
|
329 | 37 | $resultSet->setResponse($response); |
|
330 | |||
331 | /** @var ResultParserRegistry $parserRegistry */ |
||
332 | 37 | $parserRegistry = GeneralUtility::makeInstance(ResultParserRegistry::class, $this->typoScriptConfiguration); |
|
333 | 37 | $useRawDocuments = (bool)$this->typoScriptConfiguration->getValueByPathOrDefaultValue('plugin.tx_solr.features.useRawDocuments', false); |
|
334 | 37 | $searchResults = $parserRegistry->getParser($resultSet)->parse($resultSet, $useRawDocuments); |
|
335 | 37 | $resultSet->setSearchResults($searchResults); |
|
336 | |||
337 | 37 | $resultSet->setUsedPage((int)$searchRequest->getPage()); |
|
338 | 37 | $resultSet->setUsedResultsPerPage($resultsPerPage); |
|
339 | 37 | $resultSet->setUsedAdditionalFilters($this->getAdditionalFilters()); |
|
340 | |||
341 | /** @var $variantsProcessor VariantsProcessor */ |
||
342 | 37 | $variantsProcessor = GeneralUtility::makeInstance(VariantsProcessor::class, $this->typoScriptConfiguration, $this->searchResultBuilder); |
|
343 | 37 | $variantsProcessor->process($resultSet); |
|
344 | |||
345 | /** @var $searchResultReconstitutionProcessor ResultSetReconstitutionProcessor */ |
||
346 | 37 | $searchResultReconstitutionProcessor = GeneralUtility::makeInstance(ResultSetReconstitutionProcessor::class); |
|
347 | 37 | $searchResultReconstitutionProcessor->process($resultSet); |
|
348 | |||
349 | 37 | $resultSet = $this->getAutoCorrection($resultSet); |
|
350 | |||
351 | 37 | return $this->handleSearchHook('afterSearch', $resultSet); |
|
352 | } |
||
353 | |||
354 | /** |
||
355 | * Executes the search and builds a fake response for a current bug in Apache Solr 6.3 |
||
356 | * |
||
357 | * @param Query $query |
||
358 | * @param int $offSet |
||
359 | * @return \Apache_Solr_Response |
||
360 | */ |
||
361 | 38 | protected function doASearch($query, $offSet) |
|
362 | { |
||
363 | 38 | $response = $this->search->search($query, $offSet, null); |
|
364 | 37 | if($response === null) { |
|
365 | throw new SolrIncompleteResponseException('The response retrieved from solr was incomplete', 1505989678); |
||
366 | } |
||
367 | |||
368 | 37 | return $response; |
|
369 | } |
||
370 | |||
371 | /** |
||
372 | * @param SearchResultSet $searchResultSet |
||
373 | * @return SearchResultSet |
||
374 | */ |
||
375 | 37 | protected function getAutoCorrection(SearchResultSet $searchResultSet) |
|
376 | { |
||
377 | // no secondary search configured |
||
378 | 37 | if (!$this->typoScriptConfiguration->getSearchSpellcheckingSearchUsingSpellCheckerSuggestion()) { |
|
379 | 36 | return $searchResultSet; |
|
380 | } |
||
381 | |||
382 | // more then zero results |
||
383 | 1 | if ($searchResultSet->getAllResultCount() > 0) { |
|
384 | 1 | return $searchResultSet; |
|
385 | } |
||
386 | |||
387 | // no corrections present |
||
388 | 1 | if (!$searchResultSet->getHasSpellCheckingSuggestions()) { |
|
389 | return $searchResultSet; |
||
390 | } |
||
391 | |||
392 | 1 | $searchResultSet = $this->peformAutoCorrection($searchResultSet); |
|
393 | |||
394 | 1 | return $searchResultSet; |
|
395 | } |
||
396 | |||
397 | /** |
||
398 | * @param SearchResultSet $searchResultSet |
||
399 | * @return SearchResultSet |
||
400 | */ |
||
401 | 1 | protected function peformAutoCorrection(SearchResultSet $searchResultSet) |
|
402 | { |
||
403 | 1 | $searchRequest = $searchResultSet->getUsedSearchRequest(); |
|
404 | 1 | $suggestions = $searchResultSet->getSpellCheckingSuggestions(); |
|
405 | |||
406 | 1 | $maximumRuns = $this->typoScriptConfiguration->getSearchSpellcheckingNumberOfSuggestionsToTry(1); |
|
407 | 1 | $runs = 0; |
|
408 | |||
409 | 1 | foreach ($suggestions as $suggestion) { |
|
410 | 1 | $runs++; |
|
411 | |||
412 | 1 | $correction = $suggestion->getSuggestion(); |
|
413 | 1 | $initialQuery = $searchRequest->getRawUserQuery(); |
|
414 | |||
415 | 1 | $searchRequest->setRawQueryString($correction); |
|
416 | 1 | $searchResultSet = $this->search($searchRequest); |
|
417 | 1 | if ($searchResultSet->getAllResultCount() > 0) { |
|
418 | 1 | $searchResultSet->setIsAutoCorrected(true); |
|
419 | 1 | $searchResultSet->setCorrectedQueryString($correction); |
|
420 | 1 | $searchResultSet->setInitialQueryString($initialQuery); |
|
421 | 1 | break; |
|
422 | } |
||
423 | |||
424 | if ($runs > $maximumRuns) { |
||
425 | break; |
||
426 | } |
||
427 | } |
||
428 | 1 | return $searchResultSet; |
|
429 | } |
||
430 | |||
431 | /** |
||
432 | * Allows to modify a query before eventually handing it over to Solr. |
||
433 | * |
||
434 | * @param Query $query The current query before it's being handed over to Solr. |
||
435 | * @param SearchRequest $searchRequest The searchRequest, relevant in the current context |
||
436 | * @param Search $search The search, relevant in the current context |
||
437 | * @throws \UnexpectedValueException |
||
438 | * @return Query The modified query that is actually going to be given to Solr. |
||
439 | */ |
||
440 | 38 | protected function modifyQuery(Query $query, SearchRequest $searchRequest, Search $search) |
|
441 | { |
||
442 | // hook to modify the search query |
||
443 | 38 | if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchQuery'])) { |
|
444 | 32 | foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchQuery'] as $classReference) { |
|
445 | 32 | $queryModifier = GeneralUtility::getUserObj($classReference); |
|
446 | |||
447 | 32 | if ($queryModifier instanceof Modifier) { |
|
448 | 32 | if ($queryModifier instanceof SearchAware) { |
|
449 | $queryModifier->setSearch($search); |
||
450 | } |
||
451 | |||
452 | 32 | if ($queryModifier instanceof SearchRequestAware) { |
|
453 | 32 | $queryModifier->setSearchRequest($searchRequest); |
|
454 | } |
||
455 | |||
456 | 32 | $query = $queryModifier->modifyQuery($query); |
|
457 | } else { |
||
458 | throw new \UnexpectedValueException( |
||
459 | get_class($queryModifier) . ' must implement interface ' . Modifier::class, |
||
460 | 32 | 1310387414 |
|
461 | ); |
||
462 | } |
||
463 | } |
||
464 | } |
||
465 | |||
466 | 38 | return $query; |
|
467 | } |
||
468 | |||
469 | /** |
||
470 | * Retrieves a single document from solr by document id. |
||
471 | * |
||
472 | * @param string $documentId |
||
473 | * @return SearchResult |
||
474 | */ |
||
475 | 3 | public function getDocumentById($documentId) |
|
476 | { |
||
477 | /* @var $query Query */ |
||
478 | 3 | $query = GeneralUtility::makeInstance(Query::class, $documentId); |
|
479 | 3 | $query->setQueryFields(QueryFields::fromString('id')); |
|
480 | 3 | $response = $this->search->search($query, 0, 1); |
|
481 | 2 | $parsedData = $response->getParsedData(); |
|
482 | 2 | $resultDocument = isset($parsedData->response->docs[0]) ? $parsedData->response->docs[0] : null; |
|
483 | |||
484 | 2 | return $this->searchResultBuilder->fromApacheSolrDocument($resultDocument); |
|
485 | } |
||
486 | |||
487 | /** |
||
488 | * This method is used to call the registered hooks during the search execution. |
||
489 | * |
||
490 | * @param string $eventName |
||
491 | * @param SearchResultSet $resultSet |
||
492 | * @return SearchResultSet |
||
493 | */ |
||
494 | 40 | private function handleSearchHook($eventName, SearchResultSet $resultSet) |
|
495 | { |
||
496 | 40 | if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr'][$eventName])) { |
|
497 | 40 | return $resultSet; |
|
498 | } |
||
499 | |||
500 | 28 | foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr'][$eventName] as $classReference) { |
|
501 | 28 | $afterSearchProcessor = GeneralUtility::getUserObj($classReference); |
|
502 | 28 | if ($afterSearchProcessor instanceof SearchResultSetProcessor) { |
|
503 | 28 | $afterSearchProcessor->process($resultSet); |
|
504 | } |
||
505 | } |
||
506 | |||
507 | 28 | return $resultSet; |
|
508 | } |
||
509 | |||
510 | /** |
||
511 | * @return SearchResultSet |
||
512 | */ |
||
513 | 29 | public function getLastResultSet() |
|
514 | { |
||
515 | 29 | return $this->lastResultSet; |
|
516 | } |
||
517 | |||
518 | /** |
||
519 | * This method returns true when the last search was executed with an empty query |
||
520 | * string or whitespaces only. When no search was triggered it will return false. |
||
521 | * |
||
522 | * @return bool |
||
523 | */ |
||
524 | public function getLastSearchWasExecutedWithEmptyQueryString() |
||
525 | { |
||
526 | $wasEmptyQueryString = false; |
||
527 | if ($this->lastResultSet != null) { |
||
528 | $wasEmptyQueryString = $this->lastResultSet->getUsedSearchRequest()->getRawUserQueryIsEmptyString(); |
||
529 | } |
||
530 | |||
531 | return $wasEmptyQueryString; |
||
532 | } |
||
533 | |||
534 | /** |
||
535 | * @return bool |
||
536 | */ |
||
537 | 6 | protected function getInitialSearchIsConfigured() |
|
538 | { |
||
539 | 6 | return $this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchShowResultsOfInitialEmptyQuery() || $this->typoScriptConfiguration->getSearchInitializeWithQuery() || $this->typoScriptConfiguration->getSearchShowResultsOfInitialQuery(); |
|
540 | } |
||
541 | |||
542 | /** |
||
543 | * @return mixed |
||
544 | */ |
||
545 | 32 | protected function getRegisteredSearchComponents() |
|
546 | { |
||
547 | 32 | return GeneralUtility::makeInstance(SearchComponentManager::class)->getSearchComponents(); |
|
548 | } |
||
549 | |||
550 | /** |
||
551 | * @param string $rawQuery |
||
552 | * @return Query|object |
||
553 | */ |
||
554 | 32 | protected function getQueryInstance($rawQuery) |
|
559 | } |
||
560 |
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.