Completed
Push — master ( 3cd22a...608908 )
by Remy
02:58
created

Repository::getName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 2
cts 2
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Pouzor\MongoDBBundle\Repository;
4
5
6
use MongoDB\BulkWriteResult;
7
use MongoDB\Collection;
8
use MongoDB\Operation\InsertOne;
9
use Psr\Log\LoggerInterface;
10
use Psr\Log\NullLogger;
11
use Pouzor\MongoDBBundle\Constants\DriverClasses;
12
use Pouzor\MongoDBBundle\Constants\Query;
13
use Pouzor\MongoDBBundle\Exception\MalformedOperationException;
14
use Pouzor\MongoDBBundle\Services\ArrayAccessor;
15
16
17
class Repository
18
{
19
    /**
20
     * @var array
21
     */
22
    protected $indexes = [];
23
24
    /**
25
     * @var string
26
     */
27
    private $id_field = '_id';
28
29
    /**
30
     * @var string
31
     */
32
    private $name;
33
34
    /**
35
     * @var Collection
36
     */
37
    private $collection;
38
39
    /**
40
     * @var NullLogger
41
     */
42
    private $logger;
43
44
45
    private $persistence = [];
46
    
47
    /**
48
     * Repository constructor.
49
     * @param $name
50
     * @param $manager
51
     */
52 14
    public function __construct($name, $manager)
53
    {
54
55 14
        $this->collection = $manager->getDatabase()->selectCollection($name);
56
57 14
        $this->name = $name;
58
59 14
        $this->logger = $manager->getLogger() ?: new NullLogger();
60
61 14
        $this->persistence = [];
62
63 14
    }
64
65
    /**
66
     * @param $fields
67
     * @param array $options
68
     * @param null $callback
69
     */
70
    public function ensureIndex($fields, array $options = ['maxTimeMS' => 0], $rebuild = false, $callback = null)
0 ignored issues
show
Unused Code introduced by
The parameter $rebuild 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...
71
    {
72
        $this->logger->info('Creating index with fields ', $fields);
73
74
        try {
75
            $name = $this->collection->createIndex($fields, $options);
76
77
            if (is_callable($callback)) {
78
                call_user_func($callback, $name);
79
            }
80
        } catch (\Exception $re) {
81
            $this->logger->warning($re->getMessage());
82
83
            $regex = '/Index with name: (?<name>\w+) already exists with different options/';
84
85
            if (preg_match($regex, $re->getMessage(), $output)) {
86
                $name = $output['name'];
87
88
                $this->logger->info('Dropping index ' . $name);
89
                $result = $this->collection->dropIndex($name);
90
91
                $this->logger->info('Result from dropping index ' . $name, $result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->collection->dropIndex($name) on line 89 can also be of type object; however, Psr\Log\AbstractLogger::info() 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...
92
93
                $this->ensureIndex($fields, $options, $callback);
0 ignored issues
show
Documentation introduced by
$callback is of type null, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
94
            }
95
96
        }
97
98
    }
99
100
    /**
101
     * @param null $callback
102
     */
103 1
    public function buildIndexes($rebuild = false, $callback = null)
104
    {
105
        echo $this->name;
106
107
        if ($rebuild) {
108
            $this->collection->dropIndexes();
109
        }
110
111
        foreach ($this->indexes as $name => $conf) {
112
            if (is_array($conf)) {
113 1
                $this->ensureIndex(
114
                    $conf['fields'],
115
                    isset($conf['options']) ? $conf['options'] : [],
116
                    $rebuild,
117
                    $callback
118
                );
119
            } else {
120
                $this->ensureIndex([$name => $conf], [], $rebuild, $callback);
121
            }
122
        }
123
    }
124
125
126
    /**
127
     * @return int|void
128
     */
129
    public function countPendingOperations()
130
    {
131
        return count($this->persistence);
132
    }
133
    
134
    /**
135
     * Persist a document
136
     *
137
     * @param $document
138
     * @param bool $andFlush
139
     * @return BulkWriteResult
140
     */
141
    public function persist($document, $andFlush = false)
142
    {
143
144
        $this->persistence += [$document];
145
146
        if ($andFlush) {
147
            return $this->flush();
148
        }
149
    }
150
151
    /**
152
     * @return BulkWriteResult
153
     */
154
    public function flush()
155
    {
156
        $result = $this->bulk($this->persistence);
157
158
        $this->persistence = [];
159
160
        return $result;
161
    }
162
163
164
    /**
165
     * Write many operations in bulk
166
     * Ex:
167
     * $result = $repository->bulk([
168
     *            [
169
     *                "insertOne" => [
170
     *                    [
171
     *                        "name" => "Hannes Magnusson",
172
     *                        "company" => "10gen",
173
     *                    ]
174
     *                ],
175
     *            ],
176
     *            [
177
     *                "insertOne" => [
178
     *                    [
179
     *                        "name" => "Jeremy Mikola",
180
     *                        "company" => "10gen",
181
     *                    ]
182
     *                ],
183
     *            ],
184
     *            [
185
     *                "updateMany" => [
186
     *                    ["company" => "10gen"],
187
     *                    ['$set' => ["company" => "MongoDB"]],
188
     *                ],
189
     *            ],
190
     *            [
191
     *                "updateOne" => [
192
     *                    ["name" => "Hannes Magnusson"],
193
     *                    ['$set' => ["viking" => true]],
194
     *                ],
195
     *            ],
196
     * ]);
197
     * @param $operations
198
     * @return \MongoDB\BulkWriteResult
199
     * @throws MalformedOperationException
200
     */
201 1
    public function bulk($operations)
202
    {
203
204
        $this->validateBulkOperations($operations);
205
206
        $this->logger->info(
207
            'Bulk operations ',
208
            [
209
                'operations' => $operations,
210
                'col' => $this->name
211 1
            ]
212
        );
213
214
        $result = $this->collection->bulkWrite($operations);
215
216
        $this->logger->info(
217
            'Bulk operations result',
218
            [
219
                'inserted' => $result->getInsertedCount(),
220
                'upserted' => $result->getUpsertedCount(),
221
                'updated' => $result->getModifiedCount(),
222
                'deleted' => $result->getDeletedCount(),
223
                'col' => $this->name
224
            ]
225
        );
226
227
        return $result;
228
    }
229
230
    /**
231
     * @param array $query
232
     * @param array $options
233
     * @return \MongoDB\Driver\Cursor
234
     */
235 2
    public function findBy(array $query = [], array $options = ['maxTimeMS' => 0])
236
    {
237 2
        $this->validateQueryOptions($options);
238
239 2
        $this->logger->info(
240 2
            'Find many by',
241
            [
242 2
                'query' => $query,
243 2
                'col' => $this->name,
244
            ]
245 2
        );
246
247 2
        return $this->collection->find($query, $options);
248
    }
249
250
251
    /**
252
     * Find one document
253
     *
254
     * @param array $filter
255
     * @param array $options
256
     * @return \MongoDB\Driver\Cursor
257
     */
258 2 View Code Duplication
    public function findOneBy(array $filter = [], array $options = ['maxTimeMS' => 0])
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...
259
    {
260 1
        $this->validateQueryOptions($options);
261
262 1
        $this->logger->info(
263 2
            'Find one ',
264
            [
265 1
                'query' => $filter,
266 1
                'col' => $this->name,
267
            ]
268 1
        );
269
270 1
        return $this->collection->findOne($filter, $options);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->collection->findOne($filter, $options); of type array|object|null adds the type array to the return on line 270 which is incompatible with the return type documented by Pouzor\MongoDBBundle\Rep...y\Repository::findOneBy of type MongoDB\Driver\Cursor|null.
Loading history...
271
    }
272
273
274
    /**
275
     * @param $id
276
     * @param array $options
277
     * @return null|object
278
     */
279 2 View Code Duplication
    public function find($id, array $options = ['maxTimeMS' => 0])
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...
280
    {
281 2
        $this->validateQueryOptions($options);
282
283 2
        $this->logger->info(
284 2
            'Mongo find by id',
285
            [
286 2
                'id' => $id,
287 2
                'col' => $this->name
288 2
            ]
289 2
        );
290
291 2
        return $this->collection->findOne([$this->getIdField() => self::createObjectId($id)], $options);
292
    }
293
294
    /**
295
     * @param array $documents
296
     * @return \MongoDB\InsertManyResult
297
     */
298 8
    public function insertMany(array $documents = [])
299
    {
300 8
        $result = $this->collection->insertMany($documents);
301
302 8
        $this->logger->info(
303 8
            'Insert many result',
304
            [
305 8
                'result' => $result,
306 8
                'col' => $this->name,
307
            ]
308 8
        );
309
310 8
        return $result;
311
    }
312
313
    /**
314
     * @param $document
315
     * @return \MongoDB\InsertOneResult
316
     */
317 5
    public function insertOne($document)
318
    {
319 5
        $this->logger->info(
320 4
            'Insert one',
321
            [
322 4
                'document' => $document,
323 4
                'col' => $this->name,
324
            ]
325 4
        );
326
327 4
        return $this->collection->insertOne($document);
328
    }
329
330 1
    public function update($id, array $modifications = [], array $options = [])
331
    {
332
333 1
        $this->updateOne(
334 1
            [$this->getIdField() => self::createObjectId($id)],
335 1
            $modifications,
336
            $options
337 1
        );
338 1
    }
339
340
341
    /**
342
     * Update one document
343
     *
344
     * @param array $where
345
     * @param array $modifications
346
     * @param array $options
347
     * @return \MongoDB\UpdateResult
348
     */
349 1 View Code Duplication
    public function updateOne(array $where = [], array $modifications = [], array $options = [])
350
    {
351
352 1
        $this->logger->info(
353 1
            'Update one ',
354
            [
355 1
                'where' => $where,
356 1
                'update' => $modifications,
357 1
                'col' => $this->name,
358
            ]
359 1
        );
360
361 1
        return $this->collection->updateOne($where, $modifications, $options);
362
    }
363
364
365
    /**
366
     * Update many documents at the same time
367
     *
368
     * @param array $where
369
     * @param array $modifications
370
     * @param array $options
371
     * @return \MongoDB\UpdateResult
372
     */
373 1 View Code Duplication
    public function updateMany(array $where = [], array $modifications = [], array $options = [])
374
    {
375
376 1
        $this->logger->info(
377 1
            'Update many ',
378
            [
379 1
                'where' => $where,
380 1
                'update' => $modifications,
381 1
                'col' => $this->name,
382
            ]
383 1
        );
384
385 1
        return $this->collection->updateMany($where, $modifications, $options);
386
    }
387
388
    /**
389
     * Update one documents (the whole document)
390
     *
391
     * @param array $where
392
     * @param array $modifications
393
     * @param array $options
394
     * @return \MongoDB\UpdateResult
395
     */
396 1 View Code Duplication
    public function replaceOne(array $where = [], array $modifications = [], array $options = [])
397
    {
398
399 1
        $this->logger->info(
400 1
            'Replace one ',
401
            [
402 1
                'where' => $where,
403 1
                'update' => $modifications,
404 1
                'col' => $this->name,
405
            ]
406 1
        );
407
408 1
        return $this->collection->replaceOne($where, $modifications, $options);
409
    }
410
411
412
    /**
413
     * @param $id
414
     * @param array $options
415
     * @return \MongoDB\DeleteResult
416
     */
417 1
    public function delete($id, array $options = [])
418
    {
419 1
        return $this->deleteOne(
420 1
            [$this->getIdField() => self::createObjectId($id)],
421
            $options
422 1
        );
423
    }
424
425
    /**
426
     * Delete one document
427
     *
428
     * @param array $where
429
     * @param array $options
430
     * @return \MongoDB\DeleteResult
431
     */
432 2
    public function deleteOne(array $where = [], array $options = [])
433
    {
434
435 2
        $this->logger->info(
436 2
            'Delete one ',
437
            [
438 2
                'where' => $where,
439 2
                'col' => $this->name,
440
            ]
441 2
        );
442
443 2
        return $this->collection->deleteOne($where, $options);
444
    }
445
446
    /**
447
     * @param array $where
448
     * @param array $options
449
     * @return \MongoDB\DeleteResult
450
     */
451 14
    public function deleteMany(array $where = [], array $options = [])
452
    {
453
454 14
        $this->logger->info(
455 14
            'Delete many ',
456
            [
457 14
                'where' => $where,
458 14
                'col' => $this->name,
459
            ]
460 14
        );
461
462 14
        return $this->collection->deleteMany($where, $options);
463
    }
464
465
    /**
466
     * Aggregation function for mongoDB
467
     * @param array $pipelines
468
     * @param array $options
469
     * @return \Traversable
470
     */
471 1
    public function aggregate(array $pipelines = [], array $options = [])
472
    {
473
474 1
        $this->logger->info(
475 1
            'Aggregate ',
476
            [
477 1
                'pipelines' => $pipelines,
478 1
                'options' => $options,
479 1
                'col' => $this->name,
480
481
            ]
482 1
        );
483
484 1
        return $this->collection->aggregate($pipelines, $options);
485
    }
486
487
    /**
488
     * @param $field
489
     * @param null $min
490
     * @param null $max
491
     * @param array $options
492
     * @return \MongoDB\Driver\Cursor
493
     */
494
    public function findBetween($field, $min = null, $max = null, array $options = [])
495
    {
496
        $query = [
497
            $field => ['$gt' => $min, '$lt' => $max]
498
        ];
499
500
        return $this->findBy($query, $options);
501
    }
502
503
    /**
504
     * @param $field
505
     * @param $min
506
     * @param $max
507
     * @param array $options
508
     * @return int
509
     */
510
    public function countBetween($field, $min = null, $max = null, array $options = ['maxTimeMS' => 0])
511
    {
512
        $query = [$field => ['$gt' => $min, '$lte' => $max]];
513
514
        $this->logger->info(
515
            'Counting  ' . $field,
516
            [
517
                'filter' => [
518
                    $field => ['$gt' => $min, '$lte' => $max]
519
                ],
520
                'options' => $options,
521
                'col' => $this->name,
522
            ]
523
        );
524
525
        $result = $this->collection->count(
526
            $query,
527
            [
528
                Query::NO_CURSOR_TIMEOUT,
529
                Query::PROJECTION => [
530
                    $this->getIdField() => true
531
                ]
532
            ]
533
        );
534
535
        return $result;
536
    }
537
538
    /**
539
     * Return the max value for a field in a collection
540
     *
541
     * @param $field
542
     * @param array $query
543
     * @param array $options
544
     * @return mixed
545
     */
546 2 View Code Duplication
    public function max($field, array $query = [], array $options = ['maxTimeMS' => 0])
547
    {
548 1
        $this->validateQueryOptions($options);
549
550 1
        $this->logger->info(
551 1
            sprintf('Maximum value in field %s', $field),
552
            [
553 1
                'filter' => $query,
554
                'options' => $options
555 1
            ]
556 2
        );
557
558 1
        $result = $this->findBy(
559 1
            $query,
560
            [
561 1
                Query::PROJECTION => [
562
                    $field => true
563 1
                ],
564 1
                Query::SORT => [
565
                    $field => -1
566 1
                ],
567 1
                Query::LIMIT => 1
568 1
            ]
569 1
        )->toArray();
570
571 1
        return ArrayAccessor::dget($result, '0.' . $field);
572
573
    }
574
575
    /**
576
     * Return the min value for a field in a collection
577
     *
578
     * @param $field
579
     * @param array $query
580
     * @param array $options
581
     * @return \Traversable
582
     */
583 1 View Code Duplication
    public function min($field, array $query = [], array $options = ['maxTimeMS' => 0])
584
    {
585 1
        $this->validateQueryOptions($options);
586
587 1
        $this->logger->info(
588 1
            sprintf('Minimum value in field %s', $field),
589
            [
590 1
                'filter' => $query,
591
                'options' => $options
592 1
            ]
593 1
        );
594
595 1
        $result = $this->findBy(
596 1
            $query,
597
            [
598 1
                Query::PROJECTION => [
599
                    $field => true
600 1
                ],
601 1
                Query::SORT => [
602
                    $field => 1
603 1
                ],
604 1
                Query::LIMIT => 1
605 1
            ]
606 1
        )->toArray();
607
608 1
        return ArrayAccessor::dget($result, '0.' . $field);
609
    }
610
611
    /**
612
     * @param $field
613
     * @param array $filter
614
     * @param array $options
615
     * @return \mixed[]
616
     */
617
    public function distinct($field, array $filter = [], array $options = ['maxTimeMS' => 0])
618
    {
619
620
        $this->logger->info(
621
            'Distinct over ' . $field,
622
            [
623
                'filter' => $filter,
624
                'options' => $options,
625
                'col' => $this->name,
626
627
            ]
628
        );
629
630
        return $this->collection->distinct($field, $filter, $options);
631
    }
632
633
    /**
634
     * @param array $filter
635
     * @param array $options
636
     * @return int
637
     */
638 8
    public function count(array $filter = [], array $options = ['maxTimeMS' => 0])
639
    {
640
641 8
        $this->logger->info(
642 8
            'Counting ',
643
            [
644 8
                'filter' => $filter,
645 8
                'options' => $options,
646 8
                'col' => $this->name,
647
            ]
648 8
        );
649
650 8
        return $this->collection->count($filter);
651
    }
652
653
654
    /**
655
     * @param array $options
656
     */
657 5
    private function validateQueryOptions(array $options = [])
658
    {
659
        /**
660
         * Sort
661
         */
662 5 View Code Duplication
        if (isset($options[Query::SORT]) && !is_array($options[Query::SORT])) {
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...
663
            throw new \InvalidArgumentException(Query::SORT . ' option must be an array');
664
        }
665
666
        /**
667
         * Proj
668
         */
669 5 View Code Duplication
        if (isset($options[Query::PROJECTION]) && !is_array($options[Query::PROJECTION])) {
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...
670
            throw new \InvalidArgumentException(Query::PROJECTION . ' option must be an array');
671
        }
672
673
        /**
674
         *  limit
675
         */
676 5 View Code Duplication
        if (isset($options[Query::LIMIT]) && !is_integer($options[Query::LIMIT])) {
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...
677
            throw new \InvalidArgumentException(Query::LIMIT . " option must be an integer");
678
        }
679
680
        /**
681
         *  offset
682
         */
683 5 View Code Duplication
        if (isset($options[Query::OFFSET]) && !is_integer($options[Query::OFFSET])) {
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...
684
            throw new \InvalidArgumentException(Query::OFFSET . ' option must be an array');
685
        }
686
687
        // TODO validate other options
688 5
    }
689
690
691
    /**
692
     * @param $id
693
     * @return MongoDB\BSON\ObjectID
694
     */
695 3
    public static function createObjectId($id)
696
    {
697 3
        $class = DriverClasses::ID_CLASS;
698
699 3
        if ($id instanceof $class) {
700 3
            return $id;
701
        }
702
703
        return new $class($id);
704
    }
705
706
    /**
707
     * @return string
708
     */
709 3
    public function getIdField()
710
    {
711 3
        return $this->id_field;
712
    }
713
714
    /**
715
     * @param string $id_field
716
     * @return Repository
717
     */
718
    public function setIdField($id_field)
719
    {
720
        $this->id_field = $id_field;
721
722
        return $this;
723
    }
724
725
726
    /**
727
     * @param $operations
728
     * @throws MalformedOperationException
729
     */
730 1
    private function validateBulkOperations($operations)
731
    {
732
        foreach ($operations as $op) {
733
            if (count($op) > 1) {
734
                throw new MalformedOperationException();
735
            }
736
737
            $key = array_keys($op)[0];
738
739
            if (!in_array(
740
                $key,
741
                [
742
                    'insertOne',
743
                    'insertMany',
744
                    'updateOne',
745
                    'updateMany',
746
                    'deleteOne',
747
                    'deleteMany'
748
                ]
749 1
            )
750
            ) {
751
                throw new MalformedOperationException(sprintf('%s is not a valid bulk operation'));
752
            }
753
754
            if (!is_array($op[$key])) {
755
                throw new MalformedOperationException(sprintf('%s argument must be an array'));
756
            }
757
        }
758
    }
759
760 14
    public function setIndexes(array $indexes)
761
    {
762 14
        $this->indexes = $indexes;
763 14
    }
764
765 1
    public function getIndexes()
766
    {
767
768 1
        return $this->indexes;
769
    }
770
771 1
    public function getName()
772
    {
773
774 1
        return $this->name;
775
    }
776
}
777