-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for ManyToMany Criteria #885
Changes from all commits
160b07d
07654dd
738cc25
9385a60
be39afa
bb1f71f
c69b756
fbbe922
e5ba286
a55f2c4
0d5de64
f03f991
6cbd643
a45ecb5
b550d44
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ | |
|
||
namespace Doctrine\ORM\Persisters; | ||
|
||
use Doctrine\Common\Collections\Criteria; | ||
use Doctrine\ORM\PersistentCollection; | ||
|
||
/** | ||
|
@@ -136,4 +137,14 @@ public function removeKey(PersistentCollection $collection, $key); | |
* @return mixed | ||
*/ | ||
public function get(PersistentCollection $collection, $index); | ||
|
||
/** | ||
* Loads association entities matching the given Criteria object. | ||
* | ||
* @param \Doctrine\ORM\PersistentCollection $collection | ||
* @param \Doctrine\Common\Collections\Criteria $criteria | ||
* | ||
* @return array | ||
*/ | ||
public function loadCriteria(PersistentCollection $collection, Criteria $criteria); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bakura10 Shouldn't the contract here be to throw an exception if the operation is unsupported by the implementing collection persister? IMO that should be documented here as you already do throw an exception in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mimic the other methods. All the other methods (slice, containsKey...) throw an exception in the AbstractCollectionPersister but it's not in the interface. I just followed the same pattern, if I add it here I should also add it for all other methods then :). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bakura10 Okay I didn't know that. Then it's perfectly fine to me, no need to go a different way here then :) |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,8 +19,10 @@ | |
|
||
namespace Doctrine\ORM\Persisters; | ||
|
||
use Doctrine\Common\Collections\Criteria; | ||
use Doctrine\ORM\Mapping\ClassMetadata; | ||
use Doctrine\ORM\PersistentCollection; | ||
use Doctrine\ORM\Query; | ||
use Doctrine\ORM\UnitOfWork; | ||
|
||
/** | ||
|
@@ -54,7 +56,7 @@ protected function getDeleteRowSQL(PersistentCollection $coll) | |
} | ||
|
||
return 'DELETE FROM ' . $tableName | ||
. ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; | ||
. ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; | ||
} | ||
|
||
/** | ||
|
@@ -102,7 +104,7 @@ protected function getInsertRowSQL(PersistentCollection $coll) | |
} | ||
|
||
return 'INSERT INTO ' . $joinTable . ' (' . implode(', ', $columns) . ')' | ||
. ' VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')'; | ||
. ' VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')'; | ||
} | ||
|
||
/** | ||
|
@@ -178,7 +180,7 @@ protected function getDeleteSQL(PersistentCollection $coll) | |
} | ||
|
||
return 'DELETE FROM ' . $joinTable | ||
. ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; | ||
. ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; | ||
} | ||
|
||
/** | ||
|
@@ -257,7 +259,7 @@ public function count(PersistentCollection $coll) | |
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
* {@inheritDoc} | ||
*/ | ||
public function slice(PersistentCollection $coll, $offset, $length = null) | ||
{ | ||
|
@@ -276,7 +278,7 @@ public function containsKey(PersistentCollection $coll, $key) | |
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
* {@inheritDoc} | ||
*/ | ||
public function contains(PersistentCollection $coll, $element) | ||
{ | ||
|
@@ -302,7 +304,7 @@ public function contains(PersistentCollection $coll, $element) | |
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
* {@inheritDoc} | ||
*/ | ||
public function removeElement(PersistentCollection $coll, $element) | ||
{ | ||
|
@@ -485,52 +487,137 @@ public function getFilterSql($mapping) | |
return array('', ''); | ||
} | ||
|
||
$conditions = array(); | ||
// A join is needed if there is filtering on the target entity | ||
$tableName = $this->quoteStrategy->getTableName($rootClass, $this->platform); | ||
$joinSql = ' JOIN ' . $tableName . ' te' . ' ON'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it seems. |
||
$onConditions = $this->getOnConditionSQL($mapping); | ||
|
||
$joinSql .= implode(' AND ', $onConditions); | ||
|
||
return array($joinSql, $filterSql); | ||
} | ||
|
||
/** | ||
* Generates the filter SQL for a given entity and table alias. | ||
* | ||
* @param ClassMetadata $targetEntity Metadata of the target entity. | ||
* @param string $targetTableAlias The table alias of the joined/selected table. | ||
* | ||
* @return string The SQL query part to add to a query. | ||
*/ | ||
protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias) | ||
{ | ||
$filterClauses = array(); | ||
|
||
foreach ($this->em->getFilters()->getEnabledFilters() as $filter) { | ||
if ($filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) { | ||
$filterClauses[] = '(' . $filterExpr . ')'; | ||
} | ||
} | ||
|
||
$sql = implode(' AND ', $filterClauses); | ||
return $sql ? '(' . $sql . ')' : ''; | ||
} | ||
|
||
/** | ||
* Generate ON condition | ||
* | ||
* @param array $mapping | ||
* | ||
* @return array | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. picky: blank line missing before |
||
*/ | ||
protected function getOnConditionSQL($mapping) | ||
{ | ||
$association = $mapping; | ||
|
||
if ( ! $mapping['isOwningSide']) { | ||
$class = $this->em->getClassMetadata($mapping['targetEntity']); | ||
$association = $class->associationMappings[$mapping['mappedBy']]; | ||
} | ||
|
||
// A join is needed if there is filtering on the target entity | ||
$tableName = $this->quoteStrategy->getTableName($rootClass, $this->platform); | ||
$joinSql = ' JOIN ' . $tableName . ' te' . ' ON'; | ||
$targetClass = $this->em->getClassMetadata($mapping['targetEntity']); | ||
|
||
$joinColumns = $mapping['isOwningSide'] | ||
? $association['joinTable']['inverseJoinColumns'] | ||
: $association['joinTable']['joinColumns']; | ||
|
||
$conditions = array(); | ||
|
||
foreach ($joinColumns as $joinColumn) { | ||
$joinColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); | ||
$refColumnName = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform); | ||
|
||
$conditions[] = ' t.' . $joinColumnName . ' = ' . 'te.' . $refColumnName; | ||
} | ||
|
||
$joinSql .= implode(' AND ', $conditions); | ||
return $conditions; | ||
} | ||
|
||
return array($joinSql, $filterSql); | ||
/** | ||
* {@inheritDoc} | ||
*/ | ||
public function loadCriteria(PersistentCollection $coll, Criteria $criteria) | ||
{ | ||
$mapping = $coll->getMapping(); | ||
$owner = $coll->getOwner(); | ||
$ownerMetadata = $this->em->getClassMetadata(get_class($owner)); | ||
$whereClauses = $params = array(); | ||
|
||
foreach ($mapping['relationToSourceKeyColumns'] as $key => $value) { | ||
$whereClauses[] = sprintf('t.%s = ?', $key); | ||
$params[] = $ownerMetadata->getFieldValue($owner, $value); | ||
} | ||
|
||
$parameters = $this->expandCriteriaParameters($criteria); | ||
|
||
foreach ($parameters as $parameter) { | ||
list($name, $value) = $parameter; | ||
$whereClauses[] = sprintf('te.%s = ?', $name); | ||
$params[] = $value; | ||
} | ||
|
||
$mapping = $coll->getMapping(); | ||
$targetClass = $this->em->getClassMetadata($mapping['targetEntity']); | ||
$tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform); | ||
$joinTable = $this->quoteStrategy->getJoinTableName($mapping, $ownerMetadata, $this->platform); | ||
$onConditions = $this->getOnConditionSQL($mapping); | ||
|
||
$rsm = new Query\ResultSetMappingBuilder($this->em); | ||
$rsm->addRootEntityFromClassMetadata($mapping['targetEntity'], 'te'); | ||
|
||
$sql = 'SELECT ' . $rsm->generateSelectClause() . ' FROM ' . $tableName . ' te' | ||
. ' JOIN ' . $joinTable . ' t ON' | ||
. implode(' AND ', $onConditions) | ||
. ' WHERE ' . implode(' AND ', $whereClauses); | ||
|
||
$stmt = $this->conn->executeQuery($sql, $params); | ||
$hydrator = $this->em->newHydrator(Query::HYDRATE_OBJECT); | ||
|
||
return $hydrator->hydrateAll($stmt, $rsm); | ||
} | ||
|
||
/** | ||
* Generates the filter SQL for a given entity and table alias. | ||
* Expands Criteria Parameters by walking the expressions and grabbing all | ||
* parameters and types from it. | ||
* | ||
* @param ClassMetadata $targetEntity Metadata of the target entity. | ||
* @param string $targetTableAlias The table alias of the joined/selected table. | ||
* @param \Doctrine\Common\Collections\Criteria $criteria | ||
* | ||
* @return string The SQL query part to add to a query. | ||
* @return array | ||
*/ | ||
protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias) | ||
private function expandCriteriaParameters(Criteria $criteria) | ||
{ | ||
$filterClauses = array(); | ||
$expression = $criteria->getWhereExpression(); | ||
|
||
foreach ($this->em->getFilters()->getEnabledFilters() as $filter) { | ||
if ($filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) { | ||
$filterClauses[] = '(' . $filterExpr . ')'; | ||
} | ||
if ($expression === null) { | ||
return array(); | ||
} | ||
|
||
$sql = implode(' AND ', $filterClauses); | ||
return $sql ? "(" . $sql . ")" : ""; | ||
$valueVisitor = new SqlValueVisitor(); | ||
|
||
$valueVisitor->dispatch($expression); | ||
|
||
list($values, $types) = $valueVisitor->getParamsAndTypes(); | ||
|
||
return $types; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add an empty newline before this line