Completed
Pull Request — master (#941)
by
unknown
02:20
created

IndexService::findArray()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 11
Ratio 100 %

Importance

Changes 0
Metric Value
dl 11
loc 11
rs 9.9
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
/*
4
 * This file is part of the ONGR package.
5
 *
6
 * (c) NFQ Technologies UAB <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace ONGR\ElasticsearchBundle\Service;
13
14
use Elasticsearch\Client;
15
use Elasticsearch\ClientBuilder;
16
use ONGR\ElasticsearchBundle\Event\BulkEvent;
17
use ONGR\ElasticsearchBundle\Event\CommitEvent;
18
use ONGR\ElasticsearchBundle\Event\Events;
19
use ONGR\ElasticsearchBundle\Event\PostCreateClientEvent;
20
use ONGR\ElasticsearchBundle\Exception\BulkWithErrorsException;
21
use ONGR\ElasticsearchBundle\Mapping\Converter;
22
use ONGR\ElasticsearchBundle\Mapping\IndexSettings;
23
use ONGR\ElasticsearchBundle\Result\ArrayIterator;
24
use ONGR\ElasticsearchBundle\Result\RawIterator;
25
use ONGR\ElasticsearchDSL\Query\TermLevel\IdsQuery;
26
use ONGR\ElasticsearchDSL\Query\TermLevel\TermQuery;
27
use ONGR\ElasticsearchDSL\Search;
28
use ONGR\ElasticsearchDSL\Sort\FieldSort;
29
use ONGR\ElasticsearchBundle\Result\DocumentIterator;
30
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
31
32
class IndexService
33
{
34
    private $client;
35
    private $namespace;
36
    private $converter;
37
    private $eventDispatcher;
38
39
    private $stopwatch;
40
    private $bulkCommitSize = 100;
41
    private $bulkQueries = [];
42
    private $indexSettings = [];
43
    private $tracer;
44
45
    public function __construct(
46
        string $namespace,
47
        Converter $converter,
48
        EventDispatcherInterface $eventDispatcher,
49
        IndexSettings $indexSettings,
50
        $tracer = null
51
    ) {
52
        $this->namespace = $namespace;
53
        $this->converter = $converter;
54
        $this->eventDispatcher = $eventDispatcher;
55
        $this->indexSettings = $indexSettings;
0 ignored issues
show
Documentation Bug introduced by
It seems like $indexSettings of type object<ONGR\Elasticsearc...\Mapping\IndexSettings> is incompatible with the declared type array of property $indexSettings.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
56
        $this->tracer = $tracer;
57
        $this->getClient();
58
    }
59
60
    public function getNamespace(): string
61
    {
62
        return $this->namespace;
63
    }
64
65
    public function getIndexSettings()
66
    {
67
        return $this->indexSettings;
68
    }
69
70
    public function setIndexSettings($indexSettings): self
71
    {
72
        $this->indexSettings = $indexSettings;
73
        return $this;
74
    }
75
76
    public function getClient(): Client
77
    {
78
        if (!$this->client) {
79
            $client = ClientBuilder::create();
80
            $client->setHosts($this->indexSettings->getHosts());
0 ignored issues
show
Bug introduced by
The method getHosts cannot be called on $this->indexSettings (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
81
            $this->tracer && $client->setTracer($this->tracer);
82
//            $client->setLogger()
83
84
            $this->eventDispatcher->dispatch(
85
                new PostCreateClientEvent($this->namespace, $client),
0 ignored issues
show
Documentation introduced by
new \ONGR\ElasticsearchB...is->namespace, $client) is of type object<ONGR\Elasticsearc...\PostCreateClientEvent>, but the function expects a object<Symfony\Contracts\EventDispatcher\object>.

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...
86
                Events::POST_CLIENT_CREATE
87
            );
88
            $this->client = $client->build();
89
        }
90
        return $this->client;
91
    }
92
93
    public function getIndexName(): string
94
    {
95
        return $this->indexSettings->getIndexName();
0 ignored issues
show
Bug introduced by
The method getIndexName cannot be called on $this->indexSettings (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
96
    }
97
98
    public function getEventDispatcher(): EventDispatcherInterface
99
    {
100
        return $this->eventDispatcher;
101
    }
102
103
    public function getConverter(): Converter
104
    {
105
        return $this->converter;
106
    }
107
108
    public function getBulkCommitSize(): int
109
    {
110
        return $this->bulkCommitSize;
111
    }
112
113
    public function setBulkCommitSize(int $bulkCommitSize)
114
    {
115
        $this->bulkCommitSize = $bulkCommitSize;
116
        return $this;
117
    }
118
119
    public function getStopwatch()
120
    {
121
        return $this->stopwatch;
122
    }
123
124
    public function setStopwatch($stopwatch)
125
    {
126
        $this->stopwatch = $stopwatch;
127
        return $this;
128
    }
129
130
    public function createIndex($noMapping = false, $params = []): array
131
    {
132
        $params = array_merge([
133
            'index' => $this->getIndexName(),
134
            'body' => $noMapping ? [] : $this->indexSettings->getIndexMetadata(),
0 ignored issues
show
Bug introduced by
The method getIndexMetadata cannot be called on $this->indexSettings (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
135
        ], $params);
136
137
        #TODO Add event here.
138
139
        return $this->getClient()->indices()->create(array_filter($params));
140
    }
141
142
    public function dropIndex(): array
143
    {
144
        $indexName = $this->getIndexName();
145
146
        if ($this->getClient()->indices()->existsAlias(['name' => $this->getIndexName()])) {
147
            $aliases = $this->getClient()->indices()->getAlias(['name' => $this->getIndexName()]);
148
            $indexName = array_keys($aliases);
149
        }
150
151
        return $this->getClient()->indices()->delete(['index' => $indexName]);
152
    }
153
154
    public function dropAndCreateIndex($noMapping = false, $params = []): array
155
    {
156
        try {
157
            if ($this->indexExists()) {
158
                $this->dropIndex();
159
            }
160
        } catch (\Exception $e) {
161
            // Do nothing, our target is to create the new index.
162
        }
163
164
        return $this->createIndex($noMapping, $params);
165
    }
166
167
    public function indexExists(): bool
168
    {
169
        return $this->getClient()->indices()->exists(['index' => $this->getIndexName()]);
170
    }
171
172
    public function clearCache(): array
173
    {
174
        return $this->getClient()->indices()->clearCache(['index' => $this->getIndexName()]);
175
    }
176
177
    /**
178
     * Returns a single document by provided ID or null if a document was not found.
179
     */
180
    public function find($id, $params = [])
181
    {
182
        $requestParams = [
183
            'index' => $this->getIndexName(),
184
            'id' => $id,
185
        ];
186
187
        $requestParams = array_merge($requestParams, $params);
188
189
        $result = $this->getClient()->get($requestParams);
190
191
        if (!$result['found']) {
192
            return null;
193
        }
194
195
        $result['_source']['_id'] = $result['_id'];
196
197
        return $this->converter->convertArrayToDocument($this->namespace, $result['_source']);
198
    }
199
200
    public function findByIds(array $ids): DocumentIterator
201
    {
202
        $search = $this->createSearch();
203
        $search->addQuery(new IdsQuery($ids));
204
205
        return $this->findDocuments($search);
206
    }
207
208
    /**
209
     * Finds documents by a set of criteria.
210
     *
211
     * @param array      $criteria   Example: ['group' => ['best', 'worst'], 'job' => 'medic'].
212
     * @param array|null $orderBy    Example: ['name' => 'ASC', 'surname' => 'DESC'].
213
     * @param int|null   $limit      Default is 10.
214
     * @param int|null   $offset     Default is 0.
215
     *
216
     * @return array|DocumentIterator The objects.
217
     */
218
    public function findBy(
219
        array $criteria,
220
        array $orderBy = [],
221
        int $limit = 10,
222
        int $offset = 0
223
    ) {
224
        $search = $this->createSearch();
225
        $search->setSize($limit);
226
        $search->setFrom($offset);
227
228
        foreach ($criteria as $field => $value) {
229
            $search->addQuery(new TermQuery($field, $value));
230
        }
231
232
        foreach ($orderBy as $field => $direction) {
233
            $search->addSort(new FieldSort($field, $direction));
234
        }
235
236
        return $this->findDocuments($search);
237
    }
238
239
    /**
240
     * Finds a single document by a set of criteria.
241
     *
242
     * @param array      $criteria   Example: ['group' => ['best', 'worst'], 'job' => 'medic'].
243
     * @param array|null $orderBy    Example: ['name' => 'ASC', 'surname' => 'DESC'].
244
     *
245
     * @return object|null The object.
246
     */
247
    public function findOneBy(array $criteria, array $orderBy = [])
248
    {
249
        return $this->findBy($criteria, $orderBy, 1)->current();
250
    }
251
252
    public function createSearch(): Search
253
    {
254
        return new Search();
255
    }
256
257
    public function getScrollConfiguration($raw, $scrollDuration): array
258
    {
259
        $scrollConfig = [];
260
        if (isset($raw['_scroll_id'])) {
261
            $scrollConfig['_scroll_id'] = $raw['_scroll_id'];
262
            $scrollConfig['duration'] = $scrollDuration;
263
        }
264
265
        return $scrollConfig;
266
    }
267
268 View Code Duplication
    public function findDocuments(Search $search): DocumentIterator
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...
269
    {
270
        $results = $this->executeSearch($search);
271
272
        return new DocumentIterator(
273
            $results,
274
            $this,
275
            $this->converter,
276
            $this->getScrollConfiguration($results, $search->getScroll())
277
        );
278
    }
279
280 View Code Duplication
    public function findArray(Search $search): ArrayIterator
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...
281
    {
282
        $results = $this->executeSearch($search);
283
284
        return new ArrayIterator(
285
            $results,
286
            $this,
287
            $this->converter,
288
            $this->getScrollConfiguration($results, $search->getScroll())
289
        );
290
    }
291
292 View Code Duplication
    public function findRaw(Search $search): RawIterator
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...
293
    {
294
        $results = $this->executeSearch($search);
295
296
        return new RawIterator(
297
            $results,
298
            $this,
299
            $this->converter,
300
            $this->getScrollConfiguration($results, $search->getScroll())
301
        );
302
    }
303
304
    private function executeSearch(Search $search): array
305
    {
306
        return $this->search($search->toArray(), $search->getUriParams());
307
    }
308
309
    public function getIndexDocumentCount(): int
310
    {
311
        return $this->count();
312
    }
313
314
    /**
315
     * Counts documents by given search.
316
     *
317
     * @param Search|null $search
318
     * @param array  $params
319
     * @param bool   $returnRaw If set true returns raw response gotten from client.
320
     *
321
     * @return int|array
322
     */
323
    public function count(Search $search = null, array $params = [], $returnRaw = false)
324
    {
325
        $body = array_merge(
326
            [
327
                'index' => $this->getIndexName(),
328
                'body' => $search !== null ? $search->toArray() : [],
329
            ],
330
            $params
331
        );
332
333
        $results = $this->getClient()->count($body);
334
335
        if ($returnRaw) {
336
            return $results;
337
        } else {
338
            return $results['count'];
339
        }
340
    }
341
342
    public function remove($id, $routing = null)
343
    {
344
        $params = [
345
            'index' => $this->getIndexName(),
346
            'id' => $id,
347
        ];
348
349
        if ($routing) {
350
            $params['routing'] = $routing;
351
        }
352
353
        $response = $this->getClient()->delete($params);
354
355
        return $response;
356
    }
357
358
    public function update($id, array $fields = [], $script = null, array $params = []): array
359
    {
360
        $body = array_filter(
361
            [
362
                'doc' => $fields,
363
                'script' => $script,
364
            ]
365
        );
366
367
        $params = array_merge(
368
            [
369
                'id' => $id,
370
                'index' => $this->getIndexName(),
371
                'body' => $body,
372
            ],
373
            $params
374
        );
375
376
        return $this->getClient()->update($params);
377
    }
378
379
    public function search(array $query, array $params = []): array
380
    {
381
        $requestParams = [
382
            'index' => $this->getIndexName(),
383
            'body' => $query,
384
        ];
385
386
387
        if (!empty($params)) {
388
            $requestParams = array_merge($requestParams, $params);
389
        }
390
391
//        $this->stopwatch('start', 'search');
392
        $result = $this->getClient()->search($requestParams);
393
//        $this->stopwatch('stop', 'search');
394
395
        return $result;
396
    }
397
398
    /**
399
     * Usage example
400
     *
401
     * $im->bulk('index', ['_id' => 1, 'title' => 'foo']);
402
     * $im->bulk('delete', ['_id' => 2]);
403
     * $im->bulk('create', ['title' => 'foo']);
404
     */
405
    public function bulk(string $operation, array $data = [], $autoCommit = true): array
406
    {
407
        $bulkParams = [
408
            '_id' => $data['_id'] ?? null,
409
        ];
410
411
        unset($data['_index'], $data['_id']);
412
413
        $this->eventDispatcher->dispatch(
414
            new BulkEvent($operation, $bulkParams, $data),
0 ignored issues
show
Documentation introduced by
new \ONGR\ElasticsearchB...on, $bulkParams, $data) is of type object<ONGR\ElasticsearchBundle\Event\BulkEvent>, but the function expects a object<Symfony\Contracts\EventDispatcher\object>.

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...
415
            Events::BULK
416
        );
417
418
        $this->bulkQueries[] = [ $operation => $bulkParams];
419
420
        if (!empty($data)) {
421
            $this->bulkQueries[] = $data;
422
        }
423
424
        $response = [];
425
426
        // %X is not very accurate, but might be better than use counter. This place is experimental for now.
427
        if ($autoCommit && $this->getBulkCommitSize() <= count($this->bulkQueries) % $this->getBulkCommitSize() / 2) {
428
            $response = $this->commit();
429
        }
430
431
        return $response;
432
    }
