mailpoet
/
vendor-prefixed
/
doctrine
/
orm
/
src
/
Persisters
/
Collection
/
OneToManyPersister.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
OneToManyPersister.php
169 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\DBAL\Exception as DBALException; |
| 8 | use MailPoetVendor\Doctrine\DBAL\Types\Type; |
| 9 | use MailPoetVendor\Doctrine\ORM\EntityNotFoundException; |
| 10 | use MailPoetVendor\Doctrine\ORM\Mapping\MappingException; |
| 11 | use MailPoetVendor\Doctrine\ORM\PersistentCollection; |
| 12 | use MailPoetVendor\Doctrine\ORM\Utility\PersisterHelper; |
| 13 | use function array_fill; |
| 14 | use function array_keys; |
| 15 | use function array_merge; |
| 16 | use function array_reverse; |
| 17 | use function array_values; |
| 18 | use function assert; |
| 19 | use function count; |
| 20 | use function implode; |
| 21 | use function is_int; |
| 22 | use function is_string; |
| 23 | class OneToManyPersister extends AbstractCollectionPersister |
| 24 | { |
| 25 | public function delete(PersistentCollection $collection) |
| 26 | { |
| 27 | // The only valid case here is when you have weak entities. In this |
| 28 | // scenario, you have @OneToMany with orphanRemoval=true, and replacing |
| 29 | // the entire collection with a new would trigger this operation. |
| 30 | $mapping = $collection->getMapping(); |
| 31 | if (!$mapping['orphanRemoval']) { |
| 32 | // Handling non-orphan removal should never happen, as @OneToMany |
| 33 | // can only be inverse side. For owning side one to many, it is |
| 34 | // required to have a join table, which would classify as a ManyToManyPersister. |
| 35 | return; |
| 36 | } |
| 37 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
| 38 | return $targetClass->isInheritanceTypeJoined() ? $this->deleteJoinedEntityCollection($collection) : $this->deleteEntityCollection($collection); |
| 39 | } |
| 40 | public function update(PersistentCollection $collection) |
| 41 | { |
| 42 | // This can never happen. One to many can only be inverse side. |
| 43 | // For owning side one to many, it is required to have a join table, |
| 44 | // then classifying it as a ManyToManyPersister. |
| 45 | return; |
| 46 | } |
| 47 | public function get(PersistentCollection $collection, $index) |
| 48 | { |
| 49 | $mapping = $collection->getMapping(); |
| 50 | if (!isset($mapping['indexBy'])) { |
| 51 | throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.'); |
| 52 | } |
| 53 | $persister = $this->uow->getEntityPersister($mapping['targetEntity']); |
| 54 | return $persister->load([$mapping['mappedBy'] => $collection->getOwner(), $mapping['indexBy'] => $index], null, $mapping, [], null, 1); |
| 55 | } |
| 56 | public function count(PersistentCollection $collection) |
| 57 | { |
| 58 | $mapping = $collection->getMapping(); |
| 59 | $persister = $this->uow->getEntityPersister($mapping['targetEntity']); |
| 60 | // only works with single id identifier entities. Will throw an |
| 61 | // exception in Entity Persisters if that is not the case for the |
| 62 | // 'mappedBy' field. |
| 63 | $criteria = Criteria::create(\true)->where(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner())); |
| 64 | return $persister->count($criteria); |
| 65 | } |
| 66 | public function slice(PersistentCollection $collection, $offset, $length = null) |
| 67 | { |
| 68 | $mapping = $collection->getMapping(); |
| 69 | $persister = $this->uow->getEntityPersister($mapping['targetEntity']); |
| 70 | return $persister->getOneToManyCollection($mapping, $collection->getOwner(), $offset, $length); |
| 71 | } |
| 72 | public function containsKey(PersistentCollection $collection, $key) |
| 73 | { |
| 74 | $mapping = $collection->getMapping(); |
| 75 | if (!isset($mapping['indexBy'])) { |
| 76 | throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.'); |
| 77 | } |
| 78 | $persister = $this->uow->getEntityPersister($mapping['targetEntity']); |
| 79 | // only works with single id identifier entities. Will throw an |
| 80 | // exception in Entity Persisters if that is not the case for the |
| 81 | // 'mappedBy' field. |
| 82 | $criteria = Criteria::create(\true); |
| 83 | $criteria->andWhere(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner())); |
| 84 | $criteria->andWhere(Criteria::expr()->eq($mapping['indexBy'], $key)); |
| 85 | return (bool) $persister->count($criteria); |
| 86 | } |
| 87 | public function contains(PersistentCollection $collection, $element) |
| 88 | { |
| 89 | if (!$this->isValidEntityState($element)) { |
| 90 | return \false; |
| 91 | } |
| 92 | $mapping = $collection->getMapping(); |
| 93 | $persister = $this->uow->getEntityPersister($mapping['targetEntity']); |
| 94 | // only works with single id identifier entities. Will throw an |
| 95 | // exception in Entity Persisters if that is not the case for the |
| 96 | // 'mappedBy' field. |
| 97 | $criteria = Criteria::create(\true)->where(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner())); |
| 98 | return $persister->exists($element, $criteria); |
| 99 | } |
| 100 | public function loadCriteria(PersistentCollection $collection, Criteria $criteria) |
| 101 | { |
| 102 | throw new BadMethodCallException('Filtering a collection by Criteria is not supported by this CollectionPersister.'); |
| 103 | } |
| 104 | private function deleteEntityCollection(PersistentCollection $collection) : int |
| 105 | { |
| 106 | $mapping = $collection->getMapping(); |
| 107 | $identifier = $this->uow->getEntityIdentifier($collection->getOwner()); |
| 108 | $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); |
| 109 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
| 110 | $columns = []; |
| 111 | $parameters = []; |
| 112 | $types = []; |
| 113 | foreach ($targetClass->associationMappings[$mapping['mappedBy']]['joinColumns'] as $joinColumn) { |
| 114 | $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); |
| 115 | $parameters[] = $identifier[$sourceClass->getFieldForColumn($joinColumn['referencedColumnName'])]; |
| 116 | $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $sourceClass, $this->em); |
| 117 | } |
| 118 | $statement = 'DELETE FROM ' . $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; |
| 119 | if ($targetClass->isInheritanceTypeSingleTable()) { |
| 120 | $discriminatorColumn = $targetClass->getDiscriminatorColumn(); |
| 121 | $discriminatorValues = $targetClass->discriminatorValue ? [$targetClass->discriminatorValue] : array_keys($targetClass->discriminatorMap); |
| 122 | $statement .= ' AND ' . $discriminatorColumn['name'] . ' IN (' . implode(', ', array_fill(0, count($discriminatorValues), '?')) . ')'; |
| 123 | foreach ($discriminatorValues as $discriminatorValue) { |
| 124 | $parameters[] = $discriminatorValue; |
| 125 | $types[] = $discriminatorColumn['type']; |
| 126 | } |
| 127 | } |
| 128 | $numAffected = $this->conn->executeStatement($statement, $parameters, $types); |
| 129 | assert(is_int($numAffected)); |
| 130 | return $numAffected; |
| 131 | } |
| 132 | private function deleteJoinedEntityCollection(PersistentCollection $collection) : int |
| 133 | { |
| 134 | $mapping = $collection->getMapping(); |
| 135 | $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); |
| 136 | $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); |
| 137 | $rootClass = $this->em->getClassMetadata($targetClass->rootEntityName); |
| 138 | // 1) Build temporary table DDL |
| 139 | $tempTable = $this->platform->getTemporaryTableName($rootClass->getTemporaryIdTableName()); |
| 140 | $idColumnNames = $rootClass->getIdentifierColumnNames(); |
| 141 | $idColumnList = implode(', ', $idColumnNames); |
| 142 | $columnDefinitions = []; |
| 143 | foreach ($idColumnNames as $idColumnName) { |
| 144 | $columnDefinitions[$idColumnName] = ['notnull' => \true, 'type' => Type::getType(PersisterHelper::getTypeOfColumn($idColumnName, $rootClass, $this->em))]; |
| 145 | } |
| 146 | $statement = $this->platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' . $this->platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; |
| 147 | $this->conn->executeStatement($statement); |
| 148 | // 2) Build insert table records into temporary table |
| 149 | $query = $this->em->createQuery(' SELECT t0.' . implode(', t0.', $rootClass->getIdentifierFieldNames()) . ' FROM ' . $targetClass->name . ' t0 WHERE t0.' . $mapping['mappedBy'] . ' = :owner')->setParameter('owner', $collection->getOwner()); |
| 150 | $sql = $query->getSQL(); |
| 151 | assert(is_string($sql)); |
| 152 | $statement = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ') ' . $sql; |
| 153 | $parameters = array_values($sourceClass->getIdentifierValues($collection->getOwner())); |
| 154 | $numDeleted = $this->conn->executeStatement($statement, $parameters); |
| 155 | // 3) Delete records on each table in the hierarchy |
| 156 | $classNames = array_merge($targetClass->parentClasses, [$targetClass->name], $targetClass->subClasses); |
| 157 | foreach (array_reverse($classNames) as $className) { |
| 158 | $tableName = $this->quoteStrategy->getTableName($this->em->getClassMetadata($className), $this->platform); |
| 159 | $statement = 'DELETE FROM ' . $tableName . ' WHERE (' . $idColumnList . ')' . ' IN (SELECT ' . $idColumnList . ' FROM ' . $tempTable . ')'; |
| 160 | $this->conn->executeStatement($statement); |
| 161 | } |
| 162 | // 4) Drop temporary table |
| 163 | $statement = $this->platform->getDropTemporaryTableSQL($tempTable); |
| 164 | $this->conn->executeStatement($statement); |
| 165 | assert(is_int($numDeleted)); |
| 166 | return $numDeleted; |
| 167 | } |
| 168 | } |
| 169 |