blob: 6e0de49447b0a0caa69613a8132d9ce81c360ffd [file] [log] [blame]
<?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 );
}
}
}
}
?>