mailpoet
/
vendor-prefixed
/
doctrine
/
orm
/
src
/
Persisters
/
Collection
/
ManyToManyPersister.php
AbstractCollectionPersister.php
9 months ago
CollectionPersister.php
9 months ago
ManyToManyPersister.php
9 months ago
OneToManyPersister.php
9 months ago
index.php
9 months ago
ManyToManyPersister.php
437 lines
| 1 | <?php |
| 2 | declare (strict_types=1); |
| 3 | namespace MailPoetVendor\Doctrine\ORM\Persisters\Collection; |
| 4 | if (!defined('ABSPATH')) exit; |
| 5 | use BadMethodCallException; |
| 6 | use MailPoetVendor\Doctrine\Common\Collections\Criteria; |
| 7 | use MailPoetVendor\Doctrine\Common\Collections\Expr\Comparison; |
| 8 | use MailPoetVendor\Doctrine\DBAL\Exception as DBALException; |
| 9 | use MailPoetVendor\Doctrine\ORM\Internal\CriteriaOrderings; |
| 10 | use MailPoetVendor\Doctrine\ORM\Mapping\ClassMetadata; |
| 11 | use MailPoetVendor\Doctrine\ORM\PersistentCollection; |
| 12 | use MailPoetVendor\Doctrine\ORM\Persisters\SqlValueVisitor; |
| 13 | use MailPoetVendor\Doctrine\ORM\Query; |
| 14 | use MailPoetVendor\Doctrine\ORM\Utility\PersisterHelper; |
| 15 | use function array_fill; |
| 16 | use function array_merge; |
| 17 | use function array_pop; |
| 18 | use function count; |
| 19 | use function get_class; |
| 20 | use function implode; |
| 21 | use function in_array; |
| 22 | use function reset; |
| 23 | use function sprintf; |
| 24 | class ManyToManyPersister extends AbstractCollectionPersister |
| 25 | { |
| 26 | use CriteriaOrderings; |
| 27 | public function delete(PersistentCollection $collection) |
| 28 | { |
| 29 | $mapping = $collection->getMapping(); |
| 30 | if (!$mapping['isOwningSide']) { |
| 31 | return; |
| 32 | // ignore inverse side |
| 33 | } |
| 34 | $types = []; |
| 35 | $class = $this->em->getClassMetadata($mapping['sourceEntity']); |
| 36 | foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { |
| 37 | $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em); |
| 38 | } |
| 39 | $this->conn->executeStatement($this->getDeleteSQL($collection), $this->getDeleteSQLParameters($collection), $types); |
| 40 | } |
| 41 | public function update(PersistentCollection $collection) |
| 42 | { |
| 43 | $mapping = $collection->getMapping(); |
| 44 | if (!$mapping['isOwningSide']) { |
| 45 | return; |
| 46 | // ignore inverse side |
| 47 | } |
| 48 | [$deleteSql, $deleteTypes] = $this->getDeleteRowSQL($collection); |
| 49 | [$insertSql, $insertTypes] = $this->getInsertRowSQL($collection); |
| 50 | foreach ($collection->getDeleteDiff() as $element) { |
| 51 | $this->conn->executeStatement($deleteSql, $this->getDeleteRowSQLParameters($collection, $element), $deleteTypes); |
| 52 | } |
| 53 | foreach ($collection->getInsertDiff() as $element) { |
| 54 | $this->conn->executeStatement($insertSql, $this->getInsertRowSQLParameters($collection, $element), $insertTypes); |
| 55 | } |
| 56 | } |
| 57 | public function get(PersistentCollection $collection, $index) |
| 58 | { |
| 59 | $mapping = $collection->getMapping(); |
| 60 | if (!isset($mapping['indexBy'])) { |
| 61 | throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.'); |
| 62 | } |
| 63 | $persister = $this->uow->getEntityPersister($mapping['targetEntity']); |
| 64 | $mappedKey = $mapping['isOwningSide'] ? $mapping['inversedBy'] : $mapping['mappedBy']; |
| 65 | return $persister->load([$mappedKey => $collection->getOwner(), $mapping['indexBy'] => $index], null, $mapping, [], 0, 1); |
| 66 | } |
| 67 | public function count(PersistentCollection $collection) |
| 68 | { |
| 69 | $conditions = []; |
| 70 | $params = []; |
| 71 | $types = []; |
| 72 | $mapping = $collection->getMapping(); |
| 73 | $id = $this->uow->getEntityIdentifier($collection->getOwner()); |
| 74 | $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); |
| 75 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
| 76 | $association = !$mapping['isOwningSide'] ? $targetClass->associationMappings[$mapping['mappedBy']] : $mapping; |
| 77 | $joinTableName = $this->quoteStrategy->getJoinTableName($association, $sourceClass, $this->platform); |
| 78 | $joinColumns = !$mapping['isOwningSide'] ? $association['joinTable']['inverseJoinColumns'] : $association['joinTable']['joinColumns']; |
| 79 | foreach ($joinColumns as $joinColumn) { |
| 80 | $columnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $sourceClass, $this->platform); |
| 81 | $referencedName = $joinColumn['referencedColumnName']; |
| 82 | $conditions[] = 't.' . $columnName . ' = ?'; |
| 83 | $params[] = $id[$sourceClass->getFieldForColumn($referencedName)]; |
| 84 | $types[] = PersisterHelper::getTypeOfColumn($referencedName, $sourceClass, $this->em); |
| 85 | } |
| 86 | [$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($mapping); |
| 87 | if ($filterSql) { |
| 88 | $conditions[] = $filterSql; |
| 89 | } |
| 90 | // If there is a provided criteria, make part of conditions |
| 91 | // @todo Fix this. Current SQL returns something like: |
| 92 | $sql = 'SELECT COUNT(*)' . ' FROM ' . $joinTableName . ' t' . $joinTargetEntitySQL . ' WHERE ' . implode(' AND ', $conditions); |
| 93 | return (int) $this->conn->fetchOne($sql, $params, $types); |
| 94 | } |
| 95 | public function slice(PersistentCollection $collection, $offset, $length = null) |
| 96 | { |
| 97 | $mapping = $collection->getMapping(); |
| 98 | $persister = $this->uow->getEntityPersister($mapping['targetEntity']); |
| 99 | return $persister->getManyToManyCollection($mapping, $collection->getOwner(), $offset, $length); |
| 100 | } |
| 101 | public function containsKey(PersistentCollection $collection, $key) |
| 102 | { |
| 103 | $mapping = $collection->getMapping(); |
| 104 | if (!isset($mapping['indexBy'])) { |
| 105 | throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.'); |
| 106 | } |
| 107 | [$quotedJoinTable, $whereClauses, $params, $types] = $this->getJoinTableRestrictionsWithKey($collection, (string) $key, \true); |
| 108 | $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses); |
| 109 | return (bool) $this->conn->fetchOne($sql, $params, $types); |
| 110 | } |
| 111 | public function contains(PersistentCollection $collection, $element) |
| 112 | { |
| 113 | if (!$this->isValidEntityState($element)) { |
| 114 | return \false; |
| 115 | } |
| 116 | [$quotedJoinTable, $whereClauses, $params, $types] = $this->getJoinTableRestrictions($collection, $element, \true); |
| 117 | $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses); |
| 118 | return (bool) $this->conn->fetchOne($sql, $params, $types); |
| 119 | } |
| 120 | public function loadCriteria(PersistentCollection $collection, Criteria $criteria) |
| 121 | { |
| 122 | $mapping = $collection->getMapping(); |
| 123 | $owner = $collection->getOwner(); |
| 124 | $ownerMetadata = $this->em->getClassMetadata(get_class($owner)); |
| 125 | $id = $this->uow->getEntityIdentifier($owner); |
| 126 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
| 127 | $onConditions = $this->getOnConditionSQL($mapping); |
| 128 | $whereClauses = $params = []; |
| 129 | $paramTypes = []; |
| 130 | if (!$mapping['isOwningSide']) { |
| 131 | $associationSourceClass = $targetClass; |
| 132 | $mapping = $targetClass->associationMappings[$mapping['mappedBy']]; |
| 133 | $sourceRelationMode = 'relationToTargetKeyColumns'; |
| 134 | } else { |
| 135 | $associationSourceClass = $ownerMetadata; |
| 136 | $sourceRelationMode = 'relationToSourceKeyColumns'; |
| 137 | } |
| 138 | foreach ($mapping[$sourceRelationMode] as $key => $value) { |
| 139 | $whereClauses[] = sprintf('t.%s = ?', $key); |
| 140 | $params[] = $ownerMetadata->containsForeignIdentifier ? $id[$ownerMetadata->getFieldForColumn($value)] : $id[$ownerMetadata->fieldNames[$value]]; |
| 141 | $paramTypes[] = PersisterHelper::getTypeOfColumn($value, $ownerMetadata, $this->em); |
| 142 | } |
| 143 | $parameters = $this->expandCriteriaParameters($criteria); |
| 144 | foreach ($parameters as $parameter) { |
| 145 | [$name, $value, $operator] = $parameter; |
| 146 | $field = $this->quoteStrategy->getColumnName($name, $targetClass, $this->platform); |
| 147 | if ($value === null && ($operator === Comparison::EQ || $operator === Comparison::NEQ)) { |
| 148 | $whereClauses[] = sprintf('te.%s %s NULL', $field, $operator === Comparison::EQ ? 'IS' : 'IS NOT'); |
| 149 | } elseif ($operator === Comparison::IN || $operator === Comparison::NIN) { |
| 150 | $whereClauses[] = sprintf('te.%s %s (%s)', $field, $operator === Comparison::IN ? 'IN' : 'NOT IN', implode(', ', array_fill(0, count($value), '?'))); |
| 151 | foreach ($value as $item) { |
| 152 | $params = array_merge($params, PersisterHelper::convertToParameterValue($item, $this->em)); |
| 153 | $paramTypes = array_merge($paramTypes, PersisterHelper::inferParameterTypes($name, $item, $targetClass, $this->em)); |
| 154 | } |
| 155 | } else { |
| 156 | $whereClauses[] = sprintf('te.%s %s ?', $field, $operator); |
| 157 | $params = array_merge($params, PersisterHelper::convertToParameterValue($value, $this->em)); |
| 158 | $paramTypes = array_merge($paramTypes, PersisterHelper::inferParameterTypes($name, $value, $targetClass, $this->em)); |
| 159 | } |
| 160 | } |
| 161 | $tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform); |
| 162 | $joinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform); |
| 163 | $rsm = new Query\ResultSetMappingBuilder($this->em); |
| 164 | $rsm->addRootEntityFromClassMetadata($targetClass->name, 'te'); |
| 165 | $sql = 'SELECT ' . $rsm->generateSelectClause() . ' FROM ' . $tableName . ' te' . ' JOIN ' . $joinTable . ' t ON' . implode(' AND ', $onConditions) . ' WHERE ' . implode(' AND ', $whereClauses); |
| 166 | $sql .= $this->getOrderingSql($criteria, $targetClass); |
| 167 | $sql .= $this->getLimitSql($criteria); |
| 168 | $stmt = $this->conn->executeQuery($sql, $params, $paramTypes); |
| 169 | return $this->em->newHydrator(Query::HYDRATE_OBJECT)->hydrateAll($stmt, $rsm); |
| 170 | } |
| 171 | public function getFilterSql($mapping) |
| 172 | { |
| 173 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
| 174 | $rootClass = $this->em->getClassMetadata($targetClass->rootEntityName); |
| 175 | $filterSql = $this->generateFilterConditionSQL($rootClass, 'te'); |
| 176 | if ($filterSql === '') { |
| 177 | return ['', '']; |
| 178 | } |
| 179 | // A join is needed if there is filtering on the target entity |
| 180 | $tableName = $this->quoteStrategy->getTableName($rootClass, $this->platform); |
| 181 | $joinSql = ' JOIN ' . $tableName . ' te' . ' ON' . implode(' AND ', $this->getOnConditionSQL($mapping)); |
| 182 | return [$joinSql, $filterSql]; |
| 183 | } |
| 184 | protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias) |
| 185 | { |
| 186 | $filterClauses = []; |
| 187 | foreach ($this->em->getFilters()->getEnabledFilters() as $filter) { |
| 188 | $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias); |
| 189 | if ($filterExpr) { |
| 190 | $filterClauses[] = '(' . $filterExpr . ')'; |
| 191 | } |
| 192 | } |
| 193 | return $filterClauses ? '(' . implode(' AND ', $filterClauses) . ')' : ''; |
| 194 | } |
| 195 | protected function getOnConditionSQL($mapping) |
| 196 | { |
| 197 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
| 198 | $association = !$mapping['isOwningSide'] ? $targetClass->associationMappings[$mapping['mappedBy']] : $mapping; |
| 199 | $joinColumns = $mapping['isOwningSide'] ? $association['joinTable']['inverseJoinColumns'] : $association['joinTable']['joinColumns']; |
| 200 | $conditions = []; |
| 201 | foreach ($joinColumns as $joinColumn) { |
| 202 | $joinColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); |
| 203 | $refColumnName = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform); |
| 204 | $conditions[] = ' t.' . $joinColumnName . ' = te.' . $refColumnName; |
| 205 | } |
| 206 | return $conditions; |
| 207 | } |
| 208 | protected function getDeleteSQL(PersistentCollection $collection) |
| 209 | { |
| 210 | $columns = []; |
| 211 | $mapping = $collection->getMapping(); |
| 212 | $class = $this->em->getClassMetadata(get_class($collection->getOwner())); |
| 213 | $joinTable = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform); |
| 214 | foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { |
| 215 | $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); |
| 216 | } |
| 217 | return 'DELETE FROM ' . $joinTable . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; |
| 218 | } |
| 219 | protected function getDeleteSQLParameters(PersistentCollection $collection) |
| 220 | { |
| 221 | $mapping = $collection->getMapping(); |
| 222 | $identifier = $this->uow->getEntityIdentifier($collection->getOwner()); |
| 223 | // Optimization for single column identifier |
| 224 | if (count($mapping['relationToSourceKeyColumns']) === 1) { |
| 225 | return [reset($identifier)]; |
| 226 | } |
| 227 | // Composite identifier |
| 228 | $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); |
| 229 | $params = []; |
| 230 | foreach ($mapping['relationToSourceKeyColumns'] as $columnName => $refColumnName) { |
| 231 | $params[] = isset($sourceClass->fieldNames[$refColumnName]) ? $identifier[$sourceClass->fieldNames[$refColumnName]] : $identifier[$sourceClass->getFieldForColumn($refColumnName)]; |
| 232 | } |
| 233 | return $params; |
| 234 | } |
| 235 | protected function getDeleteRowSQL(PersistentCollection $collection) |
| 236 | { |
| 237 | $mapping = $collection->getMapping(); |
| 238 | $class = $this->em->getClassMetadata($mapping['sourceEntity']); |
| 239 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
| 240 | $columns = []; |
| 241 | $types = []; |
| 242 | foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { |
| 243 | $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); |
| 244 | $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em); |
| 245 | } |
| 246 | foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) { |
| 247 | $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); |
| 248 | $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em); |
| 249 | } |
| 250 | return ['DELETE FROM ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform) . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?', $types]; |
| 251 | } |
| 252 | protected function getDeleteRowSQLParameters(PersistentCollection $collection, $element) |
| 253 | { |
| 254 | return $this->collectJoinTableColumnParameters($collection, $element); |
| 255 | } |
| 256 | protected function getInsertRowSQL(PersistentCollection $collection) |
| 257 | { |
| 258 | $columns = []; |
| 259 | $types = []; |
| 260 | $mapping = $collection->getMapping(); |
| 261 | $class = $this->em->getClassMetadata($mapping['sourceEntity']); |
| 262 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
| 263 | foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { |
| 264 | $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); |
| 265 | $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em); |
| 266 | } |
| 267 | foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) { |
| 268 | $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); |
| 269 | $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em); |
| 270 | } |
| 271 | return ['INSERT INTO ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform) . ' (' . implode(', ', $columns) . ')' . ' VALUES' . ' (' . implode(', ', array_fill(0, count($columns), '?')) . ')', $types]; |
| 272 | } |
| 273 | protected function getInsertRowSQLParameters(PersistentCollection $collection, $element) |
| 274 | { |
| 275 | return $this->collectJoinTableColumnParameters($collection, $element); |
| 276 | } |
| 277 | private function collectJoinTableColumnParameters(PersistentCollection $collection, $element) : array |
| 278 | { |
| 279 | $params = []; |
| 280 | $mapping = $collection->getMapping(); |
| 281 | $isComposite = count($mapping['joinTableColumns']) > 2; |
| 282 | $identifier1 = $this->uow->getEntityIdentifier($collection->getOwner()); |
| 283 | $identifier2 = $this->uow->getEntityIdentifier($element); |
| 284 | $class1 = $class2 = null; |
| 285 | if ($isComposite) { |
| 286 | $class1 = $this->em->getClassMetadata(get_class($collection->getOwner())); |
| 287 | $class2 = $collection->getTypeClass(); |
| 288 | } |
| 289 | foreach ($mapping['joinTableColumns'] as $joinTableColumn) { |
| 290 | $isRelationToSource = isset($mapping['relationToSourceKeyColumns'][$joinTableColumn]); |
| 291 | if (!$isComposite) { |
| 292 | $params[] = $isRelationToSource ? array_pop($identifier1) : array_pop($identifier2); |
| 293 | continue; |
| 294 | } |
| 295 | if ($isRelationToSource) { |
| 296 | $params[] = $identifier1[$class1->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])]; |
| 297 | continue; |
| 298 | } |
| 299 | $params[] = $identifier2[$class2->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])]; |
| 300 | } |
| 301 | return $params; |
| 302 | } |
| 303 | private function getJoinTableRestrictionsWithKey(PersistentCollection $collection, string $key, bool $addFilters) : array |
| 304 | { |
| 305 | $filterMapping = $collection->getMapping(); |
| 306 | $mapping = $filterMapping; |
| 307 | $indexBy = $mapping['indexBy']; |
| 308 | $id = $this->uow->getEntityIdentifier($collection->getOwner()); |
| 309 | $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); |
| 310 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
| 311 | if (!$mapping['isOwningSide']) { |
| 312 | $associationSourceClass = $this->em->getClassMetadata($mapping['targetEntity']); |
| 313 | $mapping = $associationSourceClass->associationMappings[$mapping['mappedBy']]; |
| 314 | $joinColumns = $mapping['joinTable']['joinColumns']; |
| 315 | $sourceRelationMode = 'relationToTargetKeyColumns'; |
| 316 | $targetRelationMode = 'relationToSourceKeyColumns'; |
| 317 | } else { |
| 318 | $associationSourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); |
| 319 | $joinColumns = $mapping['joinTable']['inverseJoinColumns']; |
| 320 | $sourceRelationMode = 'relationToSourceKeyColumns'; |
| 321 | $targetRelationMode = 'relationToTargetKeyColumns'; |
| 322 | } |
| 323 | $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform) . ' t'; |
| 324 | $whereClauses = []; |
| 325 | $params = []; |
| 326 | $types = []; |
| 327 | $joinNeeded = !in_array($indexBy, $targetClass->identifier, \true); |
| 328 | if ($joinNeeded) { |
| 329 | // extra join needed if indexBy is not a @id |
| 330 | $joinConditions = []; |
| 331 | foreach ($joinColumns as $joinTableColumn) { |
| 332 | $joinConditions[] = 't.' . $joinTableColumn['name'] . ' = tr.' . $joinTableColumn['referencedColumnName']; |
| 333 | } |
| 334 | $tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform); |
| 335 | $quotedJoinTable .= ' JOIN ' . $tableName . ' tr ON ' . implode(' AND ', $joinConditions); |
| 336 | $columnName = $targetClass->getColumnName($indexBy); |
| 337 | $whereClauses[] = 'tr.' . $columnName . ' = ?'; |
| 338 | $params[] = $key; |
| 339 | $types[] = PersisterHelper::getTypeOfColumn($columnName, $targetClass, $this->em); |
| 340 | } |
| 341 | foreach ($mapping['joinTableColumns'] as $joinTableColumn) { |
| 342 | if (isset($mapping[$sourceRelationMode][$joinTableColumn])) { |
| 343 | $column = $mapping[$sourceRelationMode][$joinTableColumn]; |
| 344 | $whereClauses[] = 't.' . $joinTableColumn . ' = ?'; |
| 345 | $params[] = $sourceClass->containsForeignIdentifier ? $id[$sourceClass->getFieldForColumn($column)] : $id[$sourceClass->fieldNames[$column]]; |
| 346 | $types[] = PersisterHelper::getTypeOfColumn($column, $sourceClass, $this->em); |
| 347 | } elseif (!$joinNeeded) { |
| 348 | $column = $mapping[$targetRelationMode][$joinTableColumn]; |
| 349 | $whereClauses[] = 't.' . $joinTableColumn . ' = ?'; |
| 350 | $params[] = $key; |
| 351 | $types[] = PersisterHelper::getTypeOfColumn($column, $targetClass, $this->em); |
| 352 | } |
| 353 | } |
| 354 | if ($addFilters) { |
| 355 | [$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($filterMapping); |
| 356 | if ($filterSql) { |
| 357 | $quotedJoinTable .= ' ' . $joinTargetEntitySQL; |
| 358 | $whereClauses[] = $filterSql; |
| 359 | } |
| 360 | } |
| 361 | return [$quotedJoinTable, $whereClauses, $params, $types]; |
| 362 | } |
| 363 | private function getJoinTableRestrictions(PersistentCollection $collection, $element, bool $addFilters) : array |
| 364 | { |
| 365 | $filterMapping = $collection->getMapping(); |
| 366 | $mapping = $filterMapping; |
| 367 | if (!$mapping['isOwningSide']) { |
| 368 | $sourceClass = $this->em->getClassMetadata($mapping['targetEntity']); |
| 369 | $targetClass = $this->em->getClassMetadata($mapping['sourceEntity']); |
| 370 | $sourceId = $this->uow->getEntityIdentifier($element); |
| 371 | $targetId = $this->uow->getEntityIdentifier($collection->getOwner()); |
| 372 | $mapping = $sourceClass->associationMappings[$mapping['mappedBy']]; |
| 373 | } else { |
| 374 | $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); |
| 375 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
| 376 | $sourceId = $this->uow->getEntityIdentifier($collection->getOwner()); |
| 377 | $targetId = $this->uow->getEntityIdentifier($element); |
| 378 | } |
| 379 | $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $sourceClass, $this->platform); |
| 380 | $whereClauses = []; |
| 381 | $params = []; |
| 382 | $types = []; |
| 383 | foreach ($mapping['joinTableColumns'] as $joinTableColumn) { |
| 384 | $whereClauses[] = ($addFilters ? 't.' : '') . $joinTableColumn . ' = ?'; |
| 385 | if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) { |
| 386 | $targetColumn = $mapping['relationToTargetKeyColumns'][$joinTableColumn]; |
| 387 | $params[] = $targetId[$targetClass->getFieldForColumn($targetColumn)]; |
| 388 | $types[] = PersisterHelper::getTypeOfColumn($targetColumn, $targetClass, $this->em); |
| 389 | continue; |
| 390 | } |
| 391 | // relationToSourceKeyColumns |
| 392 | $targetColumn = $mapping['relationToSourceKeyColumns'][$joinTableColumn]; |
| 393 | $params[] = $sourceId[$sourceClass->getFieldForColumn($targetColumn)]; |
| 394 | $types[] = PersisterHelper::getTypeOfColumn($targetColumn, $sourceClass, $this->em); |
| 395 | } |
| 396 | if ($addFilters) { |
| 397 | $quotedJoinTable .= ' t'; |
| 398 | [$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($filterMapping); |
| 399 | if ($filterSql) { |
| 400 | $quotedJoinTable .= ' ' . $joinTargetEntitySQL; |
| 401 | $whereClauses[] = $filterSql; |
| 402 | } |
| 403 | } |
| 404 | return [$quotedJoinTable, $whereClauses, $params, $types]; |
| 405 | } |
| 406 | private function expandCriteriaParameters(Criteria $criteria) : array |
| 407 | { |
| 408 | $expression = $criteria->getWhereExpression(); |
| 409 | if ($expression === null) { |
| 410 | return []; |
| 411 | } |
| 412 | $valueVisitor = new SqlValueVisitor(); |
| 413 | $valueVisitor->dispatch($expression); |
| 414 | [, $types] = $valueVisitor->getParamsAndTypes(); |
| 415 | return $types; |
| 416 | } |
| 417 | private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass) : string |
| 418 | { |
| 419 | $orderings = self::getCriteriaOrderings($criteria); |
| 420 | if ($orderings) { |
| 421 | $orderBy = []; |
| 422 | foreach ($orderings as $name => $direction) { |
| 423 | $field = $this->quoteStrategy->getColumnName($name, $targetClass, $this->platform); |
| 424 | $orderBy[] = $field . ' ' . $direction; |
| 425 | } |
| 426 | return ' ORDER BY ' . implode(', ', $orderBy); |
| 427 | } |
| 428 | return ''; |
| 429 | } |
| 430 | private function getLimitSql(Criteria $criteria) : string |
| 431 | { |
| 432 | $limit = $criteria->getMaxResults(); |
| 433 | $offset = $criteria->getFirstResult(); |
| 434 | return $this->platform->modifyLimitQuery('', $limit, $offset ?? 0); |
| 435 | } |
| 436 | } |
| 437 |