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 ( 897603...27415b )
by Anderson
11s
created

ElasticConnection::unsetEmpties()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 8
nc 5
nop 1
1
<?php
2
3
namespace DoctrineElastic\Connection;
4
5
use DoctrineElastic\Exception\ConnectionException;
6
use DoctrineElastic\Exception\ElasticOperationException;
7
use DoctrineElastic\Exception\InvalidParamsException;
8
use DoctrineElastic\Helper\MappingHelper;
9
use DoctrineElastic\Http\CurlRequest;
10
use DoctrineElastic\Traiting\ErrorGetterTrait;
11
12
/**
13
 * Default elastic connection class for general operations
14
 * Notice that the original elastic result of most of operations can be get by $return param
15
 *
16
 * @author Andsalves <[email protected]>
17
 */
18
class ElasticConnection implements ElasticConnectionInterface {
19
20
    use ErrorGetterTrait;
21
22
    /** Override default elastic limit size query */
23
    const DEFAULT_MAX_RESULTS = 10000;
24
25
    /** @var CurlRequest */
26
    protected $curlRequest;
27
28
    /** @var float */
29
    protected $esVersion;
30
31
    public function __construct(array $hosts) {
32
        $this->curlRequest = new CurlRequest();
33
        $baseHost = reset($hosts);
34
35
        if (empty($baseHost) || !is_string($baseHost) || !preg_match('/http/', $baseHost)) {
36
            throw new ConnectionException("Elasticsearch host is invalid. ");
37
        }
38
39
        $this->curlRequest->setBaseUrl($baseHost);
40
    }
41
42
    /**
43
     * @param string $index
44
     * @param array|null $mappings
45
     * @param array|null $settings
46
     * @param array|null $aliases
47
     * @param array|null $return
48
     * @return bool
49
     */
50
    public function createIndex(
51
        $index, array $mappings = null, array $settings = null, array $aliases = null, array &$return = null
52
    ) {
53
        if ($this->indexExists($index)) {
54
            throw new \InvalidArgumentException(sprintf("'%s' index already exists", $index));
55
        }
56
57
        $params = [];
58
59
        if (is_array($mappings) && !empty($mappings)) {
60
            $params['mappings'] = MappingHelper::patchMappings($mappings, floor($this->getElasticsearchVersion()));
61
        }
62
63
        if (is_array($settings) && !empty($settings)) {
64
            $params['settings'] = $settings;
65
        }
66
67
        if (is_array($aliases) && !empty($aliases)) {
68
            $params['aliases'] = $aliases;
69
        }
70
71
        $response = $this->curlRequest->request($index, $params, 'PUT');
72
        $return = $response['content'];
73
74
        if (isset($return['acknowledged']) && $return['acknowledged']) {
75
            return $return['acknowledged'];
76
        }
77
78
        $this->setErrorFromElasticReturn($return);
79
80
        return false;
81
    }
82
83
    /**
84
     * @param string $index
85
     * @param array|null $return
86
     * @return bool
87
     * @throws ElasticOperationException
88
     */
89
    public function deleteIndex($index, array &$return = null) {
90
        if (is_string($index) && !strstr('_all', $index) && !strstr('*', $index)) {
91
            $response = $this->curlRequest->request("$index?refresh=true", [], 'DELETE');
92
            $return = $response['content'];
93
94
            if ($response['status'] == 404) {
95
                throw new ElasticOperationException("Index '$index' doesn't exist so cannot be deleted. ");
96
            }
97
98
            if (isset($return['acknowledged'])) {
99
                return $return['acknowledged'];
100
            }
101
        } else {
102
            throw new ElasticOperationException('Index name is invalid for deletion. ');
103
        }
104
105
        $this->setErrorFromElasticReturn($return);
106
107
        return false;
108
    }
109
110
    /**
111
     * @param string $index
112
     * @param string $type
113
     * @param array $mappings
114
     * @param array|null $return
115
     * @return bool
116
     * @throws ElasticOperationException
117
     */
118
    public function createType($index, $type, array $mappings = [], array &$return = null) {
119
        if (!$this->indexExists($index)) {
120
            throw new \InvalidArgumentException(sprintf("%s' index does not exists", $index));
121
        }
122
123
        if ($this->typeExists($index, $type)) {
124
            throw new \InvalidArgumentException(sprintf("Type 's%' already exists on index %s", $type, $index));
125
        }
126
127
        $mappings = MappingHelper::patchMappings($mappings, floor($this->getElasticsearchVersion()));
128
129
        $url = "$index/_mapping/$type";
130
        $response = $this->curlRequest->request($url, $mappings, 'PUT');
131
132
        $this->throwExceptionFromResponse($response, "Error creating type '$type' in '$index' index");
133
134
        $return = $response['content'];
135
136
        if (isset($return['acknowledged'])) {
137
            return $return['acknowledged'];
138
        }
139
140
        $this->setErrorFromElasticReturn($return);
141
142
        return false;
143
    }
144
145
    /**
146
     * @param string $index
147
     * @param string $type
148
     * @param array $body
149
     * @param array $queryParams
150
     * @param array|null $return
151
     * @return bool
152
     */
153
    public function insert($index, $type, array $body, array $queryParams = [], array &$return = null) {
154
        $url = "$index/$type";
155
        if (isset($body['_id'])) {
156
            $url .= '/' . $body['_id'];
157
            unset($body['_id']);
158
        }
159
160
        $url = "$url?" . http_build_query(array_merge(['refresh' => "true"], $queryParams));
161
162
        $response = $this->curlRequest->request($url, $body, 'POST');
163
164
        $this->throwExceptionFromResponse($response);
165
        $return = $response['content'];
166
167
        if (isset($return['created'])) {
168
            return $return['created'];
169
        }
170
171
        $this->setErrorFromElasticReturn($return);
172
173
        return false;
174
    }
175
176
    /**
177
     * @param string $index
178
     * @param string $type
179
     * @param string $_id
180
     * @param array $body
181
     * @param array $queryParams
182
     * @param array|null $return
183
     *
184
     * @return bool
185
     */
186
    public function update($index, $type, $_id, array $body = [], array $queryParams = [], array &$return = null) {
187
        if (!$this->indexExists($index)) {
188
            return false;
189
        }
190
191
        if (array_key_exists('doc', $body)) {
192
            $params = $body;
193
        } else {
194
            $params = ['doc' => $body];
195
        }
196
197
        $url = "$index/$type/$_id/_update?" . http_build_query(array_merge(['refresh' => 'true'], $queryParams));
198
        $response = $this->curlRequest->request($url, $params, 'POST');
199
        $this->throwExceptionFromResponse($response);
200
201
        $return = $response['content'];
202
203
        if (isset($return['_id'])) {
204
            return true;
205
        }
206
207
        $this->setErrorFromElasticReturn($return);
208
209
        return false;
210
    }
211
212
    /**
213
     * @param string $index
214
     * @param string $type
215
     * @param string $_id
216
     * @param array $queryParams
217
     * @param array|null $return
218
     * @return bool
219
     */
220
    public function delete($index, $type, $_id, array $queryParams = [], array &$return = null) {
221
        if (!$this->indexExists($index)) {
222
            return false;
223
        }
224
225
        $url = "$index/$type/$_id?" . http_build_query(array_merge(['refresh' => 'true'], $queryParams));
226
        $response = $this->curlRequest->request($url, [], 'DELETE');
227
        $this->throwExceptionFromResponse($response);
228
        $return = $response['content'];
229
230
        if (isset($return['found']) && !$return['found']) {
231
            error_log("Doc with _id '$_id' was not found for delete. Index: '$index', Type: '$type' ");
232
        }
233
234
        if (isset($return['_id'])) {
235
            return true;
236
        }
237
238
        $this->setErrorFromElasticReturn($return);
239
240
        return false;
241
    }
242
243
    public function updateWhere($index, $type, array $where, array &$return = null) {
244
        // TODO
245
    }
246
247
    public function deleteWhere($index, $type, array $where, array &$return = null) {
248
        // TODO
249
    }
250
251
    /**
252
     *
253
     * @param string $index
254
     * @param string $type
255
     * @param string $_id
256
     * @param array $queryParams
257
     * @param array|null $return
258
     * @return array|null
259
     */
260
    public function get($index, $type, $_id, array $queryParams = [], array &$return = null) {
261
        if (!$this->indexExists($index)) {
262
            return null;
263
        }
264
265
        $url = "$index/$type/$_id";
266
        $response = $this->curlRequest->request($url, [], 'GET');
267
        $return = $response['content'];
268
269
        if ($response['status'] == 404) {
270
            return null;
271
        }
272
273
        if (isset($return['found']) && boolval($return['found'])) {
274
            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...
275
        }
276
277
        return null;
278
    }
279
280
    /**
281
     * Returns the [hits][hits] array from query
282
     *
283
     * @param string $index
284
     * @param string $type
285
     * @param array $body
286
     * @param array $queryParams
287
     * @param array|null $return
288
     * @return array
289
     */
290
    public function search($index, $type, array $body = [], array $queryParams = [], array &$return = null) {
291
        if (!$this->indexExists($index)) {
292
            return [];
293
        }
294
        
295
        $queryParams = array_replace_recursive([
296
            'size' => self::DEFAULT_MAX_RESULTS
297
        ], array_filter($queryParams, function ($value) {
298
            return $value !== null;
299
        }));
300
301
        $body = $this->unsetEmpties($body);
302
303
        if (isset($body['query']) && empty($body['query'])) {
304
            unset($body['query']);
305
        }
306
307
        $url = "$index/$type/_search?" . http_build_query($queryParams);
308
309
        $cleanQuery = function ($queryPart, callable $recusiveFn) {
310
            if (!is_array($queryPart)) {
311
                return $queryPart;
312
            }
313
314
            foreach ($queryPart as $key => $item) {
315
                if ($key == 'query' && isset($queryPart['query']['bool'])) {
316
                    $queryPart['bool'] = $recusiveFn($queryPart['query']['bool'], $recusiveFn);
317
                    unset($queryPart['query']['bool']);
318
                }
319
320
                if (isset($item['query']['bool'])) {
321
                    $queryPart[$key]['bool'] = $recusiveFn($item['query']['bool'], $recusiveFn);
322
                    unset($queryPart[$key]['query']);
323
                } else if (is_array($item)) {
324
                    $queryPart[$key] = $recusiveFn($item, $recusiveFn);
325
                }
326
            }
327
328
            return $queryPart;
329
        };
330
331
        if (isset($body['query'])) {
332
            foreach ($body['query']['bool'] as $key => $item) {
333
                $body['query']['bool'][$key] = $cleanQuery($item, $cleanQuery);
334
            }
335
        }
336
337
        $response = $this->curlRequest->request($url, $body, 'POST');
338
        $this->throwExceptionFromResponse($response);
339
        $return = $response['content'];
340
341
        if (isset($return['hits']['hits'])) {
342
            return $return['hits']['hits'];
343
        }
344
345
        return [];
346
    }
347
348
    /**
349
     * @param $action
350
     * @param $index
351
     * @param $type
352
     * @param array|null $data
353
     * @return array|bool
354
     * @throws InvalidParamsException
355
     */
356
    public function bulk($action, $index, $type, array $data = null) {
357
        $bulkData = '';
358
359
        if (!in_array($action, ['create', 'index', 'update']) && !is_array($data)) {
360
            throw new InvalidParamsException(
361
                "\$data param must be an array for '$action' bulk action"
362
            );
363
        }
364
365
        switch ($action) {
366
            case 'create':
367
            case 'index':
368
                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...
369
                    $actionParams = ['_index' => $index, '_type' => $type];
370
371
                    if (isset($doc['_id'])) {
372
                        $actionParams['_id'] = $doc['_id'];
373
                        unset($doc['_id']);
374
                    }
375
376
                    $bulkData .= json_encode([$action => $actionParams]) . "\n";
377
                    $bulkData .= json_encode($doc) . "\n";
378
                }
379
                break;
380
            case 'update':
381
            case 'delete':
382
                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...
383
                    if (!isset($doc['_id'])) {
384
                        throw new InvalidParamsException(
385
                            "_id field must be provided for each item in \$data param for $action action"
386
                        );
387
                    }
388
389
                    $actionParams = ['_index' => $index, '_type' => $type, '_id' => $doc['_id']];
390
                    unset($doc['_id']);
391
392
                    if ($action == 'update') {
393
                        if (!array_key_exists('doc', $doc)) {
394
                            $doc = ['doc' => $doc];
395
                        }
396
397
                        $bulkData .= json_encode([$action => $actionParams]) . "\n";
398
                        $bulkData .= json_encode($doc) . "\n";
399
                    }
400
                }
401
402
                break;
403
            default:
404
                $bulkActions = ['index', 'create', 'delete', 'update'];
405
406
                throw new InvalidParamsException(
407
                    "Invalid 'action' param provided. Must be one of those: " . implode('|', $bulkActions)
408
                );
409
        }
410
411
        $response = $this->curlRequest->request("/_bulk", $bulkData, 'POST', [
412
            'Content-Type: application/x-ndjson; charset=utf-8'
413
        ]);
414
415
        if (isset($response['status']) && $response['status'] == 200) {
416
            return $response['content'];
417
        } elseif (isset($response['content'])) {
418
            return $response['content'];
419
        }
420
421
        return false;
422
    }
423
424
    private function unsetEmpties(array $haystack) {
425
        $selfFn = __FUNCTION__;
426
427
        foreach ($haystack as $key => $value) {
428
            if (is_array($value)) {
429
                $haystack[$key] = $this->$selfFn($haystack[$key]);
430
            }
431
432
            if (is_array($haystack[$key]) && empty($haystack[$key])) {
433
                unset($haystack[$key]);
434
            }
435
        }
436
437
        return $haystack;
438
    }
439
440
    /**
441
     * @param string $index
442
     * @return bool
443
     */
444
    public function indexExists($index) {
445
        $response = $this->curlRequest->request($index, [], 'HEAD');
446
447
        return $response['status'] === 200;
448
    }
449
450
    /**
451
     * @param string $index
452
     * @param string $type
453
     * @return bool
454
     */
455
    public function typeExists($index, $type) {
456
        $response = $this->curlRequest->request("$index/$type", [], 'HEAD');
457
458
        return $response['status'] === 200;
459
    }