433
434
    /**
435
     * Adds document to next flush.
436
     *
437
     * @param object $document
438
     */
439
    public function persist($document): void
440
    {
441
        $documentArray = array_filter($this->converter->convertDocumentToArray($document), function ($val) {
442
            // remove unset properties but keep other falsy values
443
            return !($val === null);
444
        });
445
446
        $this->bulk('index', $documentArray);
447
    }
448
449
    public function commit($commitMode = 'refresh', array $params = []): array
450
    {
451
        $bulkResponse = [];
452
        if (!empty($this->bulkQueries)) {
453
            $this->eventDispatcher->dispatch(
454
                new CommitEvent($commitMode, $this->bulkQueries, []),
0 ignored issues
show
Documentation introduced by
new \ONGR\ElasticsearchB...->bulkQueries, array()) is of type object<ONGR\Elasticsearc...ndle\Event\CommitEvent>, but the function expects a object<Symfony\Contracts\EventDispatcher\object>.

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...
455
                Events::PRE_COMMIT
456
            );
457
458
//            $this->stopwatch('start', 'bulk');
459
            $bulkResponse = $this->client->bulk(
460
                array_merge(
461
                    [
462
                    'index' => $this->getIndexName(),
463
                    'body' => $this->bulkQueries,
464
                    ],
465
                    $params
466
                )
467
            );
468
//            $this->stopwatch('stop', 'bulk');
469
470
            if ($bulkResponse['errors']) {
471
                throw new BulkWithErrorsException(
472
                    json_encode($bulkResponse),
473
                    0,
474
                    null,
475
                    $bulkResponse
476
                );
477
            }
478
479
//            $this->stopwatch('start', 'refresh');
480
            switch ($commitMode) {
481
                case 'flush':
482
                    $this->getClient()->indices()->flush();
483
                    break;
484
                case 'flush_synced':
485
                    $this->getClient()->indices()->flushSynced();
486
                    break;
487
                case 'refresh':
488
                    $this->getClient()->indices()->refresh();
489
                    break;
490
            }
491
492
            $this->eventDispatcher->dispatch(
493
                new CommitEvent($commitMode, $this->bulkQueries, $bulkResponse),
0 ignored issues
show
Documentation introduced by
new \ONGR\ElasticsearchB...Queries, $bulkResponse) is of type object<ONGR\Elasticsearc...ndle\Event\CommitEvent>, but the function expects a object<Symfony\Contracts\EventDispatcher\object>.

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...
494
                Events::POST_COMMIT
495
            );
