Completed
Pull Request — master (#1620)
by Maciej
09:05
created

Builder   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 489
Duplicated Lines 0.61 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 95.12%

Importance

Changes 0
Metric Value
wmc 56
lcom 1
cbo 9
dl 3
loc 489
ccs 156
cts 164
cp 0.9512
rs 6.5957
c 0
b 0
f 0

25 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
A requireIndexes() 0 9 1
A field() 0 7 1
B prime() 0 19 6
A eagerCursor() 0 8 3
A hydrate() 0 5 1
A readOnly() 0 5 1
A refresh() 0 5 1
A find() 0 7 1
A findAndUpdate() 0 7 1
A returnNew() 0 7 1
A findAndRemove() 0 7 1
A update() 0 11 1
A updateOne() 0 7 1
A updateMany() 0 7 1
A insert() 0 7 1
A remove() 0 7 1
A references() 0 5 1
A includesReferenceTo() 0 5 1
A group() 0 8 1
A slaveOkay() 0 11 2
C getQuery() 0 63 17
A expr() 0 7 1
B setDocumentName() 3 26 5
A getDiscriminatorValues() 0 15 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Builder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Builder, and based on these observations, apply Extract Interface, too.

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\ODM\MongoDB\DocumentManager;
23
use Doctrine\ODM\MongoDB\Hydrator;
24
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
25
26
/**
27
 * Query builder for ODM.
28
 *
29
 * @since       1.0
30
 */
31
class Builder extends \Doctrine\MongoDB\Query\Builder
32
{
33
    /**
34
     * The DocumentManager instance for this query
35
     *
36
     * @var DocumentManager
37
     */
38
    private $dm;
39
40
    /**
41
     * The ClassMetadata instance.
42
     *
43
     * @var \Doctrine\ODM\MongoDB\Mapping\ClassMetadata
44
     */
45
    private $class;
46
47
    /**
48
     * The current field we are operating on.
49
     *
50
     * @todo Change this to private once ODM requires doctrine/mongodb 1.1+
51
     * @var string
52
     */
53
    protected $currentField;
54
55
    /**
56
     * Whether or not to hydrate the data to documents.
57
     *
58
     * @var boolean
59
     */
60
    private $hydrate = true;
61
62
    /**
63
     * Whether or not to refresh the data for documents that are already in the identity map.
64
     *
65
     * @var boolean
66
     */
67
    private $refresh = false;
68
69
    /**
70
     * Array of primer Closure instances.
71
     *
72
     * @var array
73
     */
74
    private $primers = array();
75
76
    /**
77
     * Whether or not to require indexes.
78
     *
79
     * @var bool
80
     */
81
    private $requireIndexes;
82
83
    /**
84
     * Whether or not to register documents in UnitOfWork.
85
     *
86
     * @var bool
87
     */
88
    private $readOnly;
89
90
    /**
91
     * Construct a Builder
92
     *
93
     * @param DocumentManager $dm
94
     * @param string[]|string|null $documentName (optional) an array of document names, the document name, or none
95
     */
96 234
    public function __construct(DocumentManager $dm, $documentName = null)
97
    {
98 234
        $this->dm = $dm;
99 234
        $this->expr = new Expr($dm);
100 234
        if ($documentName !== null) {
101 226
            $this->setDocumentName($documentName);
102
        }
103 233
    }
104
105
    /**
106
     * Set whether or not to require indexes.
107
     *
108
     * @param bool $requireIndexes
109
     * @return $this
110
     *
111
     * @deprecated method was deprecated in 1.2 and will be removed in 2.0
112
     */
113 2
    public function requireIndexes($requireIndexes = true)
114
    {
115 2
        @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...
116 2
            'requireIndexes was deprecated in version 1.2 and will be removed altogether in 2.0.',
117 2
            E_USER_DEPRECATED
118
        );
119 2
        $this->requireIndexes = $requireIndexes;
120 2
        return $this;
121
    }
122
123
    /**
124
     * Set the current field to operate on.
125
     *
126
     * @param string $field
127
     * @return $this
128
     */
129 155
    public function field($field)
130
    {
131 155
        $this->currentField = $field;
132 155
        parent::field($field);
133
134 155
        return $this;
135
    }
136
137
    /**
138
     * Use a primer to eagerly load all references in the current field.
139
     *
140
     * If $primer is true or a callable is provided, referenced documents for
141
     * this field will loaded into UnitOfWork immediately after the query is
142
     * executed. This will avoid multiple queries due to lazy initialization of
143
     * Proxy objects.
144
     *
145
     * If $primer is false, no priming will take place. That is also the default
146
     * behavior.
147
     *
148
     * If a custom callable is used, its signature should conform to the default
149
     * Closure defined in {@link ReferencePrimer::__construct()}.
150
     *
151
     * @param boolean|callable $primer
152
     * @return $this
153
     * @throws \InvalidArgumentException If $primer is not boolean or callable
154
     */
155 27
    public function prime($primer = true)
156
    {
157 27
        if ( ! is_bool($primer) && ! is_callable($primer)) {
158 1
            throw new \InvalidArgumentException('$primer is not a boolean or callable');
159
        }
160
161 26
        if ($primer === false) {
162 1
            unset($this->primers[$this->currentField]);
163
164 1
            return $this;
165
        }
166
167 26
        if (array_key_exists('eagerCursor', $this->query) && !$this->query['eagerCursor']) {
168 1
            throw new \BadMethodCallException("Can't call prime() when setting eagerCursor to false");
169
        }
170
171 25
        $this->primers[$this->currentField] = $primer;
172 25
        return $this;
173
    }
174
175
    /**
176
     * {@inheritdoc}
177
     */
178 6
    public function eagerCursor($bool = true)
179
    {
180 6
        if ( ! $bool && ! empty($this->primers)) {
181 1
            throw new \BadMethodCallException("Can't set eagerCursor to false when using reference primers");
182
        }
183
184 5
        return parent::eagerCursor($bool);
185
    }
186
187
188
    /**
189
     * @param bool $bool
190
     * @return $this
191
     */
192 16
    public function hydrate($bool = true)
193
    {
194 16
        $this->hydrate = $bool;
195 16
        return $this;
196
    }
197
198
    /**
199
     * @param bool $bool
200
     * @return $this
201
     */
202 2
    public function readOnly($bool = true)
203
    {
204 2
        $this->readOnly = $bool;
205 2
        return $this;
206
    }
207
208
    /**
209
     * @param bool $bool
210
     * @return $this
211
     */
212 5
    public function refresh($bool = true)
213
    {
214 5
        $this->refresh = $bool;
215 5
        return $this;
216
    }
217
218
    /**
219
     * Change the query type to find and optionally set and change the class being queried.
220
     *
221
     * @param string $documentName
222
     * @return $this
223
     */
224 12
    public function find($documentName = null)
225
    {
226 12
        $this->setDocumentName($documentName);
227 12
        parent::find();
228
229 12
        return $this;
230
    }
231
232
    /**
233
     * @param string $documentName
234
     * @return $this
235
     */
236 13
    public function findAndUpdate($documentName = null)
237
    {
238 13
        $this->setDocumentName($documentName);
239 13
        parent::findAndUpdate();
240
241 13
        return $this;
242
    }
243
244
    /**
245
     * @param bool $bool
246
     * @return $this
247
     */
248 4
    public function returnNew($bool = true)
249
    {
250 4
        $this->refresh(true);
251 4
        parent::returnNew($bool);
252
253 4
        return $this;
254
    }
255
256
    /**
257
     * @param string $documentName
258
     * @return $this
259
     */
260 1
    public function findAndRemove($documentName = null)
261
    {
262 1
        $this->setDocumentName($documentName);
263 1
        parent::findAndRemove();
264
265 1
        return $this;
266
    }
267
268
    /**
269
     * @param string $documentName
270
     * @return $this
271
     *
272
     * @deprecated Deprecated in version 1.2 - use updateOne or updateMany instead
273
     */
274 12
    public function update($documentName = null)
275
    {
276 12
        @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...
277 12
            sprintf('%s was deprecated in version 1.2 - use updateOne or updateMany instead.', __METHOD__),
278 12
            E_USER_DEPRECATED
279
        );
280 12
        $this->setDocumentName($documentName);
281 12
        parent::update();
0 ignored issues
show
Deprecated Code introduced by
The method Doctrine\MongoDB\Query\Builder::update() has been deprecated with message: Deprecated in version 1.4 - use updateOne or updateMany instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
282
283 12
        return $this;
284
    }
