| <?php |
| /** |
| * File containing the ezcPersistentDeleteHandler class. |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| * @package PersistentObject |
| * @version //autogen// |
| * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 |
| */ |
| |
| /** |
| * Helper class for ezcPersistentSession to handle object deleting. |
| * |
| * An instance of this class is used internally in {@link ezcPersistentSession} |
| * and takes care for deleting objects. |
| * |
| * @package PersistentObject |
| * @version //autogen// |
| * @access private |
| */ |
| class ezcPersistentDeleteHandler extends ezcPersistentSessionHandler |
| { |
| /** |
| * Deletes the persistent object $object. |
| * |
| * This method will perform a DELETE query based on the identifier of the |
| * persistent object $object. After delete() the ID property of $object |
| * will be reset to null. It is possible to {@link save()} $object |
| * afterwards. $object will then be stored with a new ID. |
| * |
| * If you defined relations for the given object, these will be checked to |
| * be defined as cascading. If cascading is configured, the related objects |
| * with this relation will be deleted, too. |
| * |
| * Relations that support cascading are: |
| * <ul> |
| * <li>{@link ezcPersistenOneToManyRelation}</li> |
| * <li>{@link ezcPersistenOneToOne}</li> |
| * </ul> |
| * |
| * @throws ezcPersistentDefinitionNotFoundxception |
| * if $the object is not recognized as a persistent object. |
| * @throws ezcPersistentObjectNotPersistentException |
| * if the object is not persistent already. |
| * @throws ezcPersistentQueryException |
| * if the object could not be deleted. |
| * |
| * @param object $object The persistent object to delete. |
| */ |
| public function delete( $object ) |
| { |
| $class = get_class( $object ); |
| $def = $this->definitionManager->fetchDefinition( $class ); |
| $state = $this->session->getObjectState( $object ); |
| $idValue = $state[$def->idProperty->propertyName]; |
| |
| // Check that the object is persistent already. |
| // The < 0 check results from the times where only numeric IDs were allowed. |
| if ( $idValue == null || $idValue < 0 ) |
| { |
| throw new ezcPersistentObjectNotPersistentException( $class ); |
| } |
| |
| // Transaction safety for exceptions thrown while cascading. |
| $this->database->beginTransaction(); |
| |
| try |
| { |
| // Check for cascading relations to follow. |
| foreach ( $def->relations as $relatedClass => $relation ) |
| { |
| $this->cascadeDelete( $object, $relatedClass, $relation ); |
| } |
| } |
| catch ( Exception $e ) |
| { |
| // Roll back the current transaction on any exception. |
| $this->database->rollback(); |
| throw $e; |
| } |
| |
| // Create and execute query. |
| $q = $this->database->createDeleteQuery(); |
| $q->deleteFrom( |
| $this->database->quoteIdentifier( $def->table ) |
| ) |
| ->where( |
| $q->expr->eq( |
| $this->database->quoteIdentifier( $def->idProperty->columnName ), |
| $q->bindValue( $idValue, null, $def->idProperty->databaseType ) |
| ) |
| ); |
| |
| try |
| { |
| $this->session->performQuery( $q ); |
| } |
| catch ( Exception $e ) |
| { |
| $this->database->rollback(); |
| throw $e; |
| } |
| |
| // After recursion of cascades everything should be fine here, or this |
| // final commit call should perform the rollback ordered by a deeper level |
| $this->database->commit(); |
| } |
| |
| /** |
| * Removes the relation between $object and $relatedObject. |
| * |
| * This method is used to delete an existing relation between 2 objects. |
| * Like {@link addRelatedObject()} this method does not store the related |
| * object after removing its relation properties (unset), except for {@link |
| * ezcPersistentManyToManyRelation()}s, for which the relation record is |
| * deleted from the database. |
| * |
| * If multiple relations between the class of $object and $relatedObject |
| * are defined, the $relationName parameter is mandatory. |
| * |
| * @param object $object Source object of the relation. |
| * @param object $relatedObject Related object. |
| * @param string $relationName Relation name. |
| * |
| * @throws ezcPersistentRelationOperationNotSupportedException |
| * if a relation to create is marked as "reverse". |
| * @throws ezcPersistentRelationNotFoundException |
| * if the deisred relation is not defined. |
| */ |
| public function removeRelatedObject( $object, $relatedObject, $relationName = null ) |
| { |
| $class = get_class( $object ); |
| $relatedClass = get_class( $relatedObject ); |
| |
| $def = $this->definitionManager->fetchDefinition( $class ); |
| $relatedDef = $this->definitionManager->fetchDefinition( get_class( $relatedObject ) ); |
| |
| // Sanity checks. |
| if ( !isset( $def->relations[$relatedClass] ) ) |
| { |
| throw new ezcPersistentRelationNotFoundException( $class, $relatedClass ); |
| } |
| |
| $relation = $def->relations[$relatedClass]; |
| |
| // New multi-relations for a single class |
| if ( $relation instanceof ezcPersistentRelationCollection ) |
| { |
| if ( $relationName === null ) |
| { |
| throw new ezcPersistentUndeterministicRelationException( $relatedClass ); |
| } |
| if ( !isset( $relation[$relationName] ) ) |
| { |
| throw new ezcPersistentRelationNotFoundException( |
| $class, |
| $relatedClass, |
| $relationName |
| ); |
| } |
| $relation = $relation[$relationName]; |
| } |
| |
| if ( isset( $relation->reverse ) && $relation->reverse === true ) |
| { |
| throw new ezcPersistentRelationOperationNotSupportedException( |
| $class, |
| $relatedClass, |
| "deleteRelation", |
| "Relation is a reverse relation." |
| ); |
| } |
| |
| $objectState = $this->session->getObjectState( $object ); |
| $relatedObjectState = $this->session->getObjectState( $relatedObject ); |
| |
| switch ( get_class( $relation ) ) |
| { |
| case "ezcPersistentOneToManyRelation": |
| case "ezcPersistentOneToOneRelation": |
| foreach ( $relation->columnMap as $map ) |
| { |
| $relatedObjectState[ |
| $relatedDef->columns[$map->destinationColumn]->propertyName |
| ] = null; |
| } |
| break; |
| case "ezcPersistentManyToManyRelation": |
| $q = $this->database->createDeleteQuery(); |
| $q->deleteFrom( |
| $this->database->quoteIdentifier( $relation->relationTable ) |
| ); |
| foreach ( $relation->columnMap as $map ) |
| { |
| $q->where( |
| $q->expr->eq( |
| $this->database->quoteIdentifier( |
| $map->relationSourceColumn |
| ), |
| $q->bindValue( |
| $objectState[ |
| $def->columns[$map->sourceColumn]->propertyName |
| ], |
| null, |
| $def->columns[$map->sourceColumn]->databaseType |
| ) |
| ), |
| $q->expr->eq( |
| $this->database->quoteIdentifier( |
| $map->relationDestinationColumn |
| ), |
| $q->bindValue( |
| $relatedObjectState[ |
| $relatedDef->columns[$map->destinationColumn]->propertyName |
| ], |
| null, |
| $relatedDef->columns[$map->destinationColumn]->databaseType |
| ) |
| ) |
| ); |
| } |
| $this->session->performQuery( $q, true ); |
| break; |
| } |
| |
| $relatedObject->setState( $relatedObjectState ); |
| } |
| |
| /** |
| * Deletes persistent objects using the query $query. |
| * |
| * The $query should be created using {@link createDeleteQuery()}. |
| * |
| * Currently this method only executes the provided query. Future |
| * releases PersistentSession may introduce caching of persistent objects. |
| * When caching is introduced it will be required to use this method to run |
| * cusom delete queries. To avoid being incompatible with future releases it is |
| * advisable to always use this method when running custom delete queries on |
| * persistent objects. |
| * |
| * @throws ezcPersistentQueryException |
| * if the delete query failed. |
| * |
| * @param ezcQueryDelete $query |
| */ |
| public function deleteFromQuery( ezcQueryDelete $query ) |
| { |
| $this->session->performQuery( $query, true ); |
| } |
| |
| /** |
| * Returns a delete query for the given persistent object $class. |
| * |
| * The query is initialized to delete from the correct table and |
| * it is only neccessary to set the where clause. |
| * |
| * Example: |
| * <code> |
| * $q = $session->createDeleteQuery( 'Person' ); |
| * $q->where( $q->expr->gt( 'age', $q->bindValue( 15 ) ) ); |
| * $session->deleteFromQuery( $q ); |
| * </code> |
| * |
| * @throws ezcPersistentObjectException |
| * if there is no such persistent class. |
| * |
| * @param string $class |
| * |
| * @return ezcQueryDelete |
| */ |
| public function createDeleteQuery( $class ) |
| { |
| $def = $this->definitionManager->fetchDefinition( $class ); |
| |
| $q = $this->database->createDeleteQuery(); |
| $q->setAliases( $this->session->generateAliasMap( $def, false ) ); |
| $q->deleteFrom( $this->database->quoteIdentifier( $def->table ) ); |
| |
| return $q; |
| } |
| |
| /** |
| * Perform the cascading of deletes on a specific relation. |
| * |
| * This method checks a given $relation of a given $object for necessary |
| * actions on a cascaded delete and performs them. If multiple relations |
| * from $object class to $relatedClass exist, the $relationName is |
| * mandatory. |
| * |
| * @param object $object |
| * @param string $relatedClass |
| * @param ezcPersistentRelation[Collection] $relation |
| * @param string $relationName |
| * |
| * @todo Revise cascading code. So far it sends 1 delete statement per |
| * object but we can also collect them table wise and send just 1 |
| * for each table. |
| */ |
| private function cascadeDelete( $object, $relatedClass, $relation, $relationName = null ) |
| { |
| // New multi-relations for a single class |
| if ( $relation instanceof ezcPersistentRelationCollection ) |
| { |
| foreach( $relation as $relationName => $realRelation ) |
| { |
| $this->cascadeDelete( $object, $relatedClass, $realRelation, $relationName ); |
| } |
| } |
| |
| // Remove relation records for ManyToMany relations |
| if ( $relation instanceof ezcPersistentManyToManyRelation ) |
| { |
| $relatedObjects = $this->session->loadHandler->getRelatedObjects( |
| $object, |
| $relatedClass, |
| $relationName |
| ); |
| foreach ( $relatedObjects as $relatedObject ) |
| { |
| // Determine the correct direction for removal. |
| if ( $relation->reverse === true ) |
| { |
| $this->removeRelatedObject( $relatedObject, $object, $relationName ); |
| } |
| else |
| { |
| $this->removeRelatedObject( $object, $relatedObject, $relationName ); |
| } |
| } |
| } |
| |
| // Actually remove related objects |
| if ( isset( $relation->cascade ) && $relation->cascade === true ) |
| { |
| // Reverse relations never cascade |
| // @todo: Is this correct? Or do we need to cascade reverse here? |
| if ( isset( $relation->reverse ) && $relation->reverse === true ) |
| { |
| throw new ezcPersistentRelationOperationNotSupported( |
| $class, |
| $relatedClass, |
| "cascade on delete", |
| "Reverse relations do not support cascading." |
| ); |
| } |
| $relatedObjects = $this->session->loadHandler->getRelatedObjects( |
| $object, |
| $relatedClass, |
| $relationName |
| ); |
| foreach ( $relatedObjects as $relatedObject ) |
| { |
| $this->delete( $relatedObject ); |
| } |
| } |
| } |
| } |
| |
| ?> |