Author: ts Date: Fri Jan 11 13:07:22 2008 New Revision: 7125 Log: - Next step to refactor ezcPersistentSession (#12287).
Added: trunk/PersistentObject/src/handlers/ trunk/PersistentObject/src/handlers/delete_handler.php (with props) trunk/PersistentObject/src/handlers/load_handler.php (with props) trunk/PersistentObject/src/handlers/save_handler.php (with props) Modified: trunk/PersistentObject/design/class_diagram.png trunk/PersistentObject/src/persistent_autoload.php trunk/PersistentObject/src/persistent_session.php Modified: trunk/PersistentObject/design/class_diagram.png ============================================================================== Binary files - no diff available. Added: trunk/PersistentObject/src/handlers/delete_handler.php ============================================================================== --- trunk/PersistentObject/src/handlers/delete_handler.php (added) +++ trunk/PersistentObject/src/handlers/delete_handler.php [iso-8859-1] Fri Jan 11 13:07:22 2008 @@ -1,0 +1,292 @@ +<?php + +/** + * Helper class for ezcPersistentSession to handle object deleting. + * + * An instance of this class is used internally in [EMAIL PROTECTED] ezcPersistentSession} + * and takes care for deleting objects. + * + * @package PersistentObject + * @version //autogen// + * @access private + */ +class ezcPersistentDeleteHandler +{ + /** + * Session object this instance belongs to. + * + * @var ezcPersistentSession + */ + private $session; + + /** + * Creates a new delete handler. + * + * @param ezcPersistentSession $session + */ + public function __construct( ezcPersistentSession $session ) + { + $this->session = $session; + } + + /** + * 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 [EMAIL PROTECTED] 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>[EMAIL PROTECTED] ezcPersistenOneToManyRelation}</li> + * <li>[EMAIL PROTECTED] 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->session->definitionManager->fetchDefinition( $class ); // propagate exception + $state = $this->session->getObjectState( $object ); + $idValue = $state[$def->idProperty->propertyName]; + + // check that the object is persistent already + if ( $idValue == null || $idValue < 0 ) + { + $class = get_class( $object ); + throw new ezcPersistentObjectNotPersistentException( $class ); + } + + // Transaction savety for exceptions thrown while cascading + $this->session->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->session->database->rollback(); + throw $e; + } + + // create and execute query + $q = $this->session->database->createDeleteQuery(); + $q->deleteFrom( $this->session->database->quoteIdentifier( $def->table ) ) + ->where( $q->expr->eq( $this->session->database->quoteIdentifier( $def->idProperty->columnName ), + $q->bindValue( $idValue ) ) ); + + try + { + $this->session->performQuery( $q, true ); + } + catch ( Exception $e ) + { + $this->session->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->session->database->commit(); + } + + /** + * Removes the relation between $object and $relatedObject. + * + * This method is used to delete an existing relation between 2 objects. + * Like [EMAIL PROTECTED] addRelatedObject()} this method does not store the related + * object after removing its relation properties (unset), except for [EMAIL PROTECTED] + * ezcPersistentManyToManyRelation()}s, for which the relation record is + * deleted from the database. + * + * @param object $object Source object of the relation. + * @param object $relatedObject Related object. + * + * @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 ) + { + $class = get_class( $object ); + $def = $this->session->definitionManager->fetchDefinition( ( $class = get_class( $object ) ) ); + + $relatedClass = get_class( $relatedObject ); + + if ( !isset( $def->relations[$relatedClass] ) ) + { + throw new ezcPersistentRelationNotFoundException( $class, $relatedClass ); + } + if ( isset( $def->relations[$relatedClass]->reverse ) && $def->relations[$relatedClass]->reverse === true ) + { + throw new ezcPersistentRelationOperationNotSupportedException( + $class, + $relatedClass, + "deleteRelation", + "Relation is a reverse relation." + ); + } + + $objectState = $this->session->getObjectState( $object ); + $relatedObjectState = $this->session->getObjectState( $relatedObject ); + + $relatedDef = $this->session->definitionManager->fetchDefinition( get_class( $relatedObject ) ); + switch ( get_class( ( $relation = $def->relations[get_class( $relatedObject )] ) ) ) + { + case "ezcPersistentOneToManyRelation": + case "ezcPersistentOneToOneRelation": + foreach ( $relation->columnMap as $map ) + { + $relatedObjectState[$relatedDef->columns[$map->destinationColumn]->propertyName] = null; + } + break; + case "ezcPersistentManyToManyRelation": + $q = $this->session->database->createDeleteQuery(); + $q->deleteFrom( $this->session->database->quoteIdentifier( $relation->relationTable ) ); + foreach ( $relation->columnMap as $map ) + { + $q->where( + $q->expr->eq( + $this->session->database->quoteIdentifier( $map->relationSourceColumn ), + $q->bindValue( $objectState[$def->columns[$map->sourceColumn]->propertyName] ) + ), + $q->expr->eq( + $this->session->database->quoteIdentifier( $map->relationDestinationColumn ), + $q->bindValue( $relatedObjectState[$relatedDef->columns[$map->destinationColumn]->propertyName] ) + ) + ); + } + $this->session->performQuery( $q ); + break; + } + + $relatedObject->setState( $relatedObjectState ); + } + + /** + * Deletes persistent objects using the query $query. + * + * The $query should be created using [EMAIL PROTECTED] 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 ); + } + + /** + * 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->session->definitionManager->fetchDefinition( $class ); // propagate exception + + // init query + $q = $this->session->database->createDeleteQuery(); + $q->setAliases( $this->session->generateAliasMap( $def, false ) ); + $q->deleteFrom( $this->session->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. + * + * @param object $object The persistent object. + * @param string $relatedClass The class of the related persistent + * object. + * @param ezcPersistentRelation $relation The relation to check. + * + * @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, ezcPersistentRelation $relation ) + { + // Remove relation records for ManyToMany relations + if ( $relation instanceof ezcPersistentManyToManyRelation ) + { + foreach ( $this->session->loadHandler->getRelatedObjects( $object, $relatedClass ) as $relatedObject ) + { + // Need to determine the correct direction for removal + if ( $relation->reverse === true ) + { + $this->removeRelatedObject( $relatedObject, $object ); + } + else + { + $this->removeRelatedObject( $object, $relatedObject ); + } + } + } + if ( isset( $relation->cascade ) && $relation->cascade === true ) + { + if ( isset( $relation->reverse ) && $relation->reverse === true ) + { + throw new ezcPersistentRelationOperationNotSupported( + $class, + $relatedClass, + "cascade on delete", + "Reverse relations do not support cascading." + ); + } + foreach ( $this->session->loadHandler->getRelatedObjects( $object, $relatedClass ) as $relatedObject ) + { + $this->delete( $relatedObject ); + } + } + } +} + +?> Propchange: trunk/PersistentObject/src/handlers/delete_handler.php ------------------------------------------------------------------------------ svn:eol-style = native Added: trunk/PersistentObject/src/handlers/load_handler.php ============================================================================== --- trunk/PersistentObject/src/handlers/load_handler.php (added) +++ trunk/PersistentObject/src/handlers/load_handler.php [iso-8859-1] Fri Jan 11 13:07:22 2008 @@ -1,0 +1,438 @@ +<?php + +/** + * Helper class for ezcPersistentSession to handle object loading. + * + * An instance of this class is used internally in [EMAIL PROTECTED] ezcPersistentSession} + * and takes care for loading and finding objects. + * + * @package PersistentObject + * @version //autogen// + * @access private + */ +class ezcPersistentLoadHandler +{ + /** + * Session object this instance belongs to. + * + * @var ezcPersistentSession + */ + private $session; + + /** + * Creates a new load handler. + * + * @param ezcPersistentSession $session + */ + public function __construct( ezcPersistentSession $session ) + { + $this->session = $session; + } + + /** + * Returns the persistent object of class $class with id $id. + * + * @throws ezcPersistentObjectException + * if the object is not available. + * @throws ezcPersistentObjectException + * if there is no such persistent class. + * + * @param string $class + * @param int $id + * + * @return object + */ + public function load( $class, $id ) + { + $def = $this->session->definitionManager->fetchDefinition( $class ); // propagate exception + $object = new $def->class; + $this->loadIntoObject( $object, $id ); + return $object; + } + + /** + * Returns the persistent object of class $class with id $id. + * + * This method is equivalent to [EMAIL PROTECTED] load()} except that it returns null + * instead of throwing an exception if the object does not exist. + * + * @param string $class + * @param int $id + * + * @return object|null + */ + public function loadIfExists( $class, $id ) + { + $result = null; + try + { + $result = $this->load( $class, $id ); + } + catch ( Exception $e ) + { + // eat, we return null on error + } + return $result; + } + + /** + * Loads the persistent object with the id $id into the object $object. + * + * The class of the persistent object to load is determined by the class + * of $object. + * + * @throws ezcPersistentObjectException + * if the object is not available. + * @throws ezcPersistentDefinitionNotFoundException + * if $object is not of a valid persistent object type. + * @throws ezcPersistentQueryException + * if the find query failed. + * + * @param object $object + * @param int $id + */ + public function loadIntoObject( $object, $id ) + { + $def = $this->session->definitionManager->fetchDefinition( get_class( $object ) ); // propagate exception + $q = $this->session->database->createSelectQuery(); + $q->select( $this->session->getColumnsFromDefinition( $def ) ) + ->from( $this->session->database->quoteIdentifier( $def->table ) ) + ->where( $q->expr->eq( $this->session->database->quoteIdentifier( $def->idProperty->columnName ), + $q->bindValue( $id ) ) ); + + $stmt = $this->session->performQuery( $q ); + $row = $stmt->fetch( PDO::FETCH_ASSOC ); + $stmt->closeCursor(); + if ( $row !== false ) // we got a result + { + // we could check if there was more than one result here + // but we don't because of the overhead and since the Persistent + // Object would be faulty by design in that case and the user would have + // to execute custom code to get into an invalid state. + try + { + $state = ezcPersistentStateTransformer::rowToStateArray( + $row, + $def + ); + } + catch ( Exception $e ) + { + throw new ezcPersistentObjectException( + "The row data could not be correctly converted to set data.", + "Most probably there is something wrong with a custom rowToStateArray implementation" + ); + } + $object->setState( $state ); + } + else + { + $class = get_class( $object ); + throw new ezcPersistentQueryException( "No object of class '$class' with id '$id'." ); + } + } + + /** + * Syncronizes the contents of $object with those in the database. + * + * Note that calling this method is equavalent with calling [EMAIL PROTECTED] + * loadIntoObject()} on $object with the id of $object. Any changes made + * to $object prior to calling refresh() will be discarded. + * + * @throws ezcPersistentObjectException + * if $object is not of a valid persistent object type. + * @throws ezcPersistentObjectException + * if $object is not persistent already. + * @throws ezcPersistentObjectException + * if the select query failed. + * + * @param object $object + */ + public function refresh( $object ) + { + $def = $this->session->definitionManager->fetchDefinition( get_class( $object ) ); // propagate exception + $state = $this->session->getObjectState( $object ); + $idValue = $state[$def->idProperty->propertyName]; + if ( $idValue !== null ) + { + $this->loadIntoObject( $object, $idValue ); + } + else + { + $class = get_class( $object ); + throw new ezcPersistentObjectNotPersistentException( $class ); + } + } + + /** + * Returns the result of the query $query as a list of objects. + * + * Returns the persistent objects found for $class using the submitted + * $query. $query should be created using [EMAIL PROTECTED] createFindQuery()} to + * ensure correct alias mappings and can be manipulated as needed. + * + * Example: + * <code> + * $q = $session->createFindQuery( 'Person' ); + * $allPersons = $session->find( $q, 'Person' ); + * </code> + * + * If you are retrieving large result set, consider using [EMAIL PROTECTED] + * findIterator()} instead. + * + * Example: + * <code> + * $q = $session->createFindQuery( 'Person' ); + * $objects = $session->findIterator( $q, 'Person' ); + * + * foreach( $objects as $object ) + * { + * // ... + * } + * </code> + * + * @throws ezcPersistentDefinitionNotFoundException + * if there is no such persistent class. + * @throws ezcPersistentQueryException + * if the find query failed. + * + * @param ezcQuerySelect $query + * @param string $class + * + * @return array(object($class)) + */ + public function find( ezcQuerySelect $query, $class ) + { + $def = $this->session->definitionManager->fetchDefinition( $class ); // propagate exception + + $rows = $this->session->performQuery( $query )->fetchAll( PDO::FETCH_ASSOC ); + + // convert all the rows states and then objects + $result = array(); + foreach ( $rows as $row ) + { + $object = new $def->class; + $object->setState( + ezcPersistentStateTransformer::rowToStateArray( $row, $def ) + ); + $result[] = $object; + } + return $result; + } + + /** + * Returns the result of $query for the $class as an iterator. + * + * This method is similar to [EMAIL PROTECTED] find()} but returns an [EMAIL PROTECTED] + * ezcPersistentFindIterator} instead of an array of objects. This is + * useful if you are going to loop over the objects and just need them one + * at the time. Because you only instantiate one object it is faster than + * [EMAIL PROTECTED] find()}. In addition, only 1 record is retrieved from the + * database in each iteration, which may reduce the data transfered between + * the database and PHP, if you iterate only through a small subset of the + * affected records. + * + * Note that if you do not loop over the complete result set you must call + * [EMAIL PROTECTED] ezcPersistentFindIterator::flush()} before issuing another query. + * + * @throws ezcPersistentDefinitionNotFoundException + * if there is no such persistent class. + * @throws ezcPersistentQueryException + * if the find query failed. + * + * @param ezcQuerySelect $query + * @param string $class + * + * @return ezcPersistentFindIterator + */ + public function findIterator( ezcQuerySelect $query, $class ) + { + $def = $this->session->definitionManager->fetchDefinition( $class ); // propagate exception + $stmt = $this->session->performQuery( $query ); + return new ezcPersistentFindIterator( $stmt, $def ); + } + + /** + * Returns the related objects of a given $relatedClass for an $object. + * + * This method returns the related objects of type $relatedClass for the + * given $object. This method (in contrast to [EMAIL PROTECTED] getRelatedObject()}) + * always returns an array of found objects, no matter if only 1 object + * was found (e.g. [EMAIL PROTECTED] ezcPersistentManyToOneRelation}), none or several + * ([EMAIL PROTECTED] ezcPersistentManyToManyRelation}). + * + * Example: + * <code> + * $person = $session->load( "Person", 1 ); + * $relatedAddresses = $session->getRelatedObjects( $person, "Address" ); + * echo "Number of addresses found: " . count( $relatedAddresses ); + * </code> + * + * Relations that should preferably be used with this method are: + * <ul> + * <li>[EMAIL PROTECTED] ezcPersistentOneToManyRelation}</li> + * <li>[EMAIL PROTECTED] ezcPersistentManyToManyRelation}</li> + * </ul> + * For other relation types [EMAIL PROTECTED] getRelatedObject()} is recommended. + * + * @param object $object + * @param string $relatedClass + * + * @return array(int=>object($relatedClass)) + * + * @throws ezcPersistentRelationNotFoundException + * if the given $object does not have a relation to $relatedClass. + */ + public function getRelatedObjects( $object, $relatedClass ) + { + $query = $this->createRelationFindQuery( $object, $relatedClass ); + return $this->find( $query, $relatedClass ); + } + + /** + * Returns the related object of a given $relatedClass for an $object. + * + * This method returns the related object of type $relatedClass for the + * object $object. This method (in contrast to [EMAIL PROTECTED] getRelatedObjects()}) + * always returns a single result object, no matter if more related objects + * could be found (e.g. [EMAIL PROTECTED] ezcPersistentOneToManyRelation}). If no + * related object is found, an exception is thrown, while [EMAIL PROTECTED] + * getRelatedObjects()} just returns an empty array in this case. + * + * Example: + * <code> + * $person = $session->load( "Person", 1 ); + * $relatedAddress = $session->getRelatedObject( $person, "Address" ); + * echo "Address of this person: " . $relatedAddress->__toString(); + * </code> + * + * Relations that should preferably be used with this method are: + * <ul> + * <li>[EMAIL PROTECTED] ezcPersistentManyToOneRelation}</ li> + * <li>[EMAIL PROTECTED] ezcPersistentOneToOneRelation}</li> + * </ul> + * For other relation types [EMAIL PROTECTED] getRelatedObjects()} is recommended. + * + * @param object $object + * @param string $relatedClass + * + * @return object($relatedClass) + * + * @throws ezcPersistentRelationNotFoundException + * if the given $object does not have a relation to $relatedClass. + */ + public function getRelatedObject( $object, $relatedClass ) + { + $query = $this->createRelationFindQuery( $object, $relatedClass ); + // This method only needs to return 1 object + $query->limit( 1 ); + + $resArr = $this->find( $query, $relatedClass ); + if ( sizeof( $resArr ) < 1 ) + { + throw new ezcPersistentRelatedObjectNotFoundException( $object, $relatedClass ); + } + return $resArr[0]; + } + + /** + * Returns a select query for the given persistent object $class. + * + * The query is initialized to fetch all columns from the correct table and + * has correct alias mappings between columns and property names of the + * persistent $class. + * + * Example: + * <code> + * $q = $session->createFindQuery( 'Person' ); + * $allPersons = $session->find( $q, 'Person' ); + * </code> + * + * @throws ezcPersistentObjectException + * if there is no such persistent class. + * + * @param string $class + * + * @return ezcQuerySelect + */ + public function createFindQuery( $class ) + { + $def = $this->session->definitionManager->fetchDefinition( $class ); // propagate exception + + // init query + $q = $this->session->database->createSelectQuery(); + $q->setAliases( $this->session->generateAliasMap( $def ) ); + $q->select( $this->session->getColumnsFromDefinition( $def ) ) + ->from( $this->session->database->quoteIdentifier( $def->table ) ); + + return $q; + } + + /** + * Returns the base query for retrieving related objects. + * + * See [EMAIL PROTECTED] getRelatedObject()} and [EMAIL PROTECTED] getRelatedObjects()}. Can be + * modified by additional where conditions and simply be used with + * [EMAIL PROTECTED] find()} and the related class name, to retrieve a sub-set of + * related objects. + * + * @param object $object + * @param string $relatedClass + * + * @return ezcDbSelectQuery + * + * @throws ezcPersistentRelationNotFoundException + * if the given $object does not have a relation to $relatedClass. + */ + public function createRelationFindQuery( $object, $relatedClass ) + { + $def = $this->session->definitionManager->fetchDefinition( ( $class = get_class( $object ) ) ); + if ( !isset( $def->relations[$relatedClass] ) ) + { + throw new ezcPersistentRelationNotFoundException( $class, $relatedClass ); + } + $relation = $def->relations[$relatedClass]; + + $query = $this->createFindQuery( $relatedClass ); + + $objectState = $this->session->getObjectState( $object ); + + switch ( ( $relationClass = get_class( $relation ) ) ) + { + case "ezcPersistentOneToManyRelation": + case "ezcPersistentManyToOneRelation": + case "ezcPersistentOneToOneRelation": + foreach ( $relation->columnMap as $map ) + { + $query->where( + $query->expr->eq( + $this->session->database->quoteIdentifier( "{$map->destinationColumn}" ), + $query->bindValue( $objectState[$def->columns[$map->sourceColumn]->propertyName] ) + ) + ); + } + break; + case "ezcPersistentManyToManyRelation": + $query->from( $this->session->database->quoteIdentifier( $relation->relationTable ) ); + foreach ( $relation->columnMap as $map ) + { + $query->where( + $query->expr->eq( + $this->session->database->quoteIdentifier( $relation->relationTable ) . "." . $this->session->database->quoteIdentifier( $map->relationSourceColumn ), + $query->bindValue( $objectState[$def->columns[$map->sourceColumn]->propertyName] ) + ), + $query->expr->eq( + $this->session->database->quoteIdentifier( $relation->relationTable ) . "." . $this->session->database->quoteIdentifier( $map->relationDestinationColumn ), + $this->session->database->quoteIdentifier( $relation->destinationTable ) . "." . $this->session->database->quoteIdentifier( $map->destinationColumn ) + ) + ); + } + break; + default: + throw new ezcPersistentRelationInvalidException( $relationClass ); + } + return $query; + } +} + +?> Propchange: trunk/PersistentObject/src/handlers/load_handler.php ------------------------------------------------------------------------------ svn:eol-style = native Added: trunk/PersistentObject/src/handlers/save_handler.php ============================================================================== --- trunk/PersistentObject/src/handlers/save_handler.php (added) +++ trunk/PersistentObject/src/handlers/save_handler.php [iso-8859-1] Fri Jan 11 13:07:22 2008 @@ -1,0 +1,441 @@ +<?php + +/** + * Helper class for ezcPersistentSession to handle object saving. + * + * An instance of this class is used internally in [EMAIL PROTECTED] ezcPersistentSession} + * and takes care for saving and updating objects. + * + * @package PersistentObject + * @version //autogen// + * @access private + */ +class ezcPersistentSaveHandler +{ + /** + * Session object this instance belongs to. + * + * @var ezcPersistentSession + */ + private $session; + + /** + * Creates a new save handler. + * + * @param ezcPersistentSession $session + */ + public function __construct( ezcPersistentSession $session ) + { + $this->session = $session; + } + + /** + * Saves the new persistent object $object to the database using an INSERT INTO query. + * + * The correct ID is set to $object. + * + * @throws ezcPersistentObjectException if $object + * is not of a valid persistent object type. + * @throws ezcPersistentObjectException if $object + * is already stored to the database. + * @throws ezcPersistentObjectException + * if it was not possible to generate a unique identifier for the + * new object. + * @throws ezcPersistentObjectException + * if the insert query failed. + * + * @param object $object + */ + public function save( $object ) + { + $this->saveInternal( $object ); + } + + /** + * Saves the new persistent object $object to the database using an UPDATE query. + * + * @throws ezcPersistentDefinitionNotFoundException if $object is not of a valid persistent object type. + * @throws ezcPersistentObjectNotPersistentException if $object is not stored in the database already. + * @throws ezcPersistentQueryException + * @param object $object + * @return void + */ + public function update( $object ) + { + $this->updateInternal( $object ); + } + + /** + * Saves or updates the persistent object $object to the database. + * + * If the object is a new object an INSERT INTO query will be executed. If + * the object is persistent already it will be updated with an UPDATE + * query. + * + * @throws ezcPersistentDefinitionNotFoundException + * if the definition of the persistent object could not be loaded. + * @throws ezcPersistentObjectException + * if $object is not of a valid persistent object type. + * @throws ezcPersistentObjectException + * if any of the definition requirements are not met. + * @throws ezcPersistentObjectException + * if the insert or update query failed. + * @param object $object + * @return void + */ + public function saveOrUpdate( $object ) + { + $def = $this->session->definitionManager->fetchDefinition( get_class( $object ) );// propagate exception + $state = $this->session->getObjectState( $object ); + + // fetch the id generator + $idGenerator = null; + if ( ezcBaseFeatures::classExists( $def->idProperty->generator->class ) ) + { + $idGenerator = new $def->idProperty->generator->class; + if ( !( $idGenerator instanceof ezcPersistentIdentifierGenerator ) ) + { + throw new ezcPersistentIdentifierGenerationException( get_class( $object ), + "Could not initialize identifier generator: ". "{$def->idProperty->generator->class} ." ); + } + } + + if ( !$idGenerator->checkPersistence( $def, $this->session->database, $state ) ) + { + $this->saveInternal( $object, false, $idGenerator ); + } + else + { + $this->updateInternal( $object, false ); + } + } + + /** + * Create a relation between $object and $relatedObject. + * + * This method is used to create a relation between the given source + * $object and the desired $relatedObject. The related object is not stored + * in the database automatically, only the desired properties are set. An + * exception is [EMAIL PROTECTED], where the relation + * record is stored automatically and there is no need to store + * $relatedObject explicitly after establishing the relation. + * + * @param object $object + * @param object $relatedObject + * + * @throws ezcPersistentRelationOperationNotSupportedException + * if a relation to create is marked as "reverse" [EMAIL PROTECTED] + * ezcPersistentRelation->reverse}. + * @throws ezcPersistentRelationNotFoundException + * if the deisred relation is not defined. + */ + public function addRelatedObject( $object, $relatedObject ) + { + $class = get_class( $object ); + $def = $this->session->definitionManager->fetchDefinition( ( $class = get_class( $object ) ) ); + + $relatedClass = get_class( $relatedObject ); + + $objectState = $this->session->getObjectState( $object ); + $relatedObjectState = $this->session->getObjectState( $relatedObject ); + + if ( !isset( $def->relations[$relatedClass] ) ) + { + throw new ezcPersistentRelationNotFoundException( $class, $relatedClass ); + } + if ( isset( $def->relations[$relatedClass]->reverse ) && $def->relations[$relatedClass]->reverse === true ) + { + throw new ezcPersistentRelationOperationNotSupportedException( + $class, + $relatedClass, + "addRelatedObject", + "Relation is a reverse relation." + ); + } + + $relatedDef = $this->session->definitionManager->fetchDefinition( get_class( $relatedObject ) ); + switch ( get_class( ( $relation = $def->relations[get_class( $relatedObject )] ) ) ) + { + case "ezcPersistentOneToManyRelation": + case "ezcPersistentOneToOneRelation": + foreach ( $relation->columnMap as $map ) + { + $relatedObjectState[$relatedDef->columns[$map->destinationColumn]->propertyName] = + $objectState[$def->columns[$map->sourceColumn]->propertyName]; + } + break; + case "ezcPersistentManyToManyRelation": + $q = $this->session->database->createInsertQuery(); + $q->insertInto( $this->session->database->quoteIdentifier( $relation->relationTable ) ); + $insertColumns = array(); + foreach ( $relation->columnMap as $map ) + { + if ( in_array( $map->relationSourceColumn, $insertColumns ) === false ) + { + $q->set( + $this->session->database->quoteIdentifier( $map->relationSourceColumn ), + $q->bindValue( $objectState[$def->columns[$map->sourceColumn]->propertyName] ) + ); + $insertColumns[] = $map->relationSourceColumn; + } + if ( in_array( $map->relationDestinationColumn, $insertColumns ) === false ) + { + $q->set( + $this->session->database->quoteIdentifier( $map->relationDestinationColumn ), + $q->bindValue( $relatedObjectState[$relatedDef->columns[$map->destinationColumn]->propertyName] ) + ); + $insertColumns[] = $map->relationDestinationColumn; + } + } + $this->session->performQuery( $q ); + break; + } + + $relatedObject->setState( $relatedObjectState ); + } + + /** + * Returns an update query for the given persistent object $class. + * + * The query is initialized to update the correct table and + * it is only neccessary to set the correct values. + * + * @throws ezcPersistentDefinitionNotFoundException + * if there is no such persistent class. + * + * @param string $class + * + * @return ezcQueryUpdate + */ + public function createUpdateQuery( $class ) + { + $def = $this->session->definitionManager->fetchDefinition( $class ); // propagate exception + + // init query + $q = $this->session->database->createUpdateQuery(); + $q->setAliases( $this->session->generateAliasMap( $def, false ) ); + $q->update( $this->session->database->quoteIdentifier( $def->table ) ); + + return $q; + } + + /** + * Updates persistent objects using the query $query. + * + * The $query should be created using createUpdateQuery(). + * + * 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 update query failed. + * + * @param ezcQueryUpdate $query + */ + public function updateFromQuery( ezcQueryUpdate $query ) + { + $this->session->performQuery( $query ); + } + + /** + * Saves the new persistent object $object to the database using an INSERT INTO query. + * + * If $doPersistenceCheck is set this function will check if the object is persistent before + * saving. If not, the check is omitted. The correct ID is set to $object. + * + * @throws ezcPersistentObjectException + * if $object is not of a valid persistent object type. + * @throws ezcPersistentObjectException + * if $object is already stored to the database. + * @throws ezcPersistentObjectException + * if it was not possible to generate a unique identifier for the + * new object. + * @throws ezcPersistentObjectException + * if the insert query failed. + * + * @param object $object + * @param bool $doPersistenceCheck + * @param ezcPersistentIdentifierGenerator $idGenerator + */ + private function saveInternal( $object, $doPersistenceCheck = true, + ezcPersistentIdentifierGenerator $idGenerator = null ) + { + $def = $this->session->definitionManager->fetchDefinition( get_class( $object ) );// propagate exception + $state = $this->filterAndCastState( $this->session->getObjectState( $object ), $def ); + $idValue = $state[$def->idProperty->propertyName]; + + // fetch the id generator + if ( $idGenerator == null && ezcBaseFeatures::classExists( $def->idProperty->generator->class ) ) + { + $idGenerator = new $def->idProperty->generator->class; + if ( !( $idGenerator instanceof ezcPersistentIdentifierGenerator ) ) + { + throw new ezcPersistentIdentifierGenerationException( get_class( $object ), + "Could not initialize identifier generator: ". "{$def->idProperty->generator->class} ." ); + } + } + + if ( $doPersistenceCheck == true && $idGenerator->checkPersistence( $def, $this->session->database, $state ) ) + { + $class = get_class( $object ); + throw new ezcPersistentObjectAlreadyPersistentException( $class ); + } + + + // set up and execute the query + $q = $this->session->database->createInsertQuery(); + $q->insertInto( $this->session->database->quoteIdentifier( $def->table ) ); + foreach ( $state as $name => $value ) + { + if ( $name != $def->idProperty->propertyName ) // skip the id field + { + // set each of the properties + $q->set( $this->session->database->quoteIdentifier( $def->properties[$name]->columnName ), $q->bindValue( $value ) ); + } + } + + $this->session->database->beginTransaction(); + // let presave id generator do its work + $idGenerator->preSave( $def, $this->session->database, $q ); + + // execute the insert query + try + { + $this->session->performQuery( $q ); + } + catch ( Exception $e ) + { + $this->session->database->rollback(); + throw $e; + } + + // fetch the newly created id, and set it to the object + $id = $idGenerator->postSave( $def, $this->session->database ); + if ( $id === null ) + { + $this->session->database->rollback(); + throw new ezcPersistentIdentifierGenerationException( $def->class ); + } + + // everything seems to be fine, lets commit the queries to the database + // and update the object with its newly created id. + $this->session->database->commit(); + + $state[$def->idProperty->propertyName] = $id; + $object->setState( $state ); + } + + /** + * Saves the new persistent object $object to the database using an UPDATE query. + * + * If $doPersistenceCheck is set this function will check if the object is persistent before + * saving. If not, the check is omitted. + * + * @throws ezcPersistentDefinitionNotFoundException if $object is not of a valid persistent object type. + * @throws ezcPersistentObjectNotPersistentException if $object is not stored in the database already. + * @throws ezcPersistentQueryException + * @param object $object + * @param bool $doPersistenceCheck + * @return void + */ + private function updateInternal( $object, $doPersistenceCheck = true ) + { + $def = $this->session->definitionManager->fetchDefinition( get_class( $object ) ); // propagate exception + $state = $this->filterAndCastState( $this->session->getObjectState( $object ), $def ); + $idValue = $state[$def->idProperty->propertyName]; + + // fetch the id generator + $idGenerator = null; + if ( ezcBaseFeatures::classExists( $def->idProperty->generator->class ) ) + { + $idGenerator = new $def->idProperty->generator->class; + if ( !( $idGenerator instanceof ezcPersistentIdentifierGenerator ) ) + { + throw new ezcPersistentIdentifierGenerationException( get_class( $object ), + "Could not initialize identifier generator: ". "{$def->idProperty->generator->class} ." ); + } + } + + if ( $doPersistenceCheck == true && !$idGenerator->checkPersistence( $def, $this->session->database, $state ) ) + { + $class = get_class( $object ); + throw new ezcPersistentObjectNotPersistentException( get_class( $object ) ); + } + + // set up and execute the query + $q = $this->session->database->createUpdateQuery(); + $q->update( $this->session->database->quoteIdentifier( $def->table ) ); + foreach ( $state as $name => $value ) + { + if ( $name != $def->idProperty->propertyName ) // skip the id field + { + // set each of the properties + $q->set( $this->session->database->quoteIdentifier( $def->properties[$name]->columnName ), $q->bindValue( $value ) ); + } + } + $q->where( $q->expr->eq( $this->session->database->quoteIdentifier( $def->idProperty->columnName ), + $q->bindValue( $idValue ) ) ); + + $this->session->performQuery( $q ); + } + + /** + * Filters out all properties not in the definition and casts the + * values to native PHP types. + * + * @param array(string=>string) $state + * @param ezcPersistentObjectDefinition $def + * @return array(string=>mixed) + */ + private function filterAndCastState( array $state, ezcPersistentObjectDefinition $def ) + { + $typedState = array(); + foreach ( $state as $name => $value ) + { + $type = null; + if ( $name == $def->idProperty->propertyName ) + { + $type = $def->idProperty->propertyType; + $conv = null; + } + else + { + if ( !isset( $def->properties[$name] ) ) + { + continue; + } + $type = $def->properties[$name]->propertyType; + $conv = $def->properties[$name]->converter; + } + + if ( !is_null( $value ) ) + { + if ( !is_null( $conv ) ) + { + $value = $conv->toDatabase( $value ); + } + switch ( $type ) + { + case ezcPersistentObjectProperty::PHP_TYPE_INT: + $value = (int) $value; + break; + case ezcPersistentObjectProperty::PHP_TYPE_FLOAT: + $value = (float) $value; + break; + case ezcPersistentObjectProperty::PHP_TYPE_STRING: + $value = (string) $value; + break; + } + } + + $typedState[$name] = $value; + } + return $typedState; + } +} + +?> Propchange: trunk/PersistentObject/src/handlers/save_handler.php ------------------------------------------------------------------------------ svn:eol-style = native Modified: trunk/PersistentObject/src/persistent_autoload.php ============================================================================== --- trunk/PersistentObject/src/persistent_autoload.php [iso-8859-1] (original) +++ trunk/PersistentObject/src/persistent_autoload.php [iso-8859-1] Fri Jan 11 13:07:22 2008 @@ -28,9 +28,11 @@ 'ezcPersistentRelation' => 'PersistentObject/interfaces/relation.php', 'ezcPersistentCacheManager' => 'PersistentObject/managers/cache_manager.php', 'ezcPersistentCodeManager' => 'PersistentObject/managers/code_manager.php', + 'ezcPersistentDeleteHandler' => 'PersistentObject/handlers/delete_handler.php', 'ezcPersistentDoubleTableMap' => 'PersistentObject/structs/double_table_map.php', 'ezcPersistentFindIterator' => 'PersistentObject/find_iterator.php', 'ezcPersistentGeneratorDefinition' => 'PersistentObject/structs/generator_definition.php', + 'ezcPersistentLoadHandler' => 'PersistentObject/handlers/load_handler.php', 'ezcPersistentManualGenerator' => 'PersistentObject/generators/manual_generator.php', 'ezcPersistentManyToManyRelation' => 'PersistentObject/relations/many_to_many.php', 'ezcPersistentManyToOneRelation' => 'PersistentObject/relations/many_to_one.php', @@ -46,6 +48,7 @@ 'ezcPersistentOneToManyRelation' => 'PersistentObject/relations/one_to_many.php', 'ezcPersistentOneToOneRelation' => 'PersistentObject/relations/one_to_one.php', 'ezcPersistentPropertyDateTimeConverter' => 'PersistentObject/object/property_converters/date.php', + 'ezcPersistentSaveHandler' => 'PersistentObject/handlers/save_handler.php', 'ezcPersistentSequenceGenerator' => 'PersistentObject/generators/sequence_generator.php', 'ezcPersistentSession' => 'PersistentObject/persistent_session.php', 'ezcPersistentSessionInstance' => 'PersistentObject/persistent_session_instance.php', -- svn-components mailing list svn-components@lists.ez.no http://lists.ez.no/mailman/listinfo/svn-components