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 ( a11bd1...3fc1f5 )
by Anderson
02:01
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\MetaField;
19
use DoctrineElastic\Mapping\Type;
20
use DoctrineElastic\Query\ElasticQueryExecutor;
21
22
/**
23
 * Entity Persister for this doctrine elastic extension
24
 * This class implements some crud operations
25
 *
26
 * @author Ands
27
 */
28
class ElasticEntityPersister extends AbstractEntityPersister {
29
30
    /** @var array */
31
    protected $queuedInserts = [];
32
33
    /** @var AnnotationReader */
34
    private $annotationReader;
35
36
    /** @var ElasticQueryExecutor */
37
    private $queryExecutor;
38
39
    /** @var AnnotationEntityHydrator */
40
    private $hydrator;
41
42
    public function __construct(ElasticEntityManager $em, ClassMetadata $classMetadata) {
43
        parent::__construct($em, $classMetadata);
44
        $this->annotationReader = new AnnotationReader();
45
        $this->queryExecutor = new ElasticQueryExecutor($em);
46
        $this->hydrator = new AnnotationEntityHydrator();
47
        $this->validateEntity($classMetadata->name);
48
    }
49
50
    private function validateEntity($className) {
51
        $type = $this->annotationReader->getClassAnnotation($this->class->getReflectionClass(), Type::class);
52
53
        if (!($type instanceof Type)) {
54
            throw new AnnotationException(sprintf('%s annotation is missing for %s entity class',
55
                Type::class, get_class()));
56
        }
57
58
        if (!$type->isValid()) {
59
            $errorMessage = $type->getErrorMessage() . ' for %s entity class';
60
            throw new AnnotationException(sprintf($errorMessage, $className));
61
        }
62
63
        $_idSearch = $this->hydrator->extractWithAnnotation(new $className(), MetaField::class);
64
        $has_id = !empty($_idSearch);
65
66
        if (!$has_id) {
67
            $errorMessage = '_id metaField is missing in %s entity class';
68
            throw new AnnotationException(sprintf($errorMessage, $className));
69
        }
70
    }
71
72
    public function loadAll(array $criteria = [], array $orderBy = null, $limit = null, $offset = null) {
73
        $classMetadata = $this->getClassMetadata();
74
        $className = $classMetadata->getName();
75
        $type = $this->getEntityType();
76
        $sort = $must = [];
77
        $body = ['query' => ['bool' => ['must' => $must]]];
78
        /** @var Field $annotationProperty */
79
        $fieldAnnotations = $this->hydrator->extractSpecAnnotations($className, Field::class);
80
        /** @var MetaField[] $metaFieldAnnotations */
81
        $metaFieldAnnotations = $this->hydrator->extractSpecAnnotations($className, MetaField::class);
82
        $searchParams = new SearchParams();
83
84
        foreach ($criteria as $columnName => $value) {
85
            $annotation = null;
86
            if (isset($fieldAnnotations[$columnName])) {
87
                $annotation = $fieldAnnotations[$columnName];
88
            } else if (isset($metaFieldAnnotations[$columnName])) {
89
                $annotation = $metaFieldAnnotations[$columnName];
90
            }
91
92
            if (is_null($annotation)) {
93
                $msg = sprintf("field/metafield for column '%s' doesn't exist in %s entity class",
94
                    $columnName, $classMetadata->getName());
95
                throw new InvalidParamsException($msg);
96
            }
97
98
            if ($annotation->name === '_parent') {
99
                $searchParams->setParent($criteria[$columnName]);
100
            } else {
101
                $must[] = array(
102
                    'match' => array(
103
                        $annotation->name => $criteria[$columnName],
104
                    )
105
                );
106
            }
107
        }
108
109
        if (is_array($orderBy)) {
110
            foreach ($orderBy as $columnName => $order) {
111
                if (isset($fieldAnnotations[$columnName])) {
112
                    $sort[$fieldAnnotations[$columnName]->name] = $order;
113
                } else if (isset($metaFieldAnnotations[$columnName])) {
114
                    $sort[$metaFieldAnnotations[$columnName]->name] = $order;
115
                }
116
            }
117
        }
118
119
        $body['query']['bool']['must'] = $must;
120
121
        $searchParams->setIndex($type->getIndex());
122
        $searchParams->setType($type->getName());
123
        $searchParams->setBody($body);
124
        $searchParams->setSize($limit);
125
        $searchParams->setSort($sort);
126
        $searchParams->setFrom($offset);
127
128
        return $this->queryExecutor->execute($searchParams, $classMetadata->name);
129
    }
130
131
    public function getAnnotionReader() {
132
        return $this->annotationReader;
133
    }
134
135
    public function executeInserts() {
136
        foreach ($this->queuedInserts as $entity) {
137
            $type = $this->getEntityType();
138
            $entityCopy = clone $entity;
139
140
            $this->em->getEventManager()->dispatchEvent(
141
                DoctrineElasticEvents::beforeInsert, new EntityEventArgs($entityCopy)
142
            );
143
144
            $fieldsData = $this->hydrator->extractWithAnnotation($entityCopy, Field::class);
145
            $metaFieldsData = $this->hydrator->extractWithAnnotation($entityCopy, MetaField::class);
146
            $mergeParams = [];
147
148
            if (array_key_exists('_id', $metaFieldsData) && !empty($metaFieldsData['_id'])) {
149
                $mergeParams['id'] = $metaFieldsData['_id'];
150
            }
151
152
            if (isset($metaFieldsData['_parent'])) {
153
                $mergeParams['parent'] = $metaFieldsData['_parent'];
154
            }
155
156
            $this->createTypeIfNotExists($type, $this->getClassMetadata()->name);
157
158
            $this->checkIndentityConstraints($entityCopy);
159
            $return = [];
160
161
            $inserted = $this->em->getConnection()->insert(
162
                $type->getIndex(), $type->getName(), $fieldsData, $mergeParams, $return
163
            );
164
165
            if ($inserted) {
166
                $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...
167
                $this->em->getEventManager()->dispatchEvent(
168
                    DoctrineElasticEvents::postInsert, new EntityEventArgs($entity)
169
                );
170
            } else {
171
                throw new ElasticOperationException(sprintf('Unable to complete update operation, '
172
                    . 'with the following elastic return: <br><pre>%s</pre>', var_export($return)));
173
            }
174
        }
175
    }
176
177
    /**
178
     * @param Type $type
179
     * @param string $className
180
     * @throws ElasticConstraintException
181
     */
182
    private function createTypeIfNotExists(Type $type, $className) {
183
        foreach ($type->getChildClasses() as $childClass) {
184
            $this->createTypeIfNotExists($this->getEntityType($childClass), $childClass);
185
        }
186
187
        $indexName = $type->getIndex();
188
        $typeName = $type->getName();
189
190
        if (!$this->em->getConnection()->typeExists($indexName, $typeName)) {
191
            $propertiesMapping = [];
192
            /** @var Field[] $ESFields */
193
            $ESFields = $this->hydrator->extractSpecAnnotations($className, Field::class);
194
195
            foreach ($ESFields as $ESField) {
196
                if ($ESField instanceof Field) {
197
                    $propertiesMapping[$ESField->name] = ['type' => $ESField->type];
198
199
                    foreach ($ESField->getArrayCopy() as $prop => $propValue) {
200
                        if ($ESField->type == 'nested' && ($prop == 'boost' || $prop == 'index')) {
201
                            continue;
202
                        }
203
                        if (!is_null($propValue) && $prop != 'name') {
204
                            $propertiesMapping[$ESField->name][$prop] = $propValue;
205
                        }
206
                    }
207
                }
208
            }
209
210
            $mappings = array(
211
                $typeName => array(
212
                    'properties' => $propertiesMapping
213
                )
214
            );
215
216
            if ($type->getParentClass()) {
217
                $refParentClass = new \ReflectionClass($type->getParentClass());
218
                /** @var Type $parentType */
219
                $parentType = $this->getAnnotionReader()->getClassAnnotation($refParentClass, Type::class);
220
221
                if ($parentType->getIndex() != $type->getIndex()) {
222
                    throw new ElasticConstraintException('Child and parent types have different indices. ');
223
                }
224
225
                $mappings[$typeName]['_parent'] = ['type' => $parentType->getName()];
226
            }
227
228
            if (!$this->em->getConnection()->indexExists($indexName)) {
229
                $this->em->getConnection()->createIndex($indexName, $mappings);
230
            } else {
231
                $this->em->getConnection()->createType($indexName, $typeName, $mappings);
232
            }
233
        }
234
    }
235
236
    /**
237
     * Check Identity values for entity, if there are identity fields,
238
     * check if already exists items with such value (unique constraint verification)
239
     *
240
     * @param object $entity
241
     * @throws ElasticConstraintException
242
     */
243
    private function checkIndentityConstraints($entity) {
244
        $identities = $this->getClassMetadata()->getIdentifierValues($entity);
245
246
        foreach ($identities as $property => $value) {
247
            $element = $this->load([$property => $value]);
248
249
            if (boolval($element)) {
250
                throw new ElasticConstraintException(sprintf("Unique/IDENTITY field %s already has "
251
                    . "a document with value '%s'", $property, $value));
252
            }
253
        }
254
    }
255
256
    public function load(
257
        array $criteria, $entity = null, $assoc = null, array $hints = [],
258
        $lockMode = null, $limit = null, array $orderBy = null
259
    ) {
260
        $results = $this->loadAll($criteria, $orderBy, $limit);
261
262
        return count($results) ? $results[0] : null;
263
    }
264
265
    /**
266
     * @param null|string $className
267
     * @return Type
268
     * @throws MappingException
269
     */
270
    private function getEntityType($className = null) {
271
        if (is_string($className) && boolval($className)) {
272
            $refClass = new \ReflectionClass($className);
273
        } else {
274
            $refClass = $this->getClassMetadata()->getReflectionClass();
275
        }
276
277
        $type = $this->annotationReader->getClassAnnotation($refClass, Type::class);
278
279
        if ($type instanceof Type) {
280
            return $type;
281
        } else {
282
            throw new MappingException(sprintf('Unable to get Type Mapping of %s entity', $this->class->name));
283
        }
284
    }
285
286 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...
287
        if (isset($searchResult['_source'])) {
288
            $searchResult = array_merge($searchResult, $searchResult['_source']);
289
        }
290
291
        $this->hydrator->hydrate($entity, $searchResult);
292
        $this->hydrator->hydrateByAnnotation($entity, Field::class, $searchResult);
293
        $this->hydrator->hydrateByAnnotation($entity, MetaField::class, $searchResult);
294
295
        return $entity;
296
    }
