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 Type 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 Type, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
25 | class Type implements SearchableInterface |
||
26 | { |
||
27 | /** |
||
28 | * Index. |
||
29 | * |
||
30 | * @var \Elastica\Index Index object |
||
31 | */ |
||
32 | protected $_index; |
||
33 | |||
34 | /** |
||
35 | * Type name. |
||
36 | * |
||
37 | * @var string Type name |
||
38 | */ |
||
39 | protected $_name; |
||
40 | |||
41 | /** |
||
42 | * @var array|string A callable that serializes an object passed to it |
||
43 | */ |
||
44 | protected $_serializer; |
||
45 | |||
46 | /** |
||
47 | * Creates a new type object inside the given index. |
||
48 | * |
||
49 | * @param \Elastica\Index $index Index Object |
||
50 | * @param string $name Type name |
||
51 | */ |
||
52 | public function __construct(Index $index, $name) |
||
53 | { |
||
54 | $this->_index = $index; |
||
55 | $this->_name = $name; |
||
56 | } |
||
57 | |||
58 | /** |
||
59 | * Adds the given document to the search index. |
||
60 | * |
||
61 | * @param \Elastica\Document $doc Document with data |
||
62 | * |
||
63 | * @return \Elastica\Response |
||
64 | */ |
||
65 | public function addDocument(Document $doc) |
||
66 | { |
||
67 | $endpoint = new \Elasticsearch\Endpoints\Index(); |
||
68 | |||
69 | if (null !== $doc->getId() && '' !== $doc->getId()) { |
||
70 | $endpoint->setID($doc->getId()); |
||
71 | } |
||
72 | |||
73 | $options = $doc->getOptions( |
||
74 | [ |
||
75 | 'version', |
||
76 | 'version_type', |
||
77 | 'routing', |
||
78 | 'percolate', |
||
79 | 'parent', |
||
80 | 'op_type', |
||
81 | 'consistency', |
||
82 | 'replication', |
||
83 | 'refresh', |
||
84 | 'timeout', |
||
85 | ] |
||
86 | ); |
||
87 | |||
88 | $endpoint->setBody($doc->getData()); |
||
|
|||
89 | $endpoint->setParams($options); |
||
90 | |||
91 | $response = $this->requestEndpoint($endpoint); |
||
92 | |||
93 | $data = $response->getData(); |
||
94 | // set autogenerated id to document |
||
95 | if (($doc->isAutoPopulate() |
||
96 | || $this->getIndex()->getClient()->getConfigValue(['document', 'autoPopulate'], false)) |
||
97 | && $response->isOk() |
||
98 | ) { |
||
99 | if (!$doc->hasId()) { |
||
100 | if (isset($data['_id'])) { |
||
101 | $doc->setId($data['_id']); |
||
102 | } |
||
103 | } |
||
104 | if (isset($data['_version'])) { |
||
105 | $doc->setVersion($data['_version']); |
||
106 | } |
||
107 | } |
||
108 | |||
109 | return $response; |
||
110 | } |
||
111 | |||
112 | /** |
||
113 | * @param $object |
||
114 | * @param Document $doc |
||
115 | * |
||
116 | * @throws Exception\RuntimeException |
||
117 | * |
||
118 | * @return Response |
||
119 | */ |
||
120 | public function addObject($object, Document $doc = null) |
||
121 | { |
||
122 | if (!isset($this->_serializer)) { |
||
123 | throw new RuntimeException('No serializer defined'); |
||
124 | } |
||
125 | |||
126 | $data = call_user_func($this->_serializer, $object); |
||
127 | if (!$doc) { |
||
128 | $doc = new Document(); |
||
129 | } |
||
130 | $doc->setData($data); |
||
131 | |||
132 | return $this->addDocument($doc); |
||
133 | } |
||
134 | |||
135 | /** |
||
136 | * Update document, using update script. Requires elasticsearch >= 0.19.0. |
||
137 | * |
||
138 | * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html |
||
139 | * |
||
140 | * @param \Elastica\Document|\Elastica\Script\AbstractScript $data Document with update data |
||
141 | * @param array $options array of query params to use for query. For possible options check es api |
||
142 | * |
||
143 | * @throws \Elastica\Exception\InvalidException |
||
144 | * |
||
145 | * @return \Elastica\Response |
||
146 | */ |
||
147 | public function updateDocument($data, array $options = []) |
||
148 | { |
||
149 | if (!($data instanceof Document) && !($data instanceof AbstractScript)) { |
||
150 | throw new \InvalidArgumentException('Data should be a Document or Script'); |
||
151 | } |
||
152 | |||
153 | if (!$data->hasId()) { |
||
154 | throw new InvalidException('Document or Script id is not set'); |
||
155 | } |
||
156 | |||
157 | return $this->getIndex()->getClient()->updateDocument( |
||
158 | $data->getId(), |
||
159 | $data, |
||
160 | $this->getIndex()->getName(), |
||
161 | $this->getName(), |
||
162 | $options |
||
163 | ); |
||
164 | } |
||
165 | |||
166 | /** |
||
167 | * Uses _bulk to send documents to the server. |
||
168 | * |
||
169 | * @param array|\Elastica\Document[] $docs Array of Elastica\Document |
||
170 | * |
||
171 | * @return \Elastica\Bulk\ResponseSet |
||
172 | * |
||
173 | * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html |
||
174 | */ |
||
175 | public function updateDocuments(array $docs) |
||
176 | { |
||
177 | foreach ($docs as $doc) { |
||
178 | $doc->setType($this->getName()); |
||
179 | } |
||
180 | |||
181 | return $this->getIndex()->updateDocuments($docs); |
||
182 | } |
||
183 | |||
184 | /** |
||
185 | * Uses _bulk to send documents to the server. |
||
186 | * |
||
187 | * @param array|\Elastica\Document[] $docs Array of Elastica\Document |
||
188 | * |
||
189 | * @return \Elastica\Bulk\ResponseSet |
||
190 | * |
||
191 | * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html |
||
192 | */ |
||
193 | public function addDocuments(array $docs) |
||
194 | { |
||
195 | foreach ($docs as $doc) { |
||
196 | $doc->setType($this->getName()); |
||
197 | } |
||
198 | |||
199 | return $this->getIndex()->addDocuments($docs); |
||
200 | } |
||
201 | |||
202 | /** |
||
203 | * Uses _bulk to send documents to the server. |
||
204 | * |
||
205 | * @param objects[] $objects |
||
206 | * |
||
207 | * @return \Elastica\Bulk\ResponseSet |
||
208 | * |
||
209 | * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html |
||
210 | */ |
||
211 | public function addObjects(array $objects) |
||
212 | { |
||
213 | if (!isset($this->_serializer)) { |
||
214 | throw new RuntimeException('No serializer defined'); |
||
215 | } |
||
216 | |||
217 | $docs = []; |
||
218 | foreach ($objects as $object) { |
||
219 | $data = call_user_func($this->_serializer, $object); |
||
220 | $doc = new Document(); |
||
221 | $doc->setData($data); |
||
222 | $doc->setType($this->getName()); |
||
223 | $docs[] = $doc; |
||
224 | } |
||
225 | |||
226 | return $this->getIndex()->addDocuments($docs); |
||
227 | } |
||
228 | |||
229 | /** |
||
230 | * Get the document from search index. |
||
231 | * |
||
232 | * @param string $id Document id |
||
233 | * @param array $options Options for the get request. |
||
234 | * |
||
235 | * @throws \Elastica\Exception\NotFoundException |
||
236 | * @throws \Elastica\Exception\ResponseException |
||
237 | * |
||
238 | * @return \Elastica\Document |
||
239 | */ |
||
240 | public function getDocument($id, $options = []) |
||
241 | { |
||
242 | $endpoint = new \Elasticsearch\Endpoints\Get(); |
||
243 | $endpoint->setID($id); |
||
244 | $endpoint->setParams($options); |
||
245 | |||
246 | $response = $this->requestEndpoint($endpoint); |
||
247 | $result = $response->getData(); |
||
248 | |||
249 | if (!isset($result['found']) || $result['found'] === false) { |
||
250 | throw new NotFoundException('doc id '.$id.' not found'); |
||
251 | } |
||
252 | |||
253 | if (isset($result['fields'])) { |
||
254 | $data = $result['fields']; |
||
255 | } elseif (isset($result['_source'])) { |
||
256 | $data = $result['_source']; |
||
257 | } else { |
||
258 | $data = []; |
||
259 | } |
||
260 | |||
261 | $document = new Document($id, $data, $this->getName(), $this->getIndex()); |
||
262 | $document->setVersion($result['_version']); |
||
263 | |||
264 | return $document; |
||
265 | } |
||
266 | |||
267 | /** |
||
268 | * @param string $id |
||
269 | * @param array|string $data |
||
270 | * |
||
271 | * @return Document |
||
272 | */ |
||
273 | public function createDocument($id = '', $data = []) |
||
274 | { |
||
275 | $document = new Document($id, $data); |
||
276 | $document->setType($this); |
||
277 | |||
278 | return $document; |
||
279 | } |
||
280 | |||
281 | /** |
||
282 | * Returns the type name. |
||
283 | * |
||
284 | * @return string Type name |
||
285 | */ |
||
286 | public function getName() |
||
287 | { |
||
288 | return $this->_name; |
||
289 | } |
||
290 | |||
291 | /** |
||
292 | * Sets value type mapping for this type. |
||
293 | * |
||
294 | * @param \Elastica\Type\Mapping|array $mapping Elastica\Type\MappingType object or property array with all mappings |
||
295 | * |
||
296 | * @return \Elastica\Response |
||
297 | */ |
||
298 | public function setMapping($mapping) |
||
299 | { |
||
300 | $mapping = Mapping::create($mapping); |
||
301 | $mapping->setType($this); |
||
302 | |||
303 | return $mapping->send(); |
||
304 | } |
||
305 | |||
306 | /** |
||
307 | * Returns current mapping for the given type. |
||
308 | * |
||
309 | * @return array Current mapping |
||
310 | */ |
||
311 | View Code Duplication | public function getMapping() |
|
312 | { |
||
313 | $response = $this->requestEndpoint(new Get()); |
||
314 | $data = $response->getData(); |
||
315 | |||
316 | $mapping = array_shift($data); |
||
317 | if (isset($mapping['mappings'])) { |
||
318 | return $mapping['mappings']; |
||
319 | } |
||
320 | |||
321 | return []; |
||
322 | } |
||
323 | |||
324 | /** |
||
325 | * Create search object. |
||
326 | * |
||
327 | * @param string|array|\Elastica\Query $query Array with all query data inside or a Elastica\Query object |
||
328 | * @param int|array $options OPTIONAL Limit or associative array of options (option=>value) |
||
329 | * @param BuilderInterface $builder |
||
330 | * |
||
331 | * @return Search |
||
332 | */ |
||
333 | public function createSearch($query = '', $options = null, BuilderInterface $builder = null) |
||
334 | { |
||
335 | $search = $this->getIndex()->createSearch($query, $options, $builder); |
||
336 | $search->addType($this); |
||
337 | |||
338 | return $search; |
||
339 | } |
||
340 | |||
341 | /** |
||
342 | * Do a search on this type. |
||
343 | * |
||
344 | * @param string|array|\Elastica\Query $query Array with all query data inside or a Elastica\Query object |
||
345 | * @param int|array $options OPTIONAL Limit or associative array of options (option=>value) |
||
346 | * |
||
347 | * @return \Elastica\ResultSet with all results inside |
||
348 | * |
||
349 | * @see \Elastica\SearchableInterface::search |
||
350 | */ |
||
351 | public function search($query = '', $options = null) |
||
352 | { |
||
353 | $search = $this->createSearch($query, $options); |
||
354 | |||
355 | return $search->search(); |
||
356 | } |
||
357 | |||
358 | /** |
||
359 | * Count docs by query. |
||
360 | * |
||
361 | * @param string|array|\Elastica\Query $query Array with all query data inside or a Elastica\Query object |
||
362 | * |
||
363 | * @return int number of documents matching the query |
||
364 | * |
||
365 | * @see \Elastica\SearchableInterface::count |
||
366 | */ |
||
367 | public function count($query = '') |
||
368 | { |
||
369 | $search = $this->createSearch($query); |
||
370 | |||
371 | return $search->count(); |
||
372 | } |
||
373 | |||
374 | /** |
||
375 | * Returns index client. |
||
376 | * |
||
377 | * @return \Elastica\Index Index object |
||
378 | */ |
||
379 | public function getIndex() |
||
380 | { |
||
381 | return $this->_index; |
||
382 | } |
||
383 | |||
384 | /** |
||
385 | * @param \Elastica\Document $document |
||
386 | * |
||
387 | * @return \Elastica\Response |
||
388 | */ |
||
389 | public function deleteDocument(Document $document) |
||
390 | { |
||
391 | $options = $document->getOptions( |
||
392 | [ |
||
393 | 'version', |
||
394 | 'version_type', |
||
395 | 'routing', |
||
396 | 'parent', |
||
397 | 'replication', |
||
398 | 'consistency', |
||
399 | 'refresh', |
||
400 | 'timeout', |
||
401 | ] |
||
402 | ); |
||
403 | |||
404 | return $this->deleteById($document->getId(), $options); |
||
405 | } |
||
406 | |||
407 | /** |
||
408 | * Uses _bulk to delete documents from the server. |
||
409 | * |
||
410 | * @param array|\Elastica\Document[] $docs Array of Elastica\Document |
||
411 | * |
||
412 | * @return \Elastica\Bulk\ResponseSet |
||
413 | * |
||
414 | * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html |
||
415 | */ |
||
416 | public function deleteDocuments(array $docs) |
||
417 | { |
||
418 | foreach ($docs as $doc) { |
||
419 | $doc->setType($this->getName()); |
||
420 | } |
||
421 | |||
422 | return $this->getIndex()->deleteDocuments($docs); |
||
423 | } |
||
424 | |||
425 | /** |
||
426 | * Deletes an entry by its unique identifier. |
||
427 | * |
||
428 | * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete.html |
||
429 | * |
||
430 | * @param int|string $id Document id |
||
431 | * @param array $options |
||
432 | * |
||
433 | * @throws \InvalidArgumentException |
||
434 | * @throws \Elastica\Exception\NotFoundException |
||
435 | * |
||
436 | * @return \Elastica\Response Response object |
||
437 | */ |
||
438 | public function deleteById($id, array $options = []) |
||
439 | { |
||
440 | if (empty($id) || !trim($id)) { |
||
441 | throw new \InvalidArgumentException(); |
||
442 | } |
||
443 | |||
444 | $endpoint = new Delete(); |
||
445 | $endpoint->setID($id); |
||
446 | $endpoint->setParams($options); |
||
447 | |||
448 | $response = $this->requestEndpoint($endpoint); |
||
449 | |||
450 | $responseData = $response->getData(); |
||
451 | |||
452 | if (isset($responseData['found']) && false == $responseData['found']) { |
||
453 | throw new NotFoundException('Doc id '.$id.' not found and can not be deleted'); |
||
454 | } |
||
455 | |||
456 | return $response; |
||
457 | } |
||
458 | |||
459 | /** |
||
460 | * Deletes the given list of ids from this type. |
||
461 | * |
||
462 | * @param array $ids |
||
463 | * @param string|bool $routing Optional routing key for all ids |
||
464 | * |
||
465 | * @return \Elastica\Response Response object |
||
466 | */ |
||
467 | public function deleteIds(array $ids, $routing = false) |
||
468 | { |
||
469 | return $this->getIndex()->getClient()->deleteIds($ids, $this->getIndex(), $this, $routing); |
||
470 | } |
||
471 | |||
472 | /** |
||
473 | * Deletes entries in the db based on a query. |
||
474 | * |
||
475 | * @param \Elastica\Query|string $query Query object |
||
476 | * @param array $options Optional params |
||
477 | * |
||
478 | * @return \Elastica\Response |
||
479 | * |
||
480 | * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html |
||
481 | */ |
||
482 | public function deleteByQuery($query, array $options = []) |
||
483 | { |
||
484 | $query = Query::create($query); |
||
485 | |||
486 | $endpoint = new DeleteByQuery(); |
||
487 | $endpoint->setBody($query->toArray()); |
||
488 | $endpoint->setParams($options); |
||
489 | |||
490 | return $this->requestEndpoint($endpoint); |
||
491 | } |
||
492 | |||
493 | /** |
||
494 | * Makes calls to the elasticsearch server based on this type. |
||
495 | * |
||
496 | * @param string $path Path to call |
||
497 | * @param string $method Rest method to use (GET, POST, DELETE, PUT) |
||
498 | * @param array $data OPTIONAL Arguments as array |
||
499 | * @param array $query OPTIONAL Query params |
||
500 | * |
||
501 | * @return \Elastica\Response Response object |
||
502 | */ |
||
503 | public function request($path, $method, $data = [], array $query = []) |
||
504 | { |
||
505 | $path = $this->getName().'/'.$path; |
||
506 | |||
507 | return $this->getIndex()->request($path, $method, $data, $query); |
||
508 | } |
||
509 | |||
510 | /** |
||
511 | * Makes calls to the elasticsearch server with usage official client Endpoint based on this type |
||
512 | * |
||
513 | * @param AbstractEndpoint $endpoint |
||
514 | * @return Response |
||
515 | */ |
||
516 | public function requestEndpoint(AbstractEndpoint $endpoint) |
||
517 | { |
||
518 | $cloned = clone $endpoint; |
||
519 | $cloned->setType($this->getName()); |
||
520 | return $this->getIndex()->requestEndpoint($cloned); |
||
521 | } |
||
522 | |||
523 | /** |
||
524 | * Sets the serializer callable used in addObject. |
||
525 | * |
||
526 | * @see \Elastica\Type::addObject |
||
527 | * |
||
528 | * @param array|string $serializer @see \Elastica\Type::_serializer |
||
529 | * |
||
530 | * @return $this |
||
531 | */ |
||
532 | public function setSerializer($serializer) |
||
538 | |||
539 | /** |
||
540 | * Checks if the given type exists in Index. |
||
541 | * |
||
542 | * @return bool True if type exists |
||
543 | */ |
||
544 | public function exists() |
||
545 | { |
||
546 | $response = $this->requestEndpoint(new Exists()); |
||
547 | |||
548 | return $response->getStatus() === 200; |
||
549 | } |
||
550 | } |
||
551 |
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.