Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like ManyToManyPersister often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use ManyToManyPersister, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
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) |
|
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) |
|
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) |
|
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) |
|
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) |
|
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) |
|
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 | . ' 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) |
|
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) |
|
570 | |||
571 | /** |
||
572 | * @param \Doctrine\ORM\PersistentCollection $collection |
||
573 | * @param string $key |
||
574 | * @param boolean $addFilters Whether the filter SQL should be included or not. |
||
575 | * |
||
576 | * @return array ordered vector: |
||
577 | * - quoted join table name |
||
578 | * - where clauses to be added for filtering |
||
579 | * - parameters to be bound for filtering |
||
580 | * - types of the parameters to be bound for filtering |
||
581 | */ |
||
582 | 7 | private function getJoinTableRestrictionsWithKey(PersistentCollection $collection, $key, $addFilters) |
|
583 | { |
||
584 | 7 | $filterMapping = $collection->getMapping(); |
|
585 | 7 | $mapping = $filterMapping; |
|
586 | 7 | $indexBy = $mapping['indexBy']; |
|
587 | 7 | $id = $this->uow->getEntityIdentifier($collection->getOwner()); |
|
588 | 7 | $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); |
|
589 | 7 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
|
590 | |||
591 | 7 | if (! $mapping['isOwningSide']) { |
|
592 | 3 | $associationSourceClass = $this->em->getClassMetadata($mapping['targetEntity']); |
|
593 | 3 | $mapping = $associationSourceClass->associationMappings[$mapping['mappedBy']]; |
|
594 | 3 | $joinColumns = $mapping['joinTable']['joinColumns']; |
|
595 | 3 | $sourceRelationMode = 'relationToTargetKeyColumns'; |
|
596 | 3 | $targetRelationMode = 'relationToSourceKeyColumns'; |
|
597 | } else { |
||
598 | 4 | $associationSourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); |
|
599 | 4 | $joinColumns = $mapping['joinTable']['inverseJoinColumns']; |
|
600 | 4 | $sourceRelationMode = 'relationToSourceKeyColumns'; |
|
601 | 4 | $targetRelationMode = 'relationToTargetKeyColumns'; |
|
602 | } |
||
603 | |||
604 | 7 | $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform). ' t'; |
|
605 | 7 | $whereClauses = []; |
|
606 | 7 | $params = []; |
|
607 | 7 | $types = []; |
|
608 | |||
609 | 7 | $joinNeeded = ! in_array($indexBy, $targetClass->identifier); |
|
610 | |||
611 | 7 | if ($joinNeeded) { // extra join needed if indexBy is not a @id |
|
612 | 3 | $joinConditions = []; |
|
613 | |||
614 | 3 | foreach ($joinColumns as $joinTableColumn) { |
|
615 | 3 | $joinConditions[] = 't.' . $joinTableColumn['name'] . ' = tr.' . $joinTableColumn['referencedColumnName']; |
|
616 | } |
||
617 | |||
618 | 3 | $tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform); |
|
619 | 3 | $quotedJoinTable .= ' JOIN ' . $tableName . ' tr ON ' . implode(' AND ', $joinConditions); |
|
620 | 3 | $columnName = $targetClass->getColumnName($indexBy); |
|
621 | |||
622 | 3 | $whereClauses[] = 'tr.' . $columnName . ' = ?'; |
|
623 | 3 | $params[] = $key; |
|
624 | 3 | $types[] = PersisterHelper::getTypeOfColumn($columnName, $targetClass, $this->em); |
|
625 | } |
||
626 | |||
627 | 7 | foreach ($mapping['joinTableColumns'] as $joinTableColumn) { |
|
628 | 7 | if (isset($mapping[$sourceRelationMode][$joinTableColumn])) { |
|
629 | 7 | $column = $mapping[$sourceRelationMode][$joinTableColumn]; |
|
630 | 7 | $whereClauses[] = 't.' . $joinTableColumn . ' = ?'; |
|
631 | 7 | $params[] = $sourceClass->containsForeignIdentifier |
|
632 | ? $id[$sourceClass->getFieldForColumn($column)] |
||
633 | 7 | : $id[$sourceClass->fieldNames[$column]]; |
|
634 | 7 | $types[] = PersisterHelper::getTypeOfColumn($column, $sourceClass, $this->em); |
|
635 | 7 | } elseif ( ! $joinNeeded) { |
|
636 | 4 | $column = $mapping[$targetRelationMode][$joinTableColumn]; |
|
637 | |||
638 | 4 | $whereClauses[] = 't.' . $joinTableColumn . ' = ?'; |
|
639 | 4 | $params[] = $key; |
|
640 | 4 | $types[] = PersisterHelper::getTypeOfColumn($column, $targetClass, $this->em); |
|
641 | } |
||
642 | } |
||
643 | |||
644 | 7 | View Code Duplication | if ($addFilters) { |
645 | 7 | list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($filterMapping); |
|
646 | |||
647 | 7 | if ($filterSql) { |
|
648 | $quotedJoinTable .= ' ' . $joinTargetEntitySQL; |
||
649 | $whereClauses[] = $filterSql; |
||
650 | } |
||
651 | } |
||
652 | |||
653 | 7 | return [$quotedJoinTable, $whereClauses, $params, $types]; |
|
654 | } |
||
655 | |||
656 | /** |
||
657 | * @param \Doctrine\ORM\PersistentCollection $collection |
||
658 | * @param object $element |
||
659 | * @param boolean $addFilters Whether the filter SQL should be included or not. |
||
660 | * |
||
661 | * @return array ordered vector: |
||
662 | * - quoted join table name |
||
663 | * - where clauses to be added for filtering |
||
664 | * - parameters to be bound for filtering |
||
665 | * - types of the parameters to be bound for filtering |
||
666 | */ |
||
667 | 9 | private function getJoinTableRestrictions(PersistentCollection $collection, $element, $addFilters) |
|
668 | { |
||
669 | 9 | $filterMapping = $collection->getMapping(); |
|
670 | 9 | $mapping = $filterMapping; |
|
671 | |||
672 | 9 | if ( ! $mapping['isOwningSide']) { |
|
673 | 4 | $sourceClass = $this->em->getClassMetadata($mapping['targetEntity']); |
|
674 | 4 | $targetClass = $this->em->getClassMetadata($mapping['sourceEntity']); |
|
675 | 4 | $sourceId = $this->uow->getEntityIdentifier($element); |
|
676 | 4 | $targetId = $this->uow->getEntityIdentifier($collection->getOwner()); |
|
677 | |||
678 | 4 | $mapping = $sourceClass->associationMappings[$mapping['mappedBy']]; |
|
679 | } else { |
||
680 | 5 | $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); |
|
681 | 5 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
|
682 | 5 | $sourceId = $this->uow->getEntityIdentifier($collection->getOwner()); |
|
683 | 5 | $targetId = $this->uow->getEntityIdentifier($element); |
|
684 | } |
||
685 | |||
686 | 9 | $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $sourceClass, $this->platform); |
|
687 | 9 | $whereClauses = []; |
|
688 | 9 | $params = []; |
|
689 | 9 | $types = []; |
|
690 | |||
691 | 9 | foreach ($mapping['joinTableColumns'] as $joinTableColumn) { |
|
692 | 9 | $whereClauses[] = ($addFilters ? 't.' : '') . $joinTableColumn . ' = ?'; |
|
693 | |||
694 | 9 | if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) { |
|
695 | 9 | $targetColumn = $mapping['relationToTargetKeyColumns'][$joinTableColumn]; |
|
696 | 9 | $params[] = $targetId[$targetClass->getFieldForColumn($targetColumn)]; |
|
697 | 9 | $types[] = PersisterHelper::getTypeOfColumn($targetColumn, $targetClass, $this->em); |
|
698 | |||
699 | 9 | continue; |
|
700 | } |
||
701 | |||
702 | // relationToSourceKeyColumns |
||
703 | 9 | $targetColumn = $mapping['relationToSourceKeyColumns'][$joinTableColumn]; |
|
704 | 9 | $params[] = $sourceId[$sourceClass->getFieldForColumn($targetColumn)]; |
|
705 | 9 | $types[] = PersisterHelper::getTypeOfColumn($targetColumn, $sourceClass, $this->em); |
|
706 | } |
||
707 | |||
708 | 9 | View Code Duplication | if ($addFilters) { |
709 | 7 | $quotedJoinTable .= ' t'; |
|
710 | |||
711 | 7 | list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($filterMapping); |
|
712 | |||
713 | 7 | if ($filterSql) { |
|
714 | 3 | $quotedJoinTable .= ' ' . $joinTargetEntitySQL; |
|
715 | 3 | $whereClauses[] = $filterSql; |
|
716 | } |
||
717 | } |
||
718 | |||
719 | 9 | return [$quotedJoinTable, $whereClauses, $params, $types]; |
|
720 | } |
||
721 | |||
722 | /** |
||
723 | * Expands Criteria Parameters by walking the expressions and grabbing all |
||
724 | * parameters and types from it. |
||
725 | * |
||
726 | * @param \Doctrine\Common\Collections\Criteria $criteria |
||
727 | * |
||
728 | * @return array |
||
729 | */ |
||
730 | 7 | private function expandCriteriaParameters(Criteria $criteria) |
|
746 | |||
747 | /** |
||
748 | * @param Criteria $criteria |
||
749 | * @param ClassMetadata $targetClass |
||
750 | * @return string |
||
751 | */ |
||
752 | 7 | private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass) |
|
753 | { |
||
754 | 7 | $orderings = $criteria->getOrderings(); |
|
755 | 7 | if ($orderings) { |
|
756 | 2 | $orderBy = []; |
|
757 | 2 | foreach ($orderings as $name => $direction) { |
|
758 | 2 | $field = $this->quoteStrategy->getColumnName( |
|
759 | 2 | $name, |
|
760 | 2 | $targetClass, |
|
761 | 2 | $this->platform |
|
762 | ); |
||
763 | 2 | $orderBy[] = $field . ' ' . $direction; |
|
764 | } |
||
765 | |||
766 | 2 | return ' ORDER BY ' . implode(', ', $orderBy); |
|
767 | } |
||
768 | 5 | return ''; |
|
769 | } |
||
770 | |||
771 | /** |
||
772 | * @param Criteria $criteria |
||
773 | * @return string |
||
774 | * @throws \Doctrine\DBAL\DBALException |
||
775 | */ |
||
776 | 7 | private function getLimitSql(Criteria $criteria) |
|
785 | } |
||
786 |
This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.
Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.