eZ component: PersistentObject, Design, 1.5 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :Author: Tobias Schlitt :Revision: $Rev$ :Date: $Date$ :Status: Draft .. contents:: ===== Scope ===== The scope of this document is to describe the proposed enhancements for the PersistentObject component version 1.5. The general goal for this version is to implement various features described by these issues in our issue tracker: - Select query decorator. - #12473: Identity map for PersistentObject. - #13074: Fetching related objects through joins. - #11831: isRelated() Method. - #13073: Sub-select support for PersistentObject. - #13170: Support for custom relation implementations. Each of the issues and their proposed solutions are explained in a separate chapter in this document. Some issues relate to each other, which is also explained in the issue descriptions. The requirements for each of the enhancements can be found in the corresponding requirements document. ====================== Select query decorator ====================== The initial design of the PersistentObject component defined the following code flow to create a find query for certain objects and fetch the desired objects:: createFindQuery( 'SomeClass' ); $query->where( /*...*/ ); $objects = $session->find( $query, 'SomeClass' ); ?> The class name of the objects to fetch is actually submitted twice to the ezcPersistentSession instance. This is uncomfortable for the user of the component and should be solved by a custom decorator for the SELECT query classes provided by the Database component. The idea is to create a wrapper class for the (database specific) ezcQuerySelect instance that is returned by ezcPersistentSession->createFindQuery(). This decorator class will dispatch all method and attribute accesses to the internal query class, but also accepts further meta information from the ezcPersistentSession instance it was created by. ------ Design ------ The basic decorator class (ezcPersistentFindQuery) will consist only of a constructor that adds a single virtual property $className as meta data to the query:: public function __construct( ezcQuerySelect $q, string $className ); All further method calls are delegate to the given $q object using __call(). The same applies for property and attribute accesses, except for the $className property. The resulting code for user will be simplified as follows:: createFindQuery( 'SomeClass' ); $query->where( /*...*/ ); $objects = $session->find( $query ); ?> Since the decorated query class is completly transparent to the user, the old code will still work as expected, maintaining backwards compatibility. Further enhancements of the PersistentObject component may enhance ezcPersistentFindQuery by extending the class. It is mandatory that these extensions maintain BC with ezcQuerySelect for all methods that already exist in ezcPersistentSession. ================================= Identity map for PersistentObject ================================= The Identity Map pattern is described by Martin Fowler in his book `Patterns of Enterprise Application Architecture`__. The purpose of this pattern is to avoid that 2 or more objects representing the same database row reside in memory. The problems here are: a) Duplicate memory consumption b) Potential inconsistencies between different copies of the same object. .. __: http://martinfowler.com/books.html#eaa To avoid this, the data manipulation mechanisms must take care of not delivering duplicate instances of the same data. Beside that, it can cache certain queries as long as manipulations are reflected properly. The data manipulation mechanisms in our case are realized in the ezcPersistentSession and related (internal) handler classes. ------ Design ------ The Identity Map support should be optional to not break BC and keep flexibility. Therefore, a new class named ezcPersistentIdentitySession is implemented. This extends the current implementation ezcPersistentSession to make instanceof checks still work. All of the methods will be overwritten and new handler classes, extending the existing ones will be implemented to reflect the actual additions. The following list gives an overview of classes to be implemented and their role in this design: - ezcPersistentIdentitySession This class is a decorator to the existing ezcPersistentSession. An object of this class wraps around the typically used persistence session and adds the desired functionality to it. Database related method calls are still performed using the original ezcPersistentSession instance. - ezcPersistentIdentityMap This interface provides the methods that will be used by ezcPersistentIdentitySession to cache query results. A basic implementation of this interface is ezcPersistentBasicIdentityMap, realizing the typical identity map, by storing the identities in memory of the current process. An instance of this class builds the heart of ezcPersistentIdentitySession. It takes care for the in-memory management of object identities. The instance used by ezcPersistentIdentitySession is replaceable, to allow custom implementations like e.g. an identity map that uses the Cache component instead of local memory. - ezcPersistentIdentitySessionOptions This class is the options storage for ezcPersistentIdentitySession. The following sections describe the implemented classes in further detail. ezcPersistentIdentitySession ============================ This main class of the enhancement will be used exactly like ezcPersistentSession and realizes the desired functionality completely transparent to the user. This allows users to transparently replace the old version and vise versa. The class will moddel a decorator for the existing ezcPersistentSession. Functionality ------------- The constructor of the class receives an ezcPersistentSession instance to decorate and an object of type ezcPersistentIdentityMap, which realizes the identity mapping itself. This central point is used as the cache for object identities. All method calls, that are affected as described in the requirements document, will utilize this object to grab cached object identities and to reflect their changes. ezcPersistentIdentitySession will implement the same methods as ezcPersistentSession and perform the necessary cache inserts, updates and lookups around it. The following methods cannot be traced correctly and may therefore lead to inconsistencies: - createUpdateQuery() - updateFromQuery() - createDeleteQuery() - deleteFromQuery() The use of the \*FromQuery() methods will therefore result in a complete purge of the identity map to ensure consistency. Interface --------- __construct( ezcPersistentSession $session, ezcPersistentIdentityMap $map ) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The constructor receives the persistent $session to dispatch method calls to. In addition it receives the identity $map object to cache and retrieve object identities with. Both instances can be replaced by custom extensions of the original implementation to add further functionality. load( string $class, mixed $id ) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In case the requested object is found in the identity map, it will be returned from there. Otherwise the decorated persistent session will issue a normal load() operation and the result will be recorded in the identity map. loadIfExists() will react the same way. loadIntoObject( object $object, mixed $id ) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In case the given $object already is a loaded instance of an object, an exception will be thrown. If the desired object with the $id was already recorded in the identity map, an exception is thrown, too, since the operation would result in a copy. Otherwise the load is performed as desired and the resulting object instance is recorded and returned. find( ezcPersistentFindQuery $query, $class = null ) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ezcPersistentSession->find() is executed. In the resulting array, each object is checked in the identity map. If an identity is found, it is replaced into the result. If the identity of the object is not yet knowen, it is recorded. Since the new ezcPersistentFindQuery class will be introduced with this release, the optional $class parameter will be deprecated, but kept for BC reasons. It is possible to disable the identity caching globally, using the $refetch option. In this case, identities will be updated, before they are fetched from the identity map. 2 cases must be differntiated here: 1. The identity is not found in the identity map. In this case, the identity will be cretaed as usual. 2. The identity is found in the identity map. In this case, the loaded data will be used to update the existing identity (as ezcPersistentSession->refresh() would do) and the updated identity will be returned. findIterator( ezcPersistentFindQuery $query, string $class = null ) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To support this method properly, a new class ezcPersistentIdenitiyFindIterator is introduced. This one decorates the ezcPersistentFindIterator returned by ezcPersistentSession->findIterator(). The decorating class will take care of modelling the same behavior as described above with the find() method. getRelatedObjects( object $object, $relatedClass, $relationName = null ) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This method checks the relation cache of the identity map first, if a result set has already been stored for the given relation. In this case the cached result set will be returned. If the $refetch option is switched to true, the set of related objects will not be fetched from the identity map, but loaded from the database directly. If a result set already exists in memory, it will be replaced by the newly fetched set. The object identities themselves are handled as described in the find() method, including the behaviour of the $refetch option. getRelatedObject( object $object, $relatedClass, $relationName = null ) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Same as above, only for a single object. createRelationFindQuery( object $object, string $relatedClass, string $relationName = null, string $setName = null ) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This method just returns an instance of ezcPersistentFindQuery, as ezcPersistentSession->createRelationFindQuery() does. In addition to that, the optionally given $setName is set in the returned query. If the set name is given, getRelatedObjectSubset() (see `API additions for ezcPersistentIdentityMap`_) can be used to retrieve this set again. If the $setName is missing, the related object set returned by find() will not be cached, since it cannot be ensured that all related objects are fetched due to where conditions in the query. In any case, fetched object identities are cached and replaced from the identity map. save( object $object ) ^^^^^^^^^^^^^^^^^^^^^^ Whenever a new object was saved successfully, it is recorded in the identity map. saveOrUpdate( object $object ) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This method reacts the same as save() if the operation was not an update. In case of an update nothing is to do, since the object is already part of the identity map. addRelatedObject( object $sourceObject, object $relatedObject ) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This method performs the usually operation of ezcPersistentSession. In addition, the following operations will be performed: If the $relatedObject was already related to another object, it is removed from the relation cache. The new relation is reflected accordingly in the relation cache. .. note:: It should be discussed if it is feasible to perform the save() operation after a newly established relation automatically here, since otherwise inconsistencies might occur with the mapping cache. ezcPersistentSession does not do this, to avoid multiple UPDATE queries. Maybe a global switch in ezcPersistentSession to activate this overall might be sensible? delete( object $object ) ^^^^^^^^^^^^^^^^^^^^^^^^ This method must remove all references to the object from all mapping caches before actually deleting the object itself from the DB. This is the most time consuming work, since multiple isset() calls might be necessary to remove all identities from the relation cache. .. note:: The delete() operation also removes all relations and, if cascading is used, also removes related objects. Therefore the operation might even run longer. Performance should be a major concern when writing this code. removeRelatedObject( object $sourceObject, object $relatedObject, string $relationName = null ) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Beside removing the relation from the DB (in case of n:m relations), using ezcPersistentSession->removeRelatedObject(), the relation is removed from the relation cache, too. .. note:: It should be discussed if it is feasible to perform the save() operation after a newly established relation automatically here, since otherwise inconsistencies might occur with the mapping cache. ezcPersistentSession does not do this, to avoid multiple UPDATE queries. Maybe a global switch in ezcPersistentSession to activate this overall might be sensible? ezcPersistentIdenitySessionOptions ================================== The new ezcPersistentIdentitySession can be configured to switch on and off certain features. The options are: $refetch = false; Setting this to true will make the identity map indicate that no results have been cached, yet. The result is, that the persistent session will re-fetch all objects and record them in the identity map. This also affects the relation cache ($refetchRelations = true). ezcPersistentIdentityMap ======================== This interface models the internal heart of the identity map enhancement. An implmentation of it handles all caching and mapping activities globally for a session. Users shall usually not access the identity map object directly to fetch cached objects or add new ones. On the other hand, the identity map should be replaceable to allow more advanced implementations (e.g. involving the Cache component). Therefore most methods of this class will public, but marked as proteced via documentation. Functionality ------------- The following 2 areas of identity mapping will be handled: 1. The identity mapping itself. In this cache, each persistent object that is available in the session is recorded once. The mapping is:: array( => array( => , => , => , // ... ), // ... ) The values here are structs of type ezcPersistentIdentity, which store the object identity and some meta data and the relations cache in addition. 2. The relation mapping. The relations of an object are stored in its ezcPersistentIdentity struct. See the specific section of this document for further details. Interface --------- The following methods will be provided by the ezcPersistentIdentityMap interface: - setIdentity( object $object ) Sets the identity for $object. If an identity for this object was already recorded, it will be overwritten. The object using the identity map must take care about checking if the identity was already recorded. - getIdentity( string $class, mixed $id ) Returns the object recorded for $class, identified by $id. If such object is not available, null is returned. - removeIdentity( object $object ) Removes the identity for the given $object from the map. In addition, removes the object from all related object sets and named related object sets it is referenced in. - setRelatedObjects( object $sourceObject, array $relatedObjects, string $relatedClass, $relationName = null ) Sets $relatedObjects for $sourceObject of class $relatedClass. The objects in $relatedObjects must all be of $relatedClass, otherwise an exception will be thrown. If a set of $relatedObjects for $className already exists, it will be overwritten. The object using the identity map must take care about checking if the related objects were already recorded. The using object *must* call getRelatedObjects() after using setRelatedObjects(), since during the addition to the identity map, already existsing identities are replaced into the related object set. - setRelatedObjectSet( object $sourceObject, array $relatedObjects, string $setName ) Creates a named set of $relatedObjects for $sourceObject named $setName. If a set of $relatedObjects with $setName already exists, it will be overwritten. The object using the identity map must take care about checking if the related objects were already recorded. The using object *must* call getRelatedObjectSet() after using setRelatedObjectSet(), since during the addition to the identity map, already existsing identities are replaced into the related object set. - addRelatedObject( object $sourceObject, object $relatedObject, $relationName = null ) Appends a related object to an existing cached relation. Used in ezcPersistentIdentitySession->addRelatedObject(). If no relation result set has been cached, yet, it is not created, but the call is ignored. This results in a newly fetch of the relation when getRelatedObjects() is called. - removeRelatedObject( object $sourceObject, object $relatedObject, $relationName = null ) Removes a related object from the relation cache. Used in ezcPersistentIdentitySession->removeRelatedObject(). If the relation resultset is not cached, yet, the call is ignored. - getRelatedObjects( object $sourceObject, string $class, $relationName = null ) Returns an array of objects of type $class related to $sourceObject. This can also be an empty array, if the last lookup in the database resulted in such. In case no related objects have been recorded, yet, null is returned. - getRelatedObjectSet( object $sourceObject, string $setName ) Returns the related object set $setName for the $sourceObject. If no such named related set exists, null is returned. - reset() Resets the complete identity map to its original state, purging all related objects and named related object sets. The PersistentObject component will provide a basic implementation of ezcPersistentIdentityMap which simply stores the identities in memory (the original idea of an identity map): ezcPersistentBasiIdentityMap. Making ezcPersistentIdentityMap an interface allows users to create more advanced maps, e.g. involving caching mechanisms. -------------- Usage examples -------------- The following code snippes illustrate how the features described above are intended to be used. Replacing ezcPersistentSession with ezcPersistentIdentitySession ================================================================ The current code to create and use a persistent session looks like this:: ' ); $session = new ezcPersistentSession( $db, new ezcPersistentCodeManager( "path/to/definitions" ) ); ?> A user can transparently replace the $session with the new identity session:: ' ); $persistentSession = new ezcPersistentSession( $db, new ezcPersistentCodeManager( "path/to/definitions" ) ); $map = new ezcPersistentIdentityMap(); $session = new ezcPersistentIdentitySession( $persistentSession, $map ); ?> After this change, no duplicates of identities will be returned by PersistentObject. In addition, SELECT statemenst from the load() method might be avoided if an identity is to be loaded twice and duplicate relation fetches in getRelatedObjects() are avoided. Refetching object identities and relations ========================================== It might be necessary to refresh several object identities at once in an application. To prevent the persistent session to re-use the user can use the following code:: load( 'User', 23 ); $userB = $session->load( 'User', 42 ); $q = $session->createFindQuery( 'User' ); $q->where( $q->expr->lessThan( 'id, 1000 ) ); $session->options->refetch = true; $users = $session->find( $q, 'User' ); $session->options->refetch = false; ?> In this example, the User objects with IDs 23 and 42 are already loaded and their identity is cached in the identity map. Therefore, the find() of users with IDs lower than 1000 would normally return an array with 999 elements, containing the cached identities of the users 23 and 42. Since the $refetch option is switched on, the cached identities will be updated from the loaded data before they are returned. The same applies to relations:: getRelatedObjects( $user, 'Address' ); // ... $addresses = $session->getRelatedObjects( $user, 'Address' ); // ... $session->options->refetch = true; $addresses = $session->getRelatedObjects( $user, 'Address' ); $session->options->refetch = false; ?> The first fetch of related Address objects for the User object results in a database query anyway. After that, the relation result is cached. Therefore the second call to getRelatedObjecst() does not perform a database query at all, but simple returns the cached relation set. For the third call, the $refetch option is set to true. Therefore the database query is issued and the relation result set is overwritten. In addition, all found identities are updated with the newly fetched values. Note still no duplicates are created, but already existing identities are updated and existing object references are used in the result returned by getRelatedObjects(). ----------- Open issues ----------- - Since ezcPersistentIdentitySession only decorates ezcPersistentSessions, it is not seamlessly possible to replace both in an application: Instanceof checks will not work. - loadIfExists() can return null. Should this result be cached or should the method try to load again if no object has been loaded successfully, yet? - The update() method only allows to update 1 specific object right now. This results in a lot of select queries, if multiple objects need to be updated. We should allow arrays of objects here and update them in a row using IN ( ... ). ====================================== Fetching related objects through joins ====================================== Relation management with Persistent Objects is very convenient and powerful. However, it suffers from one of the common consequences of such methods: the amount of SQL queries you end up executing. Since using relations is easy, you will easily end up running dozens or hundreds of SQL queries, especially when using the templates component:: {$event->location->name} Very convenient, but this will run at least one extra query here; if you do this when listing, let's say events, you will execute one extra query for each event you display the location for. If you also display the event's responsible name, it's another query. If you show 10 events, you execute 21 queries, and this is not acceptable. ------ Design ------ A precondition of the pre-fetching of related objects is that these objects can be stored internally in the persistent session somehow and can be accessed by the user. The section `Identity map for PersistentObject`_ already describes suche a mechanism in detail. Therefore, the pre-fetching feature will be implemented inside the defined `ezcPersistentIdentitySession`_ class to utilize this features. The additions also affect the `ezcPersistentIdentityMap`_ class in some ways, to allow the fetching of a subset of related objects. API additions for ezcPersistentIdentitySession ============================================== The ezcPersistentIdentitySession (originally described in the `Identity map for PersistentObject`_ section) will be enhanced by methods to perform the following operations: - Load a persistent object including specific related objects. - Finding persistent objects including specific related objects. - Loading a persistent including a subset of related objects. - Finding persistent objects including a subset of related objects. To achieve this, the following methods will be added: loadWithRelatedObjects( string $class, mixed $id, array(ezcPersistentRelationFindDefinition) $relations ) --------------------------------------------------------------------------------------------------------- This method takes care about finding a speicifc persistent object (like the ezcPersistentSession->load() method) and finds related objects in addition. The $relations parameter contains a struct, defining the related objects to load. createFindQueryWithRelations( string $class, array $relations ) --------------------------------------------------------------- This method works like ezcPersistentSession->createFindQuery() and returns a query object, that may be manipulated by the user. The returned instance is of type ezcPersistentFindWithRelationsQuery, which is a decorator for ezcQuerySelect, that extends ezcPersistentFindQuery, defined earlier in `Select query decorator`_. Aliases for $class are defined as known from createFindQuery(). In addition, for the related objects to fetch as defined in $relations aliases are generated in the for '_', where is the array key provided in $relations for the specific relation. The query objects contains additional information about the $relations to fetch, which will be used by the findWithRelations() method to define the necessary JOINs and parse the returned objects. The query object can be used like an instance of ezcPersistentFindQuery (aka ezcQuerySelect) in general, but does not allow the following operations: - select() - selectDistinct() - from() - \*Join() - groupBy() - having() - limit() These operations throw an exception, since using them would break the query for extraction of the related objects. Calls to where() and orderBy() are still allowed. However, orderBy() should be avoided, since later re-fetching of related objects sets would result in potential inconsistencies in an application. The query decorator will detect, for which relations the user manipulates the WHERE condition. If the WHERE condition for a relation has been manipulated, findWithRelatedObjects() will store a named sub-set for this relation, instead of the normal related object set. This is necessary, since by adding a WHERE condition for a relation not the full set of related objects is fetched. The name of a named related object sub-set is determined by the key used in the $relations array for this relation. findWithRelations( ezcPersistentFindWithRelationsQuery $query ) --------------------------------------------------------------- This method returns an array of objects exactly like ezcPersistentSession->find() does. In the background, it also fetches the related objects as defined during createFindQueryWithRelations() and stores them in the identity map. The related objects can later be retreived from ezcPersistentIdentitySession by the user through ezcPersistentIdentitySession->getRelatedObjects() or ->getRelatedObjectsSubset(). Names sub sets are created as described in the previous section, on basis of WHERE conditions. getRelatedObjectsSubset( object $object, string $relatedClass, string $setName, string $relationName = null ) ------------------------------------------------------------------------------------------------------------- This method works similar to getRelatedObjects(), but allows to access a subset of related objects, fetched using findWithRelationSubset(). If the set identified by $setName does not exist, null is returned. API additions for ezcPersistentIdentityMap ========================================== Some of the additions to `ezcPersistentIdentitySession`_ (defined earlier in this section) require additions to the persistent session itself, too. The identity map must be extended to be capable of managing named subsets of related objects. The following methods are added: setRelationSet( object $sourceObject, array $relatedObjects, string $setName, string $relationName = null ) -------------------------------------------------------------------------------------------------------------- This method acts like addRelatedObjects() but adds the relation set as a named subset of relations. If the subset already exists, an exception is thrown. getRelationSet( object $object, string $relatedClass, string $setName, string $relationName = null ) ------------------------------------------------------------------------------------------------------- Returns the relation subset defined by the parameters. In case the subset is not cached, null is returned. ezcPersistentRelationFindDefinition =================================== This struct class is used to define relations for pre-fetching. An array of such structs is used in favor of deeply nested array structures. The struct looks as follows:: -------------- Usage examples -------------- The following code snippes illustrate how the features described above are intended to be used. Simple pre-fetching =================== The most common usage of the pre-fetching will be the following use case:: createFindQuery( 'Book' ); $q->where( $q->expr->gt( 'releaseDate', ( time() - 10080 ) ) ); $books = $session->find( $q, 'Book' ); foreach ( $books as $book ) { $authors = $session->getRelatedObjects( $book, 'Author' ); foreach ( $authors as $author ) { // Do something with Author objects... $addresses = $session->getRelatedObjects( $author, 'Address', 'private' ); // Do something with Address objects... } $reviews = $session->getRelatedObjects( $book, 'Review' ); // Do something with Review objects... } ?> In this code snippet, Book objects are loaded that have been released within the last week. For each of the objects, 1 SELECT query is issued to find the Author objects for the book and another one to find reviews for the book. For each found Author object, another SQL query is issued to fetch the private Address objects registered for it. Asuming that 20 books are found, this make at least 60 SQL queries to fetch all the objects from the database. With pre-fetching, this becomes much more efficient, by changing the loading of Book objects to the following code:: createFindQueryWithRelations( 'Book' array( new ezcPersistentRelationFindDefinition( 'Author', null, array( new ezcPersistentRelationFindDefinition( 'Address', 'private' ), ) ), new ezcPersistentRelationFindDefinition( 'Review' ), ) ); $q->where( $q->expr->gt( 'releaseDate', ( time() - 10080 ) ) ); $books = $session->findWithRelations( $q ); // ... ?> The rest of the code stays as showen in the first snippet. Instead of using 60 SQL queries to fetch all desired objects, 1 single query is used. Complex pre-fetching ==================== In large applications fetching all related objects for a certain object is not desired. Instead, one usually wants to define the sub-sets of related objects to fetch pretty fine graned. An example, how to realize this, is shown below:: createFindQueryWithRelationSubset( 'Book' array( new ezcPersistentRelationFindDefinition( 'Author', null, array( new ezcPersistentRelationFindDefinition( 'Address', 'private' ), ) ), new ezcPersistentRelationFindDefinition( 'Review' ), ) ); $q->where( $q->expr->gt( 'releaseDate', ( time() - 10080 ) ) ); $q->where( $q->expr->gt( 'Review_date', ( time() - 1440 ) ) ); $books = $session->findWithRelationSubset( $q, 'latest_reviews' ); ?> The example is almost the same as for the last section, except that the Review objects fetched are limited to those that have been written in the past 24 hours. Since the results fetched by this query do not correspond to the usual behavior of getRelatedObjects() anymore, a new method must be used here to retrieve the named subset:: getRelatedObjectsSubset( $book, 'Author', 'latest_reviews' ); foreach ( $authors as $author ) { // Do something with Author objects... $addresses = $session->getRelatedObjects( $author, 'Address', 'latest_reviews', 'private' ); // Do something with Address objects... } $reviews = $session->getRelatedObjects( $book, 'Review', 'latest_reviews' ); // Do something with Review objects... } ?> ----------- Open issues ----------- - Currently, the aliasing of related object attribute names is ambiguous (e.g. Author_name). A good solution could be here to make the user simply define prexifes himself:: createFindQueryWithRelationSubset( 'Book' array( 'Author' => new ezcPersistentRelationFindDefinition( 'Author', null, array( 'privAddress' => new ezcPersistentRelationFindDefinition( 'Address', 'private' ), ) ), 'Review' => new ezcPersistentRelationFindDefinition( 'Review' ), ) ); ?> In this example, the $name attribute of the Author class can be accessed via 'Author_name' and the $street attribute of the Address class through 'privAddress_street'. - It has to be decided if these enhancements should become directly part of ezcPersistentIdentitySession or if they should go into an extended class. ================== isRelated() method ================== It might be useful in an application to know if to given objects are related to each other. This way, the user does not need to remember how the relation between 2 objects is practically established. ------ Design ------ A new method:: bool isRelated( object $object1, object $object2 ) is added to ezcPersistentSession. This method will perform the following operations when being called with 2 objects: - Check if the object classes have a relation assigned in their persistent object definition (forward or backward). - If not: Return false. - Check if the objects are related to each other. - If not: Return false. - Return true. The operation will work without a database query for the following relations - One-To-One - One-To-Many - Many-To-One but will perform a database query for the Many-To-Many relation type. ======================================= Sub-select support for PersistentObject ======================================= The method ezcPersistentSession->createFindQuery() allows the user to create a pre-configured ezcQuerySelect object, which has aliases assigned from object attributes to column names. This has 2 advantages: a) The user does not need to remember any column names, but only needs to know the object attribute names. b) The user does not need to manually escape the column names. If the user wants to create a sub select query. he usually calls $query->subSelect(), which returns a new query object. For the persistent object queries, he then needs to manually configure the table name and needs to make use of the column names. ------ Design ------ The new `Select query decorator`_ which will be returned by createFindQuery() from this version on, will be enhanced by a method:: createSubFindQuery( string $className ) This method will call ezcQuerySelect->subSelect() on the inner query object and prepare the query according to the behavior of createFindQuery(). .. Local Variables: mode: rst fill-column: 79 End: vim: et syn=rst tw=79