MongoDbStorageDriver::removeAll()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * Created by IntelliJ IDEA.
4
 * User: gerk
5
 * Date: 06.01.17
6
 * Time: 11:57
7
 */
8
9
namespace PeekAndPoke\Component\Slumber\Data\MongoDb;
10
11
use MongoDB\Collection;
12
use MongoDB\Driver\Exception\WriteException;
13
use PeekAndPoke\Component\Slumber\Data\AwakingCursorIterator;
14
use PeekAndPoke\Component\Slumber\Data\Cursor;
15
use PeekAndPoke\Component\Slumber\Data\EntityPool;
16
use PeekAndPoke\Component\Slumber\Data\Error\DuplicateError;
17
use PeekAndPoke\Component\Slumber\Data\MongoDb\Error\MongoDbDuplicateError;
18
use PeekAndPoke\Component\Slumber\Data\Result;
19
use PeekAndPoke\Component\Slumber\Data\StorageDriver;
20
21
/**
22
 * @api
23
 *
24
 * @author Karsten J. Gerber <[email protected]>
25
 *
26
 * @see    https://docs.mongodb.com/php-library/master/tutorial/
27
 */
28
class MongoDbStorageDriver implements StorageDriver
29
{
30
    /** @var EntityPool */
31
    private $entityPool;
32
    /** @var Collection */
33
    private $collection;
34
    /** @var MongoDbCodecSet */
35
    private $codecSet;
36
    /** @var \ReflectionClass */
37
    private $entityBaseClass;
38
    /** @var MongoDbEntityConfig */
39
    private $entityConfig;
40
41
    public function __construct(EntityPool $entityPool, MongoDbCodecSet $codecSet, Collection $collection, \ReflectionClass $entityBaseClass)
42
    {
43
        $this->entityPool      = $entityPool;
44
        $this->codecSet        = $codecSet;
45
        $this->collection      = $collection;
46
        $this->entityBaseClass = $entityBaseClass;
47
        $this->entityConfig    = $codecSet->getLookUp()->getEntityConfig($entityBaseClass);
48
    }
49
50
    /**
51
     * @return \ReflectionClass
52
     */
53 28
    public function getEntityBaseClass()
54
    {
55 28
        return $this->entityBaseClass;
56
    }
57
58
    /**
59
     * @return array
60
     */
61
    public function buildIndexes()
62
    {
63
        return $this->codecSet->getIndexer()->ensureIndexes($this->collection, $this->entityBaseClass);
64
    }
65
66
    /**
67
     * @param $query
68
     *
69
     * @return Cursor
70
     */
71 11
    public function find(array $query = null)
72
    {
73 11
        return $this->guard(
74 11
            function () use ($query) {
75 11
                return $this->findInternal($query);
76 11
            }
77
        );
78
    }
79
80
    /**
81
     * @param $query
82
     *
83
     * @return Cursor
84
     */
85 11
    private function findInternal(array $query = null)
86
    {
87 11
        $query = $query ?: [];
88
89
        // We do not provide the cursor right away.
90
        // By doing so we post pone the query until the data is really requested by iterating it.
91 11
        $cursorProvider = function ($options) use ($query) {
92
93
            // we want raw php arrays as return types
94 6
            $options['typeMap'] = ['root' => 'array', 'document' => 'array', 'array' => 'array'];
95
96 6
            return new AwakingCursorIterator(
97 6
                $this->collection->find($query, $options),
98 4
                $this->codecSet->getAwaker(),
99 4
                $this->entityBaseClass
100
            );
101 11
        };
102
103 11
        $countProvider = function ($options) use ($query) {
104 7
            return $this->collection->count($query, $options);
105 11
        };
106
107 11
        return new MongoDbCursor($cursorProvider, $countProvider);
108
    }
109
110
    /**
111
     * @param array|null $query
112
     *
113
     * @return null
114
     */
115 19
    public function findOne(array $query = null)
116
    {
117 19
        return $this->guard(
118 19
            function () use ($query) {
119 19
                return $this->findOneInternal($query);
120 19
            }
121
        );
122
    }
123
124
    /**
125
     * @param array|null $query
126
     *
127
     * @return null
128
     */
129 19
    private function findOneInternal(array $query = null)
130
    {
131
        // TODO: find a more encapsulated way for looking it up in the pool
132
        //       can we use the propertyAccess of the ID and use the propertyName? Is the alright ?
133
134
        // do we have it in the pool ?
135 19
        if (\count($query) === 1
136 19
            && isset($query['_id'])
137 19
            && $this->entityPool->has($this->entityBaseClass, EntityPool::PRIMARY_ID, (string) $query['_id'])
138
        ) {
139 3
            return $this->entityPool->get($this->entityBaseClass, EntityPool::PRIMARY_ID, (string) $query['_id']);
140
        }
141
142 18
        $result = $this->collection->findOne(
143 18
            $query ?: [],
144
            [
145 18
                'typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array'] // we want raw php arrays as return types
146
            ]
147
        );
148
149 18
        if ($result === null) {
150
            return null;
151
        }
152
153 18
        return $this->codecSet->getAwaker()->awake($result, $this->entityBaseClass);
154
    }