285
286
    /**
287
     * @param string $documentName
288
     * @return $this
289
     */
290
    public function updateOne($documentName = null)
291
    {
292
        $this->setDocumentName($documentName);
293
        parent::updateOne();
294
295
        return $this;
296
    }
297
298
    /**
299
     * @param string $documentName
300
     * @return $this
301
     */
302
    public function updateMany($documentName = null)
303
    {
304
        $this->setDocumentName($documentName);
305
        parent::updateMany();
306
307
        return $this;
308
    }
309
310
    /**
311
     * @param string $documentName
312
     * @return $this
313
     */
314 1
    public function insert($documentName = null)
315
    {
316 1
        $this->setDocumentName($documentName);
317 1
        parent::insert();
318
319 1
        return $this;
320
    }
321
322
    /**
323
     * @param string $documentName
324
     * @return $this
325
     */
326 1
    public function remove($documentName = null)
327
    {
328 1
        $this->setDocumentName($documentName);
329 1
        parent::remove();
330
331 1
        return $this;
332
    }
333
334
    /**
335
     * @param object $document
336
     * @return $this
337
     */
338 11
    public function references($document)
339
    {
340 11
        $this->expr->references($document);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Doctrine\MongoDB\Query\Expr as the method references() does only exist in the following sub-classes of Doctrine\MongoDB\Query\Expr: Doctrine\ODM\MongoDB\Query\Expr. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
341 9
        return $this;
342
    }
