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 ( db31ee...a5f74a )
by Anderson
05:41
created

ElasticEntityPersister::load()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 3
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 Andsalves <[email protected]>
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
    /** @var ElasticEntityManager */
49
    private $em;
50
51
    public function __construct(ElasticEntityManager $em, $className) {
52
        $this->className = $className;
53
        $this->annotationReader = new AnnotationReader();
54
        $this->queryExecutor = new ElasticQueryExecutor($em);
55
        $this->hydrator = new AnnotationEntityHydrator();
56
        $this->validateEntity();
57
        $this->em = $em;
58
    }
59
60
    public function getReflectionClass() {
61
        if (is_null($this->reflectionClass)) {
62
            $this->reflectionClass = new \ReflectionClass($this->className);
63
        }
64
65
        return $this->reflectionClass;
66
    }
67
68
    private function validateEntity() {
69
        $type = $this->annotationReader->getClassAnnotation($this->getReflectionClass(), Type::class);
70
        $className = $this->className;
71
72
        if (!($type instanceof Type)) {
73
            throw new AnnotationException(sprintf('%s annotation is missing for %s entity class',
74
                Type::class, get_class()));
75
        }
76
77
        if (!$type->isValid()) {
78
            $errorMessage = $type->getErrorMessage() . ' for %s entity class';
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
        $type = $this->getEntityType();
93
        $sort = $must = [];
94
        $body = ['query' => ['bool' => ['must' => $must]]];
95
        /** @var Field $annotationProperty */
96
        $fieldAnnotations = $this->hydrator->extractSpecAnnotations($this->className, Field::class);
97
        /** @var MetaField[] $metaFieldAnnotations */
98
        $metaFieldAnnotations = $this->hydrator->extractSpecAnnotations($this->className, MetaField::class);
99
        $searchParams = new SearchParams();
100
101
        foreach ($criteria as $columnName => $value) {
102
            $annotation = null;
103
            if (isset($fieldAnnotations[$columnName])) {
104
                $annotation = $fieldAnnotations[$columnName];
105
            } else if (isset($metaFieldAnnotations[$columnName])) {
106
                $annotation = $metaFieldAnnotations[$columnName];
107
            }
108
109
            if (is_null($annotation)) {
110
                $msg = sprintf("field/metafield for column '%s' doesn't exist in %s entity class",
111
                    $columnName, $this->className);
112
                throw new InvalidParamsException($msg);
113
            }
114
115
            if ($annotation->name === '_parent') {
116
                $searchParams->setParent($criteria[$columnName]);
117
            } else {
118
                $must[] = array(
119
                    'match' => array(
120
                        $annotation->name => $criteria[$columnName],
121
                    )
122
                );
123
            }
124
        }
125
126
        if (is_array($orderBy)) {
127
            foreach ($orderBy as $columnName => $order) {
128
                if (isset($fieldAnnotations[$columnName])) {
129
                    $sort[$fieldAnnotations[$columnName]->name] = ['order' => $order];
130
                } else if (isset($metaFieldAnnotations[$columnName])) {
131
                    $sort[$metaFieldAnnotations[$columnName]->name] = ['order' => $order];
132
                }
133
            }
134
        }
135
136
        $body['query']['bool']['must'] = $must;
137
138
        $searchParams->setIndex($type->getIndex());
139
        $searchParams->setType($type->getName());
140
        $searchParams->setBody($body);
141
        $searchParams->setSize($limit);
142
        $searchParams->setSort($sort);
143
        $searchParams->setFrom($offset);
144
145
        return $this->queryExecutor->execute($searchParams, $this->className);
146
    }
147
148
    public function getAnnotionReader() {
149
        return $this->annotationReader;
150
    }
151
152
    public function executeInserts() {
153
        foreach ($this->queuedInserts as $entity) {
154
            $type = $this->getEntityType();
155
            $entityCopy = clone $entity;
156
157
            $this->em->getEventManager()->dispatchEvent(
158
                DoctrineElasticEvents::beforeInsert, new EntityEventArgs($entityCopy)
159
            );
160
161
            $fieldsData = $this->hydrator->extractWithAnnotation($entityCopy, Field::class);
162
            $metaFieldsData = $this->hydrator->extractWithAnnotation($entityCopy, MetaField::class);
163
            $mergeParams = [];
164
165
            if (array_key_exists('_id', $metaFieldsData) && !empty($metaFieldsData['_id'])) {
166
                $mergeParams['id'] = $metaFieldsData['_id'];
167
            }
168
169
            if (isset($metaFieldsData['_parent'])) {
170
                $mergeParams['parent'] = $metaFieldsData['_parent'];
171
            }
172
173
            $this->createTypeIfNotExists($type, $this->className);
174
175
            $this->checkConstraints($entityCopy);
176
            $return = [];
177
178
            $inserted = $this->em->getConnection()->insert(
179
                $type->getIndex(), $type->getName(), $fieldsData, $mergeParams, $return
180
            );
181
182
            if ($inserted) {
183
                $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...
184
                $this->em->getEventManager()->dispatchEvent(
185
                    DoctrineElasticEvents::postInsert, new EntityEventArgs($entity)
186
                );
187
            } else {
188
                throw new ElasticOperationException(sprintf(
189
                    'Unable to complete insert operation: %s', $this->em->getConnection()->getError()
190
                ));
191
            }
192
        }
193
    }
194
195
    /**
196
     * @param Type $type
197
     * @param string $className
198
     * @throws ElasticConstraintException
199
     */
200
    private function createTypeIfNotExists(Type $type, $className) {
201
        foreach ($type->getChildClasses() as $childClass) {
202
            $this->createTypeIfNotExists($this->getEntityType($childClass), $childClass);
203
        }
204
205
        $indexName = $type->getIndex();
206
        $typeName = $type->getName();
207
208
        if (!$this->em->getConnection()->typeExists($indexName, $typeName)) {
209
            $propertiesMapping = [];
210
            /** @var Field[] $ESFields */
211
            $ESFields = $this->hydrator->extractSpecAnnotations($className, Field::class);
212
213
            foreach ($ESFields as $ESField) {
214
                if ($ESField instanceof Field) {
215
                    $propertiesMapping[$ESField->name] = ['type' => $ESField->type];
216
217
                    foreach ($ESField->getArrayCopy() as $prop => $propValue) {
218
                        if ($ESField->type == 'nested' && ($prop == 'boost' || $prop == 'index')) {
219
                            continue;
220
                        }
221
                        if (!is_null($propValue) && $prop != 'name') {
222
                            $propertiesMapping[$ESField->name][$prop] = $propValue;
223
                        }
224
                    }
225
                }
226
            }
227
228
            $mappings = array(
229
                $typeName => array(
230
                    'properties' => $propertiesMapping
231
                )
232
            );
233
234
            if ($type->getParentClass()) {
235
                $refParentClass = new \ReflectionClass($type->getParentClass());
236
                /** @var Type $parentType */
237
                $parentType = $this->getAnnotionReader()->getClassAnnotation($refParentClass, Type::class);
238
239
                if ($parentType->getIndex() != $type->getIndex()) {
240
                    throw new ElasticConstraintException('Child and parent types have different indices. ');
241
                }
242
243
                $mappings[$typeName]['_parent'] = ['type' => $parentType->getName()];
244
            }
245
246
            if (!$this->em->getConnection()->indexExists($indexName)) {
247
                $created = $this->em->getConnection()->createIndex($indexName, $mappings);
248
            } else {
249
                $created = $this->em->getConnection()->createType($indexName, $typeName, $mappings);
250
            }
251
252
            if (!$created) {
253
                throw new ElasticOperationException(
254
                    'Unable to create index or type: ' . $this->em->getConnection()->getError()
255
                );
256
            }
257
        }
258
    }
259
260
    /**
261
     * Check contraints values for entity, if exists
262
     *
263
     * @param object $entity
264
     * @throws ElasticConstraintException
265
     */
266
    private function checkConstraints($entity) {
267
        /** @var Constraint[] $constraintAnnotations */
268
        $constraintAnnotations = $this->hydrator->extractSpecAnnotations($this->className, Constraint::class);
269
270
        foreach ($constraintAnnotations as $property => $annotation) {
271
            $value = $this->hydrator->extract($entity, $property);
272
273
            switch ($annotation->type) {
274
                case Constraint::UNIQUE_VALUE:
275
                    if (!is_null($value)) {
276
                        $element = $this->load([$property => $value]);
277
278
                        if (boolval($element)) {
279
                            throw new ElasticConstraintException(sprintf(
280
                                "Unique field %s already has a document with value '%s'", $property, $value
281
                            ));
282
                        }
283
                    }
284
285
                    break;
286
                case Constraint::MATCH_LENGTH:
287
                case Constraint::MAX_LENGTH:
288
                case Constraint::MIN_LENGTH:
289
                    if (isset($annotation->options['value'])) {
290
                        $baseLength = intval($annotation->options['value']);
291
292
                        if (is_array($value) || is_string($value)) {
293
                            $length = is_array($value) ? count($value) : strlen($value);
294
                            $operator = Constraint::$operators[$annotation->type];
295
296
                            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...
297
                                throw new ElasticConstraintException(sprintf(
298
                                    "Length for column %s must be %s %s. Current length: %s",
299
                                    $property, $operator, $baseLength, $length
300
                                ));
301
                            }
302
                        }
303
                    }
304
305
                    break;
306
            }
307
        }
308
    }
