From bf6655a534a6775d30cafa67bd801276bda1d98d Mon Sep 17 00:00:00 2001 From: polo Date: Tue, 13 Aug 2024 23:45:21 +0200 Subject: =?UTF-8?q?VERSION=200.2=20doctrine=20ORM=20et=20entit=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Persisters/Collection/OneToManyPersister.php | 264 +++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 vendor/doctrine/orm/src/Persisters/Collection/OneToManyPersister.php (limited to 'vendor/doctrine/orm/src/Persisters/Collection/OneToManyPersister.php') diff --git a/vendor/doctrine/orm/src/Persisters/Collection/OneToManyPersister.php b/vendor/doctrine/orm/src/Persisters/Collection/OneToManyPersister.php new file mode 100644 index 0000000..0727b1f --- /dev/null +++ b/vendor/doctrine/orm/src/Persisters/Collection/OneToManyPersister.php @@ -0,0 +1,264 @@ +getMapping($collection); + + if (! $mapping->orphanRemoval) { + // Handling non-orphan removal should never happen, as @OneToMany + // can only be inverse side. For owning side one to many, it is + // required to have a join table, which would classify as a ManyToManyPersister. + return; + } + + $targetClass = $this->em->getClassMetadata($mapping->targetEntity); + + $targetClass->isInheritanceTypeJoined() + ? $this->deleteJoinedEntityCollection($collection) + : $this->deleteEntityCollection($collection); + } + + public function update(PersistentCollection $collection): void + { + // This can never happen. One to many can only be inverse side. + // For owning side one to many, it is required to have a join table, + // then classifying it as a ManyToManyPersister. + return; + } + + public function get(PersistentCollection $collection, mixed $index): object|null + { + $mapping = $this->getMapping($collection); + + if (! $mapping->isIndexed()) { + throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.'); + } + + $persister = $this->uow->getEntityPersister($mapping->targetEntity); + + return $persister->load( + [ + $mapping->mappedBy => $collection->getOwner(), + $mapping->indexBy() => $index, + ], + null, + $mapping, + [], + null, + 1, + ); + } + + public function count(PersistentCollection $collection): int + { + $mapping = $this->getMapping($collection); + $persister = $this->uow->getEntityPersister($mapping->targetEntity); + + // only works with single id identifier entities. Will throw an + // exception in Entity Persisters if that is not the case for the + // 'mappedBy' field. + $criteria = new Criteria(Criteria::expr()->eq($mapping->mappedBy, $collection->getOwner())); + + return $persister->count($criteria); + } + + /** + * {@inheritDoc} + */ + public function slice(PersistentCollection $collection, int $offset, int|null $length = null): array + { + $mapping = $this->getMapping($collection); + $persister = $this->uow->getEntityPersister($mapping->targetEntity); + + return $persister->getOneToManyCollection($mapping, $collection->getOwner(), $offset, $length); + } + + public function containsKey(PersistentCollection $collection, mixed $key): bool + { + $mapping = $this->getMapping($collection); + + if (! $mapping->isIndexed()) { + throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.'); + } + + $persister = $this->uow->getEntityPersister($mapping->targetEntity); + + // only works with single id identifier entities. Will throw an + // exception in Entity Persisters if that is not the case for the + // 'mappedBy' field. + $criteria = new Criteria(); + + $criteria->andWhere(Criteria::expr()->eq($mapping->mappedBy, $collection->getOwner())); + $criteria->andWhere(Criteria::expr()->eq($mapping->indexBy(), $key)); + + return (bool) $persister->count($criteria); + } + + public function contains(PersistentCollection $collection, object $element): bool + { + if (! $this->isValidEntityState($element)) { + return false; + } + + $mapping = $this->getMapping($collection); + $persister = $this->uow->getEntityPersister($mapping->targetEntity); + + // only works with single id identifier entities. Will throw an + // exception in Entity Persisters if that is not the case for the + // 'mappedBy' field. + $criteria = new Criteria(Criteria::expr()->eq($mapping->mappedBy, $collection->getOwner())); + + return $persister->exists($element, $criteria); + } + + /** + * {@inheritDoc} + */ + public function loadCriteria(PersistentCollection $collection, Criteria $criteria): array + { + throw new BadMethodCallException('Filtering a collection by Criteria is not supported by this CollectionPersister.'); + } + + /** + * @throws DBALException + * @throws EntityNotFoundException + * @throws MappingException + */ + private function deleteEntityCollection(PersistentCollection $collection): int + { + $mapping = $this->getMapping($collection); + $identifier = $this->uow->getEntityIdentifier($collection->getOwner()); + $sourceClass = $this->em->getClassMetadata($mapping->sourceEntity); + $targetClass = $this->em->getClassMetadata($mapping->targetEntity); + $columns = []; + $parameters = []; + $types = []; + + foreach ($this->em->getMetadataFactory()->getOwningSide($mapping)->joinColumns as $joinColumn) { + $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); + $parameters[] = $identifier[$sourceClass->getFieldForColumn($joinColumn->referencedColumnName)]; + $types[] = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $sourceClass, $this->em); + } + + $statement = 'DELETE FROM ' . $this->quoteStrategy->getTableName($targetClass, $this->platform) + . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; + + if ($targetClass->isInheritanceTypeSingleTable()) { + $discriminatorColumn = $targetClass->getDiscriminatorColumn(); + $statement .= ' AND ' . $discriminatorColumn->name . ' = ?'; + $parameters[] = $targetClass->discriminatorValue; + $types[] = $discriminatorColumn->type; + } + + $numAffected = $this->conn->executeStatement($statement, $parameters, $types); + + assert(is_int($numAffected)); + + return $numAffected; + } + + /** + * Delete Class Table Inheritance entities. + * A temporary table is needed to keep IDs to be deleted in both parent and child class' tables. + * + * Thanks Steve Ebersole (Hibernate) for idea on how to tackle reliably this scenario, we owe him a beer! =) + * + * @throws DBALException + */ + private function deleteJoinedEntityCollection(PersistentCollection $collection): int + { + $mapping = $this->getMapping($collection); + $sourceClass = $this->em->getClassMetadata($mapping->sourceEntity); + $targetClass = $this->em->getClassMetadata($mapping->targetEntity); + $rootClass = $this->em->getClassMetadata($targetClass->rootEntityName); + + // 1) Build temporary table DDL + $tempTable = $this->platform->getTemporaryTableName($rootClass->getTemporaryIdTableName()); + $idColumnNames = $rootClass->getIdentifierColumnNames(); + $idColumnList = implode(', ', $idColumnNames); + $columnDefinitions = []; + + foreach ($idColumnNames as $idColumnName) { + $columnDefinitions[$idColumnName] = [ + 'name' => $idColumnName, + 'notnull' => true, + 'type' => Type::getType(PersisterHelper::getTypeOfColumn($idColumnName, $rootClass, $this->em)), + ]; + } + + $statement = $this->platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable + . ' (' . $this->platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; + + $this->conn->executeStatement($statement); + + // 2) Build insert table records into temporary table + $query = $this->em->createQuery( + ' SELECT t0.' . implode(', t0.', $rootClass->getIdentifierFieldNames()) + . ' FROM ' . $targetClass->name . ' t0 WHERE t0.' . $mapping->mappedBy . ' = :owner', + )->setParameter('owner', $collection->getOwner()); + + $sql = $query->getSQL(); + assert(is_string($sql)); + $statement = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ') ' . $sql; + $parameters = array_values($sourceClass->getIdentifierValues($collection->getOwner())); + $numDeleted = $this->conn->executeStatement($statement, $parameters); + + // 3) Delete records on each table in the hierarchy + $classNames = [...$targetClass->parentClasses, ...[$targetClass->name], ...$targetClass->subClasses]; + + foreach (array_reverse($classNames) as $className) { + $tableName = $this->quoteStrategy->getTableName($this->em->getClassMetadata($className), $this->platform); + $statement = 'DELETE FROM ' . $tableName . ' WHERE (' . $idColumnList . ')' + . ' IN (SELECT ' . $idColumnList . ' FROM ' . $tempTable . ')'; + + $this->conn->executeStatement($statement); + } + + // 4) Drop temporary table + $statement = $this->platform->getDropTemporaryTableSQL($tempTable); + + $this->conn->executeStatement($statement); + + assert(is_int($numDeleted)); + + return $numDeleted; + } + + private function getMapping(PersistentCollection $collection): OneToManyAssociationMapping + { + $mapping = $collection->getMapping(); + + assert($mapping->isOneToMany()); + + return $mapping; + } +} -- cgit v1.2.3