blob: d31752e99584b3d1ed7fede79e9bb58fe0e4ad4b [file] [log] [blame]
<?php
/**
* File containing the ezcPersistentLoadHandler class
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* @package PersistentObject
* @version //autogen//
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
/**
* Helper class for ezcPersistentSession to handle object loading.
*
* An instance of this class is used internally in {@link ezcPersistentSession}
* and takes care for loading and finding objects.
*
* @package PersistentObject
* @version //autogen//
* @access private
*/
class ezcPersistentLoadHandler extends ezcPersistentSessionHandler
{
/**
* Returns the persistent object of class $class with id $id.
*
* @throws ezcPersistentObjectNotFoundException
* 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->definitionManager->fetchDefinition( $class );
$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 {@link 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
* @apichange This method will catch only exceptions which are related to
* the loading itself in future major releases.
*/
public function loadIfExists( $class, $id )
{
$result = null;
try
{
$result = $this->load( $class, $id );
}
catch ( ezcPersistentObjectException $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 ezcPersistentObjectNotFoundException
* 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->definitionManager->fetchDefinition(
get_class( $object )
);
// Prepare query.
$q = $this->database->createSelectQuery();
$q->select(
$this->session->getColumnsFromDefinition( $def )
)->from(
$this->database->quoteIdentifier( $def->table )
)->where(
$q->expr->eq(
$this->database->quoteIdentifier( $def->idProperty->columnName ),
$q->bindValue( $id, null, $def->idProperty->databaseType )
)
);
// Execute and fetch rows.
$stmt = $this->session->performQuery( $q );
$row = $stmt->fetch( PDO::FETCH_ASSOC );
$stmt->closeCursor();
// Convert result into object.
if ( $row !== false )
{
// 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 ezcPersistentObjectNotFoundException( $class, $id );
}
}
/**
* Syncronizes the contents of $object with those in 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.
*
* @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 )
{
$class = get_class( $object );
$def = $this->definitionManager->fetchDefinition( $class );
$state = $this->session->getObjectState( $object );
$idValue = $state[$def->idProperty->propertyName];
if ( $idValue !== null )
{
$this->loadIntoObject( $object, $idValue );
}
else
{
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 {@link 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 {@link
* 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.
* @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.
*/
public function find( $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,
'must be present, if ezcQuerySelect is used for $query'
);
}
// Extract class name and select query form parameter
if ( $query instanceof ezcPersistentFindQuery )
{
$class = $query->className;
$query = $query->query;
}
$def = $this->definitionManager->fetchDefinition( $class );
$rows = $this->session->performQuery( $query )
->fetchAll( PDO::FETCH_ASSOC );
// Convert all the rows to states and then to objects.
$result = array();
foreach ( $rows as $row )
{
$object = new $def->class;
$object->setState(
ezcPersistentStateTransformer::rowToStateArray( $row, $def )
);
$result[$row[$def->idProperty->resultColumnName]] = $object;
}
return $result;
}
/**
* Returns the result of $query for the $class as an iterator.
*
* This method is similar to {@link find()} but returns an {@link
* 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
* {@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.
*
* @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 ezcPersistentFindIterator
* @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,
'must be present, if ezcQuerySelect is used for $query'
);
}
// 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 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 {@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}).
*
* 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 )
{
$query = $this->createRelationFindQuery( $object, $relatedClass, $relationName );
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 {@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.
*
* 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 )
{
$query = $this->createRelationFindQuery( $object, $relatedClass, $relationName );
// 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 reset( $resArr );
}
/**
* 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 ezcPersistentFindQuery
*/
public function createFindQuery( $class )
{
$def = $this->definitionManager->fetchDefinition( $class );
// Init query
$q = $this->database->createSelectQuery();
$q->setAliases( $this->session->generateAliasMap( $def ) );
$q->select(
$this->session->getColumnsFromDefinition( $def )
)->from(
$this->database->quoteIdentifier( $def->table )
);
$findQuery = new ezcPersistentFindQuery( $q, $class );
return $findQuery;
}
/**
* 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 )
{
$subSelect = $parentQuery->subSelect();
$def = $this->definitionManager->fetchDefinition( $class );
$subSelect->setAliases( $this->session->generateAliasMap( $def ) );
$subSelect->from( $this->database->quoteIdentifier( $def->table ) );
return $subSelect;
}
/**
* 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 exist to the same PHP class (defined using a
* {@link ezcPersistentRelationCollection}), the optional parameter
* $relationName becomes mandatory to determine the relation to use for
* fetching objects. If the parameter is not submitted, an exception will
* be thrown. For normal relations this parameter will be silently ignored.
*
* @param object $object
* @param string $relatedClass
* @param string $relationName
*
* @return ezcPersistentFindQuery
*
* @throws ezcPersistentRelationNotFoundException
* if the given $object does not have a relation to $relatedClass.
*/
public function createRelationFindQuery( $object, $relatedClass, $relationName = null )
{
$class = get_class( $object );
$def = $this->definitionManager->fetchDefinition( $class );
if ( !isset( $def->relations[$relatedClass] ) )
{
throw new ezcPersistentRelationNotFoundException(
$class,
$relatedClass
);
}
$relation = $def->relations[$relatedClass];
// New multi-relations for a single class
if ( $relation instanceof ezcPersistentRelationCollection )
{
if ( $relationName === null )
{
throw new ezcPersistentUndeterministicRelationException( $relatedClass );
}
if ( !isset( $relation[$relationName] ) )
{
throw new ezcPersistentRelationNotFoundException(
$class,
$relatedClass,
$relationName
);
}
$relation = $relation[$relationName];
}
$query = $this->createFindQuery( $relatedClass );
$objectState = $this->session->getObjectState( $object );
switch ( ( $relationClass = get_class( $relation ) ) )
{
case "ezcPersistentOneToManyRelation":
case "ezcPersistentManyToOneRelation":
case "ezcPersistentOneToOneRelation":
$this->createSimpleRelationFindQuery( $query, $def, $relation, $objectState );
break;
case "ezcPersistentManyToManyRelation":
$this->createComplexRelationFindQuery( $query, $def, $relation, $objectState );
break;
default:
throw new ezcPersistentRelationInvalidException( $relationClass );
}
return $query;
}
/**
* Sets find query value for simple related objects.
*
* Manipulates the find $query for objects related to the object defined in
* $objectState, defined my the relation $relation. This method is
* responsile for
* <ul>
* <li>{@link ezcPersistentOneToManyRelation}</li>
* <li>{@link ezcPersistentOneToOneRelation}</li>
* <li>{@link ezcPersistentManyToOneRelatio}n</li>
* </ul>
* for {@link ezcPersistentManyToManyRelation} see {@link
* createComplexRelationFindQuery()}.
*
* @param ezcPersistentFindQuery $query
* @param ezcPersistentObjectDefinition $def
* @param ezcPersistentRelation $relation
* @param array $objectState
*/
private function createSimpleRelationFindQuery(
ezcPersistentFindQuery $query,
ezcPersistentObjectDefinition $def,
ezcPersistentRelation $relation,
array $objectState
)
{
foreach ( $relation->columnMap as $map )
{
$query->where(
$query->expr->eq(
$this->database->quoteIdentifier(
$map->destinationColumn
),
$query->bindValue(
$objectState[$def->columns[$map->sourceColumn]->propertyName],
null,
$def->columns[$map->sourceColumn]->databaseType
)
)
);
}
}
/**
* Sets find query value for many-to-many related objects.
*
* Manipulates the find $query for objects related to the object defined in
* $objectState, defined my the relation $relation.
*
* @param ezcPersistentFindQuery $query
* @param ezcPersistentObjectDefinition $def
* @param ezcPersistentManyToManyRelation $relation
* @param array $objectState
*/
private function createComplexRelationFindQuery(
ezcPersistentFindQuery $query,
ezcPersistentObjectDefinition $def,
ezcPersistentManyToManyRelation $relation,
array $objectState
)
{
$db = $this->database;
$relationTableQuoted = $db->quoteIdentifier( $relation->relationTable );
// Join with relation table.
$query->from( $relationTableQuoted );
foreach ( $relation->columnMap as $map )
{
$query->where(
$query->expr->eq(
$relationTableQuoted . "." . $db->quoteIdentifier( $map->relationSourceColumn ),
$query->bindValue(
$objectState[$def->columns[$map->sourceColumn]->propertyName],
null,
$def->columns[$map->sourceColumn]->databaseType
)
),
$query->expr->eq(
$relationTableQuoted . "." . $db->quoteIdentifier( $map->relationDestinationColumn ),
$db->quoteIdentifier( $relation->destinationTable ) . "." . $db->quoteIdentifier( $map->destinationColumn )
)
);
}
}
}
?>