vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Collection/OneToManyPersister.php line 102

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ORM\Persisters\Collection;
  4. use BadMethodCallException;
  5. use Doctrine\Common\Collections\Criteria;
  6. use Doctrine\DBAL\Exception as DBALException;
  7. use Doctrine\DBAL\Types\Type;
  8. use Doctrine\ORM\PersistentCollection;
  9. use Doctrine\ORM\Utility\PersisterHelper;
  10. use function array_merge;
  11. use function array_reverse;
  12. use function array_values;
  13. use function assert;
  14. use function implode;
  15. use function is_string;
  16. /**
  17.  * Persister for one-to-many collections.
  18.  */
  19. class OneToManyPersister extends AbstractCollectionPersister
  20. {
  21.     /**
  22.      * {@inheritdoc}
  23.      *
  24.      * @return int|null
  25.      */
  26.     public function delete(PersistentCollection $collection)
  27.     {
  28.         // The only valid case here is when you have weak entities. In this
  29.         // scenario, you have @OneToMany with orphanRemoval=true, and replacing
  30.         // the entire collection with a new would trigger this operation.
  31.         $mapping $collection->getMapping();
  32.         if (! $mapping['orphanRemoval']) {
  33.             // Handling non-orphan removal should never happen, as @OneToMany
  34.             // can only be inverse side. For owning side one to many, it is
  35.             // required to have a join table, which would classify as a ManyToManyPersister.
  36.             return;
  37.         }
  38.         $targetClass $this->em->getClassMetadata($mapping['targetEntity']);
  39.         return $targetClass->isInheritanceTypeJoined()
  40.             ? $this->deleteJoinedEntityCollection($collection)
  41.             : $this->deleteEntityCollection($collection);
  42.     }
  43.     /**
  44.      * {@inheritdoc}
  45.      */
  46.     public function update(PersistentCollection $collection)
  47.     {
  48.         // This can never happen. One to many can only be inverse side.
  49.         // For owning side one to many, it is required to have a join table,
  50.         // then classifying it as a ManyToManyPersister.
  51.         return;
  52.     }
  53.     /**
  54.      * {@inheritdoc}
  55.      */
  56.     public function get(PersistentCollection $collection$index)
  57.     {
  58.         $mapping $collection->getMapping();
  59.         if (! isset($mapping['indexBy'])) {
  60.             throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.');
  61.         }
  62.         $persister $this->uow->getEntityPersister($mapping['targetEntity']);
  63.         return $persister->load(
  64.             [
  65.                 $mapping['mappedBy'] => $collection->getOwner(),
  66.                 $mapping['indexBy']  => $index,
  67.             ],
  68.             null,
  69.             $mapping,
  70.             [],
  71.             null,
  72.             1
  73.         );
  74.     }
  75.     /**
  76.      * {@inheritdoc}
  77.      */
  78.     public function count(PersistentCollection $collection)
  79.     {
  80.         $mapping   $collection->getMapping();
  81.         $persister $this->uow->getEntityPersister($mapping['targetEntity']);
  82.         // only works with single id identifier entities. Will throw an
  83.         // exception in Entity Persisters if that is not the case for the
  84.         // 'mappedBy' field.
  85.         $criteria = new Criteria(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner()));
  86.         return $persister->count($criteria);
  87.     }
  88.     /**
  89.      * {@inheritdoc}
  90.      */
  91.     public function slice(PersistentCollection $collection$offset$length null)
  92.     {
  93.         $mapping   $collection->getMapping();
  94.         $persister $this->uow->getEntityPersister($mapping['targetEntity']);
  95.         return $persister->getOneToManyCollection($mapping$collection->getOwner(), $offset$length);
  96.     }
  97.     /**
  98.      * {@inheritdoc}
  99.      */
  100.     public function containsKey(PersistentCollection $collection$key)
  101.     {
  102.         $mapping $collection->getMapping();
  103.         if (! isset($mapping['indexBy'])) {
  104.             throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.');
  105.         }
  106.         $persister $this->uow->getEntityPersister($mapping['targetEntity']);
  107.         // only works with single id identifier entities. Will throw an
  108.         // exception in Entity Persisters if that is not the case for the
  109.         // 'mappedBy' field.
  110.         $criteria = new Criteria();
  111.         $criteria->andWhere(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner()));
  112.         $criteria->andWhere(Criteria::expr()->eq($mapping['indexBy'], $key));
  113.         return (bool) $persister->count($criteria);
  114.     }
  115.      /**
  116.       * {@inheritdoc}
  117.       */
  118.     public function contains(PersistentCollection $collection$element)
  119.     {
  120.         if (! $this->isValidEntityState($element)) {
  121.             return false;
  122.         }
  123.         $mapping   $collection->getMapping();
  124.         $persister $this->uow->getEntityPersister($mapping['targetEntity']);
  125.         // only works with single id identifier entities. Will throw an
  126.         // exception in Entity Persisters if that is not the case for the
  127.         // 'mappedBy' field.
  128.         $criteria = new Criteria(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner()));
  129.         return $persister->exists($element$criteria);
  130.     }
  131.     /**
  132.      * {@inheritdoc}
  133.      */
  134.     public function loadCriteria(PersistentCollection $collectionCriteria $criteria)
  135.     {
  136.         throw new BadMethodCallException('Filtering a collection by Criteria is not supported by this CollectionPersister.');
  137.     }
  138.     /** @throws DBALException */
  139.     private function deleteEntityCollection(PersistentCollection $collection): int
  140.     {
  141.         $mapping     $collection->getMapping();
  142.         $identifier  $this->uow->getEntityIdentifier($collection->getOwner());
  143.         $sourceClass $this->em->getClassMetadata($mapping['sourceEntity']);
  144.         $targetClass $this->em->getClassMetadata($mapping['targetEntity']);
  145.         $columns     = [];
  146.         $parameters  = [];
  147.         foreach ($targetClass->associationMappings[$mapping['mappedBy']]['joinColumns'] as $joinColumn) {
  148.             $columns[]    = $this->quoteStrategy->getJoinColumnName($joinColumn$targetClass$this->platform);
  149.             $parameters[] = $identifier[$sourceClass->getFieldForColumn($joinColumn['referencedColumnName'])];
  150.         }
  151.         $statement 'DELETE FROM ' $this->quoteStrategy->getTableName($targetClass$this->platform)
  152.             . ' WHERE ' implode(' = ? AND '$columns) . ' = ?';
  153.         return $this->conn->executeStatement($statement$parameters);
  154.     }
  155.     /**
  156.      * Delete Class Table Inheritance entities.
  157.      * A temporary table is needed to keep IDs to be deleted in both parent and child class' tables.
  158.      *
  159.      * Thanks Steve Ebersole (Hibernate) for idea on how to tackle reliably this scenario, we owe him a beer! =)
  160.      *
  161.      * @throws DBALException
  162.      */
  163.     private function deleteJoinedEntityCollection(PersistentCollection $collection): int
  164.     {
  165.         $mapping     $collection->getMapping();
  166.         $sourceClass $this->em->getClassMetadata($mapping['sourceEntity']);
  167.         $targetClass $this->em->getClassMetadata($mapping['targetEntity']);
  168.         $rootClass   $this->em->getClassMetadata($targetClass->rootEntityName);
  169.         // 1) Build temporary table DDL
  170.         $tempTable         $this->platform->getTemporaryTableName($rootClass->getTemporaryIdTableName());
  171.         $idColumnNames     $rootClass->getIdentifierColumnNames();
  172.         $idColumnList      implode(', '$idColumnNames);
  173.         $columnDefinitions = [];
  174.         foreach ($idColumnNames as $idColumnName) {
  175.             $columnDefinitions[$idColumnName] = [
  176.                 'notnull' => true,
  177.                 'type'    => Type::getType(PersisterHelper::getTypeOfColumn($idColumnName$rootClass$this->em)),
  178.             ];
  179.         }
  180.         $statement $this->platform->getCreateTemporaryTableSnippetSQL() . ' ' $tempTable
  181.             ' (' $this->platform->getColumnDeclarationListSQL($columnDefinitions) . ')';
  182.         $this->conn->executeStatement($statement);
  183.         // 2) Build insert table records into temporary table
  184.         $query $this->em->createQuery(
  185.             ' SELECT t0.' implode(', t0.'$rootClass->getIdentifierFieldNames())
  186.             . ' FROM ' $targetClass->name ' t0 WHERE t0.' $mapping['mappedBy'] . ' = :owner'
  187.         )->setParameter('owner'$collection->getOwner());
  188.         $sql $query->getSQL();
  189.         assert(is_string($sql));
  190.         $statement  'INSERT INTO ' $tempTable ' (' $idColumnList ') ' $sql;
  191.         $parameters array_values($sourceClass->getIdentifierValues($collection->getOwner()));
  192.         $numDeleted $this->conn->executeStatement($statement$parameters);
  193.         // 3) Delete records on each table in the hierarchy
  194.         $classNames array_merge($targetClass->parentClasses, [$targetClass->name], $targetClass->subClasses);
  195.         foreach (array_reverse($classNames) as $className) {
  196.             $tableName $this->quoteStrategy->getTableName($this->em->getClassMetadata($className), $this->platform);
  197.             $statement 'DELETE FROM ' $tableName ' WHERE (' $idColumnList ')'
  198.                 ' IN (SELECT ' $idColumnList ' FROM ' $tempTable ')';
  199.             $this->conn->executeStatement($statement);
  200.         }
  201.         // 4) Drop temporary table
  202.         $statement $this->platform->getDropTemporaryTableSQL($tempTable);
  203.         $this->conn->executeStatement($statement);
  204.         return $numDeleted;
  205.     }
  206. }