Completed
Push — master ( 63553e...643204 )
by Nicolas
01:59
created

Type   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 536
Duplicated Lines 2.24 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 0
Metric Value
wmc 53
lcom 1
cbo 17
dl 12
loc 536
rs 6.96
c 0
b 0
f 0

25 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
B addDocument() 0 47 9
A addObject() 0 14 3
A updateDocument() 0 18 4
A updateDocuments() 0 8 2
A addDocuments() 0 8 2
A addObjects() 0 17 3
A getDocument() 0 26 5
A createDocument() 0 7 1
A getName() 0 4 1
A setMapping() 0 7 1
A createSearch() 0 7 1
A search() 0 6 1
A count() 0 6 1
A getIndex() 0 4 1
A deleteDocument() 0 17 1
A deleteDocuments() 0 8 2
A deleteById() 0 20 5
A deleteIds() 0 4 1
A deleteByQuery() 0 10 1
A request() 0 6 1
A getMapping() 12 12 2
A requestEndpoint() 0 10 2
A setSerializer() 0 6 1
A exists() 0 6 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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
2
3
namespace Elastica;
4
5
use Elastica\Exception\InvalidException;
6
use Elastica\Exception\NotFoundException;
7
use Elastica\Exception\RuntimeException;
8
use Elastica\ResultSet\BuilderInterface;
9
use Elastica\Script\AbstractScript;
10
use Elastica\Type\Mapping;
11
use Elasticsearch\Endpoints\AbstractEndpoint;
12
use Elasticsearch\Endpoints\Delete;
13
use Elasticsearch\Endpoints\DeleteByQuery;
14
use Elasticsearch\Endpoints\Indices\Mapping\Get;
15
use Elasticsearch\Endpoints\Indices\Type\Exists;
16
17
/**
18
 * Elastica type object.
19
 *
20
 * elasticsearch has for every types as a substructure. This object
21
 * represents a type inside a context
22
 * The hierarchy is as following: client -> index -> type -> document
23
 *
24
 * @author   Nicolas Ruflin <[email protected]>
25
 */
