Completed
Push — master ( 4983dc...87af93 )
by Andreas
24:59 queued 24:52
created

GraphLookup::fromReference()   B

Complexity

Conditions 5
Paths 10

Size

Total Lines 33
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 33
ccs 0
cts 26
cp 0
rs 8.439
cc 5
eloc 20
nc 10
nop 1
crap 30
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\Aggregation\Stage;
21
22
use Doctrine\Common\Persistence\Mapping\MappingException as BaseMappingException;
23
use Doctrine\MongoDB\Aggregation\Stage as BaseStage;
24
use Doctrine\ODM\MongoDB\Aggregation\Builder;
25
use Doctrine\ODM\MongoDB\DocumentManager;
26
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
27
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
28
use Doctrine\ODM\MongoDB\Mapping\MappingException;
29
use Doctrine\ODM\MongoDB\Types\Type;
30
31
class GraphLookup extends BaseStage\GraphLookup
32
{
33
    /**
34
     * @var DocumentManager
35
     */
36
    private $dm;
37
38
    /**
39
     * @var ClassMetadata
40
     */
41
    private $class;
42
43
    /**
44
     * @var ClassMetadata
45
     */
46
    private $targetClass;
47
48
    /**
49
     * @param Builder $builder
50
     * @param string $from Target collection for the $graphLookup operation to
51
     * search, recursively matching the connectFromField to the connectToField.
52
     * @param DocumentManager $documentManager
53
     * @param ClassMetadata $class
54
     */
55
    public function __construct(Builder $builder, $from, DocumentManager $documentManager, ClassMetadata $class)
56
    {
57
        $this->dm = $documentManager;
58
        $this->class = $class;
59
60
        parent::__construct($builder, $from);
61
    }
62
63
    /**
64
     * @param string $from
65
     * @return $this
66
     */
67 View Code Duplication
    public function from($from)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
68
    {
69
        // $from can either be
70
        // a) a field name indicating a reference to a different document. Currently, only REFERENCE_STORE_AS_ID is supported
71
        // b) a Class name
72
        // c) a collection name
73
        // In cases b) and c) the local and foreign fields need to be filled
74
        if ($this->class->hasReference($from)) {
75
            return $this->fromReference($from);
76
        }
77
78
        // Check if mapped class with given name exists
79
        try {
80
            $this->targetClass = $this->dm->getClassMetadata($from);
81
        } catch (BaseMappingException $e) {
82
            return parent::from($from);
83
        }
84
85
        if ($this->targetClass->isSharded()) {
86
            throw MappingException::cannotUseShardedCollectionInLookupStages($this->targetClass->name);
87
        }
88
89
        return parent::from($this->targetClass->getCollection());
90
    }
91
92
    public function connectFromField($connectFromField)
93
    {
94
        // No targetClass mapping - simply use field name as is
95
        if ( ! $this->targetClass) {
96
            return parent::connectFromField($connectFromField);
97
        }
98
99
        // connectFromField doesn't have to be a reference - in this case, just convert the field name
100
        if ( ! $this->targetClass->hasReference($connectFromField)) {
101
            return parent::connectFromField($this->convertTargetFieldName($connectFromField));
102
        }
103
104
        // connectFromField is a reference - do a sanity check
105
        $referenceMapping = $this->targetClass->getFieldMapping($connectFromField);
106
        if ($referenceMapping['targetDocument'] !== $this->targetClass->name) {
107
            throw MappingException::connectFromFieldMustReferenceSameDocument($connectFromField);
108
        }
109
110
        return parent::connectFromField($this->getReferencedFieldName($connectFromField, $referenceMapping));
111
    }
112
113
    public function connectToField($connectToField)
114
    {
115
        return parent::connectToField($this->convertTargetFieldName($connectToField));
116
    }
117
118
    /**
119
     * @param string $fieldName
120
     * @return $this
121
     * @throws MappingException
122
     */
123
    private function fromReference($fieldName)
124
    {
125
        if ( ! $this->class->hasReference($fieldName)) {
126
            MappingException::referenceMappingNotFound($this->class->name, $fieldName);
127
        }
128
129
        $referenceMapping = $this->class->getFieldMapping($fieldName);
130
        $this->targetClass = $this->dm->getClassMetadata($referenceMapping['targetDocument']);
131
        if ($this->targetClass->isSharded()) {
132
            throw MappingException::cannotUseShardedCollectionInLookupStages($this->targetClass->name);
133
        }
134
135
        parent::from($this->targetClass->getCollection());
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (from() instead of fromReference()). Are you sure this is correct? If so, you might want to change this to $this->from().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
136
137
        $referencedFieldName = $this->getReferencedFieldName($fieldName, $referenceMapping);
138
139
        if ($referenceMapping['isOwningSide']) {
140
            $this
141
                ->startWith('$' . $referencedFieldName)
142
                ->connectToField('_id');
143
        } else {
144
            $this
145
                ->startWith('$' . $referencedFieldName)
146
                ->connectToField('_id');
147
        }
148
149
        // A self-reference indicates that we can also fill the "connectFromField" accordingly
150
        if ($this->targetClass->name === $this->class->name) {
151
            $this->connectFromField($referencedFieldName);
152
        }
153
154
        return $this;
155
    }
156
157
    protected function convertExpression($expression)
158
    {
159
        if (is_array($expression)) {
160
            return array_map([$this, 'convertExpression'], $expression);
161
        } elseif (is_string($expression) && substr($expression, 0, 1) === '$') {
162
            return '$' . $this->getDocumentPersister($this->class)->prepareFieldName(substr($expression, 1));
163
        } else {
164
            return Type::convertPHPToDatabaseValue(parent::convertExpression($expression));
165
        }
166
    }
167
168
    protected function convertTargetFieldName($fieldName)
169
    {
170
        if (is_array($fieldName)) {
171
            return array_map([$this, 'convertTargetFieldName'], $fieldName);
172
        }
173
174
        if ( ! $this->targetClass) {
175
            return $fieldName;
176
        }
177
178
        return $this->getDocumentPersister($this->targetClass)->prepareFieldName($fieldName);
179
    }
180
181
    /**
182
     * @param ClassMetadata $class
183
     * @return \Doctrine\ODM\MongoDB\Persisters\DocumentPersister
184
     */
185
    private function getDocumentPersister(ClassMetadata $class)
186
    {
187
        return $this->dm->getUnitOfWork()->getDocumentPersister($class->name);
188
    }
189
190
    private function getReferencedFieldName($fieldName, array $mapping)
191
    {
192
        if ( ! $mapping['isOwningSide']) {
193 View Code Duplication
            if (isset($mapping['repositoryMethod']) || ! isset($mapping['mappedBy'])) {
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...
194
                throw MappingException::repositoryMethodLookupNotAllowed($this->class->name, $fieldName);
195
            }
196
197
            $mapping = $this->targetClass->getFieldMapping($mapping['mappedBy']);
198
        }
199
200 View Code Duplication
        switch ($mapping['storeAs']) {
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...
201
            case ClassMetadataInfo::REFERENCE_STORE_AS_ID:
202
            case ClassMetadataInfo::REFERENCE_STORE_AS_REF:
203
                return ClassMetadataInfo::getReferenceFieldName($mapping['storeAs'], $mapping['name']);
204
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
205
206
            default:
207
                throw MappingException::cannotLookupDbRefReference($this->class->name, $fieldName);
208
        }
209
    }
210
}
211