Completed
Push — master ( 7b9f4b...1cd743 )
by Andreas
13s queued 10s
created

Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php (1 issue)

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
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB\Aggregation\Stage;
6
7
use Doctrine\Common\Persistence\Mapping\MappingException as BaseMappingException;
8
use Doctrine\ODM\MongoDB\Aggregation\Builder;
9
use Doctrine\ODM\MongoDB\Aggregation\Stage;
10
use Doctrine\ODM\MongoDB\DocumentManager;
11
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
12
use Doctrine\ODM\MongoDB\Mapping\MappingException;
13
use Doctrine\ODM\MongoDB\Persisters\DocumentPersister;
14
15
/**
16
 * Fluent interface for building aggregation pipelines.
17
 */
18
class Lookup extends Stage
19
{
20
    /** @var DocumentManager */
21
    private $dm;
22
23
    /** @var ClassMetadata */
24
    private $class;
25
26
    /** @var ClassMetadata */
27
    private $targetClass;
28
29
    /** @var string */
30
    private $from;
31
32
    /** @var string */
33
    private $localField;
34
35
    /** @var string */
36
    private $foreignField;
37
38
    /** @var string */
39
    private $as;
40
41 14
    public function __construct(Builder $builder, string $from, DocumentManager $documentManager, ClassMetadata $class)
42
    {
43 14
        parent::__construct($builder);
44
45 14
        $this->dm    = $documentManager;
46 14
        $this->class = $class;
47
48 14
        $this->from($from);
49 12
    }
50
51
    /**
52
     * Specifies the name of the new array field to add to the input documents.
53
     *
54
     * The new array field contains the matching documents from the from
55
     * collection. If the specified name already exists in the input document,
56
     * the existing field is overwritten.
57
     */
58 12
    public function alias(string $alias) : self
59
    {
60 12
        $this->as = $alias;
61
62 12
        return $this;
63
    }
64
65
    /**
66
     * Specifies the collection or field name in the same database to perform the join with.
67
     *
68
     * The from collection cannot be sharded.
69
     */
70 14
    public function from(string $from) : self
71
    {
72
        // $from can either be
73
        // a) a field name indicating a reference to a different document. Currently, only REFERENCE_STORE_AS_ID is supported
74
        // b) a Class name
75
        // c) a collection name
76
        // In cases b) and c) the local and foreign fields need to be filled
77 14
        if ($this->class->hasReference($from)) {
78 9
            return $this->fromReference($from);
79
        }
80
81
        // Check if mapped class with given name exists
82
        try {
83 5
            $this->targetClass = $this->dm->getClassMetadata($from);
84 2
        } catch (BaseMappingException $e) {
0 ignored issues
show
The class Doctrine\Common\Persiste...apping\MappingException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
85 2
            $this->from = $from;
86 2
            return $this;
87
        }
88
89 3
        if ($this->targetClass->isSharded()) {
90 1
            throw MappingException::cannotUseShardedCollectionInLookupStages($this->targetClass->name);
91
        }
92
93 2
        $this->from = $this->targetClass->getCollection();
94 2
        return $this;
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100 12
    public function getExpression() : array
101
    {
102
        return [
103
            '$lookup' => [
104 12
                'from' => $this->from,
105 12
                'localField' => $this->localField,
106 12
                'foreignField' => $this->foreignField,
107 12
                'as' => $this->as,
108
            ],
109
        ];
110
    }
111
112
    /**
113
     * Specifies the field from the documents input to the $lookup stage.
114
     *
115
     * $lookup performs an equality match on the localField to the foreignField
116
     * from the documents of the from collection. If an input document does not
117
     * contain the localField, the $lookup treats the field as having a value of
118
     * null for matching purposes.
119
     */
120 12
    public function localField(string $localField) : self
121
    {
122 12
        $this->localField = $this->prepareFieldName($localField, $this->class);
123 12
        return $this;
124
    }
125
126
    /**
127
     * Specifies the field from the documents in the from collection.
128
     *
129
     * $lookup performs an equality match on the foreignField to the localField
130
     * from the input documents. If a document in the from collection does not
131
     * contain the foreignField, the $lookup treats the value as null for
132
     * matching purposes.
133
     */
134 12
    public function foreignField(string $foreignField) : self
135
    {
136 12
        $this->foreignField = $this->prepareFieldName($foreignField, $this->targetClass);
137 12
        return $this;
138
    }
139
140 12
    protected function prepareFieldName(string $fieldName, ?ClassMetadata $class = null) : string
141
    {
142 12
        if (! $class) {
143 2
            return $fieldName;
144
        }
145
146 12
        return $this->getDocumentPersister($class)->prepareFieldName($fieldName);
147
    }
148
149
    /**
150
     * @throws MappingException
151
     */
152 9
    private function fromReference(string $fieldName) : self
153
    {
154 9
        if (! $this->class->hasReference($fieldName)) {
155
            MappingException::referenceMappingNotFound($this->class->name, $fieldName);
156
        }
157
158 9
        $referenceMapping  = $this->class->getFieldMapping($fieldName);
159 9
        $this->targetClass = $this->dm->getClassMetadata($referenceMapping['targetDocument']);
160 9
        if ($this->targetClass->isSharded()) {
161 1
            throw MappingException::cannotUseShardedCollectionInLookupStages($this->targetClass->name);
162
        }
163
164 8
        $this->from = $this->targetClass->getCollection();
165
166 8
        if ($referenceMapping['isOwningSide']) {
167 4
            switch ($referenceMapping['storeAs']) {
168
                case ClassMetadata::REFERENCE_STORE_AS_ID:
169
                case ClassMetadata::REFERENCE_STORE_AS_REF:
170 4
                    $referencedFieldName = ClassMetadata::getReferenceFieldName($referenceMapping['storeAs'], $referenceMapping['name']);
171 4
                    break;
172
173
                default:
174
                    throw MappingException::cannotLookupDbRefReference($this->class->name, $fieldName);
175
            }
176
177
            $this
178 4
                ->foreignField('_id')
179 4
                ->localField($referencedFieldName);
180
        } else {
181 4
            if (isset($referenceMapping['repositoryMethod']) || ! isset($referenceMapping['mappedBy'])) {
182
                throw MappingException::repositoryMethodLookupNotAllowed($this->class->name, $fieldName);
183
            }
184
185 4
            $mappedByMapping = $this->targetClass->getFieldMapping($referenceMapping['mappedBy']);
186 4
            switch ($mappedByMapping['storeAs']) {
187
                case ClassMetadata::REFERENCE_STORE_AS_ID:
188
                case ClassMetadata::REFERENCE_STORE_AS_REF:
189 4
                    $referencedFieldName = ClassMetadata::getReferenceFieldName($mappedByMapping['storeAs'], $mappedByMapping['name']);
190 4
                    break;
191
192
                default:
193
                    throw MappingException::cannotLookupDbRefReference($this->class->name, $fieldName);
194
            }
195
196
            $this
197 4
                ->localField('_id')
198 4
                ->foreignField($referencedFieldName);
199
        }
200
201 8
        return $this;
202
    }
203
204 12
    private function getDocumentPersister(ClassMetadata $class) : DocumentPersister
205
    {
206 12
        return $this->dm->getUnitOfWork()->getDocumentPersister($class->name);
207
    }
208
}
209