Completed
Branch dbal-improvement (e43d29)
by Anton
06:02
created

DocumentSelector::jsonSerialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
namespace Spiral\ODM\Entities;
9
10
use Psr\Log\LoggerAwareInterface;
11
use Spiral\Core\Component;
12
use Spiral\Debug\Traits\LoggerTrait;
13
use Spiral\ODM\Document;
14
use Spiral\ODM\DocumentEntity;
15
use Spiral\ODM\ODM;
16
use Spiral\Pagination\PaginableInterface;
17
use Spiral\Pagination\Traits\PaginatorTrait;
18
19
/**
20
 * Mocks MongoCollection to aggregate query, limits and sorting values and product DocumentIterator
21
 * as result.
22
 *
23
 * @see  DocumentIterator
24
 * @link http://docs.mongodb.org/manual/tutorial/query-documents/
25
 *
26
 * Set of MongoCollection mocked methods:
27
 * @method bool getSlaveOkay()
28
 * @method bool setSlaveOkay($slave_okay)
29
 * @method array getReadPreference()
30
 * @method bool setReadPreference($read_preference, $tags)
31
 * @method array drop()
32
 * @method array validate($validate)
33
 * @method bool|array insert($array_of_fields_OR_object, $options = [])
34
 * @method mixed batchInsert($documents, $options = [])
35
 * @method bool update($old_array_of_fields_OR_object, $new_fields_OR_object, $options = [])
36
 * @method bool|array remove($array_of_fields_OR_object, $options = [])
37
 * @method bool ensureIndex($key_OR_array_of_keys, $options = [])
38
 * @method array deleteIndex($string_OR_array_of_keys)
39
 * @method array deleteIndexes()
40
 * @method array getIndexInfo()
41
 * @method save($array_of_fields_OR_object, $options = [])
42
 * @method array createDBRef($array_with_id_fields_OR_MongoID)
43
 * @method array getDBRef($reference)
44
 * @method array group($keys_or_MongoCode, $initial_value, $array_OR_MongoCode, $options = [])
45
 * @method bool|array distinct($key, $query)
46
 * @method array aggregate(array $pipeline, array $op, array $pipelineOperators)
47
 */
48
class DocumentSelector extends Component implements
49
    \Countable,
50
    \IteratorAggregate,
51
    PaginableInterface,
52
    LoggerAwareInterface,
53
    \JsonSerializable
