/** * Computes the changes of an association. * * @param AssociationMapping $assoc * @param mixed $value The value of the association. */ private function _computeAssociationChanges($assoc, $value) { if ($value instanceof PersistentCollection && $value->isDirty()) { if ($assoc->isOwningSide) { $this->_collectionUpdates[] = $value; } $this->_visitedCollections[] = $value; } if (!$assoc->isCascadeSave) { //echo "NOT CASCADING INTO " . $assoc->getSourceFieldName() . PHP_EOL; return; // "Persistence by reachability" only if save cascade specified } // Look through the entities, and in any of their associations, for transient // enities, recursively. ("Persistence by reachability") if ($assoc->isOneToOne()) { $value = array($value); } $targetClass = $this->_em->getClassMetadata($assoc->targetEntityName); foreach ($value as $entry) { $state = $this->getEntityState($entry); $oid = spl_object_hash($entry); if ($state == self::STATE_NEW) { // Get identifier, if possible (not post-insert) $idGen = $targetClass->getIdGenerator(); if (!$idGen->isPostInsertGenerator()) { $idValue = $idGen->generate($this->_em, $entry); $this->_entityStates[$oid] = self::STATE_MANAGED; if (!$idGen instanceof \Doctrine\ORM\Id\Assigned) { $this->_entityIdentifiers[$oid] = array($idValue); $targetClass->getSingleIdReflectionProperty()->setValue($entry, $idValue); } else { $this->_entityIdentifiers[$oid] = $idValue; } $this->addToIdentityMap($entry); } // Collect the original data and changeset, recursing into associations. $data = array(); $changeSet = array(); foreach ($targetClass->reflFields as $name => $refProp) { $data[$name] = $refProp->getValue($entry); $changeSet[$name] = array(null, $data[$name]); if (isset($targetClass->associationMappings[$name])) { //echo "RECURSING INTO $name" . PHP_EOL; //TODO: Prevent infinite recursion $this->_computeAssociationChanges($targetClass->associationMappings[$name], $data[$name]); } } // NEW entities are INSERTed within the current unit of work. $this->_entityInsertions[$oid] = $entry; $this->_entityChangeSets[$oid] = $changeSet; $this->_originalEntityData[$oid] = $data; } else { if ($state == self::STATE_DELETED) { throw DoctrineException::updateMe("Deleted entity in collection detected during flush." . " Make sure you properly remove deleted entities from collections."); } } // MANAGED associated entities are already taken into account // during changeset calculation anyway, since they are in the identity map. } }
/** * Computes the changes of an association. * * @param AssociationMapping $assoc * @param mixed $value The value of the association. */ private function computeAssociationChanges($assoc, $value) { if ($value instanceof PersistentCollection && $value->isDirty()) { if ($assoc->isOwningSide) { $this->collectionUpdates[] = $value; } $this->visitedCollections[] = $value; } // Look through the entities, and in any of their associations, for transient (new) // enities, recursively. ("Persistence by reachability") if ($assoc->isOneToOne()) { if ($value instanceof Proxy && !$value->__isInitialized__) { return; // Ignore uninitialized proxy objects } $value = array($value); } else { if ($value instanceof PersistentCollection) { // Unwrap. Uninitialized collections will simply be empty. $value = $value->unwrap(); } } $targetClass = $this->em->getClassMetadata($assoc->targetEntityName); foreach ($value as $entry) { $state = $this->getEntityState($entry, self::STATE_NEW); $oid = spl_object_hash($entry); if ($state == self::STATE_NEW) { if (!$assoc->isCascadePersist) { throw new InvalidArgumentException("A new entity was found through a relationship that was not" . " configured to cascade persist operations: " . self::objToStr($entry) . "." . " Explicitly persist the new entity or configure cascading persist operations" . " on the relationship."); } $this->persistNew($targetClass, $entry); $this->computeChangeSet($targetClass, $entry); } else { if ($state == self::STATE_REMOVED) { return new InvalidArgumentException("Removed entity detected during flush: " . self::objToStr($removedEntity) . ". Remove deleted entities from associations."); } else { if ($state == self::STATE_DETACHED) { // Can actually not happen right now as we assume STATE_NEW, // so the exception will be raised from the DBAL layer (constraint violation). throw new InvalidArgumentException("A detached entity was found through a " . "relationship during cascading a persist operation."); } } } // MANAGED associated entities are already taken into account // during changeset calculation anyway, since they are in the identity map. } }
/** * Computes the changes of an association. * * @param AssociationMapping $assoc * @param mixed $value The value of the association. */ private function _computeAssociationChanges($assoc, $value) { if ($value instanceof PersistentCollection && $value->isDirty()) { if ($assoc->isOwningSide) { $this->_collectionUpdates[] = $value; } $this->_visitedCollections[] = $value; } if (!$assoc->isCascadePersist) { return; // "Persistence by reachability" only if persist cascade specified } // Look through the entities, and in any of their associations, for transient // enities, recursively. ("Persistence by reachability") if ($assoc->isOneToOne()) { if ($value instanceof Proxy && !$value->__isInitialized__) { return; // Ignore uninitialized proxy objects } $value = array($value); } else { if ($value instanceof PersistentCollection) { $value = $value->unwrap(); } } $targetClass = $this->_em->getClassMetadata($assoc->targetEntityName); foreach ($value as $entry) { $state = $this->getEntityState($entry, self::STATE_NEW); $oid = spl_object_hash($entry); if ($state == self::STATE_NEW) { if (isset($targetClass->lifecycleCallbacks[Events::prePersist])) { $targetClass->invokeLifecycleCallbacks(Events::prePersist, $entry); } if ($this->_evm->hasListeners(Events::prePersist)) { $this->_evm->dispatchEvent(Events::prePersist, new LifecycleEventArgs($entry, $this->_em)); } // Get identifier, if possible (not post-insert) $idGen = $targetClass->idGenerator; if (!$idGen->isPostInsertGenerator()) { $idValue = $idGen->generate($this->_em, $entry); if (!$idGen instanceof \Doctrine\ORM\Id\AssignedGenerator) { $this->_entityIdentifiers[$oid] = array($targetClass->identifier[0] => $idValue); $targetClass->getSingleIdReflectionProperty()->setValue($entry, $idValue); } else { $this->_entityIdentifiers[$oid] = $idValue; } $this->addToIdentityMap($entry); } $this->_entityStates[$oid] = self::STATE_MANAGED; // NEW entities are INSERTed within the current unit of work. $this->_entityInsertions[$oid] = $entry; $this->computeChangeSet($targetClass, $entry); } else { if ($state == self::STATE_REMOVED) { throw ORMException::removedEntityInCollectionDetected($entity, $assoc); } } // MANAGED associated entities are already taken into account // during changeset calculation anyway, since they are in the identity map. } }
/** * Gets the SELECT SQL to select one or more entities by a set of field criteria. * * @param array $criteria * @param AssociationMapping $assoc * @param string $orderBy * @return string * @override */ protected function _getSelectEntitiesSQL(array &$criteria, $assoc = null, $orderBy = null) { $idColumns = $this->_class->getIdentifierColumnNames(); $baseTableAlias = $this->_getSQLTableAlias($this->_class); if ($this->_selectColumnListSql === null) { // Add regular columns $columnList = ''; foreach ($this->_class->fieldMappings as $fieldName => $mapping) { if ($columnList != '') { $columnList .= ', '; } $columnList .= $this->_getSelectColumnSQL($fieldName, isset($mapping['inherited']) ? $this->_em->getClassMetadata($mapping['inherited']) : $this->_class); } // Add foreign key columns foreach ($this->_class->associationMappings as $assoc) { if ($assoc->isOwningSide && $assoc->isOneToOne()) { $tableAlias = isset($this->_class->inheritedAssociationFields[$assoc->sourceFieldName]) ? $this->_getSQLTableAlias($this->_em->getClassMetadata($this->_class->inheritedAssociationFields[$assoc->sourceFieldName])) : $baseTableAlias; foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { $columnAlias = $srcColumn . $this->_sqlAliasCounter++; $columnList .= ", {$tableAlias}.{$srcColumn} AS {$columnAlias}"; $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); if (!isset($this->_resultColumnNames[$resultColumnName])) { $this->_resultColumnNames[$resultColumnName] = $srcColumn; } } } } // Add discriminator column (DO NOT ALIAS THIS COLUMN). $discrColumn = $this->_class->discriminatorColumn['name']; if ($this->_class->rootEntityName == $this->_class->name) { $columnList .= ", {$baseTableAlias}.{$discrColumn}"; } else { $columnList .= ', ' . $this->_getSQLTableAlias($this->_em->getClassMetadata($this->_class->rootEntityName)) . ".{$discrColumn}"; } $resultColumnName = $this->_platform->getSQLResultCasing($discrColumn); $this->_resultColumnNames[$resultColumnName] = $discrColumn; } // INNER JOIN parent tables $joinSql = ''; foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); $tableAlias = $this->_getSQLTableAlias($parentClass); $joinSql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; foreach ($idColumns as $idColumn) { if ($first) { $first = false; } else { $joinSql .= ' AND '; } $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } } // OUTER JOIN sub tables foreach ($this->_class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); $tableAlias = $this->_getSQLTableAlias($subClass); if ($this->_selectColumnListSql === null) { // Add subclass columns foreach ($subClass->fieldMappings as $fieldName => $mapping) { if (isset($mapping['inherited'])) { continue; } $columnList .= ', ' . $this->_getSelectColumnSQL($fieldName, $subClass); } // Add join columns (foreign keys) foreach ($subClass->associationMappings as $assoc2) { if ($assoc2->isOwningSide && $assoc2->isOneToOne() && !isset($subClass->inheritedAssociationFields[$assoc2->sourceFieldName])) { foreach ($assoc2->targetToSourceKeyColumns as $srcColumn) { $columnAlias = $srcColumn . $this->_sqlAliasCounter++; $columnList .= ', ' . $tableAlias . ".{$srcColumn} AS {$columnAlias}"; $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); if (!isset($this->_resultColumnNames[$resultColumnName])) { $this->_resultColumnNames[$resultColumnName] = $srcColumn; } } } } } // Add LEFT JOIN $joinSql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; foreach ($idColumns as $idColumn) { if ($first) { $first = false; } else { $joinSql .= ' AND '; } $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } } $conditionSql = ''; foreach ($criteria as $field => $value) { if ($conditionSql != '') { $conditionSql .= ' AND '; } if (isset($this->_class->fieldMappings[$field]['inherited'])) { $conditionSql .= $this->_getSQLTableAlias($this->_em->getClassMetadata($this->_class->fieldMappings[$field]['inherited'])) . '.'; } else { $conditionSql .= $baseTableAlias . '.'; } if (isset($this->_class->columnNames[$field])) { $conditionSql .= $this->_class->getQuotedColumnName($field, $this->_platform); } else { if ($assoc !== null) { $conditionSql .= $field; } else { throw ORMException::unrecognizedField($field); } } $conditionSql .= ' = ?'; } $orderBySql = ''; if ($orderBy !== null) { $orderBySql = $this->_getCollectionOrderBySQL($orderBy, $baseTableAlias); } if ($this->_selectColumnListSql === null) { $this->_selectColumnListSql = $columnList; } return 'SELECT ' . $this->_selectColumnListSql . ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $baseTableAlias . $joinSql . ($conditionSql != '' ? ' WHERE ' . $conditionSql : '') . $orderBySql; }