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 Cursor 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 Cursor, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
17 | class Cursor implements \Iterator, \Countable |
||
18 | { |
||
19 | /** |
||
20 | * |
||
21 | * @var \Sokil\Mongo\Client |
||
22 | */ |
||
23 | private $client; |
||
24 | |||
25 | /** |
||
26 | * |
||
27 | * @var \Sokil\Mongo\Collection |
||
28 | */ |
||
29 | private $collection; |
||
30 | |||
31 | /** |
||
32 | * |
||
33 | * @var array |
||
34 | */ |
||
35 | private $fields = array(); |
||
36 | |||
37 | /** |
||
38 | * |
||
39 | * @var \MongoCursor |
||
40 | */ |
||
41 | private $cursor; |
||
42 | /** |
||
43 | * |
||
44 | * @var \Sokil\Mongo\Expression |
||
45 | */ |
||
46 | private $expression; |
||
47 | |||
48 | /** |
||
49 | * Offset |
||
50 | * @var int |
||
51 | */ |
||
52 | private $skip = 0; |
||
53 | |||
54 | /** |
||
55 | * Limit |
||
56 | * @var int |
||
57 | */ |
||
58 | private $limit = 0; |
||
59 | |||
60 | /** |
||
61 | * Definition of sort |
||
62 | * @var array |
||
63 | */ |
||
64 | private $sort = array(); |
||
65 | |||
66 | /** |
||
67 | * Definition of read preference |
||
68 | * @var array |
||
69 | */ |
||
70 | private $readPreference = array(); |
||
71 | |||
72 | /** |
||
73 | * Return result as array or as Document instance |
||
74 | * @var boolean |
||
75 | */ |
||
76 | private $isResultAsArray = false; |
||
77 | |||
78 | /** |
||
79 | * Cursor options |
||
80 | * @var array |
||
81 | */ |
||
82 | private $options = array( |
||
83 | 'expressionClass' => '\Sokil\Mongo\Expression', |
||
84 | /** |
||
85 | * @link http://docs.mongodb.org/manual/reference/method/cursor.batchSize/ |
||
86 | * @var int number of documents to return in each batch of the response from the MongoDB instance |
||
87 | */ |
||
88 | 'batchSize' => null, |
||
89 | // client timeout |
||
90 | 'clientTimeout' => null, |
||
91 | // Specifies a cumulative time limit in milliseconds to be allowed by the server for processing operations on the cursor. |
||
92 | 'serverTimeout' => null, |
||
93 | ); |
||
94 | |||
95 | /** |
||
96 | * Use document pool to create Document object from array |
||
97 | * @var bool |
||
98 | */ |
||
99 | private $isDocumentPoolUsed = true; |
||
100 | |||
101 | /** |
||
102 | * Index hinting |
||
103 | * @param \Sokil\Mongo\Collection $collection |
||
104 | * @param array $options |
||
105 | */ |
||
106 | private $hint; |
||
107 | |||
108 | public function __construct(Collection $collection, array $options = null) |
||
121 | |||
122 | public function __call($name, $arguments) |
||
127 | |||
128 | /** |
||
129 | * Get option |
||
130 | * |
||
131 | * @param string|int $name |
||
132 | * @return mixed |
||
133 | */ |
||
134 | public function getOption($name, $default = null) |
||
138 | |||
139 | /** |
||
140 | * Get result as array |
||
141 | * @return $this |
||
142 | */ |
||
143 | public function asArray() |
||
148 | |||
149 | /** |
||
150 | * Get result as object |
||
151 | * @return $this |
||
152 | */ |
||
153 | public function asObject() |
||
158 | |||
159 | /** |
||
160 | * Check if result returned as array |
||
161 | * @return bool |
||
162 | */ |
||
163 | public function isResultAsArray() |
||
167 | |||
168 | /** |
||
169 | * Return only specified fields |
||
170 | * |
||
171 | * @param array $fields |
||
172 | * @return \Sokil\Mongo\Cursor |
||
173 | */ |
||
174 | public function fields(array $fields) |
||
182 | |||
183 | /** |
||
184 | * Return all fields except specified |
||
185 | * |
||
186 | * @param array $fields |
||
187 | * @return \Sokil\Mongo\Cursor |
||
188 | */ |
||
189 | public function skipFields(array $fields) |
||
197 | |||
198 | /** |
||
199 | * Append field to accept list |
||
200 | * |
||
201 | * @param string $field field name |
||
202 | * @return \Sokil\Mongo\Cursor |
||
203 | */ |
||
204 | public function field($field) |
||
212 | |||
213 | /** |
||
214 | * Append field to skip list |
||
215 | * |
||
216 | * @param string $field field name |
||
217 | * @return Cursor |
||
218 | */ |
||
219 | public function skipField($field) |
||
227 | |||
228 | /** |
||
229 | * Paginate list of sub-documents |
||
230 | * |
||
231 | * @param string $field |
||
232 | * @param integer $limit |
||
233 | * @param integer $skip |
||
234 | * @return \Sokil\Mongo\Cursor |
||
235 | * @throws Exception |
||
236 | */ |
||
237 | public function slice($field, $limit, $skip = null) |
||
253 | |||
254 | /** |
||
255 | * Merge expression |
||
256 | * @param \Sokil\Mongo\Expression $expression |
||
257 | * @return \Sokil\Mongo\Cursor |
||
258 | */ |
||
259 | public function query(Expression $expression) |
||
264 | |||
265 | /** |
||
266 | * Helper to create new expression |
||
267 | * |
||
268 | * @return \Sokil\Mongo\Expression |
||
269 | */ |
||
270 | public function expression() |
||
274 | |||
275 | /** |
||
276 | * Filter by list of \MongoId |
||
277 | * |
||
278 | * @param array $idList list of ids |
||
279 | * @return \Sokil\Mongo\Cursor |
||
280 | */ |
||
281 | public function byIdList(array $idList) |
||
286 | |||
287 | /** |
||
288 | * Filter by id |
||
289 | * |
||
290 | * @param string|\MongoId $id id of document |
||
291 | * @return \Sokil\Mongo\Cursor |
||
292 | */ |
||
293 | public function byId($id) |
||
307 | |||
308 | /** |
||
309 | * Skip defined number of documents |
||
310 | * |
||
311 | * @param int $skip number of documents to skip |
||
312 | * @return \Sokil\Mongo\Cursor |
||
313 | */ |
||
314 | public function skip($skip) |
||
320 | |||
321 | /** |
||
322 | * Limit result set to specified number of elements |
||
323 | * |
||
324 | * @param int $limit number of elements in result set |
||
325 | * @param int|null $offset number of elements to skip |
||
326 | * @return \Sokil\Mongo\Cursor |
||
327 | */ |
||
328 | public function limit($limit, $offset = null) |
||
338 | |||
339 | /** |
||
340 | * Specifies the number of documents to return in each batch of the response from the MongoDB instance. |
||
341 | * |
||
342 | * @param int $size number of documents |
||
343 | * @link http://docs.mongodb.org/manual/reference/method/cursor.batchSize/ |
||
344 | * @return \Sokil\Mongo\Cursor |
||
345 | */ |
||
346 | public function setBatchSize($size) |
||
352 | |||
353 | /** |
||
354 | * Instructs the driver to stop waiting for a response and throw a |
||
355 | * MongoCursorTimeoutException after a set time, |
||
356 | * A timeout can be set at any time and will affect subsequent queries on |
||
357 | * the cursor, including fetching more results from the database. |
||
358 | * @param type $ms |
||
359 | * @return \Sokil\Mongo\Cursor |
||
360 | */ |
||
361 | public function setClientTimeout($ms) |
||
367 | |||
368 | /** |
||
369 | * Server-side timeout for a query, |
||
370 | * Specifies a cumulative time limit in milliseconds to be allowed |
||
371 | * by the server for processing operations on the cursor. |
||
372 | * @param type $ms |
||
373 | * @return \Sokil\Mongo\Cursor |
||
374 | */ |
||
375 | public function setServerTimeout($ms) |
||
381 | |||
382 | /** |
||
383 | * Sort result by specified keys and directions |
||
384 | * |
||
385 | * An array of fields by which to sort. Each element in the array has as key the field name, and as value either |
||
386 | * 1 for ascending sort, or -1 for descending sort. Each result is first sorted on the first field in the array, |
||
387 | * then (if it exists) on the second field in the array, etc. This means that the order of the fields in the |
||
388 | * fields array is important. See also the examples section. |
||
389 | * |
||
390 | * @param array $sort |
||
391 | * @return \Sokil\Mongo\Cursor |
||
392 | */ |
||
393 | public function sort(array $sort) |
||
398 | |||
399 | /** |
||
400 | * |
||
401 | * @return \MongoCursor |
||
402 | */ |
||
403 | private function getCursor() |
||
463 | |||
464 | /** |
||
465 | * Count documents in result without applying limit and offset |
||
466 | * @return int count |
||
467 | */ |
||
468 | public function count() |
||
474 | |||
475 | public function explain() |
||
483 | |||
484 | /** |
||
485 | * Count documents in result with applying limit and offset |
||
486 | * @return int count |
||
487 | */ |
||
488 | public function limitedCount() |
||
494 | |||
495 | |||
496 | /** |
||
497 | * Gte list of \MongoId of current search query |
||
498 | * @return array |
||
499 | */ |
||
500 | public function getIdList() |
||
504 | |||
505 | /** |
||
506 | * Find one document which correspond to expression |
||
507 | * |
||
508 | * @return \Sokil\Mongo\Document|array|null |
||
509 | */ |
||
510 | public function findOne() |
||
540 | |||
541 | /** |
||
542 | * |
||
543 | * @return array result of searching |
||
544 | */ |
||
545 | public function findAll() |
||
549 | |||
550 | /** |
||
551 | * Get random document |
||
552 | * @return |
||
553 | */ |
||
554 | public function findRandom() |
||
571 | |||
572 | /** |
||
573 | * Get query builder's expression |
||
574 | * |
||
575 | * @return Expression |
||
576 | */ |
||
577 | public function getExpression() |
||
581 | |||
582 | /** |
||
583 | * Get MongoDB query array |
||
584 | * |
||
585 | * @return array |
||
586 | */ |
||
587 | public function getMongoQuery() |
||
591 | |||
592 | /** |
||
593 | * Return the values from a single field in the result set of documents |
||
594 | * |
||
595 | * @param string $fieldName |
||
596 | * @return array |
||
597 | */ |
||
598 | public function pluck($fieldName) |
||
617 | |||
618 | /** |
||
619 | * Pluck by dot-notated field name |
||
620 | * |
||
621 | * @param string $fieldName field name |
||
622 | * @return array |
||
623 | */ |
||
624 | private function pluckDotNotated($fieldName) |
||
641 | |||
642 | /** |
||
643 | * Get document instance and remove it from collection |
||
644 | * |
||
645 | * @return \Sokil\Mongo\Document |
||
646 | */ |
||
647 | public function findAndRemove() |
||
668 | |||
669 | /** |
||
670 | * Find first document and update it |
||
671 | * |
||
672 | * @param Operator $operator operations with document to update |
||
673 | * @param bool $upsert if document not found - create |
||
674 | * @param bool $returnUpdated if true - return updated document |
||
675 | * |
||
676 | * @return null|Document |
||
677 | */ |
||
678 | public function findAndUpdate(Operator $operator, $upsert = false, $returnUpdated = true) |
||
699 | |||
700 | View Code Duplication | public function map($handler) |
|
710 | |||
711 | View Code Duplication | public function filter($handler) |
|
725 | |||
726 | /** |
||
727 | * Get result set of documents. |
||
728 | * |
||
729 | * @return \Sokil\Mongo\ResultSet |
||
730 | */ |
||
731 | public function getResultSet() |
||
735 | |||
736 | /** |
||
737 | * Get paginator |
||
738 | * |
||
739 | * @param int $page page number |
||
740 | * @param int $itemsOnPage number of items on page |
||
741 | * @return \Sokil\Mongo\Paginator |
||
742 | */ |
||
743 | public function paginate($page, $itemsOnPage = 30) |
||
752 | |||
753 | public function current() |
||
769 | |||
770 | public function key() |
||
774 | |||
775 | public function next() |
||
780 | |||
781 | public function rewind() |
||
786 | |||
787 | public function valid() |
||
791 | |||
792 | public function readPrimaryOnly() |
||
801 | |||
802 | public function readPrimaryPreferred(array $tags = null) |
||
811 | |||
812 | public function readSecondaryOnly(array $tags = null) |
||
821 | |||
822 | public function readSecondaryPreferred(array $tags = null) |
||
831 | |||
832 | public function readNearest(array $tags = null) |
||
841 | |||
842 | /** |
||
843 | * @return array |
||
844 | */ |
||
845 | public function getReadPreference() |
||
853 | |||
854 | public function isDocumentPoolUsed() |
||
858 | |||
859 | public function useDocumentPool() |
||
864 | |||
865 | public function skipDocumentPool() |
||
870 | |||
871 | /** |
||
872 | * Specify index to use |
||
873 | * |
||
874 | * @link http://docs.mongodb.org/manual/reference/operator/meta/hint/ |
||
875 | * @param array|string $specification Specify the index either by the index name or by document |
||
876 | * @return \Sokil\Mongo\Cursor |
||
877 | */ |
||
878 | public function hint($specification) |
||
883 | |||
884 | /** |
||
885 | * Copy selected documents to another collection |
||
886 | * |
||
887 | * @param type $targetCollectionName |
||
888 | * @param type $targetDatabaseName Target database name. If not specified - use current |
||
889 | */ |
||
890 | public function copyToCollection($targetCollectionName, $targetDatabaseName = null) |
||
946 | |||
947 | /** |
||
948 | * Move selected documents to another collection. |
||
949 | * Dociuments will be removed from source collection only after |
||
950 | * copying them to target collection. |
||
951 | * |
||
952 | * @param type $targetCollectionName |
||
953 | * @param type $targetDatabaseName Target database name. If not specified - use current |
||
954 | */ |
||
955 | public function moveToCollection($targetCollectionName, $targetDatabaseName = null) |
||
963 | |||
964 | /** |
||
965 | * Used to get hash that uniquely identifies current query |
||
966 | */ |
||
967 | public function getHash() |
||
995 | |||
996 | /** |
||
997 | * Get list of MongoId objects from array of strings, MongoId's and Document's |
||
998 | * |
||
999 | * @param array $list |
||
1000 | * @return array list of \MongoId |
||
1001 | */ |
||
1002 | public static function mixedToMongoIdList(array $list) |
||
1041 | } |
||
1042 |
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.