Completed
Pull Request — 1.0.x (#1386)
by Andreas
10:42
created

Query::isIndexed()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3
Metric Value
dl 0
loc 10
ccs 7
cts 7
cp 1
rs 9.4285
cc 3
eloc 6
nc 3
nop 0
crap 3
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\MongoDB\EagerCursor as BaseEagerCursor;
26
use Doctrine\MongoDB\Iterator;
27
use Doctrine\ODM\MongoDB\Cursor;
28
use Doctrine\ODM\MongoDB\DocumentManager;
29
use Doctrine\ODM\MongoDB\EagerCursor;
30
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
31
use Doctrine\ODM\MongoDB\MongoDBException;
32
33
/**
34
 * ODM Query wraps the raw Doctrine MongoDB queries to add additional functionality
35
 * and to hydrate the raw arrays of data to Doctrine document objects.
36
 *
37
 * @since       1.0
38
 * @author      Jonathan H. Wage <[email protected]>
39
 */
40
class Query extends \Doctrine\MongoDB\Query\Query
41
{
42
    const HINT_REFRESH = 1;
43
    const HINT_SLAVE_OKAY = 2;
44
    const HINT_READ_PREFERENCE = 3;
45
    const HINT_READ_PREFERENCE_TAGS = 4;
46
47
    /**
48
     * The DocumentManager instance.
49
     *
50
     * @var DocumentManager
51
     */
52
    private $dm;
53
54
    /**
55
     * The ClassMetadata instance.
56
     *
57
     * @var ClassMetadata
58
     */
59
    private $class;
60
61
    /**
62
     * Whether to hydrate results as document class instances.
63
     *
64
     * @var boolean
65
     */
66
    private $hydrate = true;
67
68
    /**
69
     * Array of primer Closure instances.
70
     *
71
     * @var array
72
     */
73
    private $primers = array();
74
75
    /**
76
     * Whether or not to require indexes.
77
     *
78
     * @var boolean
79
     */
80
    private $requireIndexes;
81
82
    /**
83
     * Hints for UnitOfWork behavior.
84
     *
85
     * @var array
86
     */
87
    private $unitOfWorkHints = array();
88
89
    /**
90
     * Constructor.
91
     *
92
     * @param DocumentManager $dm
93
     * @param ClassMetadata $class
94
     * @param Collection $collection
95
     * @param array $query
96
     * @param array $options
97
     * @param boolean $hydrate
98
     * @param boolean $refresh
99
     * @param array $primers
100
     * @param null $requireIndexes
101
     */
102 172
    public function __construct(DocumentManager $dm, ClassMetadata $class, Collection $collection, array $query = array(), array $options = array(), $hydrate = true, $refresh = false, array $primers = array(), $requireIndexes = null)
103
    {
104 172
        $primers = array_filter($primers);
105
106 172
        if ( ! empty($primers)) {
107 17
            $query['eagerCursor'] = true;
108 17
        }
109
110 172
        if ( ! empty($query['eagerCursor'])) {
111 17
            $query['useIdentifierKeys'] = false;
112 17
        }
113
114 172
        parent::__construct($collection, $query, $options);
115 172
        $this->dm = $dm;
116 172
        $this->class = $class;
117 172
        $this->hydrate = $hydrate;
118 172
        $this->primers = $primers;
119 172
        $this->requireIndexes = $requireIndexes;
120
121 172
        $this->setRefresh($refresh);
122
123 172
        if (isset($query['slaveOkay'])) {
124 4
            $this->unitOfWorkHints[self::HINT_SLAVE_OKAY] = $query['slaveOkay'];
125 4
        }
126
127 172
        if (isset($query['readPreference'])) {
128
            $this->unitOfWorkHints[self::HINT_READ_PREFERENCE] = $query['readPreference'];
129
            $this->unitOfWorkHints[self::HINT_READ_PREFERENCE_TAGS] = $query['readPreferenceTags'];
130
        }
131 172
    }
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 to refresh hydrated documents that are already in the
165
     * identity map.
166
     *
167
     * This option has no effect if hydration is disabled.
168
     *
169
     * @param boolean $refresh
170
     */
171 172
    public function setRefresh($refresh)
172
    {
173 172
        $this->unitOfWorkHints[Query::HINT_REFRESH] = (boolean) $refresh;
174 172
    }
175
176
    /**
177
     * Gets the fields involved in this query.
178
     *
179
     * @return array $fields An array of fields names used in this query.
180
     */
181 22
    public function getFieldsInQuery()
182
    {
183 22
        $query = isset($this->query['query']) ? $this->query['query'] : array();
184 22
        $sort = isset($this->query['sort']) ? $this->query['sort'] : array();
185
186 22
        $extractor = new FieldExtractor($query, $sort);
187 22
        return $extractor->getFields();
188
    }
189
190
    /**
191
     * Check if this query is indexed.
192
     *
193
     * @return bool
194
     */
195 8
    public function isIndexed()
196
    {
197 8
        $fields = $this->getFieldsInQuery();
198 8
        foreach ($fields as $field) {
199 8
            if ( ! $this->collection->isFieldIndexed($field)) {
200 6
                return false;
201
            }
202 3
        }
203 2
        return true;
204
    }
205
206
    /**
207
     * Gets an array of the unindexed fields in this query.
208
     *
209
     * @return array
210
     */
211 6
    public function getUnindexedFields()
212
    {
213 6
        $unindexedFields = array();
214 6
        $fields = $this->getFieldsInQuery();
215 6
        foreach ($fields as $field) {
216 6
            if ( ! $this->collection->isFieldIndexed($field)) {
217 6
                $unindexedFields[] = $field;
218 6
            }
219 6
        }
220 6
        return $unindexedFields;
221
    }
222
223
    /**
224
     * Execute the query and returns the results.
225
     *
226
     * @throws \Doctrine\ODM\MongoDB\MongoDBException
227
     * @return mixed
228
     */
229 134
    public function execute()
230
    {
231 134
        if ($this->isIndexRequired() && ! $this->isIndexed()) {
232 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...
233
        }
234
235 129
        $results = parent::execute();
236
237 128
        if ( ! $this->hydrate) {
238 11
            return $results;
239
        }
240
241 121
        $uow = $this->dm->getUnitOfWork();
242
243
        /* A geoNear command returns an ArrayIterator, where each result is an
244
         * object with "dis" (computed distance) and "obj" (original document)
245
         * properties. If hydration is enabled, eagerly hydrate these results.
246
         *
247
         * Other commands results are not handled, since their results may not
248
         * resemble documents in the collection.
249
         */
250 121
        if ($this->query['type'] === self::TYPE_GEO_NEAR) {
251 2
            foreach ($results as $key => $result) {
252 2
                $document = $result['obj'];
253 2
                if ($this->class->distance !== null) {
254 2
                    $document[$this->class->distance] = $result['dis'];
255 2
                }
256 2
                $results[$key] = $uow->getOrCreateDocument($this->class->name, $document, $this->unitOfWorkHints);
257 2
            }
258 2
            $results->reset();
259 2
        }
260
261
        /* If a single document is returned from a findAndModify command and it
262
         * includes the identifier field, attempt hydration.
263
         */
264 121
        if (($this->query['type'] === self::TYPE_FIND_AND_UPDATE ||
265 121
             $this->query['type'] === self::TYPE_FIND_AND_REMOVE) &&
266 121
            is_array($results) && isset($results['_id'])) {
267
268 5
            $results = $uow->getOrCreateDocument($this->class->name, $results, $this->unitOfWorkHints);
269
270 5
            if ( ! empty($this->primers)) {
271 1
                $referencePrimer = new ReferencePrimer($this->dm, $uow);
272
273 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...
274 1
                    $primer = is_callable($primer) ? $primer : null;
275 1
                    $referencePrimer->primeReferences($this->class, array($results), $fieldName, $this->unitOfWorkHints, $primer);
276 1
                }
277 1
            }
278 5
        }
279
280 121
        return $results;
281
    }
282
283
    /**
284
     * Prepare the Cursor returned by {@link Query::execute()}.
285
     *
286
     * This method will wrap the base Cursor with an ODM Cursor or EagerCursor,
287
     * and set the hydrate option and UnitOfWork hints. This occurs in addition
288
     * to any preparation done by the base Query class.
289
     *
290
     * @see \Doctrine\MongoDB\Cursor::prepareCursor()
291
     * @param BaseCursor $cursor
292
     * @return CursorInterface
293
     */
294 113
    protected function prepareCursor(BaseCursor $cursor)
295
    {
296 113
        $cursor = parent::prepareCursor($cursor);
297
298
        // Convert the base Cursor into an ODM Cursor
299 113
        $cursorClass = ( ! empty($this->query['eagerCursor'])) ? 'Doctrine\ODM\MongoDB\EagerCursor' : 'Doctrine\ODM\MongoDB\Cursor';
300 113
        $cursor = new $cursorClass($cursor, $this->dm->getUnitOfWork(), $this->class);
301
302 113
        $cursor->hydrate($this->hydrate);
303 113
        $cursor->setHints($this->unitOfWorkHints);
304
305 113
        if ( ! empty($this->primers)) {
306 15
            $referencePrimer = new ReferencePrimer($this->dm, $this->dm->getUnitOfWork());
307 15
            $cursor->enableReferencePriming($this->primers, $referencePrimer);
308 15
        }
309
310 113
        return $cursor;
311
    }
312
313
    /**
314
     * Return whether queries on this document should require indexes.
315
     *
316
     * @return boolean
317
     */
318 134
    private function isIndexRequired()
319
    {
320 134
        return $this->requireIndexes !== null ? $this->requireIndexes : $this->class->requireIndexes;
321
    }
322
}
323