26
class Type implements SearchableInterface
27
{
28
    /**
29
     * Index.
30
     *
31
     * @var \Elastica\Index Index object
32
     */
33
    protected $_index;
34
35
    /**
36
     * Type name.
37
     *
38
     * @var string Type name
39
     */
40
    protected $_name;
41
42
    /**
43
     * @var array|string A callable that serializes an object passed to it
44
     */
45
    protected $_serializer;
46
47
    /**
48
     * Creates a new type object inside the given index.
49
     *
50
     * @param \Elastica\Index $index Index Object
51
     * @param string          $name  Type name
52
     */
53
    public function __construct(Index $index, $name)
54
    {
55
        $this->_index = $index;
56
        $this->_name = $name;
57
    }
58
59
    /**
60
     * Adds the given document to the search index.
61
     *
62
     * @param \Elastica\Document $doc Document with data
63
     *
64
     * @return \Elastica\Response
65
     */
66
    public function addDocument(Document $doc)
67
    {
68
        $endpoint = new \Elasticsearch\Endpoints\Index();
69
70
        if (null !== $doc->getId() && '' !== $doc->getId()) {
71
            $endpoint->setID($doc->getId());
72
        }
73
74
        $options = $doc->getOptions(
75
            [
76
                'version',
77
                'version_type',
78
                'routing',
79
                'percolate',
80
                'parent',
81
                'op_type',
82
                'consistency',
83
                'replication',
84
                'refresh',
85
                'timeout',
86
                'pipeline',
87
            ]
88
        );
89
90
        $endpoint->setBody($doc->getData());
0 ignored issues
show
Bug introduced by
It seems like $doc->getData() targeting Elastica\Document::getData() can also be of type string; however, Elasticsearch\Endpoints\Index::setBody() does only seem to accept array, maybe add an additional type check?

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.

Loading history...
91
        $endpoint->setParams($options);
92
93
        $response = $this->requestEndpoint($endpoint);
94
95
        $data = $response->getData();
96
        // set autogenerated id to document
97
        if (($doc->isAutoPopulate()
98
                || $this->getIndex()->getClient()->getConfigValue(['document', 'autoPopulate'], false))
99
            && $response->isOk()
100
        ) {
101
            if (!$doc->hasId()) {
102
                if (isset($data['_id'])) {
103
                    $doc->setId($data['_id']);
104
                }
105
            }
106
            if (isset($data['_version'])) {
107
                $doc->setVersion($data['_version']);
108
            }
109
        }
110
111
        return $response;
112
    }
113
114
    /**
115
     * @param $object
116
     * @param Document $doc
117
     *
118
     * @throws Exception\RuntimeException
119
     *
120
     * @return Response
121
     */
122
    public function addObject($object, Document $doc = null)
123
    {
124
        if (!isset($this->_serializer)) {
125
            throw new RuntimeException('No serializer defined');
126
        }
127
128
        $data = \call_user_func($this->_serializer, $object);
129
        if (!$doc) {
130
            $doc = new Document();
131
        }
132
        $doc->setData($data);
133
134
        return $this->addDocument($doc);
135
    }
136
137
    /**
138
     * Update document, using update script. Requires elasticsearch >= 0.19.0.
139
     *
140
     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html
141
     *
142
     * @param \Elastica\Document|\Elastica\Script\AbstractScript $data    Document with update data
143
     * @param array                                              $options array of query params to use for query. For possible options check es api
144
     *
145
     * @throws \Elastica\Exception\InvalidException
146
     *
147
     * @return \Elastica\Response
148
     */
149
    public function updateDocument($data, array $options = [])
150
    {
151
        if (!($data instanceof Document) && !($data instanceof AbstractScript)) {
152
            throw new \InvalidArgumentException('Data should be a Document or Script');
153
        }
154
155
        if (!$data->hasId()) {
156
            throw new InvalidException('Document or Script id is not set');
157
        }
158
159
        return $this->getIndex()->getClient()->updateDocument(
160
            $data->getId(),
161
            $data,
162
            $this->getIndex()->getName(),
163
            $this->getName(),
164
            $options
165
        );
166
    }
167
168
    /**
169
     * Uses _bulk to send documents to the server.
170
     *
171
     * @param array|\Elastica\Document[] $docs    Array of Elastica\Document
172
     * @param array                      $options Array of query params to use for query. For possible options check es api
173
     *
174
     * @return \Elastica\Bulk\ResponseSet
175
     *
176
     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html
177
     */
178
    public function updateDocuments(array $docs, array $options = [])
179
    {
180
        foreach ($docs as $doc) {
181
            $doc->setType($this->getName());
182
        }
183
184
        return $this->getIndex()->updateDocuments($docs, $options);
185
    }
186
187
    /**
188
     * Uses _bulk to send documents to the server.
189
     *
190
     * @param array|\Elastica\Document[] $docs    Array of Elastica\Document
191
     * @param array                      $options Array of query params to use for query. For possible options check es api
192
     *
193
     * @return \Elastica\Bulk\ResponseSet
194
     *
195
     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html
196
     */
197
    public function addDocuments(array $docs, array $options = [])
198
    {
199
        foreach ($docs as $doc) {
200
            $doc->setType($this->getName());
201
        }
202
203
        return $this->getIndex()->addDocuments($docs, $options);
204
    }
205
206
    /**
207
     * Uses _bulk to send documents to the server.
208
     *
209
     * @param object[] $objects
210
     * @param array    $options Array of query params to use for query. For possible options check es api
211
     *
212
     * @return Bulk\ResponseSet
213
     *
214
     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html
215
     */
216
    public function addObjects(array $objects, array $options = [])
217
    {
218
        if (!isset($this->_serializer)) {
219
            throw new RuntimeException('No serializer defined');
220
        }
221
222
        $docs = [];
223
        foreach ($objects as $object) {
224
            $data = \call_user_func($this->_serializer, $object);
225
            $doc = new Document();
226
            $doc->setData($data);
227
            $doc->setType($this->getName());
228
            $docs[] = $doc;
229
        }
230
231
        return $this->getIndex()->addDocuments($docs, $options);
232
    }
233
234
    /**
235
     * Get the document from search index.
236
     *
237
     * @param int|string $id      Document id
238
     * @param array      $options options for the get request
239
     *
240
     * @throws \Elastica\Exception\NotFoundException
241
     * @throws \Elastica\Exception\ResponseException
242
     *
243
     * @return \Elastica\Document
244
     */
245
    public function getDocument($id, array $options = [])
246
    {
247
        $endpoint = new \Elasticsearch\Endpoints\Get();
248
        $endpoint->setID($id);
249
        $endpoint->setParams($options);
250
251
        $response = $this->requestEndpoint($endpoint);
252
        $result = $response->getData();
253
254
        if (!isset($result['found']) || false === $result['found']) {
255
            throw new NotFoundException('doc id '.$id.' not found');
256
        }
257
258
        if (isset($result['fields'])) {
259
            $data = $result['fields'];
260
        } elseif (isset($result['_source'])) {
261
            $data = $result['_source'];
262
        } else {
263
            $data = [];
264
        }
265
266
        $document = new Document($id, $data, $this->getName(), $this->getIndex());
267
        $document->setVersion($result['_version']);
268
269
        return $document;
270
    }
271
272
    /**
273
     * @param string       $id
274
     * @param array|string $data
275
     *
276
     * @return Document
277
     */
278
    public function createDocument($id = '', $data = [])
279
    {
280
        $document = new Document($id, $data);
281
        $document->setType($this);
282
283
        return $document;
284
    }
285
286
    /**
287
     * Returns the type name.
288
     *
289
     * @return string Type name
290
     */
291
    public function getName()
292
    {
293
        return $this->_name;
294
    }
295
296
    /**
297
     * Sets value type mapping for this type.
298
     *
299
     * @param \Elastica\Type\Mapping|array $mapping Elastica\Type\MappingType object or property array with all mappings
300
     * @param array                        $query   querystring when put mapping (for example update_all_types)
301
     *
302
     * @return \Elastica\Response
303
     */
304
    public function setMapping($mapping, array $query = [])
305
    {
306
        $mapping = Mapping::create($mapping);
307
        $mapping->setType($this);
308
309
        return $mapping->send($query);
310
    }
311
312
    /**
313
     * Returns current mapping for the given type.
314
     *
315
     * @return array Current mapping
316
     */
317 View Code Duplication
    public function getMapping($includeTypeName = true)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
318
    {
319
        $response = $this->requestEndpoint(new Get(), $includeTypeName);
320
        $data = $response->getData();
321
322
        $mapping = \array_shift($data);
323
        if (isset($mapping['mappings'])) {
324
            return $mapping['mappings'];
325
        }
326
327
        return [];
328
    }
329
330
    /**
331
     * Create search object.
332
     *
333
     * @param string|array|\Elastica\Query $query   Array with all query data inside or a Elastica\Query object
334
     * @param int|array                    $options OPTIONAL Limit or associative array of options (option=>value)
335
     * @param BuilderInterface             $builder
336
     *
337
     * @return Search
338
     */
339
    public function createSearch($query = '', $options = null, BuilderInterface $builder = null)
340
    {
341
        $search = $this->getIndex()->createSearch($query, $options, $builder);
342
        $search->addType($this);
343
344
        return $search;
345
    }
346
347
    /**
348
     * Do a search on this type.
349
     *
350
     * @param string|array|\Elastica\Query $query   Array with all query data inside or a Elastica\Query object
351
     * @param int|array                    $options OPTIONAL Limit or associative array of options (option=>value)
352
     *
353
     * @return \Elastica\ResultSet with all results inside
354
     *
355
     * @see \Elastica\SearchableInterface::search
356
     */
357
    public function search($query = '', $options = null)
358
    {
359
        $search = $this->createSearch($query, $options);
360
361
        return $search->search();
362
    }
363
364
    /**
365
     * Count docs by query.
366
     *
367
     * @param string|array|\Elastica\Query $query Array with all query data inside or a Elastica\Query object
368
     *
369
     * @return int number of documents matching the query
370
     *
371
     * @see \Elastica\SearchableInterface::count
372
     */
373
    public function count($query = '')
374
    {
375
        $search = $this->createSearch($query);
376
377
        return $search->count();
0 ignored issues
show
Bug Compatibility introduced by
The expression $search->count(); of type Elastica\ResultSet|integer adds the type Elastica\ResultSet to the return on line 377 which is incompatible with the return type declared by the interface Elastica\SearchableInterface::count of type integer.
Loading history...
378
    }
379
380
    /**
381
     * Returns index client.
382
     *
383
     * @return \Elastica\Index Index object
384
     */
385
    public function getIndex()
386
    {
387
        return $this->_index;
388
    }
389
390
    /**
391
     * @param \Elastica\Document $document
392
     *
393
     * @return \Elastica\Response
394
     */
395
    public function deleteDocument(Document $document)
396
    {
397
        $options = $document->getOptions(
398
            [
399
                '_version',
400
                'version_type',
401
                'routing',
402
                'parent',
403
                'replication',
404
                'consistency',
405
                'refresh',
406
                'timeout',
407
            ]
408
        );
409
410
        return $this->deleteById($document->getId(), $options);
411
    }
412
413
    /**
414
     * Uses _bulk to delete documents from the server.
415
     *
416
     * @param array|\Elastica\Document[] $docs Array of Elastica\Document
417
     *
418
     * @return \Elastica\Bulk\ResponseSet
419
     *
420
     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html
421
     */
422
    public function deleteDocuments(array $docs)
423
    {
424
        foreach ($docs as $doc) {
425
            $doc->setType($this->getName());
426
        }
427
428
        return $this->getIndex()->deleteDocuments($docs);
429
    }
430
431
    /**
432
     * Deletes an entry by its unique identifier.
433
     *
434
     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete.html
435
     *
436
     * @param int|string $id      Document id
437
     * @param array      $options
438
     *
439
     * @throws \InvalidArgumentException
440
     * @throws \Elastica\Exception\NotFoundException
441
     *
442
     * @return \Elastica\Response Response object
443
     */
444
    public function deleteById($id, array $options = [])
445
    {
446
        if (empty($id) || !\trim($id)) {
447
            throw new \InvalidArgumentException();
448
        }
449
450
        $endpoint = new Delete();
451
        $endpoint->setID($id);
452
        $endpoint->setParams($options);
453
454
        $response = $this->requestEndpoint($endpoint);
455
456
        $responseData = $response->getData();
457
458
        if (isset($responseData['result']) && 'not_found' == $responseData['result']) {
459
            throw new NotFoundException('Doc id '.$id.' not found and can not be deleted');
460
        }
461
462
        return $response;
463
    }
464
465
    /**
466
     * Deletes the given list of ids from this type.
467
     *
468
     * @param array       $ids
469
     * @param string|bool $routing Optional routing key for all ids
470
     *
471
     * @return \Elastica\Response Response  object
472
     */
473
    public function deleteIds(array $ids, $routing = false)
474
    {
475
        return $this->getIndex()->getClient()->deleteIds($ids, $this->getIndex(), $this, $routing);
476
    }
477
478
    /**
479
     * Deletes entries in the db based on a query.
480
     *
481
     * @param \Elastica\Query|\Elastica\Query\AbstractQuery|string|array $query   Query object
482
     * @param array                                                      $options Optional params
483
     *
484
     * @return \Elastica\Response
485
     *
486
     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html
487
     */
488
    public function deleteByQuery($query, array $options = [])
489
    {
490
        $query = Query::create($query);
491
492
        $endpoint = new DeleteByQuery();
493
        $endpoint->setBody($query->toArray());
494
        $endpoint->setParams($options);
495
496
        return $this->requestEndpoint($endpoint);
497
    }
498
499
    /**
500
     * Makes calls to the elasticsearch server based on this type.
501
     *
502
     * @param string $path   Path to call
503
     * @param string $method Rest method to use (GET, POST, DELETE, PUT)
504
     * @param array  $data   OPTIONAL Arguments as array
505
     * @param array  $query  OPTIONAL Query params
506
     *
507
     * @return \Elastica\Response Response object
508
     */
509
    public function request($path, $method, $data = [], array $query = [])
510
    {
511
        $path = $this->getName().'/'.$path;
512
513
        return $this->getIndex()->request($path, $method, $data, $query);
514
    }
515
516
    /**
517
     * Makes calls to the elasticsearch server with usage official client Endpoint based on this type.
518
     *
519
     * @param AbstractEndpoint $endpoint
520
     *
521
     * @return Response
522
     */
523
    public function requestEndpoint(AbstractEndpoint $endpoint, $includeTypeName = false)
524
    {
525
        $cloned = clone $endpoint;
526
        $cloned->setType($this->getName());
527
        if ($includeTypeName) {
528
            $cloned->setParams(['include_type_name' => true]);
0 ignored issues
show
Documentation introduced by
array('include_type_name' => true) is of type array<string,boolean,{"i..._type_name":"boolean"}>, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
529
        }
530
531
        return $this->getIndex()->requestEndpoint($cloned);
532
    }
533
534
    /**
535
     * Sets the serializer callable used in addObject.
536
     *
537
     * @see \Elastica\Type::addObject
538
     *
539
     * @param array|string $serializer @see \Elastica\Type::_serializer
540
     *
541
     * @return $this
542
     */
543
    public function setSerializer($serializer)
544
    {
545
        $this->_serializer = $serializer;
546
547
        return $this;
548
    }
549
550
    /**
551
     * Checks if the given type exists in Index.
552
     *
553
     * @return bool True if type exists
554
     */
555
    public function exists()
556
    {
557
        $response = $this->requestEndpoint(new Exists());
558
559
        return 200 === $response->getStatus();
560
    }
561
}
562