343
344
    /**
345
     * @param object $document
346
     * @return $this
347
     */
348 7
    public function includesReferenceTo($document)
349
    {
350 7
        $this->expr->includesReferenceTo($document);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Doctrine\MongoDB\Query\Expr as the method includesReferenceTo() does only exist in the following sub-classes of Doctrine\MongoDB\Query\Expr: Doctrine\ODM\MongoDB\Query\Expr. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
351 5
        return $this;
352
    }
353
354
    /**
355
     * @inheritdoc
356
     * @deprecated Deprecated in version 1.2 - use Aggregation Builder's group stage instead.
357
     */
358 1
    public function group($keys, array $initial, $reduce = null, array $options = [])
359
    {
360 1
        @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...
361 1
            sprintf('%s was deprecated in version 1.2 - use Aggregation Builder\'s group stage instead.', __METHOD__),
362 1
            E_USER_DEPRECATED
363
        );
364 1
        return parent::group($keys, $initial, $reduce, $options);
365
    }
366
367
    /**
368
     * {@inheritdoc}
369
     *
370
     * @deprecated in version 1.2 - use setReadPreference instead.
371
     */
372 3
    public function slaveOkay($bool = true)
373
    {
374 3
        if ($bool) {
375 2
            @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...
376 2
                sprintf('%s was deprecated in version 1.2 - use setReadPreference instead.', __METHOD__),
377 2
                E_USER_DEPRECATED
378
            );
379
        }
380
381 3
        return parent::slaveOkay($bool);
382
    }
383
384
    /**
385
     * Gets the Query executable.
386
     *
387
     * @param array $options
388
     * @return Query $query
389
     */
390 203
    public function getQuery(array $options = array())
391
    {
392 203
        if ($this->query['type'] === Query::TYPE_MAP_REDUCE) {
393 3
            $this->hydrate = false;
394
        }
395
396 203
        $documentPersister = $this->dm->getUnitOfWork()->getDocumentPersister($this->class->name);
397
398 203
        $query = $this->query;
399
400 203
        $query['query'] = $this->expr->getQuery();
401 203
        $query['query'] = $documentPersister->addDiscriminatorToPreparedQuery($query['query']);
0 ignored issues
show
Documentation introduced by
$query['query'] is of type string, but the function expects a array.

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...
402 203
        $query['query'] = $documentPersister->addFilterToPreparedQuery($query['query']);
403
404 203
        $query['newObj'] = $this->expr->getNewObj();
405
406 203
        if (isset($query['distinct'])) {
407 2
            $query['distinct'] = $documentPersister->prepareFieldName($query['distinct']);
0 ignored issues
show
Bug introduced by
It seems like $query['distinct'] can also be of type array; however, Doctrine\ODM\MongoDB\Per...ter::prepareFieldName() does only seem to accept string, 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...
408
        }
409
410 203
        if ($this->class->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION && ! empty($query['upsert']) &&
411 203
            (empty($query['query'][$this->class->discriminatorField]) || is_array($query['query'][$this->class->discriminatorField]))) {
412 1
            throw new \InvalidArgumentException('Upsert query that is to be performed on discriminated document does not have single ' .
413 1
                'discriminator. Either not use base class or set \'' . $this->class->discriminatorField . '\' field manually.');
414
        }
415
416 202
        if ( ! empty($query['select'])) {
417 14
            $query['select'] = $documentPersister->prepareSortOrProjection($query['select']);
418 14
            if ($this->hydrate && $this->class->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION
419 14
                && ! isset($query['select'][$this->class->discriminatorField])) {
420
                $includeMode = 0 < count(array_filter($query['select'], function($mode) { return $mode == 1; }));
421 2
                if ($includeMode && ! isset($query['select'][$this->class->discriminatorField])) {
422 1
                    $query['select'][$this->class->discriminatorField] = 1;
423
                }
424
            }
425
        }
426
427 202
        if (isset($query['sort'])) {
428 28
            $query['sort'] = $documentPersister->prepareSortOrProjection($query['sort']);
429
        }
430
431 202
        if ($this->class->slaveOkay) {
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ODM\MongoDB\Map...etadataInfo::$slaveOkay has been deprecated with message: in version 1.2 and will be removed in 2.0.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
432 1
            $query['slaveOkay'] = $this->class->slaveOkay;
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ODM\MongoDB\Map...etadataInfo::$slaveOkay has been deprecated with message: in version 1.2 and will be removed in 2.0.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
433
        }
434
435 202
        if ($this->class->readPreference && ! array_key_exists('readPreference', $query)) {
436 1
            $query['readPreference'] = $this->class->readPreference;
437 1
            $query['readPreferenceTags'] = $this->class->readPreferenceTags;
438
        }
439
440 202
        return new Query(
441 202
            $this->dm,
442 202
            $this->class,
443 202
            $this->collection,
444 202
            $query,
445 202
            $options,
446 202
            $this->hydrate,
447 202
            $this->refresh,
448 202
            $this->primers,
449 202
            $this->requireIndexes,
450 202
            $this->readOnly
451
        );
452
    }
