Completed
Pull Request — 6.0-dev (#875)
by Simonas
08:47
created

IndexService::getParser()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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
use Symfony\Component\Serializer\Serializer;
32
33
class IndexService
34
{
35
    private $client;
36
    private $namespace;
37
    private $converter;
38
    private $eventDispatcher;
39
40
    private $stopwatch;
41
    private $bulkCommitSize = 100;
42
    private $bulkQueries = [];
43
    private $indexSettings = [];
44
    private $serializer;
45
    private $tracer;
46
47
    public function __construct(
48
        string $namespace,
49
        Converter $converter,
50
        EventDispatcherInterface $eventDispatcher,
51
        Serializer $serializer,
52
        IndexSettings $indexSettings,
53
        $tracer = null
54
    ) {
55
        $this->namespace = $namespace;
56
        $this->converter = $converter;
57
        $this->eventDispatcher = $eventDispatcher;
58
        $this->serializer = $serializer;
59
        $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...
60
        $this->tracer = $tracer;
61
    }
62
63
    public function getNamespace(): string
64
    {
65
        return $this->namespace;
66
    }
67
68
    /**
69
     * @deprecated will be removed in v7 since there will be no more types in the indexes.
70
     */
71
    public function getTypeName(): string
72
    {
73
        return $this->indexSettings->getType();
0 ignored issues
show
Bug introduced by
The method getType 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...
74
    }
75
76
    public function getIndexSettings()
77
    {
78
        return $this->indexSettings;
79
    }
80
81
    public function setIndexSettings($indexSettings): self
82
    {
83
        $this->indexSettings = $indexSettings;
84
        return $this;
85
    }
86
87
    public function getClient(): Client
88
    {
89
        if (!$this->client) {
90
            $client = ClientBuilder::create();
91
            $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...
92
            $this->tracer && $client->setTracer($this->tracer);
93
//            $client->setLogger()
94
95
            $this->eventDispatcher->dispatch(
96
                Events::POST_CLIENT_CREATE,
97
                new PostCreateClientEvent($this->namespace, $client)
0 ignored issues
show
Unused Code introduced by
The call to EventDispatcherInterface::dispatch() has too many arguments starting with new \ONGR\ElasticsearchB...is->namespace, $client).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
98
            );
99
            $this->client = $client->build();
100
        }
101
        return $this->client;
102
    }
103
104
    public function getIndexName(): string
105
    {
106
        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...
107
    }
108
109
    public function getEventDispatcher(): EventDispatcherInterface
110
    {
111
        return $this->eventDispatcher;
112
    }
113
114
    public function getConverter(): Converter
115
    {
116
        return $this->converter;
117
    }
118
119
    public function getBulkCommitSize(): int
120
    {
121
        return $this->bulkCommitSize;
122
    }
123
124
    public function setBulkCommitSize(int $bulkCommitSize)
125
    {
126
        $this->bulkCommitSize = $bulkCommitSize;
127
        return $this;
128
    }
129
130
    public function getStopwatch()
131
    {
132
        return $this->stopwatch;
133
    }
134
135
    public function setStopwatch($stopwatch)
136
    {
137
        $this->stopwatch = $stopwatch;
138
        return $this;
139
    }
140
141
    public function createIndex($noMapping = false, $params = []): array
142
    {
143
        $params = array_merge([
144
            'index' => $this->getIndexName(),
145
            '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...
146
        ], $params);
147
148
        #TODO Add event here.
149
150
        return $this->getClient()->indices()->create(array_filter($params));
151
    }
152
153
    public function dropIndex(): array
154
    {
155
        $indexName = $this->getIndexName();
156
157
        if ($this->getClient()->indices()->existsAlias(['name' => $this->getIndexName()])) {
158
            $aliases = $this->getClient()->indices()->getAlias(['name' => $this->getIndexName()]);
159
            $indexName = array_keys($aliases);
160
        }
161
162
        return $this->getClient()->indices()->delete(['index' => $indexName]);
163
    }
164
165
    public function dropAndCreateIndex($noMapping = false, $params = []): array
166
    {
167
        try {
168
            if ($this->indexExists()) {
169
                $this->dropIndex();
170
            }
171
        } catch (\Exception $e) {
172
            // Do nothing, our target is to create the new index.
173
        }
174
175
        return $this->createIndex($noMapping, $params);
176
    }
177
178
    public function indexExists(): bool
179
    {
180
        return $this->getClient()->indices()->exists(['index' => $this->getIndexName()]);
181
    }
182
183
    public function clearCache(): array
184
    {
185
        return $this->getClient()->indices()->clearCache(['index' => $this->getIndexName()]);
186
    }
187
188
    /**
189
     * Returns a single document by provided ID or null if a document was not found.
190
     */
191
    public function find($id, $params = [])
192
    {
193
        $requestParams = [
194
            'index' => $this->getIndexName(),
195
            'type' => $this->getTypeName(),
0 ignored issues
show
Deprecated Code introduced by
The method ONGR\ElasticsearchBundle...xService::getTypeName() has been deprecated with message: will be removed in v7 since there will be no more types in the indexes.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
196
            'id' => $id,
197
        ];
198
199
        $requestParams = array_merge($requestParams, $params);
200
201
        $result = $this->getClient()->get($requestParams);
202
203
        if (!$result['found']) {
204
            return null;
205
        }
206
207
        $result['_source']['_id'] = $result['_id'];
208
209
        return $this->converter->convertArrayToDocument($this->namespace, $result['_source'], $this->serializer);
210
    }
211
212
    public function findByIds(array $ids): DocumentIterator
213
    {
214
        $search = $this->createSearch();
215
        $search->addQuery(new IdsQuery($ids));
216
217
        return $this->findDocuments($search);
218
    }
219
220
    /**
221
     * Finds documents by a set of criteria.
222
     *
223
     * @param array      $criteria   Example: ['group' => ['best', 'worst'], 'job' => 'medic'].
224
     * @param array|null $orderBy    Example: ['name' => 'ASC', 'surname' => 'DESC'].
225
     * @param int|null   $limit      Default is 10.
226
     * @param int|null   $offset     Default is 0.
227
     *
228
     * @return array|DocumentIterator The objects.
229
     */
230
    public function findBy(
231
        array $criteria,
232
        array $orderBy = [],
233
        int $limit = 10,
234
        int $offset = 0
235
    ) {
236
        $search = $this->createSearch();
237
        $search->setSize($limit);
238
        $search->setFrom($offset);
239
240
        foreach ($criteria as $field => $value) {
241
            $search->addQuery(new TermQuery($field, $value));
242
        }
243
244
        foreach ($orderBy as $field => $direction) {
245
            $search->addSort(new FieldSort($field, $direction));
246
        }
247
248
        return $this->findDocuments($search);
249
    }
250
251
    /**
252
     * Finds a single document by a set of criteria.
253
     *
254
     * @param array      $criteria   Example: ['group' => ['best', 'worst'], 'job' => 'medic'].
255
     * @param array|null $orderBy    Example: ['name' => 'ASC', 'surname' => 'DESC'].
256
     *
257
     * @return object|null The object.
258
     */
259
    public function findOneBy(array $criteria, array $orderBy = [])
260
    {
261
        return $this->findBy($criteria, $orderBy, 1)->current();
262
    }
263
264
    public function createSearch(): Search
265
    {
266
        return new Search();
267
    }
268
269
    public function getScrollConfiguration($raw, $scrollDuration): array
270
    {
271
        $scrollConfig = [];
272
        if (isset($raw['_scroll_id'])) {
273
            $scrollConfig['_scroll_id'] = $raw['_scroll_id'];
274
            $scrollConfig['duration'] = $scrollDuration;
275
        }
276
277
        return $scrollConfig;
278
    }
279
280 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...
281
    {
282
        $results = $this->executeSearch($search);
283
284
        return new DocumentIterator(
285
            $results,
286
            $this,
287
            $this->converter,
288
            $this->serializer,
289
            $this->getScrollConfiguration($results, $search->getScroll())
290
        );
291
    }
292
293 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...
294
    {
295
        $results = $this->executeSearch($search);
296
297
        return new ArrayIterator(
298
            $results,
299
            $this,
300
            $this->converter,
301
            $this->serializer,
302
            $this->getScrollConfiguration($results, $search->getScroll())
303
        );
304
    }
305
306 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...
307
    {
308
        $results = $this->executeSearch($search);
309
310
        return new RawIterator(
311
            $results,
312
            $this,
313
            $this->converter,
314
            $this->serializer,
315
            $this->getScrollConfiguration($results, $search->getScroll())
316
        );
317
    }
318
319
    private function executeSearch(Search $search): array
320
    {
321
        return $this->search($search->toArray(), $search->getUriParams());
322
    }
323
324
    public function getIndexDocumentCount(): int
325
    {
326
        $body = [
327
            'index' => $this->getIndexName(),
328
            'type' => $this->getTypeName(),
0 ignored issues
show
Deprecated Code introduced by
The method ONGR\ElasticsearchBundle...xService::getTypeName() has been deprecated with message: will be removed in v7 since there will be no more types in the indexes.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
329
            'body' => [],
330
        ];
331
332
        $results = $this->getClient()->count($body);
333
334
        return $results['count'];
335
    }
336
337 View Code Duplication
    public function remove($id, $routing = null)
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...
338
    {
339
        $params = [
340
            'index' => $this->getIndexName(),
341
            'type' => $this->getTypeName(),
0 ignored issues
show
Deprecated Code introduced by
The method ONGR\ElasticsearchBundle...xService::getTypeName() has been deprecated with message: will be removed in v7 since there will be no more types in the indexes.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
342
            'id' => $id,
343
        ];
344
345
        if ($routing) {
346
            $params['routing'] = $routing;
347
        }
348
349
        $response = $this->getClient()->delete($params);
350
351
        return $response;
352
    }
353
354
    public function update($id, array $fields = [], $script = null, array $params = []): array
355
    {
356
        $body = array_filter(
357
            [
358
                'doc' => $fields,
359
                'script' => $script,
360
            ]
361
        );
362
363
        $params = array_merge(
364
            [
365
                'id' => $id,
366
                'index' => $this->getIndexName(),
367
                'type' => $this->getTypeName(),
0 ignored issues
show
Deprecated Code introduced by
The method ONGR\ElasticsearchBundle...xService::getTypeName() has been deprecated with message: will be removed in v7 since there will be no more types in the indexes.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
368
                'body' => $body,
369
            ],
370
            $params
371
        );
372
373
        return $this->getClient()->update($params);
374
    }
375
376 View Code Duplication
    public function search(array $query, array $params = []): array
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...
377
    {
378
        $requestParams = [
379
            'index' => $this->getIndexName(),
380
            'type' => $this->getTypeName(),
0 ignored issues
show
Deprecated Code introduced by
The method ONGR\ElasticsearchBundle...xService::getTypeName() has been deprecated with message: will be removed in v7 since there will be no more types in the indexes.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
381
            'body' => $query,
382
        ];
383
384
385
        if (!empty($params)) {
386
            $requestParams = array_merge($requestParams, $params);
387
        }
388
389
//        $this->stopwatch('start', 'search');
390
        $result = $this->getClient()->search($requestParams);
391
//        $this->stopwatch('stop', 'search');
392
393
        return $result;
394
    }
395
396
    /**
397
     * Usage example
398
     *
399
     * $im->bulk('index', ['_id' => 1, 'title' => 'foo']);
400
     * $im->bulk('delete', ['_id' => 2]);
401
     * $im->bulk('create', ['title' => 'foo']);
402
     */
403
    public function bulk(string $operation, array $data = [], $autoCommit = true): array
404
    {
405
        $bulkParams = [
406
            '_index' => $this->getIndexName(),
407
            '_type' => $this->getTypeName(),
0 ignored issues
show
Deprecated Code introduced by
The method ONGR\ElasticsearchBundle...xService::getTypeName() has been deprecated with message: will be removed in v7 since there will be no more types in the indexes.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
408
            '_id' => $data['_id'] ?? null,
409
        ];
410
411
        unset($data['_index'], $data['_type'], $data['_id']);
412
413
        $this->eventDispatcher->dispatch(
414
            Events::BULK,
415
            new BulkEvent($operation, $bulkParams, $data)
0 ignored issues
show
Unused Code introduced by
The call to EventDispatcherInterface::dispatch() has too many arguments starting with new \ONGR\ElasticsearchB...on, $bulkParams, $data).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
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, $this->serializer));
442
443
        $this->bulk('index', $documentArray);
