Completed
Pull Request — master (#1620)
by Maciej
24:08
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%

Importance

Changes 0
Metric Value
wmc 56
lcom 1
cbo 9
dl 3
loc 489
ccs 152
cts 160
cp 0.95
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 232
    public function __construct(DocumentManager $dm, $documentName = null)
97
    {
98 232
        $this->dm = $dm;
99 232
        $this->expr = new Expr($dm);
100 232
        if ($documentName !== null) {
101 224
            $this->setDocumentName($documentName);
102
        }
103 231
    }
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 3
            @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 3
                sprintf('%s was deprecated in version 1.2 - use setReadPreference instead.', __METHOD__),
377
                E_USER_DEPRECATED
378 3
            );
379
        }
380
381
        return parent::slaveOkay($bool);
382
    }
383
384
    /**
385
     * Gets the Query executable.
386
     *
387 201
     * @param array $options
388
     * @return Query $query
389 201
     */
390 3
    public function getQuery(array $options = array())
391
    {
392
        if ($this->query['type'] === Query::TYPE_MAP_REDUCE) {
393 201
            $this->hydrate = false;
394
        }
395 201
396
        $documentPersister = $this->dm->getUnitOfWork()->getDocumentPersister($this->class->name);
397 201
398 201
        $query = $this->query;
399 201
400
        $query['query'] = $this->expr->getQuery();
401 201
        $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
        $query['query'] = $documentPersister->addFilterToPreparedQuery($query['query']);
403 201
404 2
        $query['newObj'] = $this->expr->getNewObj();
405
406
        if (isset($query['distinct'])) {
407 201
            $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 201
        }
409 1
410 1
        if ($this->class->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION && ! empty($query['upsert']) &&
411
            (empty($query['query'][$this->class->discriminatorField]) || is_array($query['query'][$this->class->discriminatorField]))) {
412
            throw new \InvalidArgumentException('Upsert query that is to be performed on discriminated document does not have single ' .
413 200
                'discriminator. Either not use base class or set \'' . $this->class->discriminatorField . '\' field manually.');
414 14
        }
415 14
416 14
        if ( ! empty($query['select'])) {
417
            $query['select'] = $documentPersister->prepareSortOrProjection($query['select']);
418 2
            if ($this->hydrate && $this->class->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION
419 1
                && ! isset($query['select'][$this->class->discriminatorField])) {
420
                $includeMode = 0 < count(array_filter($query['select'], function($mode) { return $mode == 1; }));
421
                if ($includeMode && ! isset($query['select'][$this->class->discriminatorField])) {
422
                    $query['select'][$this->class->discriminatorField] = 1;
423
                }
424 200
            }
425 28
        }
426
427
        if (isset($query['sort'])) {
428 200
            $query['sort'] = $documentPersister->prepareSortOrProjection($query['sort']);
429 1
        }
430
431
        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 200
            $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 200
        }
434 200
435 200
        if ($this->class->readPreference && ! array_key_exists('readPreference', $query)) {
436 200
            $query['readPreference'] = $this->class->readPreference;
437 200
            $query['readPreferenceTags'] = $this->class->readPreferenceTags;
438 200
        }
439 200
440 200
        return new Query(
441 200
            $this->dm,
442 200
            $this->class,
443
            $this->collection,
444
            $query,
445
            $options,
446
            $this->hydrate,
447
            $this->refresh,
448
            $this->primers,
449
            $this->requireIndexes,
450
            $this->readOnly
451 25
        );
452
    }
453 25
454 25
    /**
455
     * Create a new Expr instance that can be used as an expression with the Builder
456 25
     *
457
     * @return Expr $expr
458
     */
459
    public function expr()
460
    {
461
        $expr = new Expr($this->dm);
462 231
        $expr->setClassMetadata($this->class);
463
464 231
        return $expr;
465 2
    }
466 2
467
    /**
468 2
     * @param string[]|string $documentName an array of document names or just one.
469 2
     */
470 2
    private function setDocumentName($documentName)
471
    {
472
        if (is_array($documentName)) {
473 1
            $documentNames = $documentName;
474 1
            $documentName = $documentNames[0];
475
476
            $metadata = $this->dm->getClassMetadata($documentName);
477 1
            $discriminatorField = $metadata->discriminatorField;
478
            $discriminatorValues = $this->getDiscriminatorValues($documentNames);
479
480 230
            // If a defaultDiscriminatorValue is set and it is among the discriminators being queries, add NULL to the list
481 230 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 230
                $discriminatorValues[] = null;
483
            }
484
485 230
            $this->field($discriminatorField)->in($discriminatorValues);
486
        }
487 230
488
        if ($documentName !== null) {
489
            $this->collection = $this->dm->getDocumentCollection($documentName);
490
            $this->class = $this->dm->getClassMetadata($documentName);
491
492
            // Expr also needs to know
493
            $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
    }
496 2
497
    /**
498 2
     * Get Discriminator Values
499 2
     *
500 2
     * @param \Iterator|array $classNames
501 2
     * @return array an array of discriminatorValues (mixed type)
502 2
     * @throws \InvalidArgumentException if the number of found collections > 1
503 2
     */
504 2
    private function getDiscriminatorValues($classNames)
505
    {
506 2
        $discriminatorValues = array();
507 1
        $collections = array();
508
        foreach ($classNames as $className) {
509 1
            $class = $this->dm->getClassMetadata($className);
510
            $discriminatorValues[] = $class->discriminatorValue;
511
            $key = $this->dm->getDocumentDatabase($className)->getName() . '.' . $class->getCollection();
512
            $collections[$key] = $key;
513
        }
514
        if (count($collections) > 1) {
515
            throw new \InvalidArgumentException('Documents involved are not all mapped to the same database collection.');
516
        }
517
        return $discriminatorValues;
518
    }
519
}
520