453
454
    /**
455
     * Create a new Expr instance that can be used as an expression with the Builder
456
     *
457
     * @return Expr $expr
458
     */
459 25
    public function expr()
460
    {
461 25
        $expr = new Expr($this->dm);
462 25
        $expr->setClassMetadata($this->class);
463
464 25
        return $expr;
465
    }
466
467
    /**
468
     * @param string[]|string $documentName an array of document names or just one.
469
     */
470 233
    private function setDocumentName($documentName)
471
    {
472 233
        if (is_array($documentName)) {
473 2
            $documentNames = $documentName;
474 2
            $documentName = $documentNames[0];
475
476 2
            $metadata = $this->dm->getClassMetadata($documentName);
477 2
            $discriminatorField = $metadata->discriminatorField;
478 2
            $discriminatorValues = $this->getDiscriminatorValues($documentNames);
479
480
            // If a defaultDiscriminatorValue is set and it is among the discriminators being queries, add NULL to the list
481 1 View Code Duplication
            if ($metadata->defaultDiscriminatorValue && array_search($metadata->defaultDiscriminatorValue, $discriminatorValues) !== false) {
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...
482 1
                $discriminatorValues[] = null;
483
            }
484
485 1
            $this->field($discriminatorField)->in($discriminatorValues);
486
        }
487
488 232
        if ($documentName !== null) {
489 232
            $this->collection = $this->dm->getDocumentCollection($documentName);
490 232
            $this->class = $this->dm->getClassMetadata($documentName);
491
492
            // Expr also needs to know
493 232
            $this->expr->setClassMetadata($this->class);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Doctrine\MongoDB\Query\Expr as the method setClassMetadata() does only exist in the following sub-classes of Doctrine\MongoDB\Query\Expr: Doctrine\ODM\MongoDB\Query\Expr. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
494
        }
495 232
    }
496
497
    /**
498
     * Get Discriminator Values
499
     *
500
     * @param \Iterator|array $classNames
501
     * @return array an array of discriminatorValues (mixed type)
502
     * @throws \InvalidArgumentException if the number of found collections > 1
503
     */
504 2
    private function getDiscriminatorValues($classNames)
505
    {
506 2
        $discriminatorValues = array();
507 2
        $collections = array();
508 2
        foreach ($classNames as $className) {
509 2
            $class = $this->dm->getClassMetadata($className);
510 2
            $discriminatorValues[] = $class->discriminatorValue;
511 2
            $key = $this->dm->getDocumentDatabase($className)->getName() . '.' . $class->getCollection();
512 2
            $collections[$key] = $key;
513
        }
514 2
        if (count($collections) > 1) {
515 1
            throw new \InvalidArgumentException('Documents involved are not all mapped to the same database collection.');
516
        }
517 1
        return $discriminatorValues;
518
    }
519
}
520