155
156
    /**
157
     * @param mixed $item
158
     *
159
     * @return Result\InsertOneResult
160
     *
161
     * @throws DuplicateError
162
     */
163 3
    public function insert($item)
164
    {
165 3
        return $this->guard(
166 3
            function () use ($item) {
167 3
                return $this->insertInternal($item);
168 3
            }
169
        );
170
    }
171
172
    /**
173
     * @param mixed $item
174
     *
175
     * @return Result\InsertOneResult
176
     *
177
     * @throws DuplicateError
178
     */
179 3
    private function insertInternal($item)
180
    {
181 3
        $slumbering = $this->codecSet->getSlumberer()->slumber($item);
182
183 3
        if (empty($slumbering['_id'])) {
184 2
            unset($slumbering['_id']);
185
        }
186
187 3
        $result = $this->collection->insertOne($slumbering);
0 ignored issues
show
Bug introduced by
It seems like $slumbering defined by $this->codecSet->getSlumberer()->slumber($item) on line 181 can also be of type string; however, MongoDB\Collection::insertOne() does only seem to accept array|object, 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...
188
189
        // write back the id
190 2
        $insertedId = (string) $result->getInsertedId();
191 2
        $this->setItemId($item, $insertedId);
192
193
        // set it on the entity pool
194 2
        $this->entityPool->set($this->entityBaseClass, EntityPool::PRIMARY_ID, $insertedId, $item);
195
196
        // dispatch post save events (e.g. for the Journal)
197 2
        $this->invokePostSaveListeners($item, $slumbering);
0 ignored issues
show
Bug introduced by
It seems like $slumbering defined by $this->codecSet->getSlumberer()->slumber($item) on line 181 can also be of type string; however, PeekAndPoke\Component\Sl...vokePostSaveListeners() 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...
198
199 2
        return new Result\InsertOneResult($insertedId, $result->isAcknowledged());
200
    }
201
202
    /**
203
     * Insert an item
204
     *
205
     * @param mixed $item
206
     *
207
     * @return Result\SaveOneResult
208
     *
209
     * @throws DuplicateError
210
     */
211 25
    public function save($item)
212
    {
213 25
        return $this->guard(
214 25
            function () use ($item) {
215 25
                return $this->saveInternal($item);
216 25
            }
217
        );
218
    }
219
220
    /**
221
     * Insert an item
222
     *
223
     * @param mixed $item
224
     *
225
     * @return Result\SaveOneResult
226
     *
227
     * @throws DuplicateError
228
     */
229 25
    private function saveInternal($item)
