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 GenericEngine 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 GenericEngine, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
150 | class GenericEngine extends BaseEngine |
||
151 | { |
||
152 | |||
153 | /** |
||
154 | * {@inheritDoc} |
||
155 | * |
||
156 | * - operators: A list of registered operators methods as `name` => |
||
157 | * `methodName`. |
||
158 | * |
||
159 | * - strict: Used to filter any invalid word. Set to a string representing a |
||
160 | * regular expression describing which charaters should be removed. Or set |
||
161 | * to TRUE to used default discard criteria: only letters, digits and few |
||
162 | * basic symbols (".", ",", "/", etc). Defaults to TRUE (custom filter |
||
163 | * regex). VALID ONLY when `wordsExtractor` is set to null. |
||
164 | * |
||
165 | * - bannedWords: Array list of banned words, or a callable that should decide |
||
166 | * if the given word is banned or not. Defaults to empty array (allow |
||
167 | * everything). VALID ONLY when `wordsExtractor` is set to null. |
||
168 | * |
||
169 | * - fulltext: Whether to use FULLTEXT search whenever it is possible. Defaults to |
||
170 | * TRUE. This feature is only supported for MySQL InnoDB database engines. |
||
171 | * |
||
172 | * - datasetTable: Name of the MySQL table where words dataset should be stored and |
||
173 | * read from. This allows you to split large sets into different tables. |
||
174 | * |
||
175 | * - wordsExtractor: Callable function used to extract words from each entity being |
||
176 | * indexed. Such functions will received an Entity object as first argument, and |
||
177 | * should return a string of words. e.g. `lorem ipsum dolorem`. Defaults to internal |
||
178 | * method `extractEntityWords()` |
||
179 | */ |
||
180 | protected $_defaultConfig = [ |
||
181 | 'operators' => [], |
||
182 | 'strict' => true, |
||
183 | 'bannedWords' => [], |
||
184 | 'wordsExtractor' => null, |
||
185 | 'fulltext' => true, |
||
186 | 'datasetTable' => 'search_datasets', |
||
187 | ]; |
||
188 | |||
189 | /** |
||
190 | * {@inheritDoc} |
||
191 | * |
||
192 | * @throws \Search\Engine\Generic\Exception\CompoundPrimaryKeyException When using |
||
193 | * compound primary keys |
||
194 | */ |
||
195 | public function __construct(Table $table, array $config = []) |
||
226 | |||
227 | /** |
||
228 | * {@inheritDoc} |
||
229 | */ |
||
230 | public function index(EntityInterface $entity) |
||
256 | |||
257 | /** |
||
258 | * {@inheritDoc} |
||
259 | */ |
||
260 | public function delete(EntityInterface $entity) |
||
269 | |||
270 | /** |
||
271 | * {@inheritDoc} |
||
272 | */ |
||
273 | public function get(EntityInterface $entity) |
||
283 | |||
284 | /** |
||
285 | * {@inheritDoc} |
||
286 | * |
||
287 | * It looks for search-criteria and applies them over the query object. For |
||
288 | * example, given the criteria below: |
||
289 | * |
||
290 | * "this phrase" -"and not this one" |
||
291 | * |
||
292 | * Alters the query object as follow: |
||
293 | * |
||
294 | * ```php |
||
295 | * $query->where([ |
||
296 | * 'indexed_words LIKE' => '%this phrase%', |
||
297 | * 'indexed_words NOT LIKE' => '%and not this one%' |
||
298 | * ]); |
||
299 | * ``` |
||
300 | * |
||
301 | * The `AND` & `OR` keywords are allowed to create complex conditions. For |
||
302 | * example: |
||
303 | * |
||
304 | * "this phrase" OR -"and not this one" AND "this" |
||
305 | * |
||
306 | * Will produce something like: |
||
307 | * |
||
308 | * ```php |
||
309 | * $query |
||
310 | * ->where(['indexed_words LIKE' => '%this phrase%']) |
||
311 | * ->orWhere(['indexed_words NOT LIKE' => '%and not this one%']); |
||
312 | * ->andWhere(['indexed_words LIKE' => '%this%']); |
||
313 | * ``` |
||
314 | * |
||
315 | * ### Options |
||
316 | * |
||
317 | * - `missingOperators`: Controls what to do when an undefined operator is found. |
||
318 | * Possible values are: |
||
319 | * |
||
320 | * - `event` (default): Triggers an event so other parts of the system can react |
||
321 | * to any missing operator. |
||
322 | * |
||
323 | * - `ignore`: Ignore any undefined operator. |
||
324 | * |
||
325 | * - `words`: Converts operator information into a set of literal words. |
||
326 | * |
||
327 | * - `tokenDecorator`: Callable function which is applied to every token before it |
||
328 | * gets applied. Retuning anything that is not a `TokenInterface` will skip that |
||
329 | * token from being used. |
||
330 | */ |
||
331 | public function search($criteria, Query $query, array $options = []) |
||
387 | |||
388 | /** |
||
389 | * Extracts every token found on the given search criteria. |
||
390 | * |
||
391 | * @param string $criteria A search criteria. e.g. `-hello +world` |
||
392 | * @return array List of tokens found |
||
393 | */ |
||
394 | public function tokenizer($criteria) |
||
398 | |||
399 | /** |
||
400 | * Scopes the given query using the given operator token. |
||
401 | * |
||
402 | * @param \Cake\ORM\Query $query The query to scope |
||
403 | * @param \Search\Token $token Token describing an operator. e.g `-op_name:op_value` |
||
404 | * @return \Cake\ORM\Query Scoped query |
||
405 | */ |
||
406 | protected function _scopeOperator(Query $query, TokenInterface $token) |
||
410 | |||
411 | /** |
||
412 | * Scopes the given query using the given words token. |
||
413 | * |
||
414 | * @param \Cake\ORM\Query $query The query to scope |
||
415 | * @param \Search\TokenInterface $token Token describing a words sequence. e.g `this is a phrase` |
||
416 | * @return \Cake\ORM\Query Scoped query |
||
417 | */ |
||
418 | protected function _scopeWords(Query $query, TokenInterface $token) |
||
443 | |||
444 | /** |
||
445 | * Similar to "_scopeWords" but using MySQL's fulltext indexes. |
||
446 | * |
||
447 | * @param \Cake\ORM\Query $query The query to scope |
||
448 | * @param \Search\TokenInterface $token Token describing a words sequence. e.g `this is a phrase` |
||
449 | * @return \Cake\ORM\Query Scoped query |
||
450 | */ |
||
451 | protected function _scopeWordsInFulltext(Query $query, TokenInterface $token) |
||
474 | |||
475 | /** |
||
476 | * Whether FullText index is available or not and should be used. |
||
477 | * |
||
478 | * @return bool True if enabled and should be used, false otherwise |
||
479 | */ |
||
480 | protected function _isFullTextEnabled() |
||
514 | |||
515 | /** |
||
516 | * Gets a list of storage engine's stopwords. That is words that is considered |
||
517 | * common or Trivial enough that it is omitted from the search index and ignored |
||
518 | * in search queries |
||
519 | * |
||
520 | * @return array List of words |
||
521 | */ |
||
522 | protected function _stopWords() |
||
545 | |||
546 | /** |
||
547 | * Calculates entity's primary key. |
||
548 | * |
||
549 | * @param \Cake\Datasource\EntityInterface $entity The entity |
||
550 | * @return string |
||
551 | * @deprecated Use direct access as `$entity->get($this->config('pk'))` |
||
552 | */ |
||
553 | protected function _entityId(EntityInterface $entity) |
||
557 | |||
558 | /** |
||
559 | * Extracts a list of words to by indexed for given entity. |
||
560 | * |
||
561 | * NOTE: Words can be repeated, this allows to search phrases. |
||
562 | * |
||
563 | * @param \Cake\Datasource\EntityInterface $entity The entity for which generate |
||
564 | * the list of words |
||
565 | * @return string Space-separated list of words. e.g. `cat dog this that` |
||
566 | */ |
||
567 | public function extractEntityWords(EntityInterface $entity) |
||
595 | |||
596 | /** |
||
597 | * Removes any invalid word from the given text. |
||
598 | * |
||
599 | * @param string $text The text to filter |
||
600 | * @return string Filtered text |
||
601 | */ |
||
602 | protected function _filterText($text) |
||
626 | } |
||
627 |
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.