blob: 2fd7ecccc28a715312204f67e17c7b16f2c60c5f [file] [log] [blame]
<?php
/**
* File containing the ezcPersistentSessionIdentityDecorator class.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* @package PersistentObject
* @version //autogen//
* @copyright Copyright (C) 2005-2010 eZ Systems AS. All rights reserved.
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
/**
* This class decorates ezcPersistentSession with facilities of the identity map pattern.
*
* An instance of this class is used to decorate an {@link
* ezcPersistentSession} with the facilities of the identity map pattern
* (similar to {@see http://martinfowler.com/eaaCatalog/identityMap.html}).
*
* The identity map pattern avoids inconsistencies in your application, by
* avoiding that the same database object exists in multiple different object
* instances. If your request the same object a second time, the already
* existing instance is returned instead of creating a new one. This can also
* save you some SQL queries and therefore database load, but this is not the
* primary target of the pattern.
*
* In addition to the identity map pattern, this class caches sets of related
* objects (see {@link getRelatedObjects()}) and allows you to pre-fetch nested
* related objects, using SQL joins ({@link loadWithRelatedObjects()}, {@link
* createFindQueryWithRelations()}). This can reduce database load
* significantly.
*
* An instance of this class can replace an {@link ezcPersistentSession}
* transparently, since it fulfills the same interface. To use it, you need the
* original session, an instance of {@link ezcPersistentIdentityMap} and
* potentially {@link ezcPersistentSessionIdentityDecoratorOptions}. The
* creation of the decorated session works as follows:
*
* <code>
* <?php
* // $originalSession contains a valid instance of ezcPersistentSession
* $identityMap = new ezcPersistentBasicIdentityMap(
* $originalSession->definitionManager
* );
* $identitySession = new ezcPersistentSessionIdentityDecorator(
* $originalSession,
* $identityMap
* );
* ?>
* </code>
*
* You can now transparently replace $originalSession and $identitySession in
* most cases. Only the methods {@link updateFromQuery()} and {@link
* deleteFromQuery()} won't work properly, since the identity map cannot trace
* the changes produced by this method in the database. Attention: Calling
* these methods will result in a complete reset of the identity map!
*
* Using the $options property, you can temporarely activate refetching of
* objects. Be careful with this option! While this is set to true, all object
* identities will be created from scratch and existing ones will be replaced.
* This is usually not desired! Use {@link refresh()} to update object values
* from database instead.
*
* @property-read ezcPersistentIdentityMap $identityMap
* Identity map used by this session. You should usually not
* access this property directly.
* @property ezcPersistentSessionIdentityDecoratorOptions $options
* Options to influence the behaviour of the session.
*
* @package PersistentObject
* @version //autogen//
* @mainclass
*/
class ezcPersistentSessionIdentityDecorator implements ezcPersistentSessionFoundation
{
/**
* Holds the properties of this class.
*
* @var array(string=>mixed)
*/
private $properties = array();
/**
* The persistent session this object wraps.
*
* @var ezcPersistentSession
*/
protected $session;
/**
* Query creator for relation pre-fetching.
*
* @var ezcPersistentIdentityRelationQueryCreator
*/
private $queryCreator;
/**
* Related object extractor used for pre-fetching.
*
* @var ezcPersistentIdentityRelationObjectExtractor
*/
private $objectExtractor;
/**
* Creates a new identity map decorator.
*
* This identity map decorator wraps around $session and makes use of this
* to issue the actual database operations. Object identities are stored in
* the $identityMap. The $options influence the behavior of the identity
* session, like setting the $refetch option to force reloading of objects.
*
* @param ezcPersistentSession $session
* @param ezcPersistentIdentityMap $identityMap
* @param ezcPersistentSessionIdentityDecoratorOptions $options
*/
public function __construct( ezcPersistentSession $session, ezcPersistentIdentityMap $identityMap, ezcPersistentSessionIdentityDecoratorOptions $options = null )
{
$this->session = $session;
$this->properties['identityMap'] = $identityMap;
$this->properties['options'] = (
$options === null ? new ezcPersistentSessionIdentityDecoratorOptions() : $options
);
}
/**
* Returns the persistent object of class $class with id $id.
*
* Checks if the object of $class with $id has already been loaded. If this
* is the case, the existing identity is returned. Otherwise the desired
* object is loaded from the database and its identity is recorded for
* later uses.
*
* @throws ezcPersistentObjectException
* if the object is not available.
* @throws ezcPersistentObjectException
* if there is no such persistent $class.
*
* @param string $class
* @param mixed $id
*
* @return ezcPersistentObject
*/
public function load( $class, $id )
{
$idMap = $this->properties['identityMap'];
if ( !$this->properties['options']->refetch )
{
$identity = $idMap->getIdentity( $class, $id );
if ( $identity !== null )
{
return $identity;
}
}
$identity = $this->session->load( $class, $id );
$idMap->setIdentity( $identity );
return $identity;
}
/**
* Returns the persistent object of class $class with id $id or null.
*
* This method is equivalent to {@link load()} except that it returns null
* instead of throwing an exception, if the desired object does not exist.
* A null value will not be recorded in the identity map, so a second
* attempt to load the object of $class with $id will result in another
* database query.
*
* @param string $class
* @param int $id
*
* @return ezcPersistentObject|null
*/
public function loadIfExists( $class, $id )
{
$idMap = $this->properties['identityMap'];
if ( !$this->properties['options']->refetch )
{
$identity = $idMap->getIdentity( $class, $id );
if ( $identity !== null )
{
return $identity;
}
}
$identity = $this->session->loadIfExists( $class, $id );
if ( $identity !== null )
{
$idMap->setIdentity( $identity );
}
return $identity;
}
/**
* Loads the persistent object of $class with $id into the given $object.
*
* The class of the persistent object to load is determined from $object.
* In case an identity for the given $id has already been recorded in the
* identity map and $object is not the same instance, an exception is
* thrown.
*
* @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.
* @throws ezcPersistentIdentityAlreadyExistsException
* if a different identity of the class of $object with $id already
* exists.
*
* @param ezcPersistentObject $object
* @param mixed $id
*/
public function loadIntoObject( $object, $id )
{
$idMap = $this->properties['identityMap'];
$class = get_class( $object );
if ( !$this->properties['options']->refetch )
{
$identity = $idMap->getIdentity( $class, $id );
if ( $identity !== null )
{
throw new ezcPersistentIdentityAlreadyExistsException(
$class,
$id
);
}
}
$this->session->loadIntoObject( $object, $id );
$idMap->setIdentity( $object );
}
/**
* Syncronizes the contents of $object with the database.
*
* Note that calling this method is equavalent with calling {@link
* loadIntoObject()} on $object with the ID of $object. Any changes made
* to $object prior to calling refresh() will be discarded.
*
* The refreshing of an object will result in its identity being refreshed
* automatically.
*
* @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 ezcPersistentObject $object
*/
public function refresh( $object )
{
$this->session->refresh( $object );
}
/**
* Returns the result of the $query as an array of objects.
*
* Returns the persistent objects found for $class using the submitted
* $query. $query should be created using {@link createFindQuery()} to
* ensure correct alias mappings and can be manipulated as needed. The
* $class parameter is optional, since {@link ezcPersistentFindQuery} now
* stores this information on creation using {@link createFindQuery()}.
*
* The array returned by this method is indexed by the IDs of the contained
* objects. The order of the array reflects the order in the database or as
* indicated by the ORDER BY clause of the query.
*
* The results fetched will be checked for identities that have already
* been recorded before. If an existing identity is found for an object,
* this identity will be used in the result set. Note: This does not
* prevent the database query at all, but just ensures consistency.
*
* Example:
* <code>
* <?php
*
* $q = $session->createFindQuery( 'Person' );
* $allPersons = $session->find( $q );
*
* ?>
* </code>
*
* If you are retrieving large result set, consider using {@link
* findIterator()} instead.
*
* Example:
* <code>
* <?php
*
* $q = $session->createFindQuery( 'Person' );
* $objects = $session->findIterator( $q, 'Person' );
*
* foreach( $objects as $object )
* {
* // ...
* }
*
* ?>
* </code>
*
* Identity mapping comes into action in the following example:
* <code>
* <?php
*
* $person = $session->load( 'Person', 23 );
*
* $q = $session->createFindQuery( 'Person' );
* $allPersons = $session->find( $q );
*
* ?>
* </code>
* In $allPersons, the object with ID 23 will not be a new instance, but
* the existing instance, that was already fetched by the call to {@link
* load()}.
*
* @throws ezcPersistentDefinitionNotFoundException
* if there is no such persistent class.
* @throws ezcPersistentQueryException
* if the find query failed.
* @throws ezcBaseValueException
* if $query parameter is not an instance of ezcPersistentFindQuery
* or ezcQuerySelect. Or if $class is missing if you use
* ezcQuerySelect.
*
* @param ezcPersistentFindQuery|ezcQuerySelect $query
* @param string $class
*
* @return array(object($class))
* @apichange This method will only accept an instance of
* ezcPersistentFindQuery as the $query parameter in future
* major releases. The $class parameter will be removed.
* @apichange Since version 1.6, the returned array is indexed by the
* object IDs and not by ascending integers anymore.
*/
public function find( $query, $class = null )
{
if ( $query instanceof ezcPersistentFindWithRelationsQuery )
{
return $this->findWithRelations( $query );
}
else
{
return $this->findDefault( $query, $class );
}
}
/**
* Performs a find with related objects.
*
* Performs the find operation and also registeres the related objects in
* the {@link ezcPersistentIdentityMap} as defined by the query.
*
* @param ezcPersistentFindWithRelationsQuery $query
* @param string $class
* @return array(ezcPersistentObject)
*/
private function findWithRelations( ezcPersistentFindWithRelationsQuery $query )
{
$this->initializeObjectExtractor();
$stmt = $query->prepare();
$stmt->execute();
return $this->objectExtractor->extractObjectsWithRelatedObjects(
$stmt,
$query
);
}
/**
* Performs the default find behaviour.
*
* Warps around {@link ezcPersistentSession::find()} and registeres found
* objects with the {@link ezcPersistentIdentityMap}.
*
* @param ezcQuerySelect|ezcFindQuery $query
* @param string $class
* @return array(object($class))
*/
private function findDefault( $query, $class )
{
$isRelFindQueryWithSetName = $query instanceof ezcPersistentRelationFindQuery
&& $query->relationSetName !== null;
if ( !$this->options->refetch && $isRelFindQueryWithSetName )
{
// Check if such a subset already exisist
$objects = $this->identityMap->getRelatedObjectSet(
$query->relationSource,
$query->relationSetName
);
if ( $objects !== null )
{
return $objects;
}
}
$objects = $this->performFind( $query, $class );
// Query for createRelationFindQuery() with sub-set name assigned
// No refetch check needed anymore
if ( $isRelFindQueryWithSetName )
{
$objects = $this->identityMap->setRelatedObjectSet(
$query->relationSource,
$objects,
$query->relationSetName
);
}
return $objects;
}
/**
* Performs the actual find.
*
* @param ezcQuerySelect|ezcPersistentFindQuery $query
* @param string $class
* @return array(object($class))
*/
private function performFind( $query, $class = null )
{
$objects = $this->session->find( $query, $class );
$defs = array();
foreach ( $objects as $i => $object )
{
$class = get_class( $object );
if ( !isset( $defs[$class] ) )
{
$defs[$class] = $this->session->definitionManager->fetchDefinition(
$class
);
}
$state = $object->getState();
$id = $state[$defs[$class]->idProperty->propertyName];
$identity = null;
if ( !$this->properties['options']->refetch )
{
$identity = $this->properties['identityMap']->getIdentity(
$class,
$id
);
}
if ( $identity !== null )
{
$objects[$i] = $identity;
}
else
{
$this->properties['identityMap']->setIdentity(
$object
);
}
}
return $objects;
}
/**
* Returns the result of $query for the $class as an iterator.
*
* This method is similar to {@link find()} but returns an {@link
* ezcPersistentIdentityFindIterator} 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 {@link 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
* {@link ezcPersistentFindIterator::flush()} before issuing another query.
*
* The find interator will automatically look up result objects in the
* identity map and return existing identities, if they have already been
* recorded.
*
* @throws ezcPersistentDefinitionNotFoundException
* if there is no such persistent class.
* @throws ezcPersistentQueryException
* if the find query failed.
* @throws ezcBaseValueException
* if $query parameter is not an instance of ezcPersistentFindQuery
* or ezcQuerySelect. Or if $class is missing if you use
* ezcQuerySelect.
*
* @param ezcPersistentFindQuery|ezcQuerySelect $query
* @param string $class
*
* @return ezcPersistentIdentityFindIterator
* @apichange This method will only accept an instance of
* ezcPersistentFindQuery as the $query parameter in future
* major releases. The $class parameter will be removed.
*/
public function findIterator( $query, $class = null )
{
// Sanity checks
if ( !is_object( $query )
|| ( !( $query instanceof ezcPersistentFindQuery )
&& !( $query instanceof ezcQuerySelect )
)
)
{
throw new ezcBaseValueException(
'query',
$query,
'ezcPersistentFindQuery (or ezcQuerySelect)'
);
}
if ( $query instanceof ezcQuerySelect && $class === null )
{
throw new ezcBaseValueException(
'class',
$class,
'string (mandatory, if ezcQuerySelect is used)'
);
}
// Extract class name and select query form parameter
if ( $query instanceof ezcPersistentFindQuery )
{
$class = $query->className;
$query = $query->query;
}
$def = $this->definitionManager->fetchDefinition( $class );
$stmt = $this->session->performQuery( $query );
return new ezcPersistentIdentityFindIterator(
$stmt,
$def,
$this->identityMap,
$this->properties['options']
);
}
/**
* Returns the related objects of a given $relatedClass for $object.
*
* This method returns the related objects of type $relatedClass for the
* given $object. This method (in contrast to {@link getRelatedObject()})
* always returns an array of found objects, no matter if only 1 object
* was found (e.g. {@link ezcPersistentManyToOneRelation}), none or several
* ({@link ezcPersistentManyToManyRelation}).
*
* In case the set of related objects has already been fetched earlier, the
* request to the database is not repeated, but the recorded object set is
* returned. If the set of related objects was not recorded, yet, it is
* fetched from the database and recorded afterwards.
*
* 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>{@link ezcPersistentOneToManyRelation}</li>
* <li>{@link ezcPersistentManyToManyRelation}</li>
* </ul>
* For other relation types {@link getRelatedObject()} is recommended.
*
* If multiple relations are defined for the $relatedClass (using {@link
* ezcPersistentRelationCollection}), the parameter $relationName becomes
* mandatory to determine which relation definition to use. For normal
* relations, this parameter is silently ignored.
*
* @param object $object
* @param string $relatedClass
* @param string $relationName
*
* @return array(int=>object($relatedClass))
*
* @throws ezcPersistentRelationNotFoundException
* if the given $object does not have a relation to $relatedClass.
*/
public function getRelatedObjects( $object, $relatedClass, $relationName = null )
{
if ( !$this->properties['options']->refetch )
{
$relatedObjs = $this->identityMap->getRelatedObjects(
$object,
$relatedClass,
$relationName
);
if ( $relatedObjs !== null )
{
return $relatedObjs;
}
}
$relatedObjs = $this->session->getRelatedObjects(
$object,
$relatedClass,
$relationName
);
$storedRelatedObjs = $this->identityMap->setRelatedObjects(
$object,
$relatedObjs,
$relatedClass,
$relationName,
$this->properties['options']->refetch
);
return $storedRelatedObjs;
}
/**
* Returns the related object of a given $relatedClass for $object.
*
* This method returns the related object of type $relatedClass for the
* object $object. This method (in contrast to {@link getRelatedObjects()})
* always returns a single result object, no matter if more related objects
* could be found (e.g. {@link ezcPersistentOneToManyRelation}). If no
* related object is found, an exception is thrown, while {@link
* getRelatedObjects()} just returns an empty array in this case.
*
* In case the related object has already been fetched earlier, the request
* to the database is not repeated, but the recorded object is returned. If
* the related object was not recorded, yet, it is fetched from the
* database and recorded afterwards.
*
* 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>{@link ezcPersistentManyToOneRelation}</li>
* <li>{@link ezcPersistentOneToOneRelation}</li>
* </ul>
* For other relation types {@link getRelatedObjects()} is recommended.
*
* If multiple relations are defined for the $relatedClass (using {@link
* ezcPersistentRelationCollection}), the parameter $relationName becomes
* mandatory to determine which relation definition to use. For normal
* relations, this parameter is silently ignored.
*
* @param object $object
* @param string $relatedClass
* @param string $relationName
*
* @return object($relatedClass)
*
* @throws ezcPersistentRelationNotFoundException
* if the given $object does not have a relation to $relatedClass.
*/
public function getRelatedObject( $object, $relatedClass, $relationName = null )
{
$relObjs = $this->getRelatedObjects(
$object,
$relatedClass,
$relationName
);
return reset( $relObjs );
}
/**
* Returns the named related object subset with $setName for $object.
*
* This method is used to retrieve named subsets of related objects created
* by using {@link find()} with a restricted {@link
* ezcPersistentFindWithRelationsQuery} created by {@link
* createFindQueryWithRelations()}.
*
* @see ezcPersistentFindWithRelationsQuery
* @see find()
* @see createFindQueryWithRelations()
*
* @param ezcPersistentObject $object
* @param string $setName
* @return array(ezcPersistentObject)|null
*
* @apichange This method does not require ezcPersistentObject as a type
* hint for BC reasons. In the next major version, this type
* hint will be added.
*/
public function getRelatedObjectSubset( $object, $setName )
{
return $this->identityMap->getRelatedObjectSet(
$object,
$setName
);
}
/**
* 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. The alias mapping allows you to use property names in
* WHERE conditions, instead of column names. These aliases will
* automatically be resolved before querying the database.
*
* Example:
* <code>
* <?php
*
* $q = $session->createFindQuery( 'Person' );
* $allPersons = $session->find( $q, 'Person' );
*
* ?>
* </code>
*
* Example with aliases:
* <code>
* <?php
* $q = $session->createFindQuery( 'Person' );
* $q->where(
* $q->expr->eq(
* 'zipCode',
* $q->bindValue( 12345 )
* )
* );
* $somePersons = $session->find( $q, 'Person' );
*
* ?>
* </code>
* $zipCode is the property name in the Person class, while the
* corresponding database column is named zip_code.
*
* @throws ezcPersistentObjectException
* if there is no such persistent class.
*
* @param string $class
*
* @return ezcPersistentFindQuery
*/
public function createFindQuery( $class )
{
return $this->session->createFindQuery( $class );
}
/**
* Returns a sub-select for the given $class to be used with $parentQuery.
*
* This method creates an {@link ezcPersistentFindQuery} as a {@link
* ezcQuerySubSelect} for the given $class. The returned query has already
* set aliases for the properties of $class, but (in contrast to the query
* returned by {@link createFindQuery()}) does not have the selection of all
* properties set. You need to do
*
* <code>
* <?php
* $subSelect = $session->subSelect( $existingSelectQuery, 'MyClass' );
* $subSelect->select( 'myField' );
* ?>
* </code>
*
* manually to select the fields you desire.
*
* @param ezcPersistentFindQuery $parentQuery
* @param string $class
* @return ezcQuerySubSelect
*/
public function createSubQuery( ezcPersistentFindQuery $parentQuery, $class )
{
return $this->loadHandler->createSubQuery( $parentQuery, $class );
}
/**
* Returns a select query for the given $class and its related objects as
* defined in $relations.
*
* This method creates an instance of {@link
* ezcPersistentFindWithRelationsQuery}, which can basically be used like
* {@link ezcPersistentFindQuery} aka {@link ezcQuerySelect}. The query
* object is configured to load objects of $class and has JOIN statements
* to load related objects as defined in $relations, in addition. You can
* use the {@link find()} method to perform the actual find operation. This
* one will return the objects of $class. The related objects can simply be
* obtained using {@link getRelatedObjects()}, {@link getRelatedObject()}
* or {@link getRelatedObjectSet()} (see below). Calls to these methods the
* work without issuing a new database queries, since the desired objects
* are already stored in the {@link ezcPersistentIdentityMap}.
*
* The $relations array has the following structure:
* <code>
* <?php
* array(
* 'relationAlias_1' => new ezcPersistentRelationFindDefinition(
* 'relatedClass_1',
* null,
* array(
* 'deeperAlias_1' => new ezcPersistentRelationFindDefinition(
* 'deeperRelatedClass_1'
* )
* )
* ),
* 'relationAlias_2' => new ezcPersistentRelationFindDefinition(
* 'relatedClass_2'
* )
* );
* ?>
* </code>
*
* The keys of the array define aliases for relations to be used in the
* local context. Each key has an object of {@link
* ezcPersistentRelationFindDefinition} assigned, that defines which
* relation is meant to be fetched. The first entry above assignes the
* alias 'relationAlias_1' to the related class 'relatedClass_1'. The
* second parameter to the constructor of {@linke
* ezcPersistentRelationFindDefinition} can be a relation name, if multiple
* relations to the same class exist. The third parameter defines deeper
* relations.
*
* A call to this method with $class set to 'myClass' and $relations
* defined as seen above creates a find query that by default finds:
*
* - All objects of myClass
* - Foreach object of myClass, all related objects of relatedClass_1
* - Foreach object of myClass, all related objects of relatedClass_2
* - Foreach object of relatedClass_1, all related objects of deeperRelatedClass_1
*
* The aliases defined as the keys of the $relations array can be used to
* add where() conditions to the created query. Properties of the objects
* of relatedClass_1 can be accessed by prefixing their name with
* 'relationAlias_1_' (for example 'relationAlias_1_id' to access the 'id'
* property).
*
* NOTE: If you restrict the objects to be found by a WHERE condition, not
* the full set of related objects might be returned. To avoid
* inconsistencies in the identity map, the extracted sets of related
* objects will then not be registered as usual, but as <b>named related
* sets</b>. You can retrieve these using the {@link getRelatedObjectSet()}
* method (instead of using {@link getRelatedObjects()}), with the chosen
* relation alias as the set name.
*
* @see find()
* @see ezcPersistentFindWithRelationsQuery
* @see ezcPersistentRelationFindDefinition
* @see ezcPersistentIdentityMap
*
* @param string $class
* @param array(ezcPersistentRelationFindDefinition) $relations
* @return ezcPersistentFindWithRelationsQuery
*/
public function createFindQueryWithRelations( $class, array $relations )
{
$this->initializeQueryCreator();
return $this->queryCreator->createFindQuery( $class, $relations );
}
/**
* Returns the base query for retrieving related objects.
*
* See {@link getRelatedObject()} and {@link getRelatedObjects()}. Can be
* modified by additional where conditions and simply be used with
* {@link find()} and the related class name, to retrieve a sub-set of
* related objects.
*
* If multiple relations are defined for the $relatedClass (using {@link
* ezcPersistentRelationCollection}), the parameter $relationName becomes
* mandatory to determine which relation definition to use. For normal
* relations, this parameter is silently ignored.
*
* If you provide a $setName, the resulting set of related objects fetched
* by {@link find()} is cached under the given name for $object. You can
* retrieve this set either through {@link getRelatedObjectSubset()} or by
* issueing the same query (or a query with the same $object and $setName)
* again. Overwriting a once created named set can be enfored using the
* 'refetch' option in {@link ezcPersistentSessionIdentityDecoratorOptions}.
*
* @param ezcPersistentObject $object
* @param string $relatedClass
* @param string $relationName
* @param string $setName
*
* @return ezcPersistentRelationFindQuery
*
* @throws ezcPersistentRelationNotFoundException
* if the given $object does not have a relation to $relatedClass.
*/
public function createRelationFindQuery( $object, $relatedClass, $relationName = null, $setName = null )
{
$originalQuery = $this->session->createRelationFindQuery( $object, $relatedClass, $relationName );
$q = new ezcPersistentRelationFindQuery(
$originalQuery->query,
$originalQuery->className
);
if ( $setName !== null )
{
$q->relationSetName = $setName;
$q->relationSource = $object;
}
return $q;
}
/**
* Saves the new persistent object $object to the database using an INSERT INTO query.
*
* The correct ID is set to $object, if not using the {@link
* ezcPersistentManualGenerator} (then you need to define the ID yourself).
*
* Newly saved objects are stored in the identity map.
*
* @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 ezcPersistentObject $object
*/
public function save( $object )
{
$class = get_class( $object );
$def = $this->definitionManager->fetchDefinition( $class );
$state = $object->getState();
// Sanity checks
if ( !$this->properties['options']->refetch && isset( $state[$def->idProperty->propertyName] ) )
{
$id = $state[$def->idProperty->propertyName];
$identity = $this->identityMap->getIdentity( $class, $id );
if ( $identity !== null )
{
if ( $identity === $object )
{
throw new ezcPersistentObjectAlreadyPersistentException( $class );
}
throw new ezcPersistentIdentityAlreadyExistsException( $class, $id );
}
}
$this->session->save( $object );
$this->identityMap->setIdentity( $object );
}
/**
* Updates $object in the database using an UPDATE query.
*
* Stores the changes made to $object into the database. Updates are
* automatically reflected in the identity map.
*
* @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 ezcPersistentObject $object
*/
public function update( $object )
{
// The object already must have been fetched before here, so an
// identity is already recorded.
$this->session->update( $object );
}
/**
* Saves or updates the persistent $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.
*
* Newly saved objects are automatically recorded in the identity map.
* Updates to existing objects are reflected automatically, too.
*
* @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 ezcPersistentObject $object
*/
public function saveOrUpdate( $object )
{
$this->session->saveOrUpdate( $object );
$class = get_class( $object );
$def = $this->definitionManager->fetchDefinition( $class );
$state = $object->getState();
$id = $state[$def->idProperty->propertyName];
if ( $this->properties['options']->refetch || $this->identityMap->getIdentity( $class, $id ) === null )
{
$this->identityMap->setIdentity( $object );
}
}
/**
* 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 {@ezcPersistentManyToManyRelation}s, where the relation
* record is stored automatically and there is no need to store
* $relatedObject explicitly after establishing the relation.
*
* If there are multiple relations defined between the class of $object and
* $relatedObject (via {@link ezcPersistentRelationCollection}), the
* $relationName parameter becomes mandatory to determine, which exact
* relation should be used.
*
* Newly added related objects are stored in the identity map and added to
* recorded relation sets. If not set of related object set is recorded,
* yet, the adding is ignored.
*
* Note: All named related object sets (see {@link
* ezcPersistentFindWithRelationsQuery}) for $object are invalidated and
* removed from the identity map, to avoid inconsistencies.
*
* @param ezcPersistentObject $object
* @param ezcPersistentObject $relatedObject
* @param string $relationName
*
* @throws ezcPersistentRelationOperationNotSupportedException
* if a relation to create is marked as "reverse" {@link
* ezcPersistentRelation->reverse}.
* @throws ezcPersistentRelationNotFoundException
* if the deisred relation is not defined.
*/
public function addRelatedObject( $object, $relatedObject, $relationName = null )
{
$this->session->addRelatedObject( $object, $relatedObject, $relationName );
$this->identityMap->addRelatedObject( $object, $relatedObject );
}
/**
* 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.
*
* Attention: If you use a query generated by this method to update
* objects, the internal {@link ezcPersistentIdentityMap} will be completly
* reset. This is neccessary to avoid inconsistencies, because the session
* cannot trace which objects are updated by the query.
*
* @throws ezcPersistentDefinitionNotFoundException
* if there is no such persistent class.
*
* @param string $class
*
* @return ezcQueryUpdate
*/
public function createUpdateQuery( $class )
{
return $this->session->createUpdateQuery( $class );
}
/**
* 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.
*
* Attention: Every call to this method will cause the internal {@link
* ezcPersistentIdentityMap} to be completly reset. This is neccessary to
* avoid inconsistencies, because the session cannot trace which objects
* are updated by the query.
*
* @throws ezcPersistentQueryException
* if the update query failed.
*
* @param ezcQueryUpdate $query
*/
public function updateFromQuery( ezcQueryUpdate $query )
{
$this->identityMap->reset();
return $this->session->updateFromQuery( $query );
}
/**
* Deletes the persistent $object.
*
* This method will perform a DELETE query based on the identifier of the
* persistent $object. After delete() the ID property of $object will be
* reset to null. It is possible to {@link save()} $object afterwards.
* $object will then be stored with a new ID.
*
* If you defined relations for the given object, these will be checked to
* be defined as cascading. If cascading is configured, the related objects
* with this relation will be deleted, too.
*
* The object will also be removed from the identity map and all related
* object sets in it.
*
* Relations that support cascading are:
* <ul>
* <li>{@link ezcPersistenOneToManyRelation}</li>
* <li>{@link ezcPersistenOneToOne}</li>
* </ul>
*
* @throws ezcPersistentDefinitionNotFoundxception
* if $the object is not recognized as a persistent object.
* @throws ezcPersistentObjectNotPersistentException
* if the object is not persistent already.
* @throws ezcPersistentQueryException
* if the object could not be deleted.
*
* @param ezcPersistentObject $object The persistent object to delete.
*/
public function delete( $object )
{
$this->session->delete( $object );
$class = get_class( $object );
$def = $this->session->definitionManager->fetchDefinition( $class );
$state = $object->getState();
$id = $state[$def->idProperty->propertyName];
$this->identityMap->removeIdentity( $class, $id );
}
/**
* Removes the relation between $object and $relatedObject.
*
* This method is used to delete an existing relation between 2 objects.
* Like {@link addRelatedObject()} this method does not store the related
* object after removing its relation properties (unset), except for {@link
* ezcPersistentManyToManyRelation()}s, for which the relation record is
* deleted from the database.
*
* If between the classes of $object and $relatedObject multiple relations
* are defined using a {@link ezcPersistentRelationCollection}, the
* $relationName parameter becomes necessary. It defines which exact
* relation to affect here.
*
* Removal of related objects is reflected in the identity map
* automatically and also in named related object sets.
*
* @param ezcPersistentObject $object Source object of the relation.
* @param ezcPersistentObject $relatedObject Related object.
* @param string $relationName
*
* @throws ezcPersistentRelationOperationNotSupportedException
* if a relation to create is marked as "reverse".
* @throws ezcPersistentRelationNotFoundException
* if the deisred relation is not defined.
*/
public function removeRelatedObject( $object, $relatedObject, $relationName = null )
{
$this->session->removeRelatedObject( $object, $relatedObject, $relationName );
$this->identityMap->removeRelatedObject( $object, $relatedObject, $relationName );
}
/**
* Deletes persistent objects using the given $query.
*
* The $query should be created using {@link createDeleteQuery()}.
*
* Attention: Every call to this method will cause the internal {@link
* ezcPersistentIdentityMap} to be completly reset. This is neccessary to
* avoid inconsistencies, because the session cannot trace which objects
* are updated by the query.
*
* @throws ezcPersistentQueryException
* if the delete query failed.
*
* @param ezcQueryDelete $query
*/
public function deleteFromQuery( ezcQueryDelete $query )
{
$this->identityMap->reset();
return $this->session->deleteFromQuery( $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>
*
* Attention: If you use a query generated by this method to delete objects,
* the internal {@link ezcPersistentIdentityMap} will be completly reset.
* This is neccessary to avoid inconsistencies, because the session cannot
* trace which objects are deleted by the query.
*
* @throws ezcPersistentObjectException
* if there is no such persistent class.
*
* @param string $class
*
* @return ezcQueryDelete
*/
public function createDeleteQuery( $class )
{
return $this->session->createDeleteQuery( $class );
}
/**
* Loads an object of $class with $id and related objects defined by $relations.
*
* This method loads and returns the object of $class with $id. In
* addition, all objects defined by $relations are loaded and fetched into
* the identity map. Those can then be retrieved using {@link
* getRelatedObjects()}, without issueing further database queries.
*
* Example for the $relations parameter:
* <code>
* <?php
* array(
* 'relationAlias_1' => new ezcPersistentRelationFindDefinition(
* 'relatedClass_1',
* null,
* array(
* 'deeperAlias_1' => new ezcPersistentRelationFindDefinition(
* 'deeperRelatedClass_1'
* )
* )
* ),
* 'relationAlias_2' => new ezcPersistentRelationFindDefinition(
* 'relatedClass_2'
* )
* );
* ?>
* </code>
*
* Each relation is defined by an {@link
* ezcPersistentRelationFindDefinition} struct This defines the related
* class to load objects for, optionally a relation name (second parameter)
* and possibly an array of deeper relations. All array keys on all levels
* of the $relations parameter must be unique!
*
* In the example, if $class is 'myClass' and $id is 23, the object of
* myClass with ID 23 is loaded and returned. In addition, all objects of
* relatedClass_1 and relatedClass_2, that related to the loaded object,
* are loaded and stored in the identity map. For each of these objects of
* class relatedClass_1, all related objects of class deeperRelatedClass_1
* are loaded and also stored in the identity map.
*
* @see createFindQueryWithRelations()
*
* @param string $class
* @param string $id
* @param array(string=>ezcPersistentRelationFindDefinition) $relations
* @return ezcPersistentObject
*/
public function loadWithRelatedObjects( $class, $id, array $relations )
{
$this->initializeQueryCreator();
$select = $this->queryCreator->createLoadQuery( $class, $id, $relations );
$stmt = $select->prepare();
$stmt->execute();
$this->initializeObjectExtractor();
return $this->objectExtractor->extractObjectWithRelatedObjects(
$stmt,
$class,
$id,
$relations
);
}
/**
* Returns if $relatedObject is related to $sourceObject.
*
* Checks the relation conditions between $sourceObject and $relatedObject
* and returns true, if $relatedObject is related to $sourceObject,
* otherwise false. Relation state is determined through the identity map,
* in case the relation between $sourceObject and $relatedObject has been
* recorded there. Otherwise this method dispatches to
* {@link ezcPersistentSession::isRelated()}.
*
* In case multiple relations are defined between the
* classes of $sourceObject and $relatedObject, the $relationName parameter
* becomes mandatory. If it is not provided in this case, an {@link
* ezcPersistentUndeterministicRelationException} is thrown.
*
* Note that checking relations of type {@link
* ezcPersistentManyToManyRelation} will issue a database query, if the
* relation is not recorded in the identity map. Other relations will not
* perform this query at all.
*
* @param ezcPersistentObject $sourceObj
* @param ezcPersistentObject $relatedObj
* @param string $relationName
* @return bool
*/
public function isRelated( $sourceObject, $relatedObject, $relationName = null )
{
$srcClass = get_class( $sourceObject );
$relClass = get_class( $relatedObject );
$storedRelatedObjects = $this->identityMap->getRelatedObjects(
$sourceObject,
$relClass,
$relationName
);
if ( $storedRelatedObjects !== null )
{
return in_array( $relatedObject, $storedRelatedObjects );
}
return $this->session->isRelated( $sourceObject, $relatedObject );
}
/**
* Initializes the global query creator for this session.
*
* Checks if the query creator already exists and instantiates it, if not.
*/
private function initializeQueryCreator()
{
if ( $this->queryCreator === null )
{
$this->queryCreator = new ezcPersistentIdentityRelationQueryCreator(
$this->session->definitionManager,
$this->session->database
);
}
}
/**
* Initializes the global object extractor for this session.
*
* Checks if the object extractor already exists and instantiates it, if not.
*/
private function initializeObjectExtractor()
{
if ( $this->objectExtractor === null )
{
$this->objectExtractor = new ezcPersistentIdentityRelationObjectExtractor(
$this->properties['identityMap'],
$this->session->definitionManager,
$this->properties['options']
);
}
}
/**
* Returns a hash map between property and column name for the given
* definition $def.
*
* The alias map can be used with the query classes. If $prefixTableName is
* set to false, only the column names are used as alias targets.
*
* @param ezcPersistentObjectDefinition $def Definition.
* @param bool $prefixTableName
* @return array(string=>string)
*/
public function generateAliasMap( ezcPersistentObjectDefinition $def, $prefixTableName = true )
{
return $this->session->generateAliasMap( $def, $prefixTableName );
}
/**
* Returns all the columns defined in the persistent object.
*
* If $prefixTableName is set to false, raw column names will be used,
* without prefixed table name.
*
* @param ezcPersistentObjectDefinition $def Defintion.
* @param bool $prefixTableName
* @return array(int=>string)
*/
public function getColumnsFromDefinition( ezcPersistentObjectDefinition $def, $prefixTableName = true )
{
return $this->session->getColumnsFromDefinition( $def, $prefixTableName );
}
/**
* Sets the property $name to $value.
*
* @throws ezcBasePropertyNotFoundException
* if the property does not exist.
*
* @param string $name
* @param mixed $value
*
* @ignore
*/
public function __set( $name, $value )
{
switch ( $name )
{
case 'identityMap':
throw new ezcBasePropertyPermissionException(
$name,
ezcBasePropertyPermissionException::READ
);
case 'options':
if ( !( $value instanceof ezcPersistentSessionIdentityDecoratorOptions ) )
{
throw new ezcBaseValueException(
$name,
$value,
'ezcPersistentSessionIdentityDecoratorOptions'
);
}
break;
default:
// Decorator: Dispatch unknown options to inner session
$this->session->$name = $value;
return;
}
$this->properties[$name] = $value;
}
/**
* Property get access.
*
* Simply returns a given property.
*
* @throws ezcBasePropertyNotFoundException
* If a the value for the property propertys is not an instance of
* @param string $propertyName The name of the property to get.
* @return mixed The property value.
*
* @ignore
*
* @throws ezcBasePropertyNotFoundException
* if the given property does not exist.
* @throws ezcBasePropertyPermissionException
* if the property to be set is a write-only property.
*/
public function __get( $propertyName )
{
if ( array_key_exists( $propertyName, $this->properties ) )
{
return $this->properties[$propertyName];
}
return $this->session->$propertyName;
}
/**
* Returns if a property exists.
*
* Returns true if the property exists in the {@link $properties} array
* (even if it is null) and false otherwise.
*
* @param string $propertyName Option name to check for.
* @return void
* @ignore
*/
public function __isset( $propertyName )
{
return (
array_key_exists( $propertyName, $this->properties )
|| isset( $this->session->$propertyName )
);
}
}
?>