230
    {
231 25
        $slumbering = $this->codecSet->getSlumberer()->slumber($item);
232
233
        ////  CREATE OR UPDATE ?  //////////////////////////////////////////////////////////////////////////////////////
234
235 25
        if (empty($slumbering['_id'])) {
236
237
            // unset the _id so we get an id created
238 23
            unset ($slumbering['_id']);
239
240
            try {
241 23
                $insertOneResult = $this->collection->insertOne($slumbering);
0 ignored issues
show
Bug introduced by
It seems like $slumbering defined by $this->codecSet->getSlumberer()->slumber($item) on line 231 can also be of type string; however, MongoDB\Collection::insertOne() does only seem to accept array|object, 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...
242 1
            } catch (WriteException $e) {
0 ignored issues
show
Bug introduced by
The class MongoDB\Driver\Exception\WriteException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
243
                throw MongoDbDuplicateError::from($e);
244
            }
245
246
            // write back the id
247 22
            $insertedId = (string) $insertOneResult->getInsertedId();
248 22
            $this->setItemId($item, $insertedId);
249
250
            // set it on the entity pool
251 22
            $this->entityPool->set($this->entityBaseClass, EntityPool::PRIMARY_ID, $insertedId, $item);
252
253
            // build return result
254 22
            $result = new Result\SaveOneResult($insertedId, $insertOneResult->isAcknowledged(), false);
255
256
        } else {
257
            // UPDATE or INSERT with specific id
258
            try {
259 8
                $updateOneResult = $this->collection->updateOne(
260 8
                    ['_id' => MongoDbUtil::ensureMongoId($slumbering['_id'])],
261 8
                    ['$set' => $slumbering],
262 8
                    ['upsert' => true]
263
                );
264
            } catch (WriteException $e) {
0 ignored issues
show
Bug introduced by
The class MongoDB\Driver\Exception\WriteException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
265
                throw MongoDbDuplicateError::from($e);
266
            }
267
268
            // save it on the entity pool
269 8
            $this->entityPool->set($this->entityBaseClass, EntityPool::PRIMARY_ID, $slumbering['_id'], $item);
270
271
            // build return result
272 8
            $result = new Result\SaveOneResult(
273 8
                $updateOneResult->getUpsertedId(),
274 8
                $updateOneResult->isAcknowledged(),
275 8
                $updateOneResult->getUpsertedCount() === 1
276
            );
277
        }
278
279
        // dispatch post save events (e.g. for the Journal)
280 24
        $this->invokePostSaveListeners($item, $slumbering);
0 ignored issues
show
Bug introduced by
It seems like $slumbering defined by $this->codecSet->getSlumberer()->slumber($item) on line 231 can also be of type string; however, PeekAndPoke\Component\Sl...vokePostSaveListeners() 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...
281
282 24
        return $result;
283
    }
284
285
    /**
286
     * @param mixed $entity
287
     *
288
     * @return Result\RemoveResult
289
     */
290 3
    public function remove($entity)
291
    {
292 3
        return $this->guard(
293 3
            function () use ($entity) {
294 3
                return $this->removeInternal($entity);
295 3
            }
296
        );
297
    }
298
299
    /**
300
     * @param mixed $entity
301
     *
302
     * @return Result\RemoveResult
303
     */
304 3
    private function removeInternal($entity)
305
    {
306 3
        $id = $this->getItemId($entity);
307
308 3
        $result = $this->collection->deleteOne([
309 3
            '_id' => MongoDbUtil::ensureMongoId($id),
310
        ]);
311
312 2
        return new Result\RemoveResult($result->getDeletedCount(), $result->isAcknowledged());
313
    }
314
315
    /**
316
     * Remove all from this collection
317
     *
318
     * @param array|null $query
319
     *
320
     * @return Result\RemoveResult
321
     */
322 25
    public function removeAll(array $query = null)
323
    {
324 25
        return $this->guard(
325 25
            function () use ($query) {
326 25
                return $this->removeAllInternal($query);
327 25
            }
328
        );
329
    }
330
331
    /**
332
     * Remove all from this collection
333
     *
334
     * @param array|null $query
335
     *
336
     * @return Result\RemoveResult
337
     */
338 25
    public function removeAllInternal(array $query = null)
339
    {
340 25
        if ($query === null) {
341
            return new Result\RemoveResult(0, false);
342
        }
343
344 25
        $result = $this->collection->deleteMany($query);
345
346 24
        return new Result\RemoveResult($result->getDeletedCount(), $result->isAcknowledged());
347
    }
348
349
    /**
350
     * @param mixed $item
351
     *
352
     * @return mixed
353
     */
354 3
    private function getItemId($item)
355
    {
356 3
        return $this->entityConfig->getIdAccess()->get($item);
357
    }
358
359
    /**
360
     * @param mixed $entity
361
     * @param mixed $id
362
     */
363 24
    private function setItemId($entity, $id)
364
    {
365 24
        $this->entityConfig->getIdAccess()->set($entity, $id);
366 24
    }
367
368
    /**
369
     * @param mixed $item       The item that was saved
370
     * @param array $slumbering The serialized data
371
     */
372 26
    private function invokePostSaveListeners($item, $slumbering)
373
    {
374
        // dispatch post save events (e.g. for the Journal)
375 26
        if ($this->entityConfig->hasPostSaveClassListeners()) {
376
377 4
            $postSaveEvent = $this->codecSet->createPostSaveEventFor($item, $slumbering);
378
379 4
            foreach ($this->entityConfig->getPostSaveClassListeners() as $postSave) {
380 4
                $postSave->execute($postSaveEvent);
381
            }
382
        }
383 26
    }
384
385 34
    private function guard(callable $action)
386
    {
387 34
        return MongoDbGuard::guard($action);
388
    }
389
}
390