54
{
55
    /**
56
     * Collection queries can be paginated, in addition profiling messages will be dumped into log.
57
     */
58
    use LoggerTrait, PaginatorTrait;
59
60
    /**
61
     * Sort order.
62
     *
63
     * @link http://php.net/manual/en/class.mongocollection.php#mongocollection.constants.ascending
64
     */
65
    const ASCENDING = 1;
66
67
    /**
68
     * Sort order.
69
     *
70
     * @link http://php.net/manual/en/class.mongocollection.php#mongocollection.constants.descending
71
     */
72
    const DESCENDING = -1;
73
74
    /**
75
     * @var string
76
     */
77
    private $name = '';
78
79
    /**
80
     * @var string
81
     */
82
    private $database = 'default';
83
84
    /**
85
     * Associated MongoCollection.
86
     *
87
     * @var \MongoCollection
88
     */
89
    private $collection = null;
90
91
    /**
92
     * Fields and conditions to query by.
93
     *
94
     * @link http://docs.mongodb.org/manual/tutorial/query-documents/
95
     * @var array
96
     */
97
    protected $query = [];
98
99
    /**
100
     * Fields to sort.
101
     *
102
     * @var array
103
     */
104
    protected $sort = [];
105
106
    /**
107
     * @invisible
108
     * @var ODM
109
     */
110
    protected $odm = null;
111
112
    /**
113
     * @link http://docs.mongodb.org/manual/tutorial/query-documents/
114
     * @param ODM    $odm        ODMManager component instance.
115
     * @param string $database   Associated database name/id.
116
     * @param string $collection Collection name.
117
     * @param array  $query      Fields and conditions to query by.
118
     */
119
    public function __construct(ODM $odm, $database, $collection, array $query = [])
120
    {
121
        $this->odm = $odm;
122
123
        $this->name = $collection;
124
        $this->database = $database;
125
126
        $this->query = $query;
127
    }
128
129
    /**
130
     * @return string
131
     */
132
    public function getName()
133
    {
134
        return $this->name;
135
    }
136
137
    /**
138
     * @return string
139
     */
140
    public function getDatabase()
141
    {
142
        return $this->database;
143
    }
144
145
    /**
146
     * Set additional query, fields will be merged to currently existed request using array_merge.
147
     *
148
     * @link http://docs.mongodb.org/manual/tutorial/query-documents/
149
     * @param array $query Fields and conditions to query by.
150
     * @return $this
151
     */
152
    public function query(array $query = [])
153
    {
154
        array_walk_recursive($query, function (&$value) {
155
            if ($value instanceof \DateTime) {
156
                //MongoDate is always UTC, which is good :)
157
                $value = new \MongoDate($value->getTimestamp());
158
            }
159
        });
160
161
        $this->query = array_merge($this->query, $query);
162
163
        return $this;
164
    }
165
166
    /**
167
     * Set additional query field, fields will be merged to currently existed request using
168
     * array_merge. Alias for query.
169
     *
170
     * @link http://docs.mongodb.org/manual/tutorial/query-documents/
171
     * @param array $query Fields and conditions to query by.
172
     * @return $this
173
     */
174
    public function where(array $query = [])
175
    {
176
        return $this->query($query);
177
    }
178
179
    /**
180
     * Set additional query field, fields will be merged to currently existed request using
181
     * array_merge. Alias for query.
182
     *
183
     * @link http://docs.mongodb.org/manual/tutorial/query-documents/
184
     * @param array $query Fields and conditions to query by.
185
     * @return $this
186
     */
187
    public function find(array $query = [])
188
    {
189
        return $this->query($query);
190
    }
191
192
    /**
193
     * Fetch one record from database using it's primary key. You can use INLOAD and JOIN_ONLY
194
     * loaders with HAS_MANY or MANY_TO_MANY relations with this method as no limit were used.
195
     *
196
     * @see findOne()
197
     * @param mixed $id Primary key value.
198
     * @return DocumentEntity|null
199
     */
200
    public function findByPK($id)
201
    {
202
        return $this->findOne(['_id' => $this->odm->mongoID($id)]);
203
    }
204
205
    /**
206
     * Select one document or it's fields from collection.
207
     *
208
     * @param array $query Fields and conditions to query by.
209
     * @return DocumentEntity|array
210
     */
211
    public function findOne(array $query = [])
212
    {
213
        return $this->createCursor($query, [], 1)->getNext();
214
    }
215
216
    /**
217
     * Current fields and conditions to query by.
218
     *
219
     * @link http://docs.mongodb.org/manual/tutorial/query-documents/
220
     * @return array
221
     */
222
    public function getQuery()
223
    {
224
        return $this->query;
225
    }
226
227
    /**
228
     * Sorts the results by given fields.
229
     *
230
     * @link http://www.php.net/manual/en/mongocursor.sort.php
231
     * @param array $fields An array of fields by which to sort. Each element in the array has as
232
     *                      key the field name, and as value either 1 for ascending sort, or -1 for
233
     *                      descending sort.
234
     * @return $this
235
     */
236
    public function sortBy(array $fields)
237
    {
238
        $this->sort = $fields;
239
240
        return $this;
241
    }
242
243
    /**
244
     * Fetch all available document instances from query.
245
     *
246
     * @return Document[]
247
     */
248
    public function fetchDocuments()
249
    {
250
        $result = [];
251
        foreach ($this->createCursor() as $document) {
252
            $result[] = $document;
253
        }
254
255
        return $result;
256
    }
257
258
    /**
259
     * Fetch all available documents as arrays of fields.
260
     *
261
     * @param array $fields Fields of the results to return.
262
     * @return array
263
     */
264
    public function fetchFields($fields = [])
265
    {
266
        $result = [];
267
        foreach ($this->createCursor([], $fields) as $document) {
268
            $result[] = $document;
269
        }
270
271
        return $result;
272
    }
273
274
    /**
275
     * {@inheritdoc}
276
     */
277
    public function count()
278
    {
279
        return $this->mongoCollection()->count($this->query);
280
    }
281
282
    /**
283
     * {@inheritdoc}
284
     *
285
     * @return DocumentCursor|Document[]
286
     */
287
    public function getIterator()
288
    {
289
        return $this->createCursor();
290
    }
291
292
    /**
293
     * Get instance of DocumentCursor.
294
     *
295
     * @return DocumentCursor
296
     */
297
    public function getCursor()
298
    {
299
        return $this->createCursor();
300
    }
301
302
    /**
303
     * Bypass call to MongoCollection.
304
     *
305
     * @param string $method    Method name.
306
     * @param array  $arguments Method arguments.
307
     * @return mixed
308
     */
309
    public function __call($method, array $arguments = [])
310
    {
311
        return call_user_func_array([$this->mongoCollection(), $method], $arguments);
312
    }
313
314
    /**
315
     * {@inheritdoc}
316
     */
317
    public function jsonSerialize()
318
    {
319
        return $this->getIterator();
320
    }
321
322
    /**
323
     * Destructing.
324
     */
325
    public function __destruct()
326
    {
327
        $this->odm = $this->collection = $this->paginator = null;
328
        $this->query = [];
329
    }
330
331
    /**
332
     * @return Object
333
     */
334
    public function __debugInfo()
335
    {
336
        return (object)[
337
            'collection' => $this->database . '/' . $this->name,
338
            'query'      => $this->query,
339
            'limit'      => $this->limit,
340
            'offset'     => $this->offset,
341
            'sort'       => $this->sort
342
        ];
343
    }
344
345
    /**
346
     * Create CursorReader based on stored query, limits and sorting.
347
     *
348
     * @param array    $query  Fields and conditions to query by.
349
     * @param array    $fields Fields of the results to return.
350
     * @param int|null $limit  Custom limit value.
351
     * @return DocumentCursor
352
     * @throws \MongoException
353
     */
354
    protected function createCursor($query = [], $fields = [], $limit = null)
355
    {
356
        $this->query($query);
357
        $this->applyPagination();
358
        $cursorReader = new DocumentCursor(
359
            $this->mongoCollection()->find($this->query, $fields),
360
            $this->odm,
361
            empty($fields) ? $this->primaryDocument() : null,
0 ignored issues
show
Bug introduced by
It seems like empty($fields) ? $this->primaryDocument() : null can also be of type array; however, Spiral\ODM\Entities\DocumentCursor::__construct() does only seem to accept string|null, 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...
362
            $this->sort,
363
            !empty($limit) ? $limit : $this->limit,
364
            $this->offset
365
        );
366
367
        if ((!empty($this->limit) || !empty($this->offset)) && empty($this->sort)) {
368
            //Document can travel in mongo collection
369
            $this->logger()->warning(
370
                "MongoDB query executed with limit/offset but without specified sorting."
371
            );
372
        }
373
374
        //This is not the same profiling as one defined in getProfilingLevel().
375
        if (!$this->mongoDatabase()->isProfiling()) {
376
            return $cursorReader;
377
        }
378
379
        $queryInfo = ['query' => $this->query, 'sort' => $this->sort];
380
381
        if (!empty($this->limit)) {
382
            $queryInfo['limit'] = !empty($limit) ? (int)$limit : (int)$this->limit;
383
        }
384
385
        if (!empty($this->offset)) {
386
            $queryInfo['offset'] = (int)$this->offset;
387
        }
388
389
        if ($this->mongoDatabase()->getProfiling() == MongoDatabase::PROFILE_EXPLAIN) {
390
            $queryInfo['explained'] = $cursorReader->explain();
391
        }
392
393
        $this->logger()->debug(
394
            "{database}/{collection}: " . json_encode($queryInfo, JSON_PRETTY_PRINT),
395
            [
396
                'collection' => $this->name,
397
                'database'   => $this->database,
398
                'queryInfo'  => $queryInfo
399
            ]
400
        );
401
402
        return $cursorReader;
403
    }
404
405
    /**
406
     * Associated document class.
407
     *
408
     * @return string
409
     */
410
    protected function primaryDocument()
411
    {
412
        return $this->odm->primaryDocument($this->database, $this->name);
413
    }
414
415
    /**
416
     * MongoDatabase instance.
417
     *
418
     * @return MongoDatabase
419
     */
420
    protected function mongoDatabase()
421
    {
422
        return $this->odm->database($this->database);
423
    }
424
425
    /**
426
     * Get associated mongo collection.
427
     *
428
     * @return \MongoCollection
429
     */
430
    protected function mongoCollection()
431
    {
432
        if (!empty($this->collection)) {
433
            return $this->collection;
434
        }
435
436
        return $this->collection = $this->mongoDatabase()->selectCollection($this->name);
437
    }
438
}