Completed
Push — master ( 6efea1...0e768a )
by Simonas
04:33 queued 10s
created

Repository::findBy()   C

Complexity

Conditions 7
Paths 24

Size

Total Lines 35
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 35
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 22
nc 24
nop 4
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 ONGR\ElasticsearchBundle\Result\ArrayIterator;
15
use ONGR\ElasticsearchBundle\Result\RawIterator;
16
use ONGR\ElasticsearchDSL\Query\FullText\QueryStringQuery;
17
use ONGR\ElasticsearchDSL\Query\Compound\BoolQuery;
18
use ONGR\ElasticsearchDSL\Search;
19
use ONGR\ElasticsearchDSL\Sort\FieldSort;
20
use ONGR\ElasticsearchBundle\Result\DocumentIterator;
21
22
/**
23
 * Document repository class.
24
 */
25
class Repository
26
{
27
    /**
28
     * @var Manager
29
     */
30
    private $manager;
31
32
    /**
33
     * @var string Fully qualified class name
34
     */
35
    private $className;
36
37
    /**
38
     * @var string Elasticsearch type name
39
     */
40
    private $type;
41
42
    /**
43
     * Constructor.
44
     *
45
     * @param Manager $manager
46
     * @param string  $className
47
     */
48
    public function __construct($manager, $className)
49
    {
50
        if (!is_string($className)) {
51
            throw new \InvalidArgumentException('Class name must be a string.');
52
        }
53
54
        if (!class_exists($className)) {
55
            throw new \InvalidArgumentException(
56
                sprintf('Cannot create repository for non-existing class "%s".', $className)
57
            );
58
        }
59
60
        $this->manager = $manager;
61
        $this->className = $className;
62
        $this->type = $this->resolveType($className);
63
    }
64
65
    /**
66
     * Returns elasticsearch manager used in the repository.
67
     *
68
     * @return Manager
69
     */
70
    public function getManager()
71
    {
72
        return $this->manager;
73
    }
74
75
    /**
76
     * @return array
77
     */
78
    public function getType()
79
    {
80
        return $this->type;
81
    }
82
83
    /**
84
     * Returns a single document data by ID or null if document is not found.
85
     *
86
     * @param string $id      Document ID to find
87
     * @param string $routing Custom routing for the document
88
     *
89
     * @return object
90
     */
91
    public function find($id, $routing = null)
92
    {
93
        return $this->manager->find($this->type, $id, $routing);
94
    }
95
96
    /**
97
     * Returns documents by a set of ids
98
     *
99
     * @param array $ids
100
     *
101
     * @return DocumentIterator The objects.
102
     */
103
    public function findByIds(array $ids)
104
    {
105
        $args = [];
106
        $manager = $this->getManager();
107
        $args['body']['docs'] = [];
108
        $args['index'] = $manager->getIndexName();
109
        $args['type'] = $this->getType();
110
111
        foreach ($ids as $id) {
112
            $args['body']['docs'][] = [
113
                '_id' => $id
114
            ];
115
        }
116
117
        $mgetResponse = $manager->getClient()->mget($args);
118
119
        $return = [
120
            'hits' => [
121
                'hits' => [],
122
                'total' => 0,
123
            ]
124
        ];
125
126
        foreach ($mgetResponse['docs'] as $item) {
127
            if ($item['found']) {
128
                $return['hits']['hits'][] = $item;
129
            }
130
        }
131
132
        $return['hits']['total'] = count($return['hits']['hits']);
133
134
        return new DocumentIterator($return, $manager);
135
    }
136
137
    /**
138
     * Finds documents by a set of criteria.
139
     *
140
     * @param array      $criteria   Example: ['group' => ['best', 'worst'], 'job' => 'medic'].
141
     * @param array|null $orderBy    Example: ['name' => 'ASC', 'surname' => 'DESC'].
142
     * @param int|null   $limit      Example: 5.
143
     * @param int|null   $offset     Example: 30.
144
     *
145
     * @return array|DocumentIterator The objects.
146
     */
147
    public function findBy(
148
        array $criteria,
149
        array $orderBy = [],
150
        $limit = null,
151
        $offset = null
152
    ) {
153
        $search = $this->createSearch();
154
155
        if ($limit !== null) {
156
            $search->setSize($limit);
157
        }
158
        if ($offset !== null) {
159
            $search->setFrom($offset);
160
        }
161
162
        foreach ($criteria as $field => $value) {
163
            if (preg_match('/^!(.+)$/', $field)) {
164
                $boolType = BoolQuery::MUST_NOT;
165
                $field = preg_replace('/^!/', '', $field);
166
            } else {
167
                $boolType = BoolQuery::MUST;
168
            }
169
170
            $search->addQuery(
171
                new QueryStringQuery(is_array($value) ? implode(' OR ', $value) : $value, ['default_field' => $field]),
172
                $boolType
173
            );
174
        }
175
176
        foreach ($orderBy as $field => $direction) {
177
            $search->addSort(new FieldSort($field, $direction));
178
        }
179
180
        return $this->findDocuments($search);
181
    }
182
183
    /**
184
     * Finds a single document by a set of criteria.
185
     *
186
     * @param array      $criteria   Example: ['group' => ['best', 'worst'], 'job' => 'medic'].
187
     * @param array|null $orderBy    Example: ['name' => 'ASC', 'surname' => 'DESC'].
188
     *
189
     * @return object|null The object.
190
     */
191
    public function findOneBy(array $criteria, array $orderBy = [])
192
    {
193
        return $this->findBy($criteria, $orderBy, 1, null)->current();
194
    }
195
196
    /**
197
     * Returns search instance.
198
     *
199
     * @return Search
200
     */
201
    public function createSearch()
202
    {
203
        return new Search();
204
    }
205
206
    /**
207
     * Parses scroll configuration from raw response.
208
     *
209
     * @param array  $raw
210
     * @param string $scrollDuration
211
     *
212
     * @return array
213
     */
214
    public function getScrollConfiguration($raw, $scrollDuration)
215
    {
216
        $scrollConfig = [];
217
        if (isset($raw['_scroll_id'])) {
218
            $scrollConfig['_scroll_id'] = $raw['_scroll_id'];
219
            $scrollConfig['duration'] = $scrollDuration;
220
        }
221
222
        return $scrollConfig;
223
    }
224
225
    /**
226
     * Returns DocumentIterator with composed Document objects from array response.
227
     *
228
     * @param Search $search
229
     *
230
     * @return DocumentIterator
231
     */
232 View Code Duplication
    public function findDocuments(Search $search)
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...
233
    {
234
        $results = $this->executeSearch($search);
235
236
        return new DocumentIterator(
237
            $results,
238
            $this->getManager(),
239
            $this->getScrollConfiguration($results, $search->getScroll())
240
        );
241
    }
242
243
244
    /**
245
     * Returns ArrayIterator with access to unmodified documents directly.
246
     *
247
     * @param Search $search
248
     *
249
     * @return ArrayIterator
250
     */
251 View Code Duplication
    public function findArray(Search $search)
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...
252
    {
253
        $results = $this->executeSearch($search);
254
255
        return new ArrayIterator(
256
            $results,
257
            $this->getManager(),
258
            $this->getScrollConfiguration($results, $search->getScroll())
259
        );
260
    }
261
262
    /**
263
     * Returns RawIterator with access to node with all returned values included.
264
     *
265
     * @param Search $search
266
     *
267
     * @return RawIterator
268
     */
269 View Code Duplication
    public function findRaw(Search $search)
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...
270
    {
271
        $results = $this->executeSearch($search);
272
273
        return new RawIterator(
274
            $results,
275
            $this->getManager(),
276
            $this->getScrollConfiguration($results, $search->getScroll())
277
        );
278
    }
279
280
    /**
281
     * Executes search to the elasticsearch and returns raw response.
282
     *
283
     * @param Search $search
284
     *
285
     * @return array
286
     */
287
    private function executeSearch(Search $search)
288
    {
289
        return $this->getManager()->search([$this->getType()], $search->toArray(), $search->getUriParams());
290
    }
291
292
    /**
293
     * Counts documents by given search.
294
     *
295
     * @param Search $search
296
     * @param array  $params
297
     * @param bool   $returnRaw If set true returns raw response gotten from client.
298
     *
299
     * @return int|array
300
     */
301
    public function count(Search $search, array $params = [], $returnRaw = false)
302
    {
303
        $body = array_merge(
304
            [
305
                'index' => $this->getManager()->getIndexName(),
306
                'type' => $this->type,
307
                'body' => $search->toArray(),
308
            ],
309
            $params
310
        );
311
312
        $results = $this
313
            ->getManager()
314
            ->getClient()->count($body);
315
316
        if ($returnRaw) {
317
            return $results;
318
        } else {
319
            return $results['count'];
320
        }
321
    }
322
323
    /**
324
     * Removes a single document data by ID.
325
     *
326
     * @param string $id      Document ID to remove
327
     * @param string $routing Custom routing for the document
328
     *
329
     * @return array
330
     *
331
     * @throws \LogicException
332
     */
333
    public function remove($id, $routing = null)
334
    {
335
        $params = [
336
            'index' => $this->getManager()->getIndexName(),
337
            'type' => $this->type,
338
            'id' => $id,
339
        ];
340
341
        if ($routing) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $routing of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
342
            $params['routing'] = $routing;
343
        }
344
345
        $response = $this->getManager()->getClient()->delete($params);
346
347
        return $response;
348
    }
