These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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\ORM\Persisters\Collection; |
||
21 | |||
22 | use Doctrine\Common\Collections\Criteria; |
||
23 | use Doctrine\ORM\Mapping\ClassMetadata; |
||
24 | use Doctrine\ORM\Persisters\SqlValueVisitor; |
||
25 | use Doctrine\ORM\PersistentCollection; |
||
26 | use Doctrine\ORM\Query; |
||
27 | use Doctrine\ORM\Utility\PersisterHelper; |
||
28 | |||
29 | /** |
||
30 | * Persister for many-to-many collections. |
||
31 | * |
||
32 | * @author Roman Borschel <[email protected]> |
||
33 | * @author Guilherme Blanco <[email protected]> |
||
34 | * @author Alexander <[email protected]> |
||
35 | * @since 2.0 |
||
36 | */ |
||
37 | class ManyToManyPersister extends AbstractCollectionPersister |
||
38 | { |
||
39 | /** |
||
40 | * {@inheritdoc} |
||
41 | */ |
||
42 | 17 | public function delete(PersistentCollection $collection) |
|
43 | { |
||
44 | 17 | $mapping = $collection->getMapping(); |
|
45 | |||
46 | 17 | if ( ! $mapping['isOwningSide']) { |
|
47 | return; // ignore inverse side |
||
48 | } |
||
49 | |||
50 | 17 | $types = []; |
|
51 | 17 | $class = $this->em->getClassMetadata($mapping['sourceEntity']); |
|
52 | |||
53 | 17 | foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { |
|
54 | 17 | $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em); |
|
55 | } |
||
56 | |||
57 | 17 | $this->conn->executeUpdate($this->getDeleteSQL($collection), $this->getDeleteSQLParameters($collection), $types); |
|
58 | 17 | } |
|
59 | |||
60 | /** |
||
61 | * {@inheritdoc} |
||
62 | */ |
||
63 | 329 | public function update(PersistentCollection $collection) |
|
64 | { |
||
65 | 329 | $mapping = $collection->getMapping(); |
|
66 | |||
67 | 329 | if ( ! $mapping['isOwningSide']) { |
|
68 | 231 | return; // ignore inverse side |
|
69 | } |
||
70 | |||
71 | 328 | list($deleteSql, $deleteTypes) = $this->getDeleteRowSQL($collection); |
|
72 | 328 | list($insertSql, $insertTypes) = $this->getInsertRowSQL($collection); |
|
73 | |||
74 | 328 | foreach ($collection->getDeleteDiff() as $element) { |
|
75 | 12 | $this->conn->executeUpdate( |
|
76 | 12 | $deleteSql, |
|
77 | 12 | $this->getDeleteRowSQLParameters($collection, $element), |
|
78 | 12 | $deleteTypes |
|
79 | ); |
||
80 | } |
||
81 | |||
82 | 328 | foreach ($collection->getInsertDiff() as $element) { |
|
83 | 328 | $this->conn->executeUpdate( |
|
84 | 328 | $insertSql, |
|
85 | 328 | $this->getInsertRowSQLParameters($collection, $element), |
|
86 | 328 | $insertTypes |
|
87 | ); |
||
88 | } |
||
89 | 328 | } |
|
90 | |||
91 | /** |
||
92 | * {@inheritdoc} |
||
93 | */ |
||
94 | 3 | public function get(PersistentCollection $collection, $index) |
|
95 | { |
||
96 | 3 | $mapping = $collection->getMapping(); |
|
97 | |||
98 | 3 | if ( ! isset($mapping['indexBy'])) { |
|
99 | throw new \BadMethodCallException("Selecting a collection by index is only supported on indexed collections."); |
||
100 | } |
||
101 | |||
102 | 3 | $persister = $this->uow->getEntityPersister($mapping['targetEntity']); |
|
103 | 3 | $mappedKey = $mapping['isOwningSide'] |
|
104 | 2 | ? $mapping['inversedBy'] |
|
105 | 3 | : $mapping['mappedBy']; |
|
106 | |||
107 | 3 | return $persister->load([$mappedKey => $collection->getOwner(), $mapping['indexBy'] => $index], null, $mapping, [], 0, 1); |
|
108 | } |
||
109 | |||
110 | /** |
||
111 | * {@inheritdoc} |
||
112 | */ |
||
113 | 18 | public function count(PersistentCollection $collection) |
|
114 | { |
||
115 | 18 | $conditions = []; |
|
116 | 18 | $params = []; |
|
117 | 18 | $types = []; |
|
118 | 18 | $mapping = $collection->getMapping(); |
|
119 | 18 | $id = $this->uow->getEntityIdentifier($collection->getOwner()); |
|
120 | 18 | $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); |
|
121 | 18 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
|
122 | 18 | $association = ( ! $mapping['isOwningSide']) |
|
123 | 4 | ? $targetClass->associationMappings[$mapping['mappedBy']] |
|
124 | 18 | : $mapping; |
|
125 | |||
126 | 18 | $joinTableName = $this->quoteStrategy->getJoinTableName($association, $sourceClass, $this->platform); |
|
127 | 18 | $joinColumns = ( ! $mapping['isOwningSide']) |
|
128 | 4 | ? $association['joinTable']['inverseJoinColumns'] |
|
129 | 18 | : $association['joinTable']['joinColumns']; |
|
130 | |||
131 | 18 | foreach ($joinColumns as $joinColumn) { |
|
132 | 18 | $columnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $sourceClass, $this->platform); |
|
133 | 18 | $referencedName = $joinColumn['referencedColumnName']; |
|
134 | 18 | $conditions[] = 't.' . $columnName . ' = ?'; |
|
135 | 18 | $params[] = $id[$sourceClass->getFieldForColumn($referencedName)]; |
|
136 | 18 | $types[] = PersisterHelper::getTypeOfColumn($referencedName, $sourceClass, $this->em); |
|
137 | } |
||
138 | |||
139 | 18 | list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($mapping); |
|
140 | |||
141 | 18 | if ($filterSql) { |
|
142 | 3 | $conditions[] = $filterSql; |
|
143 | } |
||
144 | |||
145 | // If there is a provided criteria, make part of conditions |
||
146 | // @todo Fix this. Current SQL returns something like: |
||
147 | // |
||
148 | /*if ($criteria && ($expression = $criteria->getWhereExpression()) !== null) { |
||
149 | // A join is needed on the target entity |
||
150 | $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform); |
||
151 | $targetJoinSql = ' JOIN ' . $targetTableName . ' te' |
||
152 | . ' ON' . implode(' AND ', $this->getOnConditionSQL($association)); |
||
153 | |||
154 | // And criteria conditions needs to be added |
||
155 | $persister = $this->uow->getEntityPersister($targetClass->name); |
||
156 | $visitor = new SqlExpressionVisitor($persister, $targetClass); |
||
157 | $conditions[] = $visitor->dispatch($expression); |
||
158 | |||
159 | $joinTargetEntitySQL = $targetJoinSql . $joinTargetEntitySQL; |
||
160 | }*/ |
||
161 | |||
162 | $sql = 'SELECT COUNT(*)' |
||
163 | 18 | . ' FROM ' . $joinTableName . ' t' |
|
164 | 18 | . $joinTargetEntitySQL |
|
165 | 18 | . ' WHERE ' . implode(' AND ', $conditions); |
|
166 | |||
167 | 18 | return $this->conn->fetchColumn($sql, $params, 0, $types); |
|
168 | } |
||
169 | |||
170 | /** |
||
171 | * {@inheritDoc} |
||
172 | */ |
||
173 | 8 | View Code Duplication | public function slice(PersistentCollection $collection, $offset, $length = null) |
174 | { |
||
175 | 8 | $mapping = $collection->getMapping(); |
|
176 | 8 | $persister = $this->uow->getEntityPersister($mapping['targetEntity']); |
|
177 | |||
178 | 8 | return $persister->getManyToManyCollection($mapping, $collection->getOwner(), $offset, $length); |
|
179 | } |
||
180 | /** |
||
181 | * {@inheritdoc} |
||
182 | */ |
||
183 | 7 | public function containsKey(PersistentCollection $collection, $key) |
|
184 | { |
||
185 | 7 | $mapping = $collection->getMapping(); |
|
186 | |||
187 | 7 | if ( ! isset($mapping['indexBy'])) { |
|
188 | throw new \BadMethodCallException("Selecting a collection by index is only supported on indexed collections."); |
||
189 | } |
||
190 | |||
191 | 7 | list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictionsWithKey($collection, $key, true); |
|
192 | |||
193 | 7 | $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses); |
|
194 | |||
195 | 7 | return (bool) $this->conn->fetchColumn($sql, $params, 0, $types); |
|
196 | } |
||
197 | |||
198 | /** |
||
199 | * {@inheritDoc} |
||
200 | */ |
||
201 | 7 | View Code Duplication | public function contains(PersistentCollection $collection, $element) |
202 | { |
||
203 | 7 | if ( ! $this->isValidEntityState($element)) { |
|
204 | 2 | return false; |
|
205 | } |
||
206 | |||
207 | 7 | list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictions($collection, $element, true); |
|
208 | |||
209 | 7 | $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses); |
|
210 | |||
211 | 7 | return (bool) $this->conn->fetchColumn($sql, $params, 0, $types); |
|
212 | } |
||
213 | |||
214 | /** |
||
215 | * {@inheritDoc} |
||
216 | */ |
||
217 | 2 | View Code Duplication | public function removeElement(PersistentCollection $collection, $element) |
218 | { |
||
219 | 2 | if ( ! $this->isValidEntityState($element)) { |
|
220 | 2 | return false; |
|
221 | } |
||
222 | |||
223 | 2 | list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictions($collection, $element, false); |
|
224 | |||
225 | 2 | $sql = 'DELETE FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses); |
|
226 | |||
227 | 2 | return (bool) $this->conn->executeUpdate($sql, $params, $types); |
|
228 | } |
||
229 | |||
230 | /** |
||
231 | * {@inheritDoc} |
||
232 | */ |
||
233 | 7 | public function loadCriteria(PersistentCollection $collection, Criteria $criteria) |
|
234 | { |
||
235 | 7 | $mapping = $collection->getMapping(); |
|
236 | 7 | $owner = $collection->getOwner(); |
|
237 | 7 | $ownerMetadata = $this->em->getClassMetadata(get_class($owner)); |
|
238 | 7 | $id = $this->uow->getEntityIdentifier($owner); |
|
239 | 7 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
|
240 | 7 | $onConditions = $this->getOnConditionSQL($mapping); |
|
241 | 7 | $whereClauses = $params = []; |
|
242 | |||
243 | 7 | if ( ! $mapping['isOwningSide']) { |
|
244 | 1 | $associationSourceClass = $targetClass; |
|
245 | 1 | $mapping = $targetClass->associationMappings[$mapping['mappedBy']]; |
|
246 | 1 | $sourceRelationMode = 'relationToTargetKeyColumns'; |
|
247 | } else { |
||
248 | 6 | $associationSourceClass = $ownerMetadata; |
|
249 | 6 | $sourceRelationMode = 'relationToSourceKeyColumns'; |
|
250 | } |
||
251 | |||
252 | 7 | foreach ($mapping[$sourceRelationMode] as $key => $value) { |
|
253 | 7 | $whereClauses[] = sprintf('t.%s = ?', $key); |
|
254 | 7 | $params[] = $ownerMetadata->containsForeignIdentifier |
|
255 | ? $id[$ownerMetadata->getFieldForColumn($value)] |
||
256 | 7 | : $id[$ownerMetadata->fieldNames[$value]]; |
|
257 | } |
||
258 | |||
259 | 7 | $parameters = $this->expandCriteriaParameters($criteria); |
|
260 | |||
261 | 7 | foreach ($parameters as $parameter) { |
|
262 | 2 | list($name, $value) = $parameter; |
|
263 | 2 | $field = $this->quoteStrategy->getColumnName($name, $targetClass, $this->platform); |
|
264 | 2 | $whereClauses[] = sprintf('te.%s = ?', $field); |
|
265 | 2 | $params[] = $value; |
|
266 | } |
||
267 | |||
268 | 7 | $tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform); |
|
269 | 7 | $joinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform); |
|
270 | |||
271 | 7 | $rsm = new Query\ResultSetMappingBuilder($this->em); |
|
272 | 7 | $rsm->addRootEntityFromClassMetadata($targetClass->name, 'te'); |
|
273 | |||
274 | 7 | $sql = 'SELECT ' . $rsm->generateSelectClause() |
|
275 | 7 | . ' FROM ' . $tableName . ' te' |
|
276 | 7 | . ' JOIN ' . $joinTable . ' t ON' |
|
277 | 7 | . implode(' AND ', $onConditions) |
|
278 | 7 | . ' WHERE ' . implode(' AND ', $whereClauses); |
|
279 | |||
280 | 7 | $sql .= $this->getOrderingSql($criteria, $targetClass); |
|
281 | |||
282 | 7 | $sql .= $this->getLimitSql($criteria); |
|
283 | |||
284 | 7 | $stmt = $this->conn->executeQuery($sql, $params); |
|
285 | |||
286 | return $this |
||
287 | 7 | ->em |
|
288 | 7 | ->newHydrator(Query::HYDRATE_OBJECT) |
|
289 | 7 | ->hydrateAll($stmt, $rsm); |
|
290 | } |
||
291 | |||
292 | /** |
||
293 | * Generates the filter SQL for a given mapping. |
||
294 | * |
||
295 | * This method is not used for actually grabbing the related entities |
||
296 | * but when the extra-lazy collection methods are called on a filtered |
||
297 | * association. This is why besides the many to many table we also |
||
298 | * have to join in the actual entities table leading to additional |
||
299 | * JOIN. |
||
300 | * |
||
301 | * @param array $mapping Array containing mapping information. |
||
302 | * |
||
303 | * @return string[] ordered tuple: |
||
304 | * - JOIN condition to add to the SQL |
||
305 | * - WHERE condition to add to the SQL |
||
306 | */ |
||
307 | 32 | public function getFilterSql($mapping) |
|
308 | { |
||
309 | 32 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
|
310 | 32 | $rootClass = $this->em->getClassMetadata($targetClass->rootEntityName); |
|
311 | 32 | $filterSql = $this->generateFilterConditionSQL($rootClass, 'te'); |
|
312 | |||
313 | 32 | if ('' === $filterSql) { |
|
314 | 32 | return ['', '']; |
|
315 | } |
||
316 | |||
317 | // A join is needed if there is filtering on the target entity |
||
318 | 6 | $tableName = $this->quoteStrategy->getTableName($rootClass, $this->platform); |
|
319 | 6 | $joinSql = ' JOIN ' . $tableName . ' te' |
|
320 | 6 | . ' ON' . implode(' AND ', $this->getOnConditionSQL($mapping)); |
|
321 | |||
322 | 6 | return [$joinSql, $filterSql]; |
|
323 | } |
||
324 | |||
325 | /** |
||
326 | * Generates the filter SQL for a given entity and table alias. |
||
327 | * |
||
328 | * @param ClassMetadata $targetEntity Metadata of the target entity. |
||
329 | * @param string $targetTableAlias The table alias of the joined/selected table. |
||
330 | * |
||
331 | * @return string The SQL query part to add to a query. |
||
332 | */ |
||
333 | 32 | View Code Duplication | protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias) |
334 | { |
||
335 | 32 | $filterClauses = []; |
|
336 | |||
337 | 32 | foreach ($this->em->getFilters()->getEnabledFilters() as $filter) { |
|
338 | 6 | if ($filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) { |
|
339 | 6 | $filterClauses[] = '(' . $filterExpr . ')'; |
|
340 | } |
||
341 | } |
||
342 | |||
343 | 32 | return $filterClauses |
|
344 | 6 | ? '(' . implode(' AND ', $filterClauses) . ')' |
|
345 | 32 | : ''; |
|
346 | } |
||
347 | |||
348 | /** |
||
349 | * Generate ON condition |
||
350 | * |
||
351 | * @param array $mapping |
||
352 | * |
||
353 | * @return array |
||
354 | */ |
||
355 | 13 | protected function getOnConditionSQL($mapping) |
|
356 | { |
||
357 | 13 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
|
358 | 13 | $association = ( ! $mapping['isOwningSide']) |
|
359 | 3 | ? $targetClass->associationMappings[$mapping['mappedBy']] |
|
360 | 13 | : $mapping; |
|
361 | |||
362 | 13 | $joinColumns = $mapping['isOwningSide'] |
|
363 | 10 | ? $association['joinTable']['inverseJoinColumns'] |
|
364 | 13 | : $association['joinTable']['joinColumns']; |
|
365 | |||
366 | 13 | $conditions = []; |
|
367 | |||
368 | 13 | View Code Duplication | foreach ($joinColumns as $joinColumn) { |
369 | 13 | $joinColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); |
|
370 | 13 | $refColumnName = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform); |
|
371 | |||
372 | 13 | $conditions[] = ' t.' . $joinColumnName . ' = ' . 'te.' . $refColumnName; |
|
373 | } |
||
374 | |||
375 | 13 | return $conditions; |
|
376 | } |
||
377 | |||
378 | /** |
||
379 | * {@inheritdoc} |
||
380 | * |
||
381 | * @override |
||
382 | */ |
||
383 | 17 | protected function getDeleteSQL(PersistentCollection $collection) |
|
384 | { |
||
385 | 17 | $columns = []; |
|
386 | 17 | $mapping = $collection->getMapping(); |
|
387 | 17 | $class = $this->em->getClassMetadata(get_class($collection->getOwner())); |
|
388 | 17 | $joinTable = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform); |
|
389 | |||
390 | 17 | foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { |
|
391 | 17 | $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); |
|
392 | } |
||
393 | |||
394 | 17 | return 'DELETE FROM ' . $joinTable |
|
395 | 17 | . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; |
|
396 | } |
||
397 | |||
398 | /** |
||
399 | * {@inheritdoc} |
||
400 | * |
||
401 | * Internal note: Order of the parameters must be the same as the order of the columns in getDeleteSql. |
||
402 | * @override |
||
403 | */ |
||
404 | 17 | protected function getDeleteSQLParameters(PersistentCollection $collection) |
|
405 | { |
||
406 | 17 | $mapping = $collection->getMapping(); |
|
407 | 17 | $identifier = $this->uow->getEntityIdentifier($collection->getOwner()); |
|
408 | |||
409 | // Optimization for single column identifier |
||
410 | 17 | if (count($mapping['relationToSourceKeyColumns']) === 1) { |
|
411 | 15 | return [reset($identifier)]; |
|
412 | } |
||
413 | |||
414 | // Composite identifier |
||
415 | 2 | $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); |
|
416 | 2 | $params = []; |
|
417 | |||
418 | 2 | foreach ($mapping['relationToSourceKeyColumns'] as $columnName => $refColumnName) { |
|
419 | 2 | $params[] = isset($sourceClass->fieldNames[$refColumnName]) |
|
420 | 1 | ? $identifier[$sourceClass->fieldNames[$refColumnName]] |
|
421 | 2 | : $identifier[$sourceClass->getFieldForColumn($columnName)]; |
|
422 | } |
||
423 | |||
424 | 2 | return $params; |
|
425 | } |
||
426 | |||
427 | /** |
||
428 | * Gets the SQL statement used for deleting a row from the collection. |
||
429 | * |
||
430 | * @param \Doctrine\ORM\PersistentCollection $collection |
||
431 | * |
||
432 | * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array |
||
433 | * of types for bound parameters |
||
434 | */ |
||
435 | 328 | protected function getDeleteRowSQL(PersistentCollection $collection) |
|
436 | { |
||
437 | 328 | $mapping = $collection->getMapping(); |
|
438 | 328 | $class = $this->em->getClassMetadata($mapping['sourceEntity']); |
|
439 | 328 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
|
440 | 328 | $columns = []; |
|
441 | 328 | $types = []; |
|
442 | |||
443 | 328 | foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { |
|
444 | 328 | $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); |
|
445 | 328 | $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em); |
|
446 | } |
||
447 | |||
448 | 328 | foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) { |
|
449 | 328 | $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); |
|
450 | 328 | $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em); |
|
451 | } |
||
452 | |||
453 | return [ |
||
454 | 328 | 'DELETE FROM ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform) |
|
455 | 328 | . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?', |
|
456 | 328 | $types, |
|
457 | ]; |
||
458 | } |
||
459 | |||
460 | /** |
||
461 | * Gets the SQL parameters for the corresponding SQL statement to delete the given |
||
462 | * element from the given collection. |
||
463 | * |
||
464 | * Internal note: Order of the parameters must be the same as the order of the columns in getDeleteRowSql. |
||
465 | * |
||
466 | * @param \Doctrine\ORM\PersistentCollection $collection |
||
467 | * @param mixed $element |
||
468 | * |
||
469 | * @return array |
||
470 | */ |
||
471 | 12 | protected function getDeleteRowSQLParameters(PersistentCollection $collection, $element) |
|
472 | { |
||
473 | 12 | return $this->collectJoinTableColumnParameters($collection, $element); |
|
474 | } |
||
475 | |||
476 | /** |
||
477 | * Gets the SQL statement used for inserting a row in the collection. |
||
478 | * |
||
479 | * @param \Doctrine\ORM\PersistentCollection $collection |
||
480 | * |
||
481 | * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array |
||
482 | * of types for bound parameters |
||
483 | */ |
||
484 | 328 | protected function getInsertRowSQL(PersistentCollection $collection) |
|
485 | { |
||
486 | 328 | $columns = []; |
|
487 | 328 | $types = []; |
|
488 | 328 | $mapping = $collection->getMapping(); |
|
489 | 328 | $class = $this->em->getClassMetadata($mapping['sourceEntity']); |
|
490 | 328 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
|
491 | |||
492 | 328 | foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { |
|
493 | 328 | $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); |
|
494 | 328 | $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em); |
|
495 | } |
||
496 | |||
497 | 328 | foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) { |
|
498 | 328 | $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); |
|
499 | 328 | $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em); |
|
500 | } |
||
501 | |||
502 | return [ |
||
503 | 328 | 'INSERT INTO ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform) |
|
504 | 328 | . ' (' . implode(', ', $columns) . ')' |
|
505 | 328 | . ' VALUES' |
|
506 | 328 | . ' (' . implode(', ', array_fill(0, count($columns), '?')) . ')', |
|
507 | 328 | $types, |
|
508 | ]; |
||
509 | } |
||
510 | |||
511 | /** |
||
512 | * Gets the SQL parameters for the corresponding SQL statement to insert the given |
||
513 | * element of the given collection into the database. |
||
514 | * |
||
515 | * Internal note: Order of the parameters must be the same as the order of the columns in getInsertRowSql. |
||
516 | * |
||
517 | * @param \Doctrine\ORM\PersistentCollection $collection |
||
518 | * @param mixed $element |
||
519 | * |
||
520 | * @return array |
||
521 | */ |
||
522 | 328 | protected function getInsertRowSQLParameters(PersistentCollection $collection, $element) |
|
523 | { |
||
524 | 328 | return $this->collectJoinTableColumnParameters($collection, $element); |
|
525 | } |
||
526 | |||
527 | /** |
||
528 | * Collects the parameters for inserting/deleting on the join table in the order |
||
529 | * of the join table columns as specified in ManyToManyMapping#joinTableColumns. |
||
530 | * |
||
531 | * @param \Doctrine\ORM\PersistentCollection $collection |
||
532 | * @param object $element |
||
533 | * |
||
534 | * @return array |
||
535 | */ |
||
536 | 328 | private function collectJoinTableColumnParameters(PersistentCollection $collection, $element) |
|
537 | { |
||
538 | 328 | $params = []; |
|
539 | 328 | $mapping = $collection->getMapping(); |
|
540 | 328 | $isComposite = count($mapping['joinTableColumns']) > 2; |
|
541 | |||
542 | 328 | $identifier1 = $this->uow->getEntityIdentifier($collection->getOwner()); |
|
543 | 328 | $identifier2 = $this->uow->getEntityIdentifier($element); |
|
544 | |||
545 | 328 | $class1 = $class2 = null; |
|
546 | 328 | if ($isComposite) { |
|
547 | 21 | $class1 = $this->em->getClassMetadata(get_class($collection->getOwner())); |
|
548 | 21 | $class2 = $collection->getTypeClass(); |
|
549 | } |
||
550 | |||
551 | 328 | foreach ($mapping['joinTableColumns'] as $joinTableColumn) { |
|
552 | 328 | $isRelationToSource = isset($mapping['relationToSourceKeyColumns'][$joinTableColumn]); |
|
553 | |||
554 | 328 | if ( ! $isComposite) { |
|
555 | 307 | $params[] = $isRelationToSource ? array_pop($identifier1) : array_pop($identifier2); |
|
556 | |||
557 | 307 | continue; |
|
558 | } |
||
559 | |||
560 | 21 | if ($isRelationToSource) { |
|
561 | 21 | $params[] = $identifier1[$class1->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])]; |
|
562 | |||
563 | 21 | continue; |
|
564 | } |
||
565 | |||
566 | 21 | $params[] = $identifier2[$class2->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])]; |
|
567 | } |
||
568 | |||
569 | 328 | return $params; |
|
570 | } |
||
571 | |||
572 | /** |
||
573 | * @param \Doctrine\ORM\PersistentCollection $collection |
||
574 | * @param string $key |
||
575 | * @param boolean $addFilters Whether the filter SQL should be included or not. |
||
576 | * |
||
577 | * @return array ordered vector: |
||
578 | * - quoted join table name |
||
579 | * - where clauses to be added for filtering |
||
580 | * - parameters to be bound for filtering |
||
581 | * - types of the parameters to be bound for filtering |
||
582 | */ |
||
583 | 7 | private function getJoinTableRestrictionsWithKey(PersistentCollection $collection, $key, $addFilters) |
|
584 | { |
||
585 | 7 | $filterMapping = $collection->getMapping(); |
|
586 | 7 | $mapping = $filterMapping; |
|
587 | 7 | $indexBy = $mapping['indexBy']; |
|
588 | 7 | $id = $this->uow->getEntityIdentifier($collection->getOwner()); |
|
589 | 7 | $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); |
|
590 | 7 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
|
591 | |||
592 | 7 | if (! $mapping['isOwningSide']) { |
|
593 | 3 | $associationSourceClass = $this->em->getClassMetadata($mapping['targetEntity']); |
|
594 | 3 | $mapping = $associationSourceClass->associationMappings[$mapping['mappedBy']]; |
|
595 | 3 | $joinColumns = $mapping['joinTable']['joinColumns']; |
|
596 | 3 | $sourceRelationMode = 'relationToTargetKeyColumns'; |
|
597 | 3 | $targetRelationMode = 'relationToSourceKeyColumns'; |
|
598 | } else { |
||
599 | 4 | $associationSourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); |
|
600 | 4 | $joinColumns = $mapping['joinTable']['inverseJoinColumns']; |
|
601 | 4 | $sourceRelationMode = 'relationToSourceKeyColumns'; |
|
602 | 4 | $targetRelationMode = 'relationToTargetKeyColumns'; |
|
603 | } |
||
604 | |||
605 | 7 | $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform). ' t'; |
|
606 | 7 | $whereClauses = []; |
|
607 | 7 | $params = []; |
|
608 | 7 | $types = []; |
|
609 | |||
610 | 7 | $joinNeeded = ! in_array($indexBy, $targetClass->identifier); |
|
611 | |||
612 | 7 | if ($joinNeeded) { // extra join needed if indexBy is not a @id |
|
613 | 3 | $joinConditions = []; |
|
614 | |||
615 | 3 | foreach ($joinColumns as $joinTableColumn) { |
|
616 | 3 | $joinConditions[] = 't.' . $joinTableColumn['name'] . ' = tr.' . $joinTableColumn['referencedColumnName']; |
|
617 | } |
||
618 | |||
619 | 3 | $tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform); |
|
620 | 3 | $quotedJoinTable .= ' JOIN ' . $tableName . ' tr ON ' . implode(' AND ', $joinConditions); |
|
621 | 3 | $columnName = $targetClass->getColumnName($indexBy); |
|
622 | |||
623 | 3 | $whereClauses[] = 'tr.' . $columnName . ' = ?'; |
|
624 | 3 | $params[] = $key; |
|
625 | 3 | $types[] = PersisterHelper::getTypeOfColumn($columnName, $targetClass, $this->em); |
|
626 | } |
||
627 | |||
628 | 7 | foreach ($mapping['joinTableColumns'] as $joinTableColumn) { |
|
629 | 7 | if (isset($mapping[$sourceRelationMode][$joinTableColumn])) { |
|
630 | 7 | $column = $mapping[$sourceRelationMode][$joinTableColumn]; |
|
631 | 7 | $whereClauses[] = 't.' . $joinTableColumn . ' = ?'; |
|
632 | 7 | $params[] = $sourceClass->containsForeignIdentifier |
|
633 | ? $id[$sourceClass->getFieldForColumn($column)] |
||
634 | 7 | : $id[$sourceClass->fieldNames[$column]]; |
|
635 | 7 | $types[] = PersisterHelper::getTypeOfColumn($column, $sourceClass, $this->em); |
|
636 | 7 | } elseif ( ! $joinNeeded) { |
|
637 | 4 | $column = $mapping[$targetRelationMode][$joinTableColumn]; |
|
638 | |||
639 | 4 | $whereClauses[] = 't.' . $joinTableColumn . ' = ?'; |
|
640 | 4 | $params[] = $key; |
|
641 | 7 | $types[] = PersisterHelper::getTypeOfColumn($column, $targetClass, $this->em); |
|
642 | } |
||
643 | } |
||
644 | |||
645 | 7 | View Code Duplication | if ($addFilters) { |
0 ignored issues
–
show
|
|||
646 | 7 | list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($filterMapping); |
|
647 | |||
648 | 7 | if ($filterSql) { |
|
649 | $quotedJoinTable .= ' ' . $joinTargetEntitySQL; |
||
650 | $whereClauses[] = $filterSql; |
||
651 | } |
||
652 | } |
||
653 | |||
654 | 7 | return [$quotedJoinTable, $whereClauses, $params, $types]; |
|
655 | } |
||
656 | |||
657 | /** |
||
658 | * @param \Doctrine\ORM\PersistentCollection $collection |
||
659 | * @param object $element |
||
660 | * @param boolean $addFilters Whether the filter SQL should be included or not. |
||
661 | * |
||
662 | * @return array ordered vector: |
||
663 | * - quoted join table name |
||
664 | * - where clauses to be added for filtering |
||
665 | * - parameters to be bound for filtering |
||
666 | * - types of the parameters to be bound for filtering |
||
667 | */ |
||
668 | 9 | private function getJoinTableRestrictions(PersistentCollection $collection, $element, $addFilters) |
|
669 | { |
||
670 | 9 | $filterMapping = $collection->getMapping(); |
|
671 | 9 | $mapping = $filterMapping; |
|
672 | |||
673 | 9 | if ( ! $mapping['isOwningSide']) { |
|
674 | 4 | $sourceClass = $this->em->getClassMetadata($mapping['targetEntity']); |
|
675 | 4 | $targetClass = $this->em->getClassMetadata($mapping['sourceEntity']); |
|
676 | 4 | $sourceId = $this->uow->getEntityIdentifier($element); |
|
677 | 4 | $targetId = $this->uow->getEntityIdentifier($collection->getOwner()); |
|
678 | |||
679 | 4 | $mapping = $sourceClass->associationMappings[$mapping['mappedBy']]; |
|
680 | } else { |
||
681 | 5 | $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); |
|
682 | 5 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
|
683 | 5 | $sourceId = $this->uow->getEntityIdentifier($collection->getOwner()); |
|
684 | 5 | $targetId = $this->uow->getEntityIdentifier($element); |
|
685 | } |
||
686 | |||
687 | 9 | $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $sourceClass, $this->platform); |
|
688 | 9 | $whereClauses = []; |
|
689 | 9 | $params = []; |
|
690 | 9 | $types = []; |
|
691 | |||
692 | 9 | foreach ($mapping['joinTableColumns'] as $joinTableColumn) { |
|
693 | 9 | $whereClauses[] = ($addFilters ? 't.' : '') . $joinTableColumn . ' = ?'; |
|
694 | |||
695 | 9 | if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) { |
|
696 | 9 | $targetColumn = $mapping['relationToTargetKeyColumns'][$joinTableColumn]; |
|
697 | 9 | $params[] = $targetId[$targetClass->getFieldForColumn($targetColumn)]; |
|
698 | 9 | $types[] = PersisterHelper::getTypeOfColumn($targetColumn, $targetClass, $this->em); |
|
699 | |||
700 | 9 | continue; |
|
701 | } |
||
702 | |||
703 | // relationToSourceKeyColumns |
||
704 | 9 | $targetColumn = $mapping['relationToSourceKeyColumns'][$joinTableColumn]; |
|
705 | 9 | $params[] = $sourceId[$sourceClass->getFieldForColumn($targetColumn)]; |
|
706 | 9 | $types[] = PersisterHelper::getTypeOfColumn($targetColumn, $sourceClass, $this->em); |
|
707 | } |
||
708 | |||
709 | 9 | View Code Duplication | if ($addFilters) { |
710 | 7 | $quotedJoinTable .= ' t'; |
|
711 | |||
712 | 7 | list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($filterMapping); |
|
713 | |||
714 | 7 | if ($filterSql) { |
|
715 | 3 | $quotedJoinTable .= ' ' . $joinTargetEntitySQL; |
|
716 | 3 | $whereClauses[] = $filterSql; |
|
717 | } |
||
718 | } |
||
719 | |||
720 | 9 | return [$quotedJoinTable, $whereClauses, $params, $types]; |
|
721 | } |
||
722 | |||
723 | /** |
||
724 | * Expands Criteria Parameters by walking the expressions and grabbing all |
||
725 | * parameters and types from it. |
||
726 | * |
||
727 | * @param \Doctrine\Common\Collections\Criteria $criteria |
||
728 | * |
||
729 | * @return array |
||
730 | */ |
||
731 | 7 | private function expandCriteriaParameters(Criteria $criteria) |
|
732 | { |
||
733 | 7 | $expression = $criteria->getWhereExpression(); |
|
734 | |||
735 | 7 | if ($expression === null) { |
|
736 | 5 | return []; |
|
737 | } |
||
738 | |||
739 | 2 | $valueVisitor = new SqlValueVisitor(); |
|
740 | |||
741 | 2 | $valueVisitor->dispatch($expression); |
|
742 | |||
743 | 2 | list(, $types) = $valueVisitor->getParamsAndTypes(); |
|
744 | |||
745 | 2 | return $types; |
|
746 | } |
||
747 | |||
748 | /** |
||
749 | * @param Criteria $criteria |
||
750 | * @param ClassMetadata $targetClass |
||
751 | * @return string |
||
752 | */ |
||
753 | 7 | private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass) |
|
754 | { |
||
755 | 7 | $orderings = $criteria->getOrderings(); |
|
756 | 7 | if ($orderings) { |
|
757 | 2 | $orderBy = []; |
|
758 | 2 | foreach ($orderings as $name => $direction) { |
|
759 | 2 | $field = $this->quoteStrategy->getColumnName( |
|
760 | 2 | $name, |
|
761 | 2 | $targetClass, |
|
762 | 2 | $this->platform |
|
763 | ); |
||
764 | 2 | $orderBy[] = $field . ' ' . $direction; |
|
765 | } |
||
766 | |||
767 | 2 | return ' ORDER BY ' . implode(', ', $orderBy); |
|
768 | } |
||
769 | 5 | return ''; |
|
770 | } |
||
771 | |||
772 | /** |
||
773 | * @param Criteria $criteria |
||
774 | * @return string |
||
775 | * @throws \Doctrine\DBAL\DBALException |
||
776 | */ |
||
777 | 7 | private function getLimitSql(Criteria $criteria) |
|
778 | { |
||
779 | 7 | $limit = $criteria->getMaxResults(); |
|
780 | 7 | $offset = $criteria->getFirstResult(); |
|
781 | 7 | View Code Duplication | if ($limit !== null || $offset !== null) { |
782 | 3 | return $this->platform->modifyLimitQuery('', $limit, $offset); |
|
783 | } |
||
784 | 4 | return ''; |
|
785 | } |
||
786 | } |
||
787 |
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.