Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like ElasticSearcher 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 ElasticSearcher, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
17 | class ElasticSearcher { |
||
18 | /** |
||
19 | * Comma separated list of SilverStripe ClassNames to search. Leave blank for all |
||
20 | * @var string |
||
21 | */ |
||
22 | private $classes = ''; |
||
23 | |||
24 | /** |
||
25 | * Array of aggregation selected mapped to the value selected, e.g. 'Aperture' => '11' |
||
26 | * @var array |
||
27 | */ |
||
28 | private $filters = array(); |
||
29 | |||
30 | /** |
||
31 | * The locale to search, is set to current locale or default locale by default |
||
32 | * but can be overriden. This is the code in the form en_US, th_TH etc |
||
33 | */ |
||
34 | private $locale = null; |
||
35 | |||
36 | /** |
||
37 | * Object just to manipulate the query and result, used for aggregations |
||
38 | * @var ElasticaSearchHelper |
||
39 | */ |
||
40 | private $manipulator; |
||
41 | |||
42 | /** |
||
43 | * Offset from zero to return search results from |
||
44 | * @var integer |
||
45 | */ |
||
46 | private $start = 0; |
||
47 | |||
48 | /** |
||
49 | * How many search results to return |
||
50 | * @var integer |
||
51 | */ |
||
52 | private $pageLength = 10; |
||
53 | |||
54 | /** |
||
55 | * After a search is performed aggregrations are saved here |
||
56 | * @var array |
||
57 | */ |
||
58 | private $aggregations = null; |
||
59 | |||
60 | /** |
||
61 | * Array of highlighted fields, e.g. Title, Title.standard. If this is empty then the |
||
62 | * ShowHighlight field of SearchableField is used to determine which fields to highlight |
||
63 | * @var array |
||
64 | */ |
||
65 | private $highlightedFields = array(); |
||
66 | |||
67 | |||
68 | /* |
||
69 | Allow an empty search to return either no results (default) or all results, useful for |
||
70 | showing some results during aggregation |
||
71 | */ |
||
72 | private $showResultsForEmptySearch = false; |
||
73 | |||
74 | |||
75 | private $SuggestedQuery = null; |
||
76 | |||
77 | |||
78 | // ---- variables for more like this searching, defaults as per Elasticsearch ---- |
||
79 | private $minTermFreq = 2; |
||
80 | |||
81 | private $maxTermFreq = 25; |
||
82 | |||
83 | private $minDocFreq = 2; |
||
84 | |||
85 | private $maxDocFreq = 0; |
||
86 | |||
87 | private $minWordLength = 0; |
||
88 | |||
89 | private $maxWordLength = 0; |
||
90 | |||
91 | private $minShouldMatch = '30%'; |
||
92 | |||
93 | private $similarityStopWords = ''; |
||
94 | |||
95 | |||
96 | /* |
||
97 | Show results for an empty search string |
||
98 | */ |
||
99 | 9 | public function showResultsForEmptySearch() { |
|
102 | |||
103 | |||
104 | /* |
||
105 | Hide results for an empty search |
||
106 | */ |
||
107 | public function hideResultsForEmptySearch() { |
||
110 | |||
111 | |||
112 | /** |
||
113 | * Accessor the variable to determine whether or not to show results for an empty search |
||
114 | * @return boolean true to show results for empty search, otherwise false |
||
115 | */ |
||
116 | public function getShowResultsForEmptySearch() { |
||
119 | |||
120 | /** |
||
121 | * Update the list of Classes to search, use SilverStripe ClassName comma separated |
||
122 | * @param string $newClasses comma separated list of SilverStripe ClassNames |
||
123 | */ |
||
124 | 10 | public function setClasses($newClasses) { |
|
127 | |||
128 | /** |
||
129 | * Set the manipulator, mainly used for aggregation |
||
130 | * @param ElasticaSearchHelper $newManipulator manipulator used for aggregation |
||
131 | */ |
||
132 | 10 | public function setQueryResultManipulator($newManipulator) { |
|
135 | |||
136 | /** |
||
137 | * Update the start variable |
||
138 | * @param int $newStart Offset for search |
||
139 | */ |
||
140 | 10 | public function setStart($newStart) { |
|
143 | |||
144 | /** |
||
145 | * Update the page length variable |
||
146 | * @param int $newPageLength the number of results to be returned |
||
147 | */ |
||
148 | 10 | public function setPageLength($newPageLength) { |
|
151 | |||
152 | /** |
||
153 | * Set a new locale |
||
154 | * @param string $newLocale locale in short form, e.g. th_TH |
||
155 | */ |
||
156 | public function setLocale($newLocale) { |
||
159 | |||
160 | /** |
||
161 | * Add a filter to the current query in the form of a key/value pair |
||
162 | * @param string $field the name of the indexed field to filter on |
||
163 | * @param string|boolean|integer $value the value of the indexed field to filter on |
||
164 | */ |
||
165 | 4 | public function addFilter($field, $value) { |
|
168 | |||
169 | /** |
||
170 | * Accessor to the aggregations, to be used after a search |
||
171 | * @return array Aggregations returned after a search |
||
172 | */ |
||
173 | 1 | public function getAggregations() { |
|
176 | |||
177 | /** |
||
178 | * Set the minimum term frequency for term to be considered in input query |
||
179 | */ |
||
180 | public function setMinTermFreq($newMinTermFreq) { |
||
183 | |||
184 | /** |
||
185 | * Set the maximum term frequency for term to be considered in input query |
||
186 | */ |
||
187 | public function setMaxTermFreq($newMaxTermFreq) { |
||
190 | |||
191 | /** |
||
192 | * Set the minimum number of documents a term can reside in for consideration as |
||
193 | * part of the input query |
||
194 | */ |
||
195 | public function setMinDocFreq($newMinDocFreq) { |
||
198 | |||
199 | /** |
||
200 | * Set the maximum number of documents a term can reside in for consideration as |
||
201 | * part of the input query |
||
202 | */ |
||
203 | public function setMaxDocFreq($newMaxDocFreq) { |
||
206 | |||
207 | /** |
||
208 | * Set the minimum word length for a term to be considered part of the query |
||
209 | */ |
||
210 | public function setMinWordLength($newMinWordLength) { |
||
213 | |||
214 | /** |
||
215 | * Set the maximum word length for a term to be considered part of the query |
||
216 | */ |
||
217 | public function setMaxWordLength($newMaxWordLength) { |
||
220 | |||
221 | /* |
||
222 | Number or percentage of chosen terms that match |
||
223 | */ |
||
224 | public function setMinShouldMatch($newMinShouldMatch) { |
||
227 | |||
228 | public function setSimilarityStopWords($newSimilarityStopWords) { |
||
231 | |||
232 | |||
233 | /* |
||
234 | Set the highlight fields for subsequent searches |
||
235 | */ |
||
236 | |||
237 | /** |
||
238 | * @param string[] $newHighlightedFields |
||
239 | */ |
||
240 | public function setHighlightedFields($newHighlightedFields) { |
||
243 | |||
244 | |||
245 | /** |
||
246 | * Search against elastica using the criteria already provided, such as page length, start, |
||
247 | * and of course the filters |
||
248 | * @param string $queryText query string, e.g. 'New Zealand' |
||
249 | * @param array $fieldsToSearch Mapping of name to an array of mapping Weight and Elastic mapping, |
||
250 | * e.g. array('Title' => array('Weight' => 2, 'Type' => 'string')) |
||
251 | * @return \PaginatedList SilverStripe DataObjects returned from the search against ElasticSearch |
||
252 | */ |
||
253 | public function search($queryText, $fieldsToSearch = null, $testMode = false) { |
||
313 | |||
314 | |||
315 | /* Perform an autocomplete search */ |
||
316 | |||
317 | /** |
||
318 | * @param string $queryText |
||
319 | */ |
||
320 | public function autocomplete_search($queryText, $field) { |
||
361 | |||
362 | |||
363 | /** |
||
364 | * Perform a 'More Like This' search, aka relevance feedback, using the provided indexed DataObject |
||
365 | * @param \DataObject $indexedItem A DataObject that has been indexed in Elasticsearch |
||
366 | * @param array $fieldsToSearch array of fieldnames to search, mapped to weighting |
||
367 | * @param $$testMode Use all shards, not just one, for consistent results during unit testing. See |
||
368 | * https://www.elastic.co/guide/en/elasticsearch/guide/current/relevance-is-broken.html#relevance-is-broken |
||
369 | * @return \PaginatedList List of results |
||
370 | */ |
||
371 | public function moreLikeThis($indexedItem, $fieldsToSearch, $testMode = false) { |
||
463 | |||
464 | |||
465 | public function hasSuggestedQuery() { |
||
469 | |||
470 | /** |
||
471 | * @return string |
||
472 | */ |
||
473 | public function getSuggestedQuery() { |
||
476 | |||
477 | public function getSuggestedQueryHighlighted() { |
||
480 | |||
481 | } |
||
482 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.