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 |
||
41 | class Cursor implements CursorInterface |
||
42 | { |
||
43 | /** |
||
44 | * The Doctrine\MongoDB\Cursor instance being wrapped. |
||
45 | * |
||
46 | * @var CursorInterface |
||
47 | */ |
||
48 | private $baseCursor; |
||
49 | |||
50 | /** |
||
51 | * The ClassMetadata instance for the document class being queried. |
||
52 | * |
||
53 | * @var ClassMetadata |
||
54 | */ |
||
55 | private $class; |
||
56 | |||
57 | /** |
||
58 | * Whether or not to hydrate results as document class instances. |
||
59 | * |
||
60 | * @var boolean |
||
61 | */ |
||
62 | private $hydrate = true; |
||
63 | |||
64 | /** |
||
65 | * The UnitOfWork instance used for result hydration and preparing arguments |
||
66 | * for {@link Cursor::sort()}. |
||
67 | * |
||
68 | * @var UnitOfWork |
||
69 | */ |
||
70 | private $unitOfWork; |
||
71 | |||
72 | /** |
||
73 | * Hints for UnitOfWork behavior. |
||
74 | * |
||
75 | * @var array |
||
76 | */ |
||
77 | private $unitOfWorkHints = array(); |
||
78 | |||
79 | /** |
||
80 | * ReferencePrimer object for priming references |
||
81 | * |
||
82 | * @var ReferencePrimer |
||
83 | */ |
||
84 | private $referencePrimer; |
||
85 | |||
86 | /** |
||
87 | * Primers |
||
88 | * |
||
89 | * @var array |
||
90 | */ |
||
91 | private $primers = array(); |
||
92 | |||
93 | /** |
||
94 | * Whether references have been primed |
||
95 | * |
||
96 | * @var bool |
||
97 | */ |
||
98 | private $referencesPrimed = false; |
||
99 | |||
100 | /** |
||
101 | * Constructor. |
||
102 | * |
||
103 | * @param CursorInterface $baseCursor Cursor instance being wrapped |
||
104 | * @param UnitOfWork $unitOfWork UnitOfWork for result hydration and query preparation |
||
105 | * @param ClassMetadata $class ClassMetadata for the document class being queried |
||
106 | */ |
||
107 | 149 | public function __construct(CursorInterface $baseCursor, UnitOfWork $unitOfWork, ClassMetadata $class) |
|
113 | |||
114 | /** |
||
115 | * Return the wrapped Doctrine\MongoDB\Cursor instance. |
||
116 | * |
||
117 | * @return CursorInterface |
||
118 | */ |
||
119 | public function getBaseCursor() |
||
123 | |||
124 | /** |
||
125 | * Return the database connection for this cursor. |
||
126 | * |
||
127 | * @see \Doctrine\MongoDB\Cursor::getConnection() |
||
128 | * @return Connection |
||
129 | */ |
||
130 | public function getConnection() |
||
134 | |||
135 | /** |
||
136 | * Return the collection for this cursor. |
||
137 | * |
||
138 | * @see CursorInterface::getCollection() |
||
139 | * @return Collection |
||
140 | */ |
||
141 | public function getCollection() |
||
145 | |||
146 | /** |
||
147 | * Return the selected fields (projection). |
||
148 | * |
||
149 | * @see CursorInterface::getFields() |
||
150 | * @return array |
||
151 | */ |
||
152 | public function getFields() |
||
156 | |||
157 | /** |
||
158 | * Get hints for UnitOfWork behavior. |
||
159 | * |
||
160 | * @return array |
||
161 | */ |
||
162 | 18 | public function getHints() |
|
166 | |||
167 | /** |
||
168 | * Set hints for UnitOfWork behavior. |
||
169 | * |
||
170 | * @param array $hints |
||
171 | */ |
||
172 | 133 | public function setHints(array $hints) |
|
176 | |||
177 | /** |
||
178 | * Return the query criteria. |
||
179 | * |
||
180 | * @see CursorInterface::getQuery() |
||
181 | * @return array |
||
182 | */ |
||
183 | public function getQuery() |
||
187 | |||
188 | /** |
||
189 | * Wrapper method for MongoCursor::addOption(). |
||
190 | * |
||
191 | * @see CursorInterface::addOption() |
||
192 | * @see http://php.net/manual/en/mongocursor.addoption.php |
||
193 | * @param string $key |
||
194 | * @param mixed $value |
||
195 | * @return $this |
||
196 | */ |
||
197 | public function addOption($key, $value) |
||
202 | |||
203 | /** |
||
204 | * Wrapper method for MongoCursor::batchSize(). |
||
205 | * |
||
206 | * @see CursorInterface::batchSize() |
||
207 | * @see http://php.net/manual/en/mongocursor.batchsize.php |
||
208 | * @param integer $num |
||
209 | * @return $this |
||
210 | */ |
||
211 | public function batchSize($num) |
||
216 | |||
217 | /** |
||
218 | * Wrapper method for MongoCursor::count(). |
||
219 | * |
||
220 | * @see CursorInterface::count() |
||
221 | * @see http://php.net/manual/en/countable.count.php |
||
222 | * @see http://php.net/manual/en/mongocursor.count.php |
||
223 | * @param boolean $foundOnly |
||
224 | * @return integer |
||
225 | */ |
||
226 | 25 | public function count($foundOnly = false) |
|
230 | |||
231 | /** |
||
232 | * Wrapper method for MongoCursor::current(). |
||
233 | * |
||
234 | * If configured, the result may be a hydrated document class instance. |
||
235 | * |
||
236 | * @see CursorInterface::current() |
||
237 | * @see http://php.net/manual/en/iterator.current.php |
||
238 | * @see http://php.net/manual/en/mongocursor.current.php |
||
239 | * @return array|object|null |
||
240 | */ |
||
241 | 60 | public function current() |
|
247 | |||
248 | /** |
||
249 | * Wrapper method for MongoCursor::dead(). |
||
250 | * |
||
251 | * @see CursorInterface::dead() |
||
252 | * @see http://php.net/manual/en/mongocursor.dead.php |
||
253 | * @return boolean |
||
254 | */ |
||
255 | public function dead() |
||
259 | |||
260 | /** |
||
261 | * Wrapper method for MongoCursor::explain(). |
||
262 | * |
||
263 | * @see CursorInterface::explain() |
||
264 | * @see http://php.net/manual/en/mongocursor.explain.php |
||
265 | * @return array |
||
266 | */ |
||
267 | public function explain() |
||
271 | |||
272 | /** |
||
273 | * Wrapper method for MongoCursor::fields(). |
||
274 | * |
||
275 | * @param array $f Fields to return (or not return). |
||
276 | * |
||
277 | * @see CursorInterface::fields() |
||
278 | * @see http://php.net/manual/en/mongocursor.fields.php |
||
279 | * @return $this |
||
280 | */ |
||
281 | public function fields(array $f) |
||
286 | |||
287 | /** |
||
288 | * Wrapper method for MongoCursor::getNext(). |
||
289 | * |
||
290 | * If configured, the result may be a hydrated document class instance. |
||
291 | * |
||
292 | * @see CursorInterface::getNext() |
||
293 | * @see http://php.net/manual/en/mongocursor.getnext.php |
||
294 | * @return array|object|null |
||
295 | */ |
||
296 | 3 | public function getNext() |
|
302 | |||
303 | /** |
||
304 | * Wrapper method for MongoCursor::getReadPreference(). |
||
305 | * |
||
306 | * @see CursorInterface::getReadPreference() |
||
307 | * @see http://php.net/manual/en/mongocursor.getreadpreference.php |
||
308 | * @return array |
||
309 | */ |
||
310 | public function getReadPreference() |
||
314 | |||
315 | /** |
||
316 | * Wrapper method for MongoCursor::setReadPreference(). |
||
317 | * |
||
318 | * @see CursorInterface::setReadPreference() |
||
319 | * @see http://php.net/manual/en/mongocursor.setreadpreference.php |
||
320 | * @param string $readPreference |
||
321 | * @param array $tags |
||
322 | * @return $this |
||
323 | */ |
||
324 | public function setReadPreference($readPreference, array $tags = null) |
||
331 | |||
332 | /** |
||
333 | * Reset the cursor and return its first result. |
||
334 | * |
||
335 | * The cursor will be reset both before and after the single result is |
||
336 | * fetched. The original cursor limit (if any) will remain in place. |
||
337 | * |
||
338 | * @see Iterator::getSingleResult() |
||
339 | * @return array|object|null |
||
340 | */ |
||
341 | 89 | public function getSingleResult() |
|
348 | |||
349 | /** |
||
350 | * {@inheritDoc} |
||
351 | */ |
||
352 | 54 | public function getUseIdentifierKeys() |
|
356 | |||
357 | /** |
||
358 | * {@inheritDoc} |
||
359 | */ |
||
360 | 54 | public function setUseIdentifierKeys($useIdentifierKeys) |
|
366 | |||
367 | /** |
||
368 | * {@inheritDoc} |
||
369 | */ |
||
370 | public function hasNext() |
||
374 | |||
375 | /** |
||
376 | * Wrapper method for MongoCursor::hint(). |
||
377 | * |
||
378 | * This method is intended for setting MongoDB query hints, which are |
||
379 | * unrelated to UnitOfWork hints. |
||
380 | * |
||
381 | * @see CursorInterface::hint() |
||
382 | * @see http://php.net/manual/en/mongocursor.hint.php |
||
383 | * @param array|string $keyPattern |
||
384 | * @return $this |
||
385 | */ |
||
386 | public function hint($keyPattern) |
||
391 | |||
392 | /** |
||
393 | * Set whether to hydrate results as document class instances. |
||
394 | * |
||
395 | * @param boolean $hydrate |
||
396 | * @return $this |
||
397 | */ |
||
398 | 133 | public function hydrate($hydrate = true) |
|
403 | |||
404 | /** |
||
405 | * @param array $document |
||
406 | * @return array|object|null |
||
407 | */ |
||
408 | 133 | private function hydrateDocument($document) |
|
416 | |||
417 | /** |
||
418 | * Wrapper method for MongoCursor::immortal(). |
||
419 | * |
||
420 | * @see CursorInterface::immortal() |
||
421 | * @see http://php.net/manual/en/mongocursor.immortal.php |
||
422 | * @param boolean $liveForever |
||
423 | * @return $this |
||
424 | */ |
||
425 | public function immortal($liveForever = true) |
||
430 | |||
431 | /** |
||
432 | * Wrapper method for MongoCursor::info(). |
||
433 | * |
||
434 | * @see CursorInterface::info() |
||
435 | * @see http://php.net/manual/en/mongocursor.info.php |
||
436 | * @return array |
||
437 | */ |
||
438 | public function info() |
||
442 | |||
443 | /** |
||
444 | * Wrapper method for MongoCursor::key(). |
||
445 | * |
||
446 | * @see CursorInterface::key() |
||
447 | * @see http://php.net/manual/en/iterator.key.php |
||
448 | * @see http://php.net/manual/en/mongocursor.key.php |
||
449 | * @return string |
||
450 | */ |
||
451 | 5 | public function key() |
|
455 | |||
456 | /** |
||
457 | * Wrapper method for MongoCursor::limit(). |
||
458 | * |
||
459 | * @see CursorInterface::limit() |
||
460 | * @see http://php.net/manual/en/mongocursor.limit.php |
||
461 | * @param integer $num |
||
462 | * @return $this |
||
463 | */ |
||
464 | 13 | public function limit($num) |
|
469 | |||
470 | /** |
||
471 | * Wrapper method for MongoCursor::next(). |
||
472 | * |
||
473 | * @see CursorInterface::next() |
||
474 | * @see http://php.net/manual/en/iterator.next.php |
||
475 | * @see http://php.net/manual/en/mongocursor.next.php |
||
476 | */ |
||
477 | 58 | public function next() |
|
481 | |||
482 | /** |
||
483 | * Recreates the internal MongoCursor. |
||
484 | * |
||
485 | * @see CursorInterface::recreate() |
||
486 | */ |
||
487 | 2 | public function recreate() |
|
491 | |||
492 | /** |
||
493 | * Set whether to refresh hydrated documents that are already in the |
||
494 | * identity map. |
||
495 | * |
||
496 | * This option has no effect if hydration is disabled. |
||
497 | * |
||
498 | * @param boolean $refresh |
||
499 | * @return $this |
||
500 | */ |
||
501 | public function refresh($refresh = true) |
||
506 | |||
507 | /** |
||
508 | * Wrapper method for MongoCursor::reset(). |
||
509 | * |
||
510 | * @see CursorInterface::reset() |
||
511 | * @see http://php.net/manual/en/iterator.reset.php |
||
512 | * @see http://php.net/manual/en/mongocursor.reset.php |
||
513 | */ |
||
514 | 1 | public function reset() |
|
518 | |||
519 | /** |
||
520 | * Wrapper method for MongoCursor::rewind(). |
||
521 | * |
||
522 | * @see CursorInterface::rewind() |
||
523 | * @see http://php.net/manual/en/iterator.rewind.php |
||
524 | * @see http://php.net/manual/en/mongocursor.rewind.php |
||
525 | */ |
||
526 | 61 | public function rewind() |
|
530 | |||
531 | /** |
||
532 | * Wrapper method for MongoCursor::skip(). |
||
533 | * |
||
534 | * @see CursorInterface::skip() |
||
535 | * @see http://php.net/manual/en/mongocursor.skip.php |
||
536 | * @param integer $num |
||
537 | * @return $this |
||
538 | */ |
||
539 | 2 | public function skip($num) |
|
544 | |||
545 | /** |
||
546 | * Wrapper method for MongoCursor::slaveOkay(). |
||
547 | * |
||
548 | * @see CursorInterface::slaveOkay() |
||
549 | * @see http://php.net/manual/en/mongocursor.slaveokay.php |
||
550 | * @param boolean $ok |
||
551 | * @return $this |
||
552 | * |
||
553 | * @deprecated in version 1.2 - use setReadPreference instead. |
||
554 | */ |
||
555 | public function slaveOkay($ok = true) |
||
566 | |||
567 | /** |
||
568 | * Wrapper method for MongoCursor::snapshot(). |
||
569 | * |
||
570 | * @see CursorInterface::snapshot() |
||
571 | * @see http://php.net/manual/en/mongocursor.snapshot.php |
||
572 | * @return $this |
||
573 | */ |
||
574 | public function snapshot() |
||
579 | |||
580 | /** |
||
581 | * Wrapper method for MongoCursor::sort(). |
||
582 | * |
||
583 | * Field names will be prepared according to the document mapping. |
||
584 | * |
||
585 | * @see CursorInterface::sort() |
||
586 | * @see http://php.net/manual/en/mongocursor.sort.php |
||
587 | * @param array $fields |
||
588 | * @return $this |
||
589 | */ |
||
590 | 14 | public function sort($fields) |
|
599 | |||
600 | /** |
||
601 | * Wrapper method for MongoCursor::tailable(). |
||
602 | * |
||
603 | * @see CursorInterface::tailable() |
||
604 | * @see http://php.net/manual/en/mongocursor.tailable.php |
||
605 | * @param boolean $tail |
||
606 | * @return $this |
||
607 | */ |
||
608 | public function tailable($tail = true) |
||
613 | |||
614 | /** |
||
615 | * Wrapper method for MongoCursor::timeout(). |
||
616 | * |
||
617 | * @see CursorInterface::timeout() |
||
618 | * @see http://php.net/manual/en/mongocursor.timeout.php |
||
619 | * @param integer $ms |
||
620 | * @return $this |
||
621 | */ |
||
622 | public function timeout($ms) |
||
627 | |||
628 | /** |
||
629 | * Return the cursor's results as an array. |
||
630 | * |
||
631 | * If documents in the result set use BSON objects for their "_id", the |
||
632 | * $useKeys parameter may be set to false to avoid errors attempting to cast |
||
633 | * arrays (i.e. BSON objects) to string keys. |
||
634 | * |
||
635 | * @see Iterator::toArray() |
||
636 | * @param boolean $useIdentifierKeys |
||
637 | * @return array |
||
638 | */ |
||
639 | 54 | public function toArray($useIdentifierKeys = null) |
|
656 | |||
657 | /** |
||
658 | * Wrapper method for MongoCursor::valid(). |
||
659 | * |
||
660 | * @see CursorInterface::valid() |
||
661 | * @see http://php.net/manual/en/iterator.valid.php |
||
662 | * @see http://php.net/manual/en/mongocursor.valid.php |
||
663 | * @return boolean |
||
664 | */ |
||
665 | 65 | public function valid() |
|
669 | |||
670 | /** |
||
671 | * @param array $primers |
||
672 | * @param ReferencePrimer $referencePrimer |
||
673 | * @return $this |
||
674 | */ |
||
675 | 23 | public function enableReferencePriming(array $primers, ReferencePrimer $referencePrimer) |
|
685 | |||
686 | /** |
||
687 | * Prime references |
||
688 | */ |
||
689 | 62 | protected function primeReferences() |
|
704 | |||
705 | /** |
||
706 | * Primes all references for a single document only. This avoids iterating |
||
707 | * over the entire cursor when getSingleResult() is called. |
||
708 | * |
||
709 | * @param object $document |
||
710 | */ |
||
711 | 89 | protected function primeReferencesForSingleResult($document) |
|
722 | } |
||
723 |
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.