Completed
Push — master ( 87aba0...0e8729 )
by Andreas
25s queued 16s
created

lib/Doctrine/ODM/MongoDB/Query/Query.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
    /** @deprecated */
41
    const HINT_SLAVE_OKAY = 2;
42
    const HINT_READ_PREFERENCE = 3;
43
    const HINT_READ_PREFERENCE_TAGS = 4;
44
    const HINT_READ_ONLY = 5;
45
46
    /**
47
     * The DocumentManager instance.
48
     *
49
     * @var DocumentManager
50
     */
51
    private $dm;
52
53
    /**
54
     * The ClassMetadata instance.
55
     *
56
     * @var ClassMetadata
57
     */
58
    private $class;
59
60
    /**
61
     * Whether to hydrate results as document class instances.
62
     *
63
     * @var boolean
64
     */
65
    private $hydrate = true;
66
67
    /**
68
     * Array of primer Closure instances.
69
     *
70
     * @var array
71
     */
72
    private $primers = array();
73
74
    /**
75
     * Whether or not to require indexes.
76
     *
77
     * @var boolean
78
     */
79
    private $requireIndexes;
80
81
    /**
82
     * Hints for UnitOfWork behavior.
83
     *
84
     * @var array
85
     */
86
    private $unitOfWorkHints = array();
87
88
    /**
89
     * Constructor.
90
     *
91
     * Please note that $requireIndexes was deprecated in 1.2 and will be removed in 2.0
92
     *
93
     * @param DocumentManager $dm
94
     * @param ClassMetadata $class
95
     * @param Collection $collection
96
     * @param array $query
97
     * @param array $options
98
     * @param boolean $hydrate
99
     * @param boolean $refresh
100
     * @param array $primers
101
     * @param null $requireIndexes deprecated
102
     * @param boolean $readOnly
103
     */
104 200
    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)
105
    {
106 200
        $primers = array_filter($primers);
107
108 200
        if ( ! empty($primers)) {
109 23
            $query['eagerCursor'] = true;
110
        }
111
112 200
        if ( ! empty($query['eagerCursor'])) {
113 24
            $query['useIdentifierKeys'] = false;
114
        }
115
116 200
        parent::__construct($collection, $query, $options);
117 200
        $this->dm = $dm;
118 200
        $this->class = $class;
119 200
        $this->hydrate = $hydrate;
120 200
        $this->primers = $primers;
121 200
        $this->requireIndexes = $requireIndexes;
122
123 200
        $this->setReadOnly($readOnly);
124 200
        $this->setRefresh($refresh);
125
126 200
        if (isset($query['slaveOkay'])) {
127 4
            $this->unitOfWorkHints[self::HINT_SLAVE_OKAY] = $query['slaveOkay'];
128
        }
129
130 200
        if (isset($query['readPreference'])) {
131 3
            $this->unitOfWorkHints[self::HINT_READ_PREFERENCE] = $query['readPreference'];
132 3
            $this->unitOfWorkHints[self::HINT_READ_PREFERENCE_TAGS] = $query['readPreferenceTags'];
133
        }
134 200
    }
135
136
    /**
137
     * Gets the DocumentManager instance.
138
     *
139
     * @return DocumentManager $dm
140
     */
141
    public function getDocumentManager()
142
    {
143
        return $this->dm;
144
    }
145
146
    /**
147
     * Gets the ClassMetadata instance.
148
     *
149
     * @return ClassMetadata $class
150
     */
151
    public function getClass()
152
    {
153
        return $this->class;
154
    }
155
156
    /**
157
     * Sets whether or not to hydrate the documents to objects.
158
     *
159
     * @param boolean $hydrate
160
     */
161
    public function setHydrate($hydrate)
162
    {
163
        $this->hydrate = (boolean) $hydrate;
164
    }
165
166
    /**
167
     * Set whether documents should be registered in UnitOfWork. If document would
168
     * already be managed it will be left intact and new instance returned.
169
     * 
170
     * This option has no effect if hydration is disabled.
171
     * 
172
     * @param boolean $readOnly
173
     */
174 200
    public function setReadOnly($readOnly)
175
    {
176 200
        $this->unitOfWorkHints[Query::HINT_READ_ONLY] = (boolean) $readOnly;
177 200
    }
178
179
    /**
180
     * Set whether to refresh hydrated documents that are already in the
181
     * identity map.
182
     *
183
     * This option has no effect if hydration is disabled.
184
     *
185
     * @param boolean $refresh
186
     */
187 200
    public function setRefresh($refresh)
188
    {
189 200
        $this->unitOfWorkHints[Query::HINT_REFRESH] = (boolean) $refresh;
190 200
    }
191
192
    /**
193
     * Gets the fields involved in this query.
194
     *
195
     * @return array $fields An array of fields names used in this query.
196
     *
197
     * @deprecated method was deprecated in 1.2 and will be removed in 2.0
198
     */
199 22
    public function getFieldsInQuery()
200
    {
201 22
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
202 22
            sprintf('%s was deprecated in version 1.2 and will be removed altogether in 2.0.', __METHOD__),
203 22
            E_USER_DEPRECATED
204
        );
205 22
        $query = isset($this->query['query']) ? $this->query['query'] : array();
206 22
        $sort = isset($this->query['sort']) ? $this->query['sort'] : array();
207
208 22
        $extractor = new FieldExtractor($query, $sort);
209 22
        return $extractor->getFields();
210
    }
