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

Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php (2 issues)

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) {
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);
0 ignored issues
show
Accessing name on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
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);
0 ignored issues
show
Accessing name on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
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