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 ( 619fa8...db31ee )
by Anderson
02:20
created

ElasticEntityPersister::loadAll()   C

Complexity

Conditions 10
Paths 17

Size

Total Lines 56
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 56
rs 6.7741
c 0
b 0
f 0
cc 10
eloc 37
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\MappingException;
8
use DoctrineElastic\ElasticEntityManager;
9
use DoctrineElastic\Elastic\DoctrineElasticEvents;
10
use DoctrineElastic\Elastic\SearchParams;
11
use DoctrineElastic\Event\EntityEventArgs;
12
use DoctrineElastic\Exception\ElasticConstraintException;
13
use DoctrineElastic\Exception\ElasticOperationException;
14
use DoctrineElastic\Exception\InvalidParamsException;
15
use DoctrineElastic\Hydrate\AnnotationEntityHydrator;
16
use DoctrineElastic\Mapping\Constraint;
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 {
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
    /** @var string */
43
    private $className;
44
45
    /** @var \ReflectionClass */
46
    private $reflectionClass;
47
48
    public function __construct(ElasticEntityManager $em, $className) {
49
        $this->className = $className;
50
        $this->annotationReader = new AnnotationReader();
51
        $this->queryExecutor = new ElasticQueryExecutor($em);
52
        $this->hydrator = new AnnotationEntityHydrator();
53
        $this->validateEntity();
54
        $this->em = $em;
0 ignored issues
show
Bug introduced by
The property em does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
55
    }
56
57
    public function getReflectionClass() {
58
        if (is_null($this->reflectionClass)) {
59
            $this->reflectionClass = new \ReflectionClass($this->className);
60
        }
61
62
        return $this->reflectionClass;
63
    }
64
65
    private function validateEntity() {
66
        $type = $this->annotationReader->getClassAnnotation($this->getReflectionClass(), Type::class);
67
        $className = $this->className;
68
69
        if (!($type instanceof Type)) {
70
            throw new AnnotationException(sprintf('%s annotation is missing for %s entity class',
71
                Type::class, get_class()));
72
        }
73
74
        if (!$type->isValid()) {
75
            $errorMessage = $type->getErrorMessage() . ' for %s entity class';
76
            throw new AnnotationException(sprintf($errorMessage, $className));
77
        }
78
79
        $_idSearch = $this->hydrator->extractWithAnnotation(new $className(), MetaField::class);
80
        $has_id = !empty($_idSearch);
81
82
        if (!$has_id) {
83
            $errorMessage = '_id metaField is missing in %s entity class';
84
            throw new AnnotationException(sprintf($errorMessage, $className));
85
        }
86
    }
87
88
    public function loadAll(array $criteria = [], array $orderBy = null, $limit = null, $offset = null) {
89
        $type = $this->getEntityType();
90
        $sort = $must = [];
91
        $body = ['query' => ['bool' => ['must' => $must]]];
92
        /** @var Field $annotationProperty */
93
        $fieldAnnotations = $this->hydrator->extractSpecAnnotations($this->className, Field::class);
94
        /** @var MetaField[] $metaFieldAnnotations */
95
        $metaFieldAnnotations = $this->hydrator->extractSpecAnnotations($this->className, MetaField::class);
96
        $searchParams = new SearchParams();
97
98
        foreach ($criteria as $columnName => $value) {
99
            $annotation = null;
100
            if (isset($fieldAnnotations[$columnName])) {
101
                $annotation = $fieldAnnotations[$columnName];
102
            } else if (isset($metaFieldAnnotations[$columnName])) {
103
                $annotation = $metaFieldAnnotations[$columnName];
104
            }
105
106
            if (is_null($annotation)) {
107
                $msg = sprintf("field/metafield for column '%s' doesn't exist in %s entity class",
108
                    $columnName, $this->className);
109
                throw new InvalidParamsException($msg);
110
            }
111
112
            if ($annotation->name === '_parent') {
113
                $searchParams->setParent($criteria[$columnName]);
114
            } else {
115
                $must[] = array(
116
                    'match' => array(
117
                        $annotation->name => $criteria[$columnName],
118
                    )
119
                );
120
            }
121
        }
122
123
        if (is_array($orderBy)) {
124
            foreach ($orderBy as $columnName => $order) {
125
                if (isset($fieldAnnotations[$columnName])) {
126
                    $sort[$fieldAnnotations[$columnName]->name] = ['order' => $order];
127
                } else if (isset($metaFieldAnnotations[$columnName])) {
128
                    $sort[$metaFieldAnnotations[$columnName]->name] = ['order' => $order];
129
                }
130
            }
131
        }
132
133
        $body['query']['bool']['must'] = $must;
134
135
        $searchParams->setIndex($type->getIndex());
136
        $searchParams->setType($type->getName());
137
        $searchParams->setBody($body);
138
        $searchParams->setSize($limit);
139
        $searchParams->setSort($sort);
140
        $searchParams->setFrom($offset);
141
142
        return $this->queryExecutor->execute($searchParams, $this->className);
143
    }
144
145
    public function getAnnotionReader() {
146
        return $this->annotationReader;
147
    }
148
149
    public function executeInserts() {
150
        foreach ($this->queuedInserts as $entity) {
151
            $type = $this->getEntityType();
152
            $entityCopy = clone $entity;
153
154
            $this->em->getEventManager()->dispatchEvent(
155
                DoctrineElasticEvents::beforeInsert, new EntityEventArgs($entityCopy)
156
            );
157
158
            $fieldsData = $this->hydrator->extractWithAnnotation($entityCopy, Field::class);
159
            $metaFieldsData = $this->hydrator->extractWithAnnotation($entityCopy, MetaField::class);
160
            $mergeParams = [];
161
162
            if (array_key_exists('_id', $metaFieldsData) && !empty($metaFieldsData['_id'])) {
163
                $mergeParams['id'] = $metaFieldsData['_id'];
164
            }
165
166
            if (isset($metaFieldsData['_parent'])) {
167
                $mergeParams['parent'] = $metaFieldsData['_parent'];
168
            }
169
170
            $this->createTypeIfNotExists($type, $this->className);
171
172
            $this->checkConstraints($entityCopy);
173
            $return = [];
174
175
            $inserted = $this->em->getConnection()->insert(
176
                $type->getIndex(), $type->getName(), $fieldsData, $mergeParams, $return
177
            );
178
179
            if ($inserted) {
180
                $this->hydrateEntityByResult($entity, $return);
181
                $this->em->getEventManager()->dispatchEvent(
182
                    DoctrineElasticEvents::postInsert, new EntityEventArgs($entity)
183
                );
184
            } else {
185
                throw new ElasticOperationException(sprintf('Unable to complete update operation, '
186
                    . 'with the following elastic return: <br><pre>%s</pre>', var_export($return)));
187
            }
188
        }
189
    }
190
191
    /**
192
     * @param Type $type
193
     * @param string $className
194
     * @throws ElasticConstraintException
195
     */
196
    private function createTypeIfNotExists(Type $type, $className) {
197
        foreach ($type->getChildClasses() as $childClass) {
198
            $this->createTypeIfNotExists($this->getEntityType($childClass), $childClass);
199
        }
200
201
        $indexName = $type->getIndex();
202
        $typeName = $type->getName();
203
204
        if (!$this->em->getConnection()->typeExists($indexName, $typeName)) {
205
            $propertiesMapping = [];
206
            /** @var Field[] $ESFields */
207
            $ESFields = $this->hydrator->extractSpecAnnotations($className, Field::class);
208
209
            foreach ($ESFields as $ESField) {
210
                if ($ESField instanceof Field) {
211
                    $propertiesMapping[$ESField->name] = ['type' => $ESField->type];
212
213
                    foreach ($ESField->getArrayCopy() as $prop => $propValue) {
214
                        if ($ESField->type == 'nested' && ($prop == 'boost' || $prop == 'index')) {
215
                            continue;
216
                        }
217
                        if (!is_null($propValue) && $prop != 'name') {
218
                            $propertiesMapping[$ESField->name][$prop] = $propValue;
219
                        }
220
                    }
221
                }
222
            }
223
224
            $mappings = array(
225
                $typeName => array(
226
                    'properties' => $propertiesMapping
227
                )
228
            );
229
230
            if ($type->getParentClass()) {
231
                $refParentClass = new \ReflectionClass($type->getParentClass());
232
                /** @var Type $parentType */
233
                $parentType = $this->getAnnotionReader()->getClassAnnotation($refParentClass, Type::class);
234
235
                if ($parentType->getIndex() != $type->getIndex()) {
236
                    throw new ElasticConstraintException('Child and parent types have different indices. ');
237
                }
238
239
                $mappings[$typeName]['_parent'] = ['type' => $parentType->getName()];
240
            }
241
242
            if (!$this->em->getConnection()->indexExists($indexName)) {
243
                $this->em->getConnection()->createIndex($indexName, $mappings);
244
            } else {
245
                $this->em->getConnection()->createType($indexName, $typeName, $mappings);
246
            }
247
        }
248
    }
249
250
    /**
251
     * Check contraints values for entity, if exists
252
     *
253
     * @param object $entity
254
     * @throws ElasticConstraintException
255
     */
256
    private function checkConstraints($entity) {
257
        /** @var Constraint[] $constraintAnnotations */
258
        $constraintAnnotations = $this->hydrator->extractSpecAnnotations($this->className, Constraint::class);
259
260
        foreach ($constraintAnnotations as $property => $annotation) {
261
            $value = $this->hydrator->extract($entity, $property);
262
263
            switch ($annotation->type) {
264
                case Constraint::UNIQUE_VALUE:
265
                    if (!is_null($value)) {
266
                        $element = $this->load([$property => $value]);
267
268
                        if (boolval($element)) {
269
                            throw new ElasticConstraintException(sprintf(
270
                                "Unique field %s already has a document with value '%s'", $property, $value
271
                            ));
272
                        }
273
                    }
274
275
                    break;
276
                case Constraint::MATCH_LENGTH:
277
                case Constraint::MAX_LENGTH:
278
                case Constraint::MIN_LENGTH:
279
                    if (isset($annotation->options['value'])) {
280
                        $baseLength = intval($annotation->options['value']);
281
282
                        if (is_array($value) || is_string($value)) {
283
                            $length = is_array($value) ? count($value) : strlen($value);
284
                            $operator = Constraint::$operators[$annotation->type];
285
286
                            if (!eval(sprintf('%s %s %s', $length, $operator, $baseLength))) {
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
287
                                throw new ElasticConstraintException(sprintf(
288
                                    "Length for column %s must be %s %s. Current length: %s",
289
                                    $property, $operator, $baseLength, $length
290
                                ));
291
                            }
292
                        }
293
                    }
294
295
                    break;
296
            }
297
        }
298
    }
299
300
    public function load(
301
        array $criteria, $entity = null, $assoc = null, array $hints = [],
0 ignored issues
show
Unused Code introduced by
The parameter $entity 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...
Unused Code introduced by
The parameter $assoc 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...
Unused Code introduced by
The parameter $hints 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...
302
        $lockMode = null, $limit = null, array $orderBy = null
0 ignored issues
show
Unused Code introduced by
The parameter $lockMode 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...
303
    ) {
304
        $results = $this->loadAll($criteria, $orderBy, $limit);
305
306
        return count($results) ? $results[0] : null;
307
    }
308
309
    /**
310
     * @param null|string $className
311
     * @return Type
312
     * @throws MappingException
313
     */
314
    public function getEntityType($className = null) {
315
        if (boolval($className)) {
316
            $refClass = new \ReflectionClass($className);
317
        } else {
318
            $refClass = $this->getReflectionClass();
319
        }
320
321
        $type = $this->annotationReader->getClassAnnotation($refClass, Type::class);
322
323
        if ($type instanceof Type) {
324
            return $type;
325
        } else {
326
            throw new MappingException(sprintf('Unable to get Type Mapping of %s entity', $className));
327
        }
328
    }
329
330
    private function hydrateEntityByResult($entity, array $searchResult) {
331
        if (isset($searchResult['_source'])) {
332
            $searchResult = array_merge($searchResult, $searchResult['_source']);
333
        }
334
335
        $this->hydrator->hydrate($entity, $searchResult);
336
        $this->hydrator->hydrateByAnnotation($entity, Field::class, $searchResult);
337
        $this->hydrator->hydrateByAnnotation($entity, MetaField::class, $searchResult);
338
339
        return $entity;
340
    }
341
342
    public function update($entity) {
343
        $type = $this->getEntityType();
344
        $dataUpdate = $this->hydrator->extractWithAnnotation($entity, Field::class);
345
        $_id = $this->hydrator->extract($entity, '_id');
346
        $return = [];
347
348
        $updated = $this->em->getConnection()->update(
349
            $type->getIndex(), $type->getName(), $_id, $dataUpdate, [], $return
350
        );
351
352
        if ($updated) {
353
            $this->hydrateEntityByResult($entity, $return);
354
        } else {
355
            throw new ElasticOperationException(sprintf('Unable to complete update operation, '
356
                . 'with the following elastic return: <br><pre>%s</pre>', var_export($return)));
357
        }
358
    }
359
360
    public function addInsert($entity) {
361
        $oid = spl_object_hash($entity);
362
        $this->queuedInserts[$oid] = $entity;
363
    }
364
365
    public function loadById(array $_idArray, $entity = null) {
366
        $type = $this->getEntityType();
367
368
        if (is_object($entity) && get_class($entity) != $this->class->name) {
0 ignored issues
show
Bug introduced by
The property class does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
369
            throw new \InvalidArgumentException('You can only get an element by _id with its properly persister');
370
        }
371
372
        $id = isset($_idArray['_id']) ? $_idArray['_id'] : reset($_idArray);
373
374
        $documentData = $this->em->getConnection()->get($type->getIndex(), $type->getName(), $id);
375
376
        if ($documentData) {
377
            $entity = is_object($entity) ? $entity : new $this->class->name;
378
            $this->hydrateEntityByResult($entity, $documentData);
379
380
            return $entity;
381
        }
382
383
        return null;
384
    }
385
386
    public function delete($entity) {
387
        $type = $this->getEntityType();
388
        $return = [];
389
        $_id = $this->hydrator->extract($entity, '_id');
390
391
        $deletion = $this->em->getConnection()->delete(
392
            $type->getIndex(), $type->getName(), $_id, [], $return
393
        );
394
395
        if ($deletion) {
396
            return true;
397
        } else {
398
            throw new ElasticOperationException(sprintf('Unable to complete delete operation, '
399
                . 'with the following elastic return: <br><pre>%s</pre>', var_export($return)));
400
        }
401
    }
402
}
403