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\Persisters; |
||
6 | |||
7 | use Doctrine\ODM\MongoDB\DocumentManager; |
||
8 | use Doctrine\ODM\MongoDB\LockException; |
||
9 | use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; |
||
10 | use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionInterface; |
||
11 | use Doctrine\ODM\MongoDB\UnitOfWork; |
||
12 | use Doctrine\ODM\MongoDB\Utility\CollectionHelper; |
||
13 | use LogicException; |
||
14 | use UnexpectedValueException; |
||
15 | use function array_map; |
||
16 | use function array_reverse; |
||
17 | use function array_values; |
||
18 | use function get_class; |
||
19 | use function implode; |
||
20 | use function sprintf; |
||
21 | |||
22 | /** |
||
23 | * The CollectionPersister is responsible for persisting collections of embedded |
||
24 | * or referenced documents. When a PersistentCollection is scheduledForDeletion |
||
25 | * in the UnitOfWork by calling PersistentCollection::clear() or is |
||
26 | * de-referenced in the domain application code, CollectionPersister::delete() |
||
27 | * will be called. When documents within the PersistentCollection are added or |
||
28 | * removed, CollectionPersister::update() will be called, which may set the |
||
29 | * entire collection or delete/insert individual elements, depending on the |
||
30 | * mapping strategy. |
||
31 | */ |
||
32 | class CollectionPersister |
||
33 | { |
||
34 | /** @var DocumentManager */ |
||
35 | private $dm; |
||
36 | |||
37 | /** @var PersistenceBuilder */ |
||
38 | private $pb; |
||
39 | |||
40 | /** @var UnitOfWork */ |
||
41 | private $uow; |
||
42 | |||
43 | 1121 | public function __construct(DocumentManager $dm, PersistenceBuilder $pb, UnitOfWork $uow) |
|
44 | { |
||
45 | 1121 | $this->dm = $dm; |
|
46 | 1121 | $this->pb = $pb; |
|
47 | 1121 | $this->uow = $uow; |
|
48 | 1121 | } |
|
49 | |||
50 | /** |
||
51 | * Deletes a PersistentCollection instance completely from a document using $unset. |
||
52 | */ |
||
53 | 35 | public function delete(PersistentCollectionInterface $coll, array $options) : void |
|
54 | { |
||
55 | 35 | $mapping = $coll->getMapping(); |
|
56 | 35 | if ($mapping['isInverseSide']) { |
|
57 | return; // ignore inverse side |
||
58 | } |
||
59 | 35 | if (CollectionHelper::isAtomic($mapping['strategy'])) { |
|
60 | throw new UnexpectedValueException($mapping['strategy'] . ' delete collection strategy should have been handled by DocumentPersister. Please report a bug in issue tracker'); |
||
61 | } |
||
62 | 35 | [$propertyPath, $parent] = $this->getPathAndParent($coll); |
|
0 ignored issues
–
show
|
|||
63 | 35 | $query = ['$unset' => [$propertyPath => true]]; |
|
64 | 35 | $this->executeQuery($parent, $query, $options); |
|
65 | 34 | } |
|
66 | |||
67 | /** |
||
68 | * Updates a PersistentCollection instance deleting removed rows and |
||
69 | * inserting new rows. |
||
70 | */ |
||
71 | 100 | public function update(PersistentCollectionInterface $coll, array $options) : void |
|
72 | { |
||
73 | 100 | $mapping = $coll->getMapping(); |
|
74 | |||
75 | 100 | if ($mapping['isInverseSide']) { |
|
76 | return; // ignore inverse side |
||
77 | } |
||
78 | |||
79 | 100 | switch ($mapping['strategy']) { |
|
80 | case ClassMetadata::STORAGE_STRATEGY_ATOMIC_SET: |
||
81 | case ClassMetadata::STORAGE_STRATEGY_ATOMIC_SET_ARRAY: |
||
82 | throw new UnexpectedValueException($mapping['strategy'] . ' update collection strategy should have been handled by DocumentPersister. Please report a bug in issue tracker'); |
||
83 | |||
84 | case ClassMetadata::STORAGE_STRATEGY_SET: |
||
85 | case ClassMetadata::STORAGE_STRATEGY_SET_ARRAY: |
||
86 | 10 | $this->setCollection($coll, $options); |
|
87 | 10 | break; |
|
88 | |||
89 | case ClassMetadata::STORAGE_STRATEGY_ADD_TO_SET: |
||
90 | case ClassMetadata::STORAGE_STRATEGY_PUSH_ALL: |
||
91 | 91 | $coll->initialize(); |
|
92 | 91 | $this->deleteElements($coll, $options); |
|
93 | 91 | $this->insertElements($coll, $options); |
|
94 | 90 | break; |
|
95 | |||
96 | default: |
||
97 | throw new UnexpectedValueException('Unsupported collection strategy: ' . $mapping['strategy']); |
||
98 | } |
||
99 | 99 | } |
|
100 | |||
101 | /** |
||
102 | * Sets a PersistentCollection instance. |
||
103 | * |
||
104 | * This method is intended to be used with the "set" or "setArray" |
||
105 | * strategies. The "setArray" strategy will ensure that the collection is |
||
106 | * set as a BSON array, which means the collection elements will be |
||
107 | * reindexed numerically before storage. |
||
108 | */ |
||
109 | 10 | private function setCollection(PersistentCollectionInterface $coll, array $options) : void |
|
110 | { |
||
111 | 10 | [$propertyPath, $parent] = $this->getPathAndParent($coll); |
|
0 ignored issues
–
show
The variable
$propertyPath does not exist. Did you forget to declare it?
This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.
Loading history...
|
|||
112 | 10 | $coll->initialize(); |
|
113 | 10 | $mapping = $coll->getMapping(); |
|
114 | 10 | $setData = $this->pb->prepareAssociatedCollectionValue($coll, CollectionHelper::usesSet($mapping['strategy'])); |
|
115 | 10 | $query = ['$set' => [$propertyPath => $setData]]; |
|
116 | 10 | $this->executeQuery($parent, $query, $options); |
|
117 | 10 | } |
|
118 | |||
119 | /** |
||
120 | * Deletes removed elements from a PersistentCollection instance. |
||
121 | * |
||
122 | * This method is intended to be used with the "pushAll" and "addToSet" |
||
123 | * strategies. |
||
124 | */ |
||
125 | 91 | private function deleteElements(PersistentCollectionInterface $coll, array $options) : void |
|
126 | { |
||
127 | 91 | $deleteDiff = $coll->getDeleteDiff(); |
|
128 | |||
129 | 91 | if (empty($deleteDiff)) { |
|
130 | 71 | return; |
|
131 | } |
||
132 | |||
133 | 29 | [$propertyPath, $parent] = $this->getPathAndParent($coll); |
|
0 ignored issues
–
show
The variable
$propertyPath does not exist. Did you forget to declare it?
This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.
Loading history...
|
|||
134 | |||
135 | 29 | $query = ['$unset' => []]; |
|
136 | |||
137 | 29 | foreach ($deleteDiff as $key => $document) { |
|
138 | 29 | $query['$unset'][$propertyPath . '.' . $key] = true; |
|
139 | } |
||
140 | |||
141 | 29 | $this->executeQuery($parent, $query, $options); |
|
142 | |||
143 | /** |
||
144 | * @todo This is a hack right now because we don't have a proper way to |
||
145 | * remove an element from an array by its key. Unsetting the key results |
||
146 | * in the element being left in the array as null so we have to pull |
||
147 | * null values. |
||
148 | */ |
||
149 | 29 | $this->executeQuery($parent, ['$pull' => [$propertyPath => null]], $options); |
|
150 | 29 | } |
|
151 | |||
152 | /** |
||
153 | * Inserts new elements for a PersistentCollection instance. |
||
154 | * |
||
155 | * This method is intended to be used with the "pushAll" and "addToSet" |
||
156 | * strategies. |
||
157 | */ |
||
158 | 91 | private function insertElements(PersistentCollectionInterface $coll, array $options) : void |
|
159 | { |
||
160 | 91 | $insertDiff = $coll->getInsertDiff(); |
|
161 | |||
162 | 91 | if (empty($insertDiff)) { |
|
163 | 21 | return; |
|
164 | } |
||
165 | |||
166 | 76 | $mapping = $coll->getMapping(); |
|
167 | |||
168 | 76 | switch ($mapping['strategy']) { |
|
169 | case ClassMetadata::STORAGE_STRATEGY_PUSH_ALL: |
||
170 | 72 | $operator = 'push'; |
|
171 | 72 | break; |
|
172 | |||
173 | case ClassMetadata::STORAGE_STRATEGY_ADD_TO_SET: |
||
174 | 6 | $operator = 'addToSet'; |
|
175 | 6 | break; |
|
176 | |||
177 | default: |
||
178 | throw new LogicException(sprintf('Invalid strategy %s given for insertElements', $mapping['strategy'])); |
||
179 | } |
||
180 | |||
181 | 76 | [$propertyPath, $parent] = $this->getPathAndParent($coll); |
|
0 ignored issues
–
show
The variable
$propertyPath does not exist. Did you forget to declare it?
This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.
Loading history...
|
|||
182 | |||
183 | 76 | $callback = isset($mapping['embedded']) |
|
184 | ? function ($v) use ($mapping) { |
||
185 | 40 | return $this->pb->prepareEmbeddedDocumentValue($mapping, $v); |
|
186 | 40 | } |
|
187 | : function ($v) use ($mapping) { |
||
188 | 37 | return $this->pb->prepareReferencedDocumentValue($mapping, $v); |
|
189 | 76 | }; |
|
190 | |||
191 | 76 | $value = array_values(array_map($callback, $insertDiff)); |
|
192 | |||
193 | 76 | $query = ['$' . $operator => [$propertyPath => ['$each' => $value]]]; |
|
194 | |||
195 | 76 | $this->executeQuery($parent, $query, $options); |
|
196 | 75 | } |
|
197 | |||
198 | /** |
||
199 | * Gets the parent information for a given PersistentCollection. It will |
||
200 | * retrieve the top-level persistent Document that the PersistentCollection |
||
201 | * lives in. We can use this to issue queries when updating a |
||
202 | * PersistentCollection that is multiple levels deep inside an embedded |
||
203 | * document. |
||
204 | * |
||
205 | * <code> |
||
206 | * list($path, $parent) = $this->getPathAndParent($coll) |
||
207 | * </code> |
||
208 | */ |
||
209 | 111 | private function getPathAndParent(PersistentCollectionInterface $coll) : array |
|
210 | { |
||
211 | 111 | $mapping = $coll->getMapping(); |
|
212 | 111 | $fields = []; |
|
213 | 111 | $parent = $coll->getOwner(); |
|
214 | 111 | while (($association = $this->uow->getParentAssociation($parent)) !== null) { |
|
215 | 15 | [$m, $owner, $field] = $association; |
|
216 | 15 | if (isset($m['reference'])) { |
|
217 | break; |
||
218 | } |
||
219 | 15 | $parent = $owner; |
|
220 | 15 | $fields[] = $field; |
|
221 | } |
||
222 | 111 | $propertyPath = implode('.', array_reverse($fields)); |
|
223 | 111 | $path = $mapping['name']; |
|
224 | 111 | if ($propertyPath) { |
|
225 | 15 | $path = $propertyPath . '.' . $path; |
|
226 | } |
||
227 | 111 | return [$path, $parent]; |
|
228 | } |
||
229 | |||
230 | /** |
||
231 | * Executes a query updating the given document. |
||
232 | */ |
||
233 | 111 | private function executeQuery(object $document, array $newObj, array $options) : void |
|
234 | { |
||
235 | 111 | $className = get_class($document); |
|
236 | 111 | $class = $this->dm->getClassMetadata($className); |
|
237 | 111 | $id = $class->getDatabaseIdentifierValue($this->uow->getDocumentIdentifier($document)); |
|
238 | 111 | $query = ['_id' => $id]; |
|
239 | 111 | if ($class->isVersioned) { |
|
240 | 5 | $query[$class->fieldMappings[$class->versionField]['name']] = $class->reflFields[$class->versionField]->getValue($document); |
|
241 | } |
||
242 | 111 | $collection = $this->dm->getDocumentCollection($className); |
|
243 | 111 | $result = $collection->updateOne($query, $newObj, $options); |
|
244 | 111 | if ($class->isVersioned && ! $result->getMatchedCount()) { |
|
245 | 2 | throw LockException::lockFailed($document); |
|
246 | } |
||
247 | 109 | } |
|
248 | } |
||
249 |
This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.