349
350
    /**
351
     * Partial document update.
352
     *
353
     * @param string $id     Document id to update.
354
     * @param array  $fields Fields array to update.
355
     * @param string $script Groovy script to update fields.
356
     * @param array  $params Additional parameters to pass to the client.
357
     *
358
     * @return array
359
     */
360
    public function update($id, array $fields = [], $script = null, array $params = [])
361
    {
362
        $body = array_filter(
363
            [
364
                'doc' => $fields,
365
                'script' => $script,
366
            ]
367
        );
368
369
        $params = array_merge(
370
            [
371
                'id' => $id,
372
                'index' => $this->getManager()->getIndexName(),
373
                'type' => $this->type,
374
                'body' => $body,
375
            ],
376
            $params
377
        );
378
379
        return $this->getManager()->getClient()->update($params);
380
    }
381
382
    /**
383
     * Resolves elasticsearch type by class name.
384
     *
385
     * @param string $className
386
     *
387
     * @return array
388
     */
389
    private function resolveType($className)
390
    {
391
        return $this->getManager()->getMetadataCollector()->getDocumentType($className);
392
    }
393
394
    /**
395
     * Returns fully qualified class name.
396
     *
397
     * @return string
398
     */
399
    public function getClassName()
400
    {
401
        return $this->className;
402
    }
403
}
404