Completed
Pull Request — master (#1419)
by Robert
11:09 queued 01:09
created

Query::isIndexRequired()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 2
eloc 2
nc 2
nop 0
crap 2
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ODM\MongoDB\Query;
21
22
use Doctrine\MongoDB\Collection;
23
use Doctrine\MongoDB\Cursor as BaseCursor;
24
use Doctrine\MongoDB\CursorInterface;
25
use Doctrine\ODM\MongoDB\Cursor;
26
use Doctrine\ODM\MongoDB\DocumentManager;
27
use Doctrine\ODM\MongoDB\EagerCursor;
28
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
29
use Doctrine\ODM\MongoDB\MongoDBException;
30
31
/**
32
 * ODM Query wraps the raw Doctrine MongoDB queries to add additional functionality
33
 * and to hydrate the raw arrays of data to Doctrine document objects.
34
 *
35
 * @since       1.0
36
 */
37
class Query extends \Doctrine\MongoDB\Query\Query
38
{
39
    const HINT_REFRESH = 1;
40
    const HINT_SLAVE_OKAY = 2;
41
    const HINT_READ_PREFERENCE = 3;
42
    const HINT_READ_PREFERENCE_TAGS = 4;
43
    const HINT_READ_ONLY = 5;
44
45
    /**
46
     * The DocumentManager instance.
47
     *
48
     * @var DocumentManager
49
     */
50
    private $dm;
51
52
    /**
53
     * The ClassMetadata instance.
54
     *
55
     * @var ClassMetadata
56
     */
57
    private $class;
58
59
    /**
60
     * Whether to hydrate results as document class instances.
61
     *
62
     * @var boolean
63
     */
64
    private $hydrate = true;
65
66
    /**
67
     * Array of primer Closure instances.
68
     *
69
     * @var array
70
     */
71
    private $primers = array();
72
73
    /**
74
     * Whether or not to require indexes.
75
     *
76
     * @var boolean
77
     */
78
    private $requireIndexes;
79
80
    /**
81
     * Hints for UnitOfWork behavior.
82
     *
83
     * @var array
84
     */
85
    private $unitOfWorkHints = array();
86
87
    /**
88
     * Constructor.
89
     *
90
     * @param DocumentManager $dm
91
     * @param ClassMetadata $class
92
     * @param Collection $collection
93
     * @param array $query
94
     * @param array $options
95
     * @param boolean $hydrate
96
     * @param boolean $refresh
97
     * @param array $primers
98
     * @param null $requireIndexes
99
     * @param boolean $readOnly
100
     */
101 190
    public function __construct(DocumentManager $dm, ClassMetadata $class, Collection $collection, array $query = array(), array $options = array(), $hydrate = true, $refresh = false, array $primers = array(), $requireIndexes = null, $readOnly = false)
102
    {
103 190
        $primers = array_filter($primers);
104
105 190
        if ( ! empty($primers)) {
106 19
            $query['eagerCursor'] = true;
107
        }
108
109 190
        if ( ! empty($query['eagerCursor'])) {
110 19
            $query['useIdentifierKeys'] = false;
111
        }
112
113 190
        parent::__construct($collection, $query, $options);
114 190
        $this->dm = $dm;
115 190
        $this->class = $class;
116 190
        $this->hydrate = $hydrate;
117 190
        $this->primers = $primers;
118 190
        $this->requireIndexes = $requireIndexes;
119
120 190
        $this->setReadOnly($readOnly);
121 190
        $this->setRefresh($refresh);
122
123 190
        if (isset($query['slaveOkay'])) {
124 4
            $this->unitOfWorkHints[self::HINT_SLAVE_OKAY] = $query['slaveOkay'];
125
        }
126
127 190
        if (isset($query['readPreference'])) {
128 3
            $this->unitOfWorkHints[self::HINT_READ_PREFERENCE] = $query['readPreference'];
129 3
            $this->unitOfWorkHints[self::HINT_READ_PREFERENCE_TAGS] = $query['readPreferenceTags'];
130
        }
131 190
    }
132
133
    /**
134
     * Gets the DocumentManager instance.
135
     *
136
     * @return DocumentManager $dm
137
     */
138
    public function getDocumentManager()
139
    {
140
        return $this->dm;
141
    }
142
143
    /**
144
     * Gets the ClassMetadata instance.
145
     *
146
     * @return ClassMetadata $class
147
     */
148
    public function getClass()
149
    {
150
        return $this->class;
151
    }
152
153
    /**
154
     * Sets whether or not to hydrate the documents to objects.
155
     *
156
     * @param boolean $hydrate
157
     */
158
    public function setHydrate($hydrate)
159
    {
160
        $this->hydrate = (boolean) $hydrate;
161
    }
162
163
    /**
164
     * Set whether documents should be registered in UnitOfWork. If document would
165
     * already be managed it will be left intact and new instance returned.
166
     * 
167
     * This option has no effect if hydration is disabled.
168
     * 
169
     * @param boolean $readOnly
170
     */
171 190
    public function setReadOnly($readOnly)
172
    {
173 190
        $this->unitOfWorkHints[Query::HINT_READ_ONLY] = (boolean) $readOnly;
174 190
    }
175
176
    /**
177
     * Set whether to refresh hydrated documents that are already in the
178
     * identity map.
179
     *
180
     * This option has no effect if hydration is disabled.
181
     *
182
     * @param boolean $refresh
183
     */
184 190
    public function setRefresh($refresh)
185
    {
186 190
        $this->unitOfWorkHints[Query::HINT_REFRESH] = (boolean) $refresh;
187 190
    }
188
189
    /**
190
     * Gets the fields involved in this query.
191
     *
192
     * @return array $fields An array of fields names used in this query.
193
     */
194 22
    public function getFieldsInQuery()
195
    {
196 22
        $query = isset($this->query['query']) ? $this->query['query'] : array();
197 22
        $sort = isset($this->query['sort']) ? $this->query['sort'] : array();
198
199 22
        $extractor = new FieldExtractor($query, $sort);
200 22
        return $extractor->getFields();
201
    }
202
203
    /**
204
     * Check if this query is indexed.
205
     *
206
     * @return bool
207
     */
208 8
    public function isIndexed()
209
    {
210 8
        $fields = $this->getFieldsInQuery();
211 8
        foreach ($fields as $field) {
212 8
            if ( ! $this->collection->isFieldIndexed($field)) {
213 8
                return false;
214
            }
215
        }
216 2
        return true;
217
    }
218
219
    /**
220
     * Gets an array of the unindexed fields in this query.
221
     *
222
     * @return array
223
     */
224 6
    public function getUnindexedFields()
225
    {
226 6
        $unindexedFields = array();
227 6
        $fields = $this->getFieldsInQuery();
228 6
        foreach ($fields as $field) {
229 6
            if ( ! $this->collection->isFieldIndexed($field)) {
230 6
                $unindexedFields[] = $field;
231
            }
232
        }
233 6
        return $unindexedFields;
234
    }
235
236
    /**
237
     * Execute the query and returns the results.
238
     *
239
     * @throws \Doctrine\ODM\MongoDB\MongoDBException
240
     * @return mixed
241
     */
242 150
    public function execute()
243
    {
244 150
        if ($this->isIndexRequired() && ! $this->isIndexed()) {
245 5
            throw MongoDBException::queryNotIndexed($this->class->name, $this->getUnindexedFields());
0 ignored issues
show
Documentation introduced by
$this->getUnindexedFields() is of type array, but the function expects a string.

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...
246
        }
247
248 145
        $results = parent::execute();
249
250 144
        if ( ! $this->hydrate) {
251 10
            return $results;
252
        }
253
254 137
        $uow = $this->dm->getUnitOfWork();
255
256
        /* A geoNear command returns an ArrayIterator, where each result is an
257
         * object with "dis" (computed distance) and "obj" (original document)
258
         * properties. If hydration is enabled, eagerly hydrate these results.
259
         *
260
         * Other commands results are not handled, since their results may not
261
         * resemble documents in the collection.
262
         */
263 137
        if ($this->query['type'] === self::TYPE_GEO_NEAR) {
264 2
            foreach ($results as $key => $result) {
265 2
                $document = $result['obj'];
266 2
                if ($this->class->distance !== null) {
267 2
                    $document[$this->class->distance] = $result['dis'];
268
                }
269 2
                $results[$key] = $uow->getOrCreateDocument($this->class->name, $document, $this->unitOfWorkHints);
270
            }
271 2
            $results->reset();
272
        }
273
274
        /* If a single document is returned from a findAndModify command and it
275
         * includes the identifier field, attempt hydration.
276
         */
277 137
        if (($this->query['type'] === self::TYPE_FIND_AND_UPDATE ||
278 137
             $this->query['type'] === self::TYPE_FIND_AND_REMOVE) &&
279 137
            is_array($results) && isset($results['_id'])) {
280
281 5
            $results = $uow->getOrCreateDocument($this->class->name, $results, $this->unitOfWorkHints);
282
283 5
            if ( ! empty($this->primers)) {
284 1
                $referencePrimer = new ReferencePrimer($this->dm, $uow);
285
286 1 View Code Duplication
                foreach ($this->primers as $fieldName => $primer) {
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...
287 1
                    $primer = is_callable($primer) ? $primer : null;
288 1
                    $referencePrimer->primeReferences($this->class, array($results), $fieldName, $this->unitOfWorkHints, $primer);
289
                }
290
            }
291
        }
292
293 137
        return $results;
294
    }
295
296
    /**
297
     * Prepare the Cursor returned by {@link Query::execute()}.
298
     *
299
     * This method will wrap the base Cursor with an ODM Cursor or EagerCursor,
300
     * and set the hydrate option and UnitOfWork hints. This occurs in addition
301
     * to any preparation done by the base Query class.
302
     *
303
     * @see \Doctrine\MongoDB\Cursor::prepareCursor()
304
     * @param BaseCursor $cursor
305
     * @return CursorInterface
306
     */
307 129
    protected function prepareCursor(BaseCursor $cursor)
308
    {
309 129
        $cursor = parent::prepareCursor($cursor);
310
311
        // Convert the base Cursor into an ODM Cursor
312 129
        $cursorClass = ( ! empty($this->query['eagerCursor'])) ? EagerCursor::class : Cursor::class;
313 129
        $cursor = new $cursorClass($cursor, $this->dm->getUnitOfWork(), $this->class);
314
315 129
        $cursor->hydrate($this->hydrate);
316 129
        $cursor->setHints($this->unitOfWorkHints);
317
318 129
        if ( ! empty($this->primers)) {
319 17
            $referencePrimer = new ReferencePrimer($this->dm, $this->dm->getUnitOfWork());
320 17
            $cursor->enableReferencePriming($this->primers, $referencePrimer);
321
        }
322
323 129
        return $cursor;
324
    }
325
326
    /**
327
     * Return whether queries on this document should require indexes.
328
     *
329
     * @return boolean
330
     */
331 150
    private function isIndexRequired()
332
    {
333 150
        return $this->requireIndexes !== null ? $this->requireIndexes : $this->class->requireIndexes;
334
    }
335
}
336