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

Reply via email to