309
310
    public function load(array $criteria, $limit = null, array $orderBy = null) {
311
        $results = $this->loadAll($criteria, $orderBy, $limit);
312
313
        return count($results) ? $results[0] : null;
314
    }
315
316
    /**
317
     * @param null|string $className
318
     * @return Type
319
     * @throws MappingException
320
     */
321
    public function getEntityType($className = null) {
322
        if (boolval($className)) {
323
            $refClass = new \ReflectionClass($className);
324
        } else {
325
            $refClass = $this->getReflectionClass();
326
        }
327
328
        $type = $this->annotationReader->getClassAnnotation($refClass, Type::class);
329
330
        if ($type instanceof Type) {
331
            return $type;
332
        } else {
333
            throw new MappingException(sprintf('Unable to get Type Mapping of %s entity', $className));
334
        }
335
    }
336
337
    private function hydrateEntityByResult($entity, array $searchResult) {
338
        if (isset($searchResult['_source'])) {
339
            $searchResult = array_merge($searchResult, $searchResult['_source']);
340
        }
341
342
        $this->hydrator->hydrate($entity, $searchResult);
343
344
        return $entity;
345
    }
346
347
    public function update($entity) {
348
        $type = $this->getEntityType();
349
        $dataUpdate = $this->hydrator->extractWithAnnotation($entity, Field::class);
350
        $_id = $this->hydrator->extract($entity, '_id');
351
        $return = [];
352
353
        $updated = $this->em->getConnection()->update(
354
            $type->getIndex(), $type->getName(), $_id, $dataUpdate, [], $return
355
        );
356
357
        if ($updated) {
358
            $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...
359
        } else {
360
            throw new ElasticOperationException(sprintf(
361
                'Unable to complete update operation: %s ', $this->em->getConnection()->getError()
362
            ));
363
        }
364
    }