297
298
    public function update($entity) {
299
        $type = $this->getEntityType();
300
        $dataUpdate = $this->hydrator->extractWithAnnotation($entity, Field::class);
301
        $_id = $this->hydrator->extract($entity, '_id');
302
        $return = [];
303
304
        $updated = $this->em->getConnection()->update(
305
            $type->getIndex(), $type->getName(), $_id, $dataUpdate, [], $return
306
        );
307
308
        if ($updated) {
309
            $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...
310
        } else {
311
            throw new ElasticOperationException(sprintf('Unable to complete update operation, '
312
                . 'with the following elastic return: <br><pre>%s</pre>', var_export($return)));
313
        }
314
    }
315
316
    public function addInsert($entity) {
317
        $oid = spl_object_hash($entity);
318
        $this->queuedInserts[$oid] = $entity;
319
    }
320
321
    public function loadById(array $_idArray, $entity = null) {
322
        $type = $this->getEntityType();
323
324
        if (is_object($entity) && get_class($entity) != $this->class->name) {
325
            throw new \InvalidArgumentException('You can only get an element by _id with its properly persister');
326
        }
327
328
        $id = isset($_idArray['_id']) ? $_idArray['_id'] : reset($_idArray);
329
330
        $documentData = $this->em->getConnection()->get($type->getIndex(), $type->getName(), $id);
331
332
        if ($documentData) {
333
            $entity = is_object($entity) ? $entity : new $this->class->name;
334
            $this->hydrateEntityByResult($entity, $documentData);
335
336
            return $entity;
337
        }
338
339
        return null;
340
    }
341
342
    public function delete($entity) {
343
        $type = $this->getEntityType();
344
        $return = [];
345
        $_id = $this->hydrator->extract($entity, '_id');
346
347
        $deletion = $this->em->getConnection()->delete(
348
            $type->getIndex(), $type->getName(), $_id, [], $return
349
        );
350
351
        if ($deletion) {
352
            return true;
353
        } else {
354
            throw new ElasticOperationException(sprintf('Unable to complete update operation, '
355
                . 'with the following elastic return: <br><pre>%s</pre>', var_export($return)));
356
        }
357
    }
358
}
359