211
212
    /**
213
     * Check if this query is indexed.
214
     *
215
     * @return bool
216
     *
217
     * @deprecated method was deprecated in 1.2 and will be removed in 2.0
218
     */
219 8
    public function isIndexed()
220
    {
221 8
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
222 8
            sprintf('%s was deprecated in version 1.2 and will be removed altogether in 2.0.', __METHOD__),
223 8
            E_USER_DEPRECATED
224
        );
225 8
        $fields = $this->getFieldsInQuery();
226 8
        foreach ($fields as $field) {
227 8
            if ( ! $this->collection->isFieldIndexed($field)) {
228 6
                return false;
229
            }
230
        }
231 2
        return true;
232
    }
233
234
    /**
235
     * Gets an array of the unindexed fields in this query.
236
     *
237
     * @return array
238
     *
239
     * @deprecated method was deprecated in 1.2 and will be removed in 2.0
240
     */
241 6
    public function getUnindexedFields()
242
    {
243 6
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
244 6
            sprintf('%s was deprecated in version 1.2 and will be removed altogether in 2.0.', __METHOD__),
245 6
            E_USER_DEPRECATED
246
        );
247 6
        $unindexedFields = array();
248 6
        $fields = $this->getFieldsInQuery();
249 6
        foreach ($fields as $field) {
250 6
            if ( ! $this->collection->isFieldIndexed($field)) {
251 6
                $unindexedFields[] = $field;
252
            }
253
        }
254 6
        return $unindexedFields;
255
    }
256
257
    /**
258
     * Execute the query and returns the results.
259
     *
260
     * @throws \Doctrine\ODM\MongoDB\MongoDBException
261
     * @return mixed
262
     */
263 154
    public function execute()
264
    {
265 154
        if ($this->isIndexRequired() && ! $this->isIndexed()) {
266 5
            throw MongoDBException::queryNotIndexed($this->class->name, $this->getUnindexedFields());
267
        }
268
269 149
        $results = parent::execute();
270
271 148
        if ( ! $this->hydrate) {
272 10
            return $results;
273
        }
274
275 141
        $uow = $this->dm->getUnitOfWork();
276
277
        /* A geoNear command returns an ArrayIterator, where each result is an
278
         * object with "dis" (computed distance) and "obj" (original document)
279
         * properties. If hydration is enabled, eagerly hydrate these results.
280
         *
281
         * Other commands results are not handled, since their results may not
282
         * resemble documents in the collection.
283
         */
284 141
        if ($this->query['type'] === self::TYPE_GEO_NEAR) {
285 2
            foreach ($results as $key => $result) {
286 2
                $document = $result['obj'];
287 2
                if ($this->class->distance !== null) {
288 2
                    $document[$this->class->distance] = $result['dis'];
289
                }
290 2
                $results[$key] = $uow->getOrCreateDocument($this->class->name, $document, $this->unitOfWorkHints);
291
            }
292 2
            $results->reset();
293
        }
294
295
        /* If a single document is returned from a findAndModify command and it
296
         * includes the identifier field, attempt hydration.
297
         */
298 141
        if (($this->query['type'] === self::TYPE_FIND_AND_UPDATE ||
299 138
             $this->query['type'] === self::TYPE_FIND_AND_REMOVE) &&
300 6
            is_array($results) && isset($results['_id'])) {
301
302 5
            $results = $uow->getOrCreateDocument($this->class->name, $results, $this->unitOfWorkHints);
303
304 5
            if ( ! empty($this->primers)) {
305 1
                $referencePrimer = new ReferencePrimer($this->dm, $uow);
306
307 1 View Code Duplication
                foreach ($this->primers as $fieldName => $primer) {
308 1
                    $primer = is_callable($primer) ? $primer : null;
309 1
                    $referencePrimer->primeReferences($this->class, array($results), $fieldName, $this->unitOfWorkHints, $primer);
310
                }
311
            }
312
        }
313
314 141
        return $results;
315
    }
316
317
    /**
318
     * Prepare the Cursor returned by {@link Query::execute()}.
319
     *
320
     * This method will wrap the base Cursor with an ODM Cursor or EagerCursor,
321
     * and set the hydrate option and UnitOfWork hints. This occurs in addition
322
     * to any preparation done by the base Query class.
323
     *
324
     * @see \Doctrine\MongoDB\Cursor::prepareCursor()
325
     * @param BaseCursor $cursor
326
     * @return CursorInterface
327
     */
328 133
    protected function prepareCursor(BaseCursor $cursor)
329
    {
330 133
        $cursor = parent::prepareCursor($cursor);
331
332
        // Convert the base Cursor into an ODM Cursor
333 133
        $cursorClass = ( ! empty($this->query['eagerCursor'])) ? EagerCursor::class : Cursor::class;
334 133
        $cursor = new $cursorClass($cursor, $this->dm->getUnitOfWork(), $this->class);
335
336 133
        $cursor->hydrate($this->hydrate);
337 133
        $cursor->setHints($this->unitOfWorkHints);
338
339 133
        if ( ! empty($this->primers)) {
340 21
            $referencePrimer = new ReferencePrimer($this->dm, $this->dm->getUnitOfWork());
341 21
            $cursor->enableReferencePriming($this->primers, $referencePrimer);
342
        }
343
344 133
        return $cursor;
345
    }
346
347
    /**
348
     * Return whether queries on this document should require indexes.
349
     *
350
     * @return boolean
351
     */
352 154
    private function isIndexRequired()
353
    {
354 154
        return $this->requireIndexes !== null ? $this->requireIndexes : $this->class->requireIndexes;
355
    }
356
}
357