blob: ba9ea0fffabe2f8cd609614efc6e482c29612d5a [file] [log] [blame]
<?php
/**
* File containing the ezcPersistentIdentityRelationQueryCreator 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
*/
/**
* Creates JOIN queries to fetch related objects for a given object.
*
* An instance of this class is used in {@link ezcPersistentSessionIdentityDecorator} to
* generate a {@link ezcQuerySelect} object to fetch related objects for a
* certain object. The results of the generated query are then extracted using
* {@link ezcPersistentIdentityRelationObjectExtractor}.
*
* @package PersistentObject
* @version //autogen//
* @access private
*/
class ezcPersistentIdentityRelationQueryCreator
{
/**
* Definition Manager.
*
* @var ezcPersistentDefinitionManager
*/
protected $defManager;
/**
* Database handler.
*
* @var ezcDbHandler
*/
protected $db;
/**
* Aliases to be registered in query objects.
*
* @var array(string=>string)
*/
protected $aliases = array();
/**
* Creates a new query generator.
*
* Creates a new query generator that will receive needed persistentence
* definitions from $defManager.
*
* @param ezcPersistentDefinitionManager $defManager
* @param ezcDbHandler $database
*/
public function __construct( ezcPersistentDefinitionManager $defManager, ezcDbHandler $database )
{
$this->defManager = $defManager;
$this->db = $database;
}
/**
* Creates a load object query with relation pre-fetching.
*
* This method generates a query that loads the object of $class with $id
* and its related objects as specified by $relations.
*
* @param string $class
* @param mixed $id
* @param array(string=>ezcPersistentRelationFindDefinition) $relations
* @return ezcDbQuerySelect
*/
public function createLoadQuery( $class, $id, array $relations )
{
$srcDef = $this->defManager->fetchDefinition( $class );
$q = $this->createBasicFindQuery( $srcDef, $relations );
$q->where(
$q->expr->eq(
$this->getColumnName(
$srcDef->table,
$srcDef->idProperty->columnName
),
$q->bindValue( $id )
)
);
$this->aliases = array();
return $q;
}
/**
* Creates a find object query for $class with relation pre-fetching for $relations.
*
* This method generates a query that finds objects of $class together with
* their related objects as defined in $relations.
*
* @param mixed $class
* @param array(string=>ezcPersistentRelationFindDefinition) $relations
* @return ezcPersistentFindWithRelationsQuery
*/
public function createFindQuery( $class, array $relations )
{
$srcDef = $this->defManager->fetchDefinition( $class );
$q = $this->createBasicFindQuery( $srcDef, $relations );
$q->setAliases( $this->aliases );
$this->aliases = array();
return new ezcPersistentFindWithRelationsQuery(
$q,
$class,
$relations
);
}
/**
* Generates a basic find query with relation pre-fetching.
*
* This method generates a basic find query for the table/class defined in
* $srcDef, with relation pre-fetching for $relations.
*
* @param ezcPersistentObjectDefinition $srcDef
* @param array(string=>ezcPersistentRelationFindDefinition) $relations
* @return ezcQuerySelect
*/
protected function createBasicFindQuery( ezcPersistentObjectDefinition $srcDef, array $relations )
{
$this->fetchDefinitions( $srcDef, $relations );
$q = $this->db->createSelectQuery();
// Select the desired object columns as main
$q->select(
$q->alias(
$this->getColumnName( $srcDef->table, $srcDef->idProperty->columnName ),
$this->db->quoteIdentifier( $srcDef->idProperty->propertyName )
)
);
$this->registerAlias(
$this->getColumnName( $srcDef->table, $srcDef->idProperty->columnName ),
$srcDef->idProperty->propertyName
);
foreach ( $srcDef->properties as $property )
{
$q->select(
$q->alias(
$this->getColumnName( $srcDef->table, $property->columnName ),
$this->db->quoteIdentifier( $property->propertyName )
)
);
$this->registerAlias(
$this->getColumnName( $srcDef->table, $property->columnName ),
$property->propertyName
);
}
$this->createSelects( $q, $relations );
$q->from( $this->db->quoteIdentifier( $srcDef->table ) );
$this->createJoins( $q, $srcDef->table, $relations );
return $q;
}
/**
* Fetches the needed definitions for $relations and stores them.
*
* Fetches {@link ezcPersistentObjectDefinition} and relation definitions
* for all relations defined in $relations. $srcDef is the the object
* definition, where $relations should be fetched for. The definitions are
* stored inside the {@link ezcPersistentRelationFindDefinition} objects
* the correspond to.
*
* @param ezcPersistentObjectDefinition $srcDef
* @param array(string=>ezcPersistentRelationFindDefinition) $relations
*/
protected function fetchDefinitions( ezcPersistentObjectDefinition $srcDef, array $relations )
{
foreach ( $relations as $relation )
{
if ( !isset( $srcDef->relations[$relation->relatedClass] ) )
{
throw new ezcPersistentRelationNotFoundException(
$srcDef->class, $relation->relatedClass
);
}
$srcRelDef = $srcDef->relations[$relation->relatedClass];
if ( $relation->relationName !== null )
{
$srcRelDef = $srcRelDef[$relation->relationName];
}
$relation->relationDefinition = $srcRelDef;
$relation->definition = $this->defManager->fetchDefinition( $relation->relatedClass );
if ( $relation->furtherRelations !== array() )
{
$this->fetchDefinitions( $relation->definition, $relation->furtherRelations );
}
}
}
/**
* Adds all columns to be selected to $q.
*
* Adds the columns from all tables defined in $relations to the select
* query $q. Columns are selected using an alias to identify which relation
* they belong too, created using the tables alias. In addition, aliases
* are created in the query $q, for usage ease.
*
* @param ezcQuerySelect $q
* @param array(ezcPersistentRelationFindDefinition) $relations
*/
protected function createSelects( ezcQuerySelect $q, array $relations )
{
foreach ( $relations as $tableAlias => $relation )
{
$q->select(
$q->alias(
$this->getColumnName(
$tableAlias,
$relation->definition->idProperty->columnName
),
$this->getColumnAlias(
$tableAlias,
$relation->definition->idProperty->propertyName
)
)
);
$this->registerAlias(
$this->getColumnName( $tableAlias, $relation->definition->idProperty->columnName ),
// Register unquoted alias in query object!
$this->getColumnAlias( $tableAlias, $relation->definition->idProperty->propertyName, false )
);
foreach ( $relation->definition->properties as $property )
{
$q->select(
$q->alias(
$this->getColumnName( $tableAlias, $property->columnName ),
$this->getColumnAlias( $tableAlias, $property->propertyName )
)
);
$this->registerAlias(
$this->getColumnName( $tableAlias, $property->columnName ),
// Register unquoted alias in query object!
$this->getColumnAlias( $tableAlias, $property->propertyName, false )
);
}
if ( $relation->furtherRelations !== array() )
{
$this->createSelects( $q, $relation->furtherRelations );
}
}
}
/**
* Returns an alias for $column from $table.
*
* Returns the alias for $column from $table to be used in the select
* query. If $quote is set to false, the alias name will not be quoted as
* an identifier.
*
* @param string $table
* @param string $property
* @param bool $quote
* @return string
*/
protected function getColumnAlias( $table, $property, $quote = true )
{
$alias = sprintf(
'%s_%s',
$table,
$property
);
if ( $quote )
{
$alias = $this->db->quoteIdentifier( $alias );
}
return $alias;
}
/**
* Returns the full qualified column name for the $column in $table.
*
* The returned qualified name already contains identifier quotings.
*
* @param string $table
* @param string $column
* @return string
*/
protected function getColumnName( $table, $column )
{
return sprintf(
'%s.%s',
$this->db->quoteIdentifier( $table ),
$this->db->quoteIdentifier( $column )
);
}
/**
* Creates the joins to select $relations from $srcTableAlias.
*
* Creates the necessary JOIN statements in $q to select all related
* objects defined by $relations, seen from $srcTableAlias point of view.
* $srcTableAlias must already be the alias table name of the source table.
*
* @param ezcQuerySelect $q
* @param string $srcTableAlias
* @param array(string=>ezcPersistentRelationFindDefinition) $relations
*/
protected function createJoins( ezcQuerySelect $q, $srcTableAlias, array $relations )
{
foreach ( $relations as $dstTableAlias => $relation )
{
$this->createJoin( $q, $srcTableAlias, $dstTableAlias, $relation );
$this->createJoins( $q, $dstTableAlias, $relation->furtherRelations );
}
}
/**
* Creates the JOIN necessary to fetch related objects of $relation.
*
* Detects if $relation needs a n:m-relation JOIN or just a simple join and
* dispatches to the correct methods.
*
* @param ezcQuerySelect $q
* @param string $srcTableAlias
* @param string $dstTableAlias
* @param ezcPersistentRelationFindDefinition $relation
*/
protected function createJoin( ezcQuerySelect $q, $srcTableAlias, $dstTableAlias, ezcPersistentRelationFindDefinition $relation )
{
if ( $relation->relationDefinition instanceof ezcPersistentManyToManyRelation )
{
$this->createComplexJoin( $q, $srcTableAlias, $dstTableAlias, $relation );
}
else
{
$this->createSimpleJoin( $q, $srcTableAlias, $dstTableAlias, $relation );
}
}
/**
* Creates an n:m relation JOIN to fetch $relation.
*
* Uses the aliases defined in $relation and the relations definition to
* create 2 LEFT JOIN statements in $q. These 2 JOINs are used to fetch the
* objects defined in $relation.
*
* @param ezcQuerySelect $q
* @param string $srcTableAlias
* @param string $dstTableAlias
* @param ezcPersistentRelationFindDefinition $relation
*/
protected function createComplexJoin( ezcQuerySelect $q, $srcTableAlias, $dstTableAlias, ezcPersistentRelationFindDefinition $relation )
{
$relationDefinition = $relation->relationDefinition;
$relTableAlias = sprintf( '%s__%s', $srcTableAlias, $dstTableAlias );
$first = true;
$srcJoinCond = null;
$destJoinCond = null;
// Build join conditions in paralell
foreach ( $relationDefinition->columnMap as $mapping )
{
$srcColumn = $this->getColumnName(
$srcTableAlias,
$mapping->sourceColumn
);
$relSrcColumn = $this->getColumnName(
$relTableAlias,
$mapping->relationSourceColumn
);
$relDestColumn = $this->getColumnName(
$relTableAlias,
$mapping->relationDestinationColumn
);
$destColumn = $this->getColumnName(
$dstTableAlias,
$mapping->destinationColumn
);
if ( $first )
{
$srcJoinCond = $q->expr->eq( $srcColumn, $relSrcColumn );
$destJoinCond = $q->expr->eq( $relDestColumn, $destColumn );
$first = false;
}
else
{
$srcJoinCond = $q->expr->and(
$srcJoinCond,
$q->expr->eq( $srcColumn, $relSrcColumn )
);
$destJoinCond = $q->expr->and(
$destJoinCond,
$q->expr->eq( $relDestColumn, $destColumn )
);
}
}
// Add 2 joins
$q->leftJoin(
$q->alias(
$this->db->quoteIdentifier( $relationDefinition->relationTable ),
$this->db->quoteIdentifier( $relTableAlias )
),
$srcJoinCond
);
$q->leftJoin(
$q->alias(
$this->db->quoteIdentifier( $relationDefinition->destinationTable ),
$this->db->quoteIdentifier( $dstTableAlias )
),
$destJoinCond
);
}
/**
* Creates a simple JOIN to fetch the objects defined by $relation.
*
* Creates a simple LEFT JOIN using the aliases defined in $relation and
* the $srcTableAlias, to fetch all objects defined by $relation, which are
* related to the source object, fetched by $srcTableAlias.
*
* @param ezcQuerySelect $q
* @param string $srcTableAlias
* @param string $dstTableAlias
* @param ezcPersistentRelationFindDefinition $relation
*/
protected function createSimpleJoin( ezcQuerySelect $q, $srcTableAlias, $dstTableAlias, ezcPersistentRelationFindDefinition $relation )
{
$relationDefinition = $relation->relationDefinition;
$first = true;
$joinCond = null;
foreach ( $relationDefinition->columnMap as $mapping )
{
$srcColumn = $this->getColumnName( $srcTableAlias, $mapping->sourceColumn );
$destColumn = $this->getColumnName( $dstTableAlias, $mapping->destinationColumn );
if ( $first )
{
$joinCond = $q->expr->eq( $srcColumn, $destColumn );
$first = false;
}
else
{
$joinCond = $q->expr->and(
$joinCond,
$q->expr->eq( $srcColumn, $destColumn )
);
}
}
$q->leftJoin(
$q->alias(
$this->db->quoteIdentifier( $relationDefinition->destinationTable ),
$this->db->quoteIdentifier( $dstTableAlias )
),
$joinCond
);
}
/**
* Register an alias to be used in {@link ezcPersistentFindQuery}.
*
* @param string $identifier
* @param string $alias
* @return void
*/
protected function registerAlias( $identifier, $alias )
{
$this->aliases[$alias] = $identifier;
}
}
?>