Completed
Push — master ( 80e8df...60de96 )
by Maciej
18s
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
    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
     * Please note that $requireIndexes was deprecated in 1.2 and will be removed in 2.0
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 deprecated
101
     * @param boolean $readOnly
102
     */
103 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)
104
    {
105 200
        $primers = array_filter($primers);
106
107 200
        if ( ! empty($primers)) {
108 23
            $query['eagerCursor'] = true;
109
        }
110
111 200
        if ( ! empty($query['eagerCursor'])) {
112 24
            $query['useIdentifierKeys'] = false;
113
        }
114
115 200
        parent::__construct($collection, $query, $options);
116 200
        $this->dm = $dm;
117 200
        $this->class = $class;
118 200
        $this->hydrate = $hydrate;
119 200
        $this->primers = $primers;
120 200
        $this->requireIndexes = $requireIndexes;
121
122 200
        $this->setReadOnly($readOnly);
123 200
        $this->setRefresh($refresh);
124
125 200
        if (isset($query['slaveOkay'])) {
126 4
            $this->unitOfWorkHints[self::HINT_SLAVE_OKAY] = $query['slaveOkay'];
127
        }
128
129 200
        if (isset($query['readPreference'])) {
130 3
            $this->unitOfWorkHints[self::HINT_READ_PREFERENCE] = $query['readPreference'];
131 3
            $this->unitOfWorkHints[self::HINT_READ_PREFERENCE_TAGS] = $query['readPreferenceTags'];
132
        }
133 200
    }
134
135
    /**
136
     * Gets the DocumentManager instance.
137
     *
138
     * @return DocumentManager $dm
139
     */
140
    public function getDocumentManager()
141
    {
142
        return $this->dm;
143
    }
144
145
    /**
146
     * Gets the ClassMetadata instance.
147
     *
148
     * @return ClassMetadata $class
149
     */
150
    public function getClass()
151
    {
152
        return $this->class;
153
    }
154
155
    /**
156
     * Sets whether or not to hydrate the documents to objects.
157
     *
158
     * @param boolean $hydrate
159
     */
160
    public function setHydrate($hydrate)
161
    {
162
        $this->hydrate = (boolean) $hydrate;
163
    }
164
165
    /**
166
     * Set whether documents should be registered in UnitOfWork. If document would
167
     * already be managed it will be left intact and new instance returned.
168
     * 
169
     * This option has no effect if hydration is disabled.
170
     * 
171
     * @param boolean $readOnly
172
     */
173 200
    public function setReadOnly($readOnly)
174
    {
175 200
        $this->unitOfWorkHints[Query::HINT_READ_ONLY] = (boolean) $readOnly;
176 200
    }
177
178
    /**
179
     * Set whether to refresh hydrated documents that are already in the
180
     * identity map.
181
     *
182
     * This option has no effect if hydration is disabled.
183
     *
184
     * @param boolean $refresh
185
     */
186 200
    public function setRefresh($refresh)
187
    {
188 200
        $this->unitOfWorkHints[Query::HINT_REFRESH] = (boolean) $refresh;
189 200
    }
190
191
    /**
192
     * Gets the fields involved in this query.
193
     *
194
     * @return array $fields An array of fields names used in this query.
195
     *
196
     * @deprecated method was deprecated in 1.2 and will be removed in 2.0
197
     */
198 22
    public function getFieldsInQuery()
199
    {
200 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...
201 22
            sprintf('%s was deprecated in version 1.2 and will be removed altogether in 2.0.', __METHOD__),
202 22
            E_USER_DEPRECATED
203
        );
204 22
        $query = isset($this->query['query']) ? $this->query['query'] : array();
205 22
        $sort = isset($this->query['sort']) ? $this->query['sort'] : array();
206
207 22
        $extractor = new FieldExtractor($query, $sort);
208 22
        return $extractor->getFields();
209
    }
210
211
    /**
212
     * Check if this query is indexed.
213
     *
214
     * @return bool
215
     *
216
     * @deprecated method was deprecated in 1.2 and will be removed in 2.0
217
     */
218 8
    public function isIndexed()
219
    {
220 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...
221 8
            sprintf('%s was deprecated in version 1.2 and will be removed altogether in 2.0.', __METHOD__),
222 8
            E_USER_DEPRECATED
223
        );
224 8
        $fields = $this->getFieldsInQuery();
225 8
        foreach ($fields as $field) {
226 8
            if ( ! $this->collection->isFieldIndexed($field)) {
227 6
                return false;
228
            }
229
        }
230 2
        return true;
231
    }
232
233
    /**
234
     * Gets an array of the unindexed fields in this query.
235
     *
236
     * @return array
237
     *
238
     * @deprecated method was deprecated in 1.2 and will be removed in 2.0
239
     */