365
366
    public function addInsert($entity) {
367
        $oid = spl_object_hash($entity);
368
        $this->queuedInserts[$oid] = $entity;
369
    }
370
371
    public function loadById(array $_idArray, $entity = null) {
372
        $type = $this->getEntityType();
373
374
        if (is_object($entity) && get_class($entity) != $this->className) {
375
            throw new \InvalidArgumentException('You can only get an element by _id with its properly persister');
376
        }
377
378
        $id = isset($_idArray['_id']) ? $_idArray['_id'] : reset($_idArray);
379
380
        $documentData = $this->em->getConnection()->get($type->getIndex(), $type->getName(), $id);
381
382
        if ($documentData) {
383
            $entity = is_object($entity) ? $entity : new $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...
384
            $this->hydrateEntityByResult($entity, $documentData);
385
386
            return $entity;
387
        }
388
389
        return null;
390
    }
391
392
    public function delete($entity) {
393
        $type = $this->getEntityType();
394
        $return = [];
395
        $_id = $this->hydrator->extract($entity, '_id');
396
397
        $deletion = $this->em->getConnection()->delete(
398
            $type->getIndex(), $type->getName(), $_id, [], $return
399
        );
400
401
        if ($deletion) {
402
            return true;
403
        } else {
404
            throw new ElasticOperationException(sprintf(
405
                'Unable to complete delete operation: %s', $this->em->getConnection()->getError()
406
            ));
407
        }
408
    }
409
}
410