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
Pull Request — master (#11)
by
unknown
06:26
created

ElasticEntityPersister::loadAll()   C

Complexity

Conditions 10
Paths 17

Size

Total Lines 58
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 58
rs 6.6515
c 0
b 0
f 0
cc 10
eloc 39
nc 17
nop 4

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace DoctrineElastic\Persister;
4
5
use Doctrine\Common\Annotations\AnnotationException;
6
use Doctrine\Common\Annotations\AnnotationReader;
7
use Doctrine\ORM\Mapping\ClassMetadata;
8
use Doctrine\ORM\Mapping\MappingException;
9
use DoctrineElastic\ElasticEntityManager;
10
use DoctrineElastic\Elastic\DoctrineElasticEvents;
11
use DoctrineElastic\Elastic\SearchParams;
12
use DoctrineElastic\Event\EntityEventArgs;
13
use DoctrineElastic\Exception\ElasticConstraintException;
14
use DoctrineElastic\Exception\ElasticOperationException;
15
use DoctrineElastic\Exception\InvalidParamsException;
16
use DoctrineElastic\Hydrate\AnnotationEntityHydrator;
17
use DoctrineElastic\Mapping\Field;
18
use DoctrineElastic\Mapping\Index;
19
use DoctrineElastic\Mapping\MetaField;
20
use DoctrineElastic\Mapping\Type;
21
use DoctrineElastic\Query\ElasticQueryExecutor;
22
23
/**
24
 * Entity Persister for this doctrine elastic extension
25
 * This class implements some crud operations
26
 *
27
 * @author Ands
28
 */
29
class ElasticEntityPersister extends AbstractEntityPersister {
30
31
    /** @var array */
32
    protected $queuedInserts = [];
33
34
    /** @var AnnotationReader */
35
    private $annotationReader;
36
37
    /** @var ElasticQueryExecutor */
38
    private $queryExecutor;
39
40
    /** @var AnnotationEntityHydrator */
41
    private $hydrator;
42
43
    private $refresh;
44
45
    public function __construct(ElasticEntityManager $em, ClassMetadata $classMetadata) {
46
        parent::__construct($em, $classMetadata);
47
        $this->annotationReader = new AnnotationReader();
48
        $this->queryExecutor = new ElasticQueryExecutor($em);
49
        $this->hydrator = new AnnotationEntityHydrator();
50
        $this->validateEntity($classMetadata->name);
51
    }
52
53
    public function enableRefresh()
54
    {
55
        $this->refresh = true;
56
    }
57
58
    public function disableRefresh()
59
    {
60
        $this->refresh = false;
61
    }
62
63
    private function validateEntity($className) {
64
        $type = $this->annotationReader->getClassAnnotation($this->class->getReflectionClass(), Type::class);
65
        /** @var Index $index */
66
        $index = $this->annotationReader->getClassAnnotation($this->class->getReflectionClass(), Index::class);
67
68
        if (!($type instanceof Type)) {
69
            throw new AnnotationException(sprintf('%s annotation is missing for %s entity class',
70
                Type::class, get_class()));
71
        }
72
73
        if ($index && $index->getName()) {
74
            $type->setIndex($index->getName());
75
        }
76
77
        if (!$type->isValid()) {
78
            $errorMessage = reset($type->getErrorMessage()) . ' for %s entity class';
0 ignored issues
show
Bug introduced by
$type->getErrorMessage() cannot be passed to reset() as the parameter $array expects a reference.
Loading history...
79
            throw new AnnotationException(sprintf($errorMessage, $className));
80
        }
81
82
        $_idSearch = $this->hydrator->extractWithAnnotation(new $className(), MetaField::class);
83
        $has_id = !empty($_idSearch);
84
85
        if (!$has_id) {
86
            $errorMessage = '_id metaField is missing in %s entity class';
87
            throw new AnnotationException(sprintf($errorMessage, $className));
88
        }
89
    }
90
91
    public function loadAll(array $criteria = [], array $orderBy = null, $limit = null, $offset = null) {
92
        $classMetadata = $this->getClassMetadata();
93
        $className = $classMetadata->getName();
94
        $type = $this->getEntityType();
95
        $sort = $must = [];
96
        $body = ['query' => ['bool' => ['must' => $must]]];
97
        /** @var Field $annotationProperty */
98
        $fieldAnnotations = $this->hydrator->extractSpecAnnotations($className, Field::class);
99
        /** @var MetaField[] $metaFieldAnnotations */
100
        $metaFieldAnnotations = $this->hydrator->extractSpecAnnotations($className, MetaField::class);
101
        $searchParams = new SearchParams();
102
103
        foreach ($criteria as $columnName => $value) {
104
            $annotation = null;
105
            if (isset($fieldAnnotations[$columnName])) {
106
                $annotation = $fieldAnnotations[$columnName];
107
            } else if (isset($metaFieldAnnotations[$columnName])) {
108
                $annotation = $metaFieldAnnotations[$columnName];
109
            }
110
111
            if (is_null($annotation)) {
112
                $msg = sprintf("field/metafield for column '%s' doesn't exist in %s entity class",
113
                    $columnName, $classMetadata->getName());
114
                throw new InvalidParamsException($msg);
115
            }
116
117
            if ($annotation->name === '_parent') {
118
                $searchParams->setParent($criteria[$columnName]);
119
            } else {
120
                $must[] = array(
121
                    'match' => array(
122
                        $annotation->name => $criteria[$columnName],
123
                    )
124
                );
125
            }
126
        }
127
128
        if (is_array($orderBy)) {
129
            foreach ($orderBy as $columnName => $order) {
130
                if (isset($fieldAnnotations[$columnName])) {
131
                    $sort[$fieldAnnotations[$columnName]->name] = ['order' => $order];
132
                } else if (isset($metaFieldAnnotations[$columnName])) {
133
                    $sort[$metaFieldAnnotations[$columnName]->name] = ['order' => $order];
134
                }
135
            }
136
        }
137
138
        $body['query']['bool']['must'] = $must;
139
140
        $searchParams->setIndex($type->getIndex());
141
        $searchParams->setType($type->getName());
142
        $searchParams->setBody($body);
143
        $searchParams->setSize($limit);
144
        $searchParams->setSort($sort);
145
        $searchParams->setFrom($offset);
146
147
        return $this->queryExecutor->execute($searchParams, $classMetadata->name);
148
    }
149
150
    public function getAnnotionReader() {
151
        return $this->annotationReader;
152
    }
153
154
    public function executeInserts() {
155
        foreach ($this->queuedInserts as $entity) {
156
            $type = $this->getEntityType();
157
            /** @var Index $index */
158
            $index = $this->getEntityIndex();
159
            $entityCopy = clone $entity;
160
161
            $this->em->getEventManager()->dispatchEvent(
162
                DoctrineElasticEvents::beforeInsert, new EntityEventArgs($entityCopy)
163
            );
164
165
            $fieldsData = $this->hydrator->extractWithAnnotation($entityCopy, Field::class);
166
            $metaFieldsData = $this->hydrator->extractWithAnnotation($entityCopy, MetaField::class);
167
            $mergeParams = [];
168
169
            if (array_key_exists('_id', $metaFieldsData) && !empty($metaFieldsData['_id'])) {
170
                $mergeParams['id'] = $metaFieldsData['_id'];
171
            }
172
173
            if (isset($metaFieldsData['_parent'])) {
174
                $mergeParams['parent'] = $metaFieldsData['_parent'];
175
            }
176
177
            $mergeParams['refresh'] = $this->refresh ? "true" : "false";
178
179
            if ($index) {
180
                $this->createIndexIfNotExists($index, $this->getClassMetadata()->name);
181
            }
182
            $this->createTypeIfNotExists($type, $this->getClassMetadata()->name, $index);
183
            $indexName = ($index && $index->getName()) ? $index->getName() : $type->getIndex();
184
185
186
            $this->checkIndentityConstraints($entityCopy);
187
            $return = [];
188
189
            $inserted = $this->em->getConnection()->insert(
190
                $indexName, $type->getName(), $fieldsData, $mergeParams, $return
191
            );
192
193
            if ($inserted) {
194
                $this->hydrateEntityByResult($entity, $return);
0 ignored issues
show
Bug introduced by
It seems like $return can also be of type null; however, DoctrineElastic\Persiste...hydrateEntityByResult() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
195
                $this->em->getEventManager()->dispatchEvent(
196
                    DoctrineElasticEvents::postInsert, new EntityEventArgs($entity)
197
                );
198
            } else {
199
                throw new ElasticOperationException(sprintf('Unable to complete update operation, '
200
                    . 'with the following elastic return: <br><pre>%s</pre>', var_export($return)));
201
            }
202
        }
203
    }
204
205
    private function createIndexIfNotExists(Index $index, $className = null)
0 ignored issues
show
Unused Code introduced by
The parameter $className is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
206
    {
207
        $indexName = $index->getName();
208
209
        if (!$this->em->getConnection()->indexExists($indexName)) {
210
            $indexSettings = $index->getArrayCopy()['settings'];
211
212
            $this->em->getConnection()->createIndex($indexName, null, $indexSettings);
213
        }
214
    }
215
216
    /**
217
     * @param Type $type
218
     * @param string $className
219
     * @param Index $index
220
     * @throws ElasticConstraintException
221
     */
222
    private function createTypeIfNotExists(Type $type, $className, Index $index = null) {
223
        foreach ($type->getChildClasses() as $childClass) {
224
            $this->createTypeIfNotExists($this->getEntityType($childClass), $childClass);
225
        }
226
227
        $indexName = ($index && $index->getName()) ? $index->getName() : $type->getIndex();
228
        $typeName = $type->getName();
229
230
        if (!$this->em->getConnection()->typeExists($indexName, $typeName)) {
231
            $propertiesMapping = [];
232
            /** @var Field[] $ESFields */
233
            $ESFields = $this->hydrator->extractSpecAnnotations($className, Field::class);
234
235
            foreach ($ESFields as $ESField) {
236
                if ($ESField instanceof Field) {
237
                    $propertiesMapping[$ESField->name] = ['type' => $ESField->type];
238
239
                    foreach ($ESField->getArrayCopy() as $prop => $propValue) {
240
                        if ($ESField->type == 'nested' && ($prop == 'boost' || $prop == 'index')) {
241
                            continue;
242
                        }
243
                        if (!is_null($propValue) && $prop != 'name') {
244
                            $propertiesMapping[$ESField->name][$prop] = $propValue;
245
                        }
246
                    }
247
                }
248
            }
249
250
            $mappings = array(
251
                $typeName => array(
252
                    'properties' => $propertiesMapping
253
                )
254
            );
255
256
            if ($type->getParentClass()) {
257
                $refParentClass = new \ReflectionClass($type->getParentClass());
258
                /** @var Type $parentType */
259
                $parentType = $this->getAnnotionReader()->getClassAnnotation($refParentClass, Type::class);
260
261
                if ($parentType->getIndex() != $type->getIndex()) {
262
                    throw new ElasticConstraintException('Child and parent types have different indices. ');
263
                }
264
265
                $mappings[$typeName]['_parent'] = ['type' => $parentType->getName()];
266
            }
267
268
            if (!$this->em->getConnection()->indexExists($indexName)) {
269
                $this->em->getConnection()->createIndex($indexName, $mappings);
270
            } else {
271
                $this->em->getConnection()->createType($indexName, $typeName, $mappings);
272
            }
273
        }
274
    }
275
276
    /**
277
     * Check Identity values for entity, if there are identity fields,
278
     * check if already exists items with such value (unique constraint verification)
279
     *
280
     * @param object $entity
281
     * @throws ElasticConstraintException
282
     */
283
    private function checkIndentityConstraints($entity) {
284
        $identities = $this->getClassMetadata()->getIdentifierValues($entity);
285
286
        foreach ($identities as $property => $value) {
287
            $element = $this->load([$property => $value]);
288
289
            if (boolval($element)) {
290
                throw new ElasticConstraintException(sprintf("Unique/IDENTITY field %s already has "
291
                    . "a document with value '%s'", $property, $value));
292
            }
293
        }
294
    }
295
296
    public function load(
297
        array $criteria, $entity = null, $assoc = null, array $hints = [],
298
        $lockMode = null, $limit = null, array $orderBy = null
299
    ) {
300
        $results = $this->loadAll($criteria, $orderBy, $limit);
301
302
        return count($results) ? $results[0] : null;
303
    }
304
305
    /**
306
     * @param null|string $className
307
     * @return Type
308
     * @throws MappingException
309
     */
310
    private function getEntityType($className = null) {
311 View Code Duplication
        if (is_string($className) && boolval($className)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
312
            $refClass = new \ReflectionClass($className);
313
        } else {
314
            $refClass = $this->getClassMetadata()->getReflectionClass();
315
        }
316
317
        /** @var Type $type */
318
        $type = $this->annotationReader->getClassAnnotation($refClass, Type::class);
319
        /** @var Index $index */
320
        $index = $this->annotationReader->getClassAnnotation($refClass, Index::class);
321
322
        if ($index && $index->getName()) {
323
            $type->setIndex($index->getName());
324
        }
325
326
        if ($type instanceof Type) {
327
            return $type;
328
        } else {
329
            throw new MappingException(sprintf('Unable to get Type Mapping of %s entity', $this->class->name));
330
        }
331
    }
332
333
    private function getEntityIndex($className = null)
334
    {
335 View Code Duplication
        if (is_string($className) && boolval($className)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
336
            $refClass = new \ReflectionClass($className);
337
        } else {
338
            $refClass = $this->getClassMetadata()->getReflectionClass();
339
        }
340
341
        $index = $this->annotationReader->getClassAnnotation($refClass, Index::class);
342
343
        if ($index instanceof Index) {
344
            return $index;
345
        }
346
347
        return null;
348
    }
349
350 View Code Duplication
    private function hydrateEntityByResult($entity, array $searchResult) {
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...
351
        if (isset($searchResult['_source'])) {
352
            $searchResult = array_merge($searchResult, $searchResult['_source']);
353
        }
354
355
        $this->hydrator->hydrate($entity, $searchResult);
356
        $this->hydrator->hydrateByAnnotation($entity, Field::class, $searchResult);
357
        $this->hydrator->hydrateByAnnotation($entity, MetaField::class, $searchResult);
358
359
        return $entity;
360
    }
361
362
    public function update($entity) {
363
        $type = $this->getEntityType();
364
        $dataUpdate = $this->hydrator->extractWithAnnotation($entity, Field::class);
365
        $_id = $this->hydrator->extract($entity, '_id');
366
        $return = [];
367
368
        $mergeParams = $this->refresh ? ["refresh" => "true"] : ["refresh" => "false"];
369
370
        $updated = $this->em->getConnection()->update(
371
            $type->getIndex(), $type->getName(), $_id, $dataUpdate, $mergeParams, $return
372
        );
373
374
        if ($updated) {
375
            $this->hydrateEntityByResult($entity, $return);
0 ignored issues
show
Bug introduced by
It seems like $return can also be of type null; however, DoctrineElastic\Persiste...hydrateEntityByResult() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
376
        } else {
377
            throw new ElasticOperationException(sprintf('Unable to complete update operation, '
378
                . 'with the following elastic return: <br><pre>%s</pre>', var_export($return)));
379
        }
380
    }
381
382
    public function addInsert($entity) {
383
        $oid = spl_object_hash($entity);
384
        $this->queuedInserts[$oid] = $entity;
385
    }
386
387
    public function loadById(array $_idArray, $entity = null) {
388
        $type = $this->getEntityType();
389
390
        if (is_object($entity) && get_class($entity) != $this->class->name) {
391
            throw new \InvalidArgumentException('You can only get an element by _id with its properly persister');
392
        }
393
394
        $id = isset($_idArray['_id']) ? $_idArray['_id'] : reset($_idArray);
395
396
        $documentData = $this->em->getConnection()->get($type->getIndex(), $type->getName(), $id);
397
398
        if ($documentData) {
399
            $entity = is_object($entity) ? $entity : new $this->class->name;
400
            $this->hydrateEntityByResult($entity, $documentData);
401
402
            return $entity;
403
        }
404
405
        return null;
406
    }
407
408
    public function delete($entity) {
409
        $type = $this->getEntityType();
410
        $return = [];
411
        $_id = $this->hydrator->extract($entity, '_id');
412
413
        $deletion = $this->em->getConnection()->delete(
414
            $type->getIndex(), $type->getName(), $_id, [], $return
415
        );
416
417
        if ($deletion) {
418
            return true;
419
        } else {
420
            throw new ElasticOperationException(sprintf('Unable to complete delete operation, '
421
                . 'with the following elastic return: <br><pre>%s</pre>', var_export($return)));
422
        }
423
    }
424
}
425