Completed
Pull Request — master (#1654)
by Andreas
09:45
created

GraphLookup::connectFromField()   C

Complexity

Conditions 10
Paths 10

Size

Total Lines 51
Code Lines 30

Duplication

Lines 18
Ratio 35.29 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 18
loc 51
ccs 0
cts 39
cp 0
rs 6
cc 10
eloc 30
nc 10
nop 1
crap 110

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
        if ($referenceMapping['isOwningSide']) {
111
            switch ($referenceMapping['storeAs']) {
112
                case ClassMetadataInfo::REFERENCE_STORE_AS_ID:
113
                case ClassMetadataInfo::REFERENCE_STORE_AS_REF:
114
                    $referencedFieldName = ClassMetadataInfo::getReferenceFieldName($referenceMapping['storeAs'], $referenceMapping['name']);
115
                    break;
116
117
                default:
118
                    throw MappingException::cannotLookupDbRefReference($this->class->name, $connectFromField);
119
            }
120
121
            parent::connectFromField($referencedFieldName);
122 View Code Duplication
        } else {
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...
123
            if (isset($referenceMapping['repositoryMethod'])) {
124
                throw MappingException::repositoryMethodLookupNotAllowed($this->class->name, $connectFromField);
125
            }
126
127
            $mappedByMapping = $this->targetClass->getFieldMapping($referenceMapping['mappedBy']);
128
            switch ($mappedByMapping['storeAs']) {
129
                case ClassMetadataInfo::REFERENCE_STORE_AS_ID:
130
                case ClassMetadataInfo::REFERENCE_STORE_AS_REF:
131
                    $referencedFieldName = ClassMetadataInfo::getReferenceFieldName($mappedByMapping['storeAs'], $mappedByMapping['name']);
132
                    break;
133
134
                default:
135
                    throw MappingException::cannotLookupDbRefReference($this->class->name, $connectFromField);
136
            }
137
138
            parent::connectFromField($referencedFieldName);
139
        }
140
141
        return $this;
142
    }
143
144
    public function connectToField($connectToField)
145
    {
146
        return parent::connectToField($this->convertTargetFieldName($connectToField));
147
    }
148
149
    /**
150
     * @param string $fieldName
151
     * @return $this
152
     * @throws MappingException
153
     */
154
    private function fromReference($fieldName)
155
    {
156
        if (! $this->class->hasReference($fieldName)) {
157
            MappingException::referenceMappingNotFound($this->class->name, $fieldName);
158
        }
159
160
        $referenceMapping = $this->class->getFieldMapping($fieldName);
161
        $this->targetClass = $this->dm->getClassMetadata($referenceMapping['targetDocument']);
162
        if ($this->targetClass->isSharded()) {
163
            throw MappingException::cannotUseShardedCollectionInLookupStages($this->targetClass->name);
164
        }
165
166
        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...
167
168
        if ($referenceMapping['isOwningSide']) {
169
            switch ($referenceMapping['storeAs']) {
170
                case ClassMetadataInfo::REFERENCE_STORE_AS_ID:
171
                case ClassMetadataInfo::REFERENCE_STORE_AS_REF:
172
                    $referencedFieldName = ClassMetadataInfo::getReferenceFieldName($referenceMapping['storeAs'], $referenceMapping['name']);
173
                    break;
174
175
                default:
176
                    throw MappingException::cannotLookupDbRefReference($this->class->name, $fieldName);
177
            }
178
179
            $this
180
                ->startWith('$' . $referencedFieldName)
181
                ->connectToField('_id');
182
183
            // A self-reference indicates that we can also fill the "connectFromField" accordingly
184
            if ($this->targetClass->name === $this->class->name) {
185
                $this->connectFromField($referencedFieldName);
186
            }
187
        } else {
188
            if (isset($referenceMapping['repositoryMethod'])) {
189
                throw MappingException::repositoryMethodLookupNotAllowed($this->class->name, $fieldName);
190
            }
191
192
            $mappedByMapping = $this->targetClass->getFieldMapping($referenceMapping['mappedBy']);
193
            switch ($mappedByMapping['storeAs']) {
194
                case ClassMetadataInfo::REFERENCE_STORE_AS_ID:
195
                case ClassMetadataInfo::REFERENCE_STORE_AS_REF:
196
                    $referencedFieldName = ClassMetadataInfo::getReferenceFieldName($mappedByMapping['storeAs'], $mappedByMapping['name']);
197
                    break;
198
199
                default:
200
                    throw MappingException::cannotLookupDbRefReference($this->class->name, $fieldName);
201
            }
202
203
            $this
204
                ->startWith('$' . $referencedFieldName)
205
                ->connectToField('_id');
206
207
            // A self-reference indicates that we can also fill the "connectFromField" accordingly
208
            if ($this->targetClass->name === $this->class->name) {
209
                $this->connectFromField($referencedFieldName);
210
            }
211
        }
212
213
        return $this;
214
    }
215
216
    protected function convertExpression($expression)
217
    {
218
        if (is_array($expression)) {
219
            return array_map([$this, 'convertExpression'], $expression);
220
        } elseif (is_string($expression) && substr($expression, 0, 1) === '$') {
221
            return '$' . $this->getDocumentPersister($this->class)->prepareFieldName(substr($expression, 1));
222
        } else {
223
            return Type::convertPHPToDatabaseValue(parent::convertExpression($expression));
224
        }
225
    }
226
227
    protected function convertTargetFieldName($fieldName)
228
    {
229
        if (is_array($fieldName)) {
230
            return array_map([$this, 'convertTargetFieldName'], $fieldName);
231
        }
232
233
        if (!$this->targetClass) {
234
            return $fieldName;
235
        }
236
237
        return $this->getDocumentPersister($this->targetClass)->prepareFieldName($fieldName);
238
    }
239
240
    /**
241
     * @param ClassMetadata $class
242
     * @return \Doctrine\ODM\MongoDB\Persisters\DocumentPersister
243
     */
244
    private function getDocumentPersister(ClassMetadata $class)
245
    {
246
        return $this->dm->getUnitOfWork()->getDocumentPersister($class->name);
247
    }
248
}
249