444
    }
445
446
    public function commit($commitMode = 'flush', array $params = []): array
447
    {
448
        $bulkResponse = [];
449
        if (!empty($this->bulkQueries)) {
450
            $this->eventDispatcher->dispatch(
451
                Events::PRE_COMMIT,
452
                new CommitEvent($commitMode, $this->bulkQueries, [])
0 ignored issues
show
Unused Code introduced by
The call to EventDispatcherInterface::dispatch() has too many arguments starting with new \ONGR\ElasticsearchB...->bulkQueries, array()).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
453
            );
454
455
//            $this->stopwatch('start', 'bulk');
456
            $bulkResponse = $this->client->bulk(
457
                array_merge(
458
                    [
459
                    'index' => $this->getIndexName(),
460
                    'body' => $this->bulkQueries,
461
                    ],
462
                    $params
463
                )
464
            );
465
//            $this->stopwatch('stop', 'bulk');
466
467
            if ($bulkResponse['errors']) {
468
                throw new BulkWithErrorsException(
469
                    json_encode($bulkResponse),
470
                    0,
471
                    null,
472
                    $bulkResponse
473
                );
474
            }
475
476
//            $this->stopwatch('start', 'refresh');
477
            switch ($commitMode) {
478
                case 'flush':
479
                    $this->getClient()->indices()->flush();
480
                    break;
481
                case 'flush_synced':
482
                    $this->getClient()->indices()->flushSynced();
483
                    break;
484
                case 'refresh':
485
                    $this->getClient()->indices()->refresh();
486
                    break;
487
            }
488
489
            $this->eventDispatcher->dispatch(
490
                Events::POST_COMMIT,
491
                new CommitEvent($commitMode, $this->bulkQueries, $bulkResponse)
0 ignored issues
show
Unused Code introduced by
The call to EventDispatcherInterface::dispatch() has too many arguments starting with new \ONGR\ElasticsearchB...Queries, $bulkResponse).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

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