460
461
    private function throwExceptionFromResponse($response, $appendPrefix = '') {
462
        if (isset($response['content']['error']['reason'])) {
463
            if (!empty($appendPrefix)) {
464
                $appendPrefix .= ': ';
465
            }
466
467
            throw new ElasticOperationException($appendPrefix . $response['content']['error']['reason']);
468
        }
469
    }
470
471
    public function hasConnection() {
472
        $response = $this->curlRequest->request('', [], 'HEAD');
473
474
        return $response['status'] == 200;
475
    }
476
477
    private function setErrorFromElasticReturn($return) {
478
        if (isset($return['error']['root_cause'][0]['reason'])) {
479
            $this->setError($return['error']['root_cause'][0]['reason']);
480
        } else if (isset($return['error']['reason'])) {
481
            $this->setError($return['error']['reason']);
482
        }
483
    }
484
485
    public function getElasticsearchVersion() {
486
        if (is_null($this->esVersion)) {
487
            $response = $this->curlRequest->request('', [], 'GET');
488
489
            if (isset($response['content']['version']['number'])) {
490
                $this->esVersion = floatval($response['content']['version']['number']);
491
            } else {
492
                throw new ConnectionException('Unable to fetch elasticsearch version. ');
493
            }
494
        }
495
496
        return $this->esVersion;
497
    }
498
}
499