GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 2e8805...dcc098 )
by Anderson
08:35
created

ElasticConnection::delete()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 8.6737
c 0
b 0
f 0
cc 5
eloc 13
nc 5
nop 5
1
<?php
2
3
namespace DoctrineElastic\Connection;
4
5
use DoctrineElastic\Exception\ConnectionException;
6
use DoctrineElastic\Exception\ElasticOperationException;
7
use DoctrineElastic\Exception\InvalidIndexNameException;
8
use DoctrineElastic\Exception\InvalidParamsException;
9
use DoctrineElastic\Helper\IndexHelper;
10
use DoctrineElastic\Helper\MappingHelper;
11
use DoctrineElastic\Http\CurlRequest;
12
use DoctrineElastic\Traiting\ErrorGetterTrait;
13
14
/**
15
 * Default elastic connection class for general operations
16
 * Notice that the original elastic result of most of operations can be get by $return param
17
 *
18
 * @author Andsalves <[email protected]>
19
 */
20
class ElasticConnection implements ElasticConnectionInterface {
21
22
    use ErrorGetterTrait;
23
24
    /** Override default elastic limit size query */
25
    const DEFAULT_MAX_RESULTS = 10000;
26
27
    /** @var CurlRequest */
28
    protected $curlRequest;
29
30
    /** @var float */
31
    protected $esVersion;
32
33
    public function __construct($host, $version = null) {
34
        $this->curlRequest = new CurlRequest();
35
        $baseHost = is_array($host) ? reset($host) : $host;
36
37
        if (empty($baseHost) || !is_string($baseHost) || !preg_match('/http/', $baseHost)) {
38
            throw new ConnectionException("Elasticsearch host is invalid. ");
39
        }
40
41
        $this->curlRequest->setBaseUrl($baseHost);
42
43
        if (!is_null($version)) {
44
            $this->esVersion = $version;
45
        }
46
    }
47
48
    /**
49
     * @param string $index
50
     * @param array|null $mappings
51
     * @param array|null $settings
52
     * @param array|null $aliases
53
     * @param array|null $return
54
     * @return bool
55
     */
56
    public function createIndex(
57
        $index, array $mappings = null, array $settings = null, array $aliases = null, array &$return = null
58
    ) {
59
        if ($this->indexExists($index)) {
60
            throw new \InvalidArgumentException(sprintf("'%s' index already exists", $index));
61
        }
62
63
        $params = [];
64
65
        if (is_array($mappings) && !empty($mappings)) {
66
            $params['mappings'] = MappingHelper::patchMappings($mappings, floor($this->getElasticsearchVersion()));
67
        }
68
69
        if (is_array($settings) && !empty($settings)) {
70
            $params['settings'] = $settings;
71
        }
72
73
        if (is_array($aliases) && !empty($aliases)) {
74
            $params['aliases'] = $aliases;
75
        }
76
77
        $response = $this->curlRequest->request($index, $params, 'PUT');
78
        $return = $response['content'];
79
80
        if (isset($return['acknowledged']) && $return['acknowledged']) {
81
            return $return['acknowledged'];
82
        }
83
84
        $this->setErrorFromElasticReturn($return);
85
86
        return false;
87
    }
88
89
    /**
90
     * @param string $index
91
     * @param array|null $return
92
     * @return bool
93
     * @throws ElasticOperationException
94
     */
95
    public function deleteIndex($index, array &$return = null) {
96
        if (boolval($index) && is_string($index) && !strstr('_all', $index) && $index != '*') {
97
            $response = $this->curlRequest->request("$index?refresh=true", [], 'DELETE');
98
            $return = $response['content'];
99
100
            if ($response['status'] == 404) {
101
                throw new ElasticOperationException("Index '$index' doesn't exist so cannot be deleted. ");
102
            }
103
104
            if (isset($return['acknowledged'])) {
105
                return $return['acknowledged'];
106
            }
107
        } else {
108
            $this->setError("Index name is invalid for deletion. Index name: '$index'");
109
            return false;
110
        }
111
112
        $this->setErrorFromElasticReturn($return);
113
114
        return false;
115
    }
116
117
    /**
118
     * @param string $index
119
     * @param string $type
120
     * @param array $mappings
121
     * @param array|null $return
122
     * @return bool
123
     * @throws ElasticOperationException
124
     */
125
    public function createType($index, $type, array $mappings = [], array &$return = null) {
126
        if (!$this->indexExists($index)) {
127
            throw new \InvalidArgumentException(sprintf("%s' index does not exists", $index));
128
        }
129
130
        if ($this->typeExists($index, $type)) {
131
            throw new \InvalidArgumentException(sprintf("Type 's%' already exists on index %s", $type, $index));
132
        }
133
134
        $mappings = MappingHelper::patchMappings($mappings, floor($this->getElasticsearchVersion()));
135
136
        $url = "$index/_mapping/$type";
137
        $response = $this->curlRequest->request($url, $mappings, 'PUT');
138
139
        $this->throwExceptionFromResponse($response, "Error creating type '$type' in '$index' index");
140
141
        $return = $response['content'];
142
143
        if (isset($return['acknowledged'])) {
144
            return $return['acknowledged'];
145
        }
146
147
        $this->setErrorFromElasticReturn($return);
148
149
        return false;
150
    }
151
152
    /**
153
     * @param string $index
154
     * @param string $type
155
     * @param array $body
156
     * @param array $queryParams
157
     * @param array|null $return
158
     * @return bool
159
     */
160
    public function insert($index, $type, array $body, array $queryParams = [], array &$return = null) {
161
        $url = "$index/$type";
162
        if (isset($body['_id'])) {
163
            $url .= '/' . $body['_id'];
164
            unset($body['_id']);
165
        }
166
167
        $url = "$url?" . http_build_query(array_merge(['refresh' => "true"], $queryParams));
168
169
        $response = $this->curlRequest->request($url, $body, 'POST');
170
171
        $this->throwExceptionFromResponse($response);
172
        $return = $response['content'];
173
174
        if (isset($return['created'])) {
175
            return $return['created'];
176
        }
177
178
        $this->setErrorFromElasticReturn($return);
179
180
        return false;
181
    }
182
183
    /**
184
     * @param string $index
185
     * @param string $type
186
     * @param string $_id
187
     * @param array $body
188
     * @param array $queryParams
189
     * @param array|null $return
190
     *
191
     * @return bool
192
     */
193
    public function update($index, $type, $_id, array $body = [], array $queryParams = [], array &$return = null) {
194
        if (!$this->indexExists($index)) {
195
            return false;
196
        }
197
198
        if (array_key_exists('doc', $body)) {
199
            $params = $body;
200
        } else {
201
            $params = ['doc' => $body];
202
        }
203
204
        $url = "$index/$type/$_id/_update?" . http_build_query(array_merge(['refresh' => 'true'], $queryParams));
205
        $response = $this->curlRequest->request($url, $params, 'POST');
206
        $this->throwExceptionFromResponse($response);
207
208
        $return = $response['content'];
209
210
        if (isset($return['_id'])) {
211
            return true;
212
        }
213
214
        $this->setErrorFromElasticReturn($return);
215
216
        return false;
217
    }
218
219
    /**
220
     * @param string $index
221
     * @param string $type
222
     * @param string $_id
223
     * @param array $queryParams
224
     * @param array|null $return
225
     * @return bool
226
     */
227
    public function delete($index, $type, $_id, array $queryParams = [], array &$return = null) {
228
        if (!$this->indexExists($index)) {
229
            return false;
230
        }
231
232
        $url = "$index/$type/$_id?" . http_build_query(array_merge(['refresh' => 'true'], $queryParams));
233
        $response = $this->curlRequest->request($url, [], 'DELETE');
234
        $this->throwExceptionFromResponse($response);
235
        $return = $response['content'];
236
237
        if (isset($return['found']) && !$return['found']) {
238
            error_log("Doc with _id '$_id' was not found for delete. Index: '$index', Type: '$type' ");
239
        }
240
241
        if (isset($return['_id'])) {
242
            return true;
243
        }
244
245
        $this->setErrorFromElasticReturn($return);
246
247
        return false;
248
    }
249
250
    public function updateWhere($index, $type, array $where, array &$return = null) {
251
        // TODO
252
    }
253
254
    public function deleteWhere($index, $type, array $where, array &$return = null) {
255
        // TODO
256
    }
257
258
    /**
259
     *
260
     * @param string $index
261
     * @param string $type
262
     * @param string $_id
263
     * @param array $queryParams
264
     * @param array|null $return
265
     * @return array|null
266
     */
267
    public function get($index, $type, $_id, array $queryParams = [], array &$return = null) {
268
        if (!$this->indexExists($index)) {
269
            return null;
270
        }
271
272
        $url = "$index/$type/$_id";
273
        $response = $this->curlRequest->request($url, [], 'GET');
274
        $return = $response['content'];
275
276
        if ($response['status'] == 404) {
277
            return null;
278
        }
279
280
        if (isset($return['found']) && boolval($return['found'])) {
281
            return $return;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $return; (object|integer|double|string|array|boolean) is incompatible with the return type declared by the interface DoctrineElastic\Connecti...onnectionInterface::get of type array|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
282
        }
283
284
        return null;
285
    }
286
287
    /**
288
     * Returns the [hits][hits] array from query
289
     *
290
     * @param string $index
291
     * @param string $type
292
     * @param array $body
293
     * @param array $queryParams
294
     * @param array|null $return
295
     * @return array
296
     */
297
    public function search($index, $type, array $body = [], array $queryParams = [], array &$return = null) {
298
        if (!$this->indexExists($index)) {
299
            return [];
300
        }
301
302
        $queryParams = array_replace_recursive([
303
            'size' => self::DEFAULT_MAX_RESULTS
304
        ], array_filter($queryParams, function ($value) {
305
            return $value !== null;
306
        }));
307
308
        $body = $this->unsetEmpties($body);
309
310
        if (isset($body['query']) && empty($body['query'])) {
311
            unset($body['query']);
312
        }
313
314
        $url = "$index/$type/_search?" . http_build_query($queryParams);
315
316
        $cleanQuery = function ($queryPart, callable $recusiveFn) {
317
            if (!is_array($queryPart)) {
318
                return $queryPart;
319
            }
320
321
            foreach ($queryPart as $key => $item) {
322
                if ($key == 'query' && isset($queryPart['query']['bool'])) {
323
                    $queryPart['bool'] = $recusiveFn($queryPart['query']['bool'], $recusiveFn);
324
                    unset($queryPart['query']['bool']);
325
                }
326
327
                if (isset($item['query']['bool'])) {
328
                    $queryPart[$key]['bool'] = $recusiveFn($item['query']['bool'], $recusiveFn);
329
                    unset($queryPart[$key]['query']);
330
                } else if (is_array($item)) {
331
                    $queryPart[$key] = $recusiveFn($item, $recusiveFn);
332
                }
333
            }
334
335
            return $queryPart;
336
        };
337
338
        if (isset($body['query'])) {
339
            foreach ($body['query']['bool'] as $key => $item) {
340
                $body['query']['bool'][$key] = $cleanQuery($item, $cleanQuery);
341
            }
342
        }
343
344
        $response = $this->curlRequest->request($url, $body, 'POST');
345
        $this->throwExceptionFromResponse($response);
346
        $return = $response['content'];
347
348
        if (isset($return['hits']['hits'])) {
349
            return $return['hits']['hits'];
350
        }
351
352
        return [];
353
    }
354
355
    /**
356
     * @param $action
357
     * @param $index
358
     * @param $type
359
     * @param array|null $data
360
     * @return array|bool
361
     * @throws InvalidParamsException
362
     */
363
    public function bulk($action, $index, $type, array $data = null) {
364
        $bulkData = '';
365
366
        if (!in_array($action, ['create', 'index', 'update']) && !is_array($data)) {
367
            throw new InvalidParamsException(
368
                "\$data param must be an array for '$action' bulk action"
369
            );
370
        }
371
372
        switch ($action) {
373
            case 'create':
374
            case 'index':
375
                foreach ($data as $doc) {
0 ignored issues
show
Bug introduced by
The expression $data of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
376
                    $actionParams = ['_index' => $index, '_type' => $type];
377
378
                    if (isset($doc['_id'])) {
379
                        $actionParams['_id'] = $doc['_id'];
380
                        unset($doc['_id']);
381
                    }
382
383
                    $bulkData .= json_encode([$action => $actionParams]) . "\n";
384
                    $bulkData .= json_encode($doc) . "\n";
385
                }
386
                break;
387
            case 'update':
388
            case 'delete':
389
                foreach ($data as $doc) {
0 ignored issues
show
Bug introduced by
The expression $data of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
390
                    if (!isset($doc['_id'])) {
391
                        throw new InvalidParamsException(
392
                            "_id field must be provided for each item in \$data param for $action action"
393
                        );
394
                    }
395
396
                    $actionParams = ['_index' => $index, '_type' => $type, '_id' => $doc['_id']];
397
                    unset($doc['_id']);
398
399
                    if ($action == 'update') {
400
                        if (!array_key_exists('doc', $doc)) {
401
                            $doc = ['doc' => $doc];
402
                        }
403
404
                        $bulkData .= json_encode([$action => $actionParams]) . "\n";
405
                        $bulkData .= json_encode($doc) . "\n";
406
                    }
407
                }
408
409
                break;
410
            default:
411
                $bulkActions = ['index', 'create', 'delete', 'update'];
412
413
                throw new InvalidParamsException(
414
                    "Invalid 'action' param provided. Must be one of those: " . implode('|', $bulkActions)
415
                );
416
        }
417
418
        $response = $this->curlRequest->request("/_bulk", $bulkData, 'POST', [
419
            'Content-Type: application/x-ndjson; charset=utf-8'
420
        ]);
421
422
        if (isset($response['status']) && $response['status'] == 200) {
423
            return $response['content'];
424
        } elseif (isset($response['content'])) {
425
            return $response['content'];
426
        }
427
428
        return false;
429
    }
430
431
    private function unsetEmpties(array $haystack) {
432
        $selfFn = __FUNCTION__;
433
434
        foreach ($haystack as $key => $value) {
435
            if (is_array($value)) {
436
                $haystack[$key] = $this->$selfFn($haystack[$key]);
437
            }
438
439
            if (is_array($haystack[$key]) && empty($haystack[$key])) {
440
                unset($haystack[$key]);
441
            }
442
        }
443
444
        return $haystack;
445
    }
446
447
    /**
448
     * @param string $index
449
     * @return bool
450
     */
451
    public function indexExists($index) {
452
        $this->validateIndex($index);
453
454
        $response = $this->curlRequest->request($index, [], 'HEAD');
455
456
        return $response['status'] === 200;
457
    }
458
459
    /**
460
     * @param string $index
461
     * @param string $type
462
     * @return bool
463
     */
464
    public function typeExists($index, $type) {
465
        $response = $this->curlRequest->request("$index/$type", [], 'HEAD');
466
467
        return $response['status'] === 200;
468
    }
469
470
    private function throwExceptionFromResponse($response, $appendPrefix = '') {
471
        if (isset($response['content']['error']['reason'])) {
472
            if (!empty($appendPrefix)) {
473
                $appendPrefix .= ': ';
474
            }
475
476
            throw new ElasticOperationException($appendPrefix . $response['content']['error']['reason']);
477
        }
478
    }
479
480
    public function hasConnection() {
481
        $response = $this->curlRequest->request('', [], 'HEAD');
482
483
        return $response['status'] == 200;
484
    }
485
486
    private function setErrorFromElasticReturn($return) {
487
        if (isset($return['error']['root_cause'][0]['reason'])) {
488
            $this->setError($return['error']['root_cause'][0]['reason']);
489
        } else if (isset($return['error']['reason'])) {
490
            $this->setError($return['error']['reason']);
491
        }
492
    }
493
494
    private function validateIndex($index) {
495
        if (!IndexHelper::indexIsValid($index)) {
496
            throw new InvalidIndexNameException(sprintf(
497
                "The index name '$index' is invalid. It must not be empty or contain the following characters: [%s]",
498
                implode(',', IndexHelper::$invalidIndexCharacters)
499
            ));
500
        }
501
    }
502
503
    public function getElasticsearchVersion() {
504
        if (is_null($this->esVersion)) {
505
            $response = $this->curlRequest->request('', [], 'GET');
506
507
            if (isset($response['content']['version']['number'])) {
508
                $this->esVersion = floatval($response['content']['version']['number']);
509
            } else {
510
                throw new ConnectionException('Unable to fetch elasticsearch version. ');
511
            }
512
        }
513
514
        return $this->esVersion;
515
    }
516
}
517