496
497
            $this->bulkQueries = [];
498
499
//            $this->stopwatch('stop', $this->getCommitMode());
500
        }
501
502
        return $bulkResponse;
503
    }
504
505
    public function flush(array $params = []): array
506
    {
507
        return $this->client->indices()->flush(array_merge(['index' => $this->getIndexName()], $params));
508
    }
509
510
    public function refresh(array $params = []): array
511
    {
512
        return $this->client->indices()->refresh(array_merge(['index' => $this->getIndexName()], $params));
513
    }
514
515
    public function scroll($scrollId, $scrollDuration = '5m'): array
516
    {
517
        $results = $this->getClient()->scroll(['scroll_id' => $scrollId, 'scroll' => $scrollDuration]);
518
519
        return $results;
520
    }
521
522
    public function clearScroll($scrollId): array
523
    {
524
        return $this->getClient()->clearScroll(['scroll_id' => $scrollId]);
525
    }
526
527
    public function resetClient(): void
528
    {
529
        $this->client = null;
530
    }
531
532
    public function clearElasticIndexCache(): array
533
    {
534
        return $this->getClient()->indices()->clearCache(['index' => $this->getIndexName()]);
535
    }
536
537
    private function stopwatch($action, $name): void
538
    {
539
        if ($this->stopwatch && ($action == 'start' || $action == 'stop')) {
540
            $this->stopwatch->$action('ongr_es: '.$name, 'ongr_es');
541
        }
542
    }
543
}
544