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:
1 | <?php |
||
25 | abstract class AbstractCursor |
||
26 | { |
||
27 | use ReadPreference; |
||
28 | |||
29 | /** |
||
30 | * @var int|null |
||
31 | */ |
||
32 | protected $batchSize = null; |
||
33 | |||
34 | /** |
||
35 | * @var Collection |
||
36 | */ |
||
37 | protected $collection; |
||
38 | |||
39 | /** |
||
40 | * @var \MongoClient |
||
41 | */ |
||
42 | protected $connection; |
||
43 | |||
44 | /** |
||
45 | * @var Cursor |
||
46 | */ |
||
47 | protected $cursor; |
||
48 | |||
49 | /** |
||
50 | * @var \MongoDB\Database |
||
51 | */ |
||
52 | protected $db; |
||
53 | |||
54 | /** |
||
55 | * @var \Iterator |
||
56 | */ |
||
57 | protected $iterator; |
||
58 | |||
59 | /** |
||
60 | * @var string |
||
61 | */ |
||
62 | protected $ns; |
||
63 | |||
64 | /** |
||
65 | * @var bool |
||
66 | */ |
||
67 | protected $startedIterating = false; |
||
68 | |||
69 | /** |
||
70 | * @var bool |
||
71 | */ |
||
72 | protected $cursorNeedsAdvancing = true; |
||
73 | |||
74 | /** |
||
75 | * @var mixed |
||
76 | */ |
||
77 | private $current = null; |
||
78 | |||
79 | /** |
||
80 | * @var mixed |
||
81 | */ |
||
82 | private $key = null; |
||
83 | |||
84 | /** |
||
85 | * @var mixed |
||
86 | */ |
||
87 | private $valid = false; |
||
88 | |||
89 | /** |
||
90 | * @var int |
||
91 | */ |
||
92 | protected $position = 0; |
||
93 | |||
94 | /** |
||
95 | * @var array |
||
96 | */ |
||
97 | protected $optionNames = [ |
||
98 | 'batchSize', |
||
99 | 'readPreference', |
||
100 | ]; |
||
101 | |||
102 | /** |
||
103 | * @return Cursor |
||
104 | */ |
||
105 | abstract protected function ensureCursor(); |
||
106 | |||
107 | /** |
||
108 | * @return array |
||
109 | */ |
||
110 | abstract protected function getCursorInfo(); |
||
111 | |||
112 | /** |
||
113 | * Create a new cursor |
||
114 | * @link http://www.php.net/manual/en/mongocursor.construct.php |
||
115 | * @param \MongoClient $connection Database connection. |
||
116 | * @param string $ns Full name of database and collection. |
||
117 | */ |
||
118 | 78 | public function __construct(\MongoClient $connection, $ns) |
|
119 | { |
||
120 | 78 | $this->connection = $connection; |
|
121 | 78 | $this->ns = $ns; |
|
122 | |||
123 | 78 | $nsParts = explode('.', $ns); |
|
124 | 78 | $dbName = array_shift($nsParts); |
|
125 | 78 | $collectionName = implode('.', $nsParts); |
|
126 | |||
127 | 78 | $this->db = $connection->selectDB($dbName)->getDb(); |
|
128 | |||
129 | 78 | if ($collectionName) { |
|
130 | 56 | $this->collection = $connection->selectCollection($dbName, $collectionName)->getCollection(); |
|
131 | 56 | } |
|
132 | 78 | } |
|
133 | |||
134 | /** |
||
135 | * Returns the current element |
||
136 | * @link http://www.php.net/manual/en/mongocursor.current.php |
||
137 | * @return array |
||
138 | */ |
||
139 | 44 | public function current() |
|
140 | { |
||
141 | 44 | return $this->current; |
|
142 | } |
||
143 | |||
144 | /** |
||
145 | * Returns the current result's _id |
||
146 | * @link http://www.php.net/manual/en/mongocursor.key.php |
||
147 | * @return string The current result's _id as a string. |
||
148 | */ |
||
149 | 28 | public function key() |
|
150 | { |
||
151 | 28 | return $this->key; |
|
152 | } |
||
153 | |||
154 | /** |
||
155 | * Advances the cursor to the next result, and returns that result |
||
156 | * @link http://www.php.net/manual/en/mongocursor.next.php |
||
157 | * @throws \MongoConnectionException |
||
158 | * @throws \MongoCursorTimeoutException |
||
159 | * @return array Returns the next object |
||
160 | */ |
||
161 | 46 | public function next() |
|
162 | { |
||
163 | 46 | View Code Duplication | if (! $this->startedIterating) { |
|
|||
164 | 1 | $this->ensureIterator(); |
|
165 | 1 | $this->startedIterating = true; |
|
166 | 1 | } else { |
|
167 | 46 | if ($this->cursorNeedsAdvancing) { |
|
168 | 45 | $this->ensureIterator()->next(); |
|
169 | 45 | } |
|
170 | |||
171 | 46 | $this->cursorNeedsAdvancing = true; |
|
172 | 46 | $this->position++; |
|
173 | } |
||
174 | |||
175 | 46 | return $this->storeIteratorState(); |
|
176 | } |
||
177 | |||
178 | /** |
||
179 | * Returns the cursor to the beginning of the result set |
||
180 | * @throws \MongoConnectionException |
||
181 | * @throws \MongoCursorTimeoutException |
||
182 | * @return void |
||
183 | */ |
||
184 | 63 | public function rewind() |
|
185 | { |
||
186 | // We can recreate the cursor to allow it to be rewound |
||
187 | 63 | $this->reset(); |
|
188 | 63 | $this->startedIterating = true; |
|
189 | 63 | $this->position = 0; |
|
190 | 63 | $this->ensureIterator()->rewind(); |
|
191 | 61 | $this->storeIteratorState(); |
|
192 | 61 | } |
|
193 | |||
194 | /** |
||
195 | * Checks if the cursor is reading a valid result. |
||
196 | * @link http://www.php.net/manual/en/mongocursor.valid.php |
||
197 | * @return boolean If the current result is not null. |
||
198 | */ |
||
199 | 61 | public function valid() |
|
200 | { |
||
201 | 61 | return $this->valid; |
|
202 | } |
||
203 | |||
204 | /** |
||
205 | * Limits the number of elements returned in one batch. |
||
206 | * |
||
207 | * @link http://docs.php.net/manual/en/mongocursor.batchsize.php |
||
208 | * @param int|null $batchSize The number of results to return per batch |
||
209 | * @return $this Returns this cursor. |
||
210 | */ |
||
211 | 2 | public function batchSize($batchSize) |
|
217 | |||
218 | /** |
||
219 | * Checks if there are documents that have not been sent yet from the database for this cursor |
||
220 | * @link http://www.php.net/manual/en/mongocursor.dead.php |
||
221 | * @return boolean Returns if there are more results that have not been sent to the client, yet. |
||
222 | */ |
||
223 | public function dead() |
||
227 | |||
228 | /** |
||
229 | * @return array |
||
230 | */ |
||
231 | 5 | public function info() |
|
235 | |||
236 | /** |
||
237 | * @link http://www.php.net/manual/en/mongocursor.setreadpreference.php |
||
238 | * @param string $readPreference |
||
239 | * @param array $tags |
||
240 | * @return $this Returns this cursor. |
||
241 | */ |
||
242 | 78 | public function setReadPreference($readPreference, $tags = null) |
|
248 | |||
249 | /** |
||
250 | * Sets a client-side timeout for this query |
||
251 | * @link http://www.php.net/manual/en/mongocursor.timeout.php |
||
252 | * @param int $ms The number of milliseconds for the cursor to wait for a response. By default, the cursor will wait forever. |
||
253 | * @return $this Returns this cursor |
||
254 | */ |
||
255 | public function timeout($ms) |
||
256 | { |
||
257 | trigger_error('The ' . __METHOD__ . ' method is not implemented in mongo-php-adapter', E_USER_WARNING); |
||
258 | return $this; |
||
259 | } |
||
260 | |||
261 | /** |
||
262 | * Applies all options set on the cursor, overwriting any options that have already been set |
||
263 | * |
||
264 | * @param array $optionNames Array of option names to be applied (will be read from properties) |
||
265 | * @return array |
||
266 | */ |
||
267 | 72 | protected function getOptions($optionNames = null) |
|
288 | |||
289 | /** |
||
290 | * @return \Iterator |
||
291 | */ |
||
292 | 65 | protected function ensureIterator() |
|
293 | { |
||
294 | 65 | if ($this->iterator === null) { |
|
295 | // MongoDB\Driver\Cursor needs to be wrapped into a \Generator so that a valid \Iterator with working implementations of |
||
296 | // next, current, valid, key and rewind is returned. These methods don't work if we wrap the Cursor inside an \IteratorIterator |
||
297 | 65 | $this->iterator = $this->wrapTraversable($this->ensureCursor()); |
|
298 | 63 | } |
|
299 | |||
300 | 63 | return $this->iterator; |
|
301 | } |
||
302 | |||
303 | /** |
||
304 | * @param \Traversable $traversable |
||
305 | * @return \Generator |
||
306 | */ |
||
307 | 22 | protected function wrapTraversable(\Traversable $traversable) |
|
308 | { |
||
309 | 22 | foreach ($traversable as $key => $value) { |
|
310 | 22 | yield $key => $value; |
|
311 | 22 | } |
|
312 | 22 | } |
|
313 | |||
314 | /** |
||
315 | * @throws \MongoCursorException |
||
316 | */ |
||
317 | 25 | protected function errorIfOpened() |
|
325 | |||
326 | /** |
||
327 | * @return array |
||
328 | */ |
||
329 | 5 | protected function getIterationInfo() |
|
330 | { |
||
331 | $iterationInfo = [ |
||
332 | 5 | 'started_iterating' => $this->cursor !== null, |
|
333 | 5 | ]; |
|
334 | |||
335 | 5 | if ($this->cursor !== null) { |
|
336 | 5 | switch ($this->cursor->getServer()->getType()) { |
|
337 | 5 | case \MongoDB\Driver\Server::TYPE_RS_ARBITER: |
|
338 | $typeString = 'ARBITER'; |
||
339 | break; |
||
340 | 5 | case \MongoDB\Driver\Server::TYPE_MONGOS: |
|
341 | $typeString = 'MONGOS'; |
||
342 | break; |
||
343 | 5 | case \MongoDB\Driver\Server::TYPE_RS_PRIMARY: |
|
344 | $typeString = 'PRIMARY'; |
||
345 | break; |
||
346 | 5 | case \MongoDB\Driver\Server::TYPE_RS_SECONDARY: |
|
347 | $typeString = 'SECONDARY'; |
||
348 | break; |
||
349 | 5 | default: |
|
350 | 5 | $typeString = 'STANDALONE'; |
|
351 | 5 | } |
|
352 | |||
353 | 5 | $cursorId = (string) $this->cursor->getId(); |
|
354 | $iterationInfo += [ |
||
355 | 5 | 'id' => (int) $cursorId, |
|
356 | 5 | 'at' => $this->position, |
|
357 | 5 | 'numReturned' => $this->position, // This can't be obtained from the new cursor |
|
358 | 5 | 'server' => sprintf('%s:%d;-;.;%d', $this->cursor->getServer()->getHost(), $this->cursor->getServer()->getPort(), getmypid()), |
|
359 | 5 | 'host' => $this->cursor->getServer()->getHost(), |
|
360 | 5 | 'port' => $this->cursor->getServer()->getPort(), |
|
361 | 5 | 'connection_type_desc' => $typeString, |
|
362 | ]; |
||
363 | 5 | } |
|
364 | |||
365 | 5 | return $iterationInfo; |
|
366 | } |
||
367 | |||
368 | /** |
||
369 | * @throws \Exception |
||
370 | */ |
||
371 | protected function notImplemented() |
||
375 | |||
376 | /** |
||
377 | * Clears the cursor |
||
378 | * |
||
379 | * This is generic but implemented as protected since it's only exposed in MongoCursor |
||
380 | */ |
||
381 | 64 | protected function reset() |
|
382 | { |
||
383 | 64 | $this->startedIterating = false; |
|
384 | 64 | $this->cursorNeedsAdvancing = true; |
|
385 | 64 | $this->cursor = null; |
|
389 | |||
390 | /** |
||
391 | * @return array |
||
392 | */ |
||
393 | 3 | public function __sleep() |
|
397 | |||
398 | /** |
||
399 | * Stores the current cursor element. |
||
400 | * |
||
401 | * This is necessary because hasNext() might advance the iterator but we still |
||
402 | * need to be able to return the current object. |
||
403 | */ |
||
404 | 65 | protected function storeIteratorState() |
|
423 | } |
||
424 |
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.