240 6
    public function getUnindexedFields()
241
    {
242 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...
243 6
            sprintf('%s was deprecated in version 1.2 and will be removed altogether in 2.0.', __METHOD__),
244 6
            E_USER_DEPRECATED
245
        );
246 6
        $unindexedFields = array();
247 6
        $fields = $this->getFieldsInQuery();
248 6
        foreach ($fields as $field) {
249 6
            if ( ! $this->collection->isFieldIndexed($field)) {
250 6
                $unindexedFields[] = $field;
251
            }
252
        }
253 6
        return $unindexedFields;
254
    }
255
256
    /**
257
     * Execute the query and returns the results.
258
     *
259
     * @throws \Doctrine\ODM\MongoDB\MongoDBException
260
     * @return mixed
261
     */
262 154
    public function execute()
263
    {
264 154
        if ($this->isIndexRequired() && ! $this->isIndexed()) {
265 5
            throw MongoDBException::queryNotIndexed($this->class->name, $this->getUnindexedFields());
266
        }
267
268 149
        $results = parent::execute();
269
270 148
        if ( ! $this->hydrate) {
271 10
            return $results;
272
        }
273
274 141
        $uow = $this->dm->getUnitOfWork();
275
276
        /* A geoNear command returns an ArrayIterator, where each result is an
277
         * object with "dis" (computed distance) and "obj" (original document)
278
         * properties. If hydration is enabled, eagerly hydrate these results.
279
         *
280
         * Other commands results are not handled, since their results may not
281
         * resemble documents in the collection.
282
         */
283 141
        if ($this->query['type'] === self::TYPE_GEO_NEAR) {
284 2
            foreach ($results as $key => $result) {
285 2
                $document = $result['obj'];
286 2
                if ($this->class->distance !== null) {
287 2
                    $document[$this->class->distance] = $result['dis'];
288
                }
289 2
                $results[$key] = $uow->getOrCreateDocument($this->class->name, $document, $this->unitOfWorkHints);
290
            }
291 2
            $results->reset();
292
        }
293
294
        /* If a single document is returned from a findAndModify command and it
295
         * includes the identifier field, attempt hydration.
296
         */
297 141
        if (($this->query['type'] === self::TYPE_FIND_AND_UPDATE ||
298 138
             $this->query['type'] === self::TYPE_FIND_AND_REMOVE) &&
299 6
            is_array($results) && isset($results['_id'])) {
300
301 5
            $results = $uow->getOrCreateDocument($this->class->name, $results, $this->unitOfWorkHints);
302
303 5
            if ( ! empty($this->primers)) {
304 1
                $referencePrimer = new ReferencePrimer($this->dm, $uow);
305
306 1 View Code Duplication
                foreach ($this->primers as $fieldName => $primer) {
307 1
                    $primer = is_callable($primer) ? $primer : null;
308 1
                    $referencePrimer->primeReferences($this->class, array($results), $fieldName, $this->unitOfWorkHints, $primer);
309
                }
310
            }
311
        }
312
313 141
        return $results;
314
    }
315
316
    /**
317
     * Prepare the Cursor returned by {@link Query::execute()}.
318
     *
319
     * This method will wrap the base Cursor with an ODM Cursor or EagerCursor,
320
     * and set the hydrate option and UnitOfWork hints. This occurs in addition
321
     * to any preparation done by the base Query class.
322
     *
323
     * @see \Doctrine\MongoDB\Cursor::prepareCursor()
324
     * @param BaseCursor $cursor
325
     * @return CursorInterface
326
     */
327 133
    protected function prepareCursor(BaseCursor $cursor)
328
    {
329 133
        $cursor = parent::prepareCursor($cursor);
330
331
        // Convert the base Cursor into an ODM Cursor
332 133
        $cursorClass = ( ! empty($this->query['eagerCursor'])) ? EagerCursor::class : Cursor::class;
333 133
        $cursor = new $cursorClass($cursor, $this->dm->getUnitOfWork(), $this->class);
334
335 133
        $cursor->hydrate($this->hydrate);
336 133
        $cursor->setHints($this->unitOfWorkHints);
337
338 133
        if ( ! empty($this->primers)) {
339 21
            $referencePrimer = new ReferencePrimer($this->dm, $this->dm->getUnitOfWork());
340 21
            $cursor->enableReferencePriming($this->primers, $referencePrimer);
341
        }
342
343 133
        return $cursor;
344
    }
345
346
    /**
347
     * Return whether queries on this document should require indexes.
348
     *
349
     * @return boolean
350
     */
351 154
    private function isIndexRequired()
352
    {
353 154
        return $this->requireIndexes !== null ? $this->requireIndexes : $this->class->requireIndexes;
354
    }
355
}
356