Merge remote branch 'upstream/master'
This commit is contained in:
commit
bf9024b622
@ -496,6 +496,31 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
|||||||
return $this->_attributes['classMetadataFactoryName'];
|
return $this->_attributes['classMetadataFactoryName'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a filter to the list of possible filters.
|
||||||
|
*
|
||||||
|
* @param string $name The name of the filter.
|
||||||
|
* @param string $className The class name of the filter.
|
||||||
|
*/
|
||||||
|
public function addFilter($name, $className)
|
||||||
|
{
|
||||||
|
$this->_attributes['filters'][$name] = $className;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the class name for a given filter name.
|
||||||
|
*
|
||||||
|
* @param string $name The name of the filter.
|
||||||
|
*
|
||||||
|
* @return string The class name of the filter, or null of it is not
|
||||||
|
* defined.
|
||||||
|
*/
|
||||||
|
public function getFilterClassName($name)
|
||||||
|
{
|
||||||
|
return isset($this->_attributes['filters'][$name]) ?
|
||||||
|
$this->_attributes['filters'][$name] : null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set default repository class.
|
* Set default repository class.
|
||||||
*
|
*
|
||||||
|
@ -27,7 +27,8 @@ use Closure, Exception,
|
|||||||
Doctrine\ORM\Mapping\ClassMetadata,
|
Doctrine\ORM\Mapping\ClassMetadata,
|
||||||
Doctrine\ORM\Mapping\ClassMetadataFactory,
|
Doctrine\ORM\Mapping\ClassMetadataFactory,
|
||||||
Doctrine\ORM\Query\ResultSetMapping,
|
Doctrine\ORM\Query\ResultSetMapping,
|
||||||
Doctrine\ORM\Proxy\ProxyFactory;
|
Doctrine\ORM\Proxy\ProxyFactory,
|
||||||
|
Doctrine\ORM\Query\FilterCollection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The EntityManager is the central access point to ORM functionality.
|
* The EntityManager is the central access point to ORM functionality.
|
||||||
@ -110,6 +111,13 @@ class EntityManager implements ObjectManager
|
|||||||
*/
|
*/
|
||||||
private $closed = false;
|
private $closed = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of query filters.
|
||||||
|
*
|
||||||
|
* @var Doctrine\ORM\Query\FilterCollection
|
||||||
|
*/
|
||||||
|
private $filterCollection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new EntityManager that operates on the given database connection
|
* Creates a new EntityManager that operates on the given database connection
|
||||||
* and uses the given Configuration and EventManager implementations.
|
* and uses the given Configuration and EventManager implementations.
|
||||||
@ -788,4 +796,39 @@ class EntityManager implements ObjectManager
|
|||||||
|
|
||||||
return new EntityManager($conn, $config, $conn->getEventManager());
|
return new EntityManager($conn, $config, $conn->getEventManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the enabled filters.
|
||||||
|
*
|
||||||
|
* @return FilterCollection The active filter collection.
|
||||||
|
*/
|
||||||
|
public function getFilters()
|
||||||
|
{
|
||||||
|
if (null === $this->filterCollection) {
|
||||||
|
$this->filterCollection = new FilterCollection($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->filterCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the state of the filter collection is clean.
|
||||||
|
*
|
||||||
|
* @return boolean True, if the filter collection is clean.
|
||||||
|
*/
|
||||||
|
public function isFiltersStateClean()
|
||||||
|
{
|
||||||
|
return null === $this->filterCollection
|
||||||
|
|| $this->filterCollection->isClean();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the Entity Manager has filters.
|
||||||
|
*
|
||||||
|
* @return True, if the EM has a filter collection.
|
||||||
|
*/
|
||||||
|
public function hasFilters()
|
||||||
|
{
|
||||||
|
return null !== $this->filterCollection;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -298,7 +298,7 @@ class ClassMetadataBuilder
|
|||||||
$builder = $this->createManyToOne($name, $targetEntity);
|
$builder = $this->createManyToOne($name, $targetEntity);
|
||||||
|
|
||||||
if ($inversedBy) {
|
if ($inversedBy) {
|
||||||
$builder->setInversedBy($inversedBy);
|
$builder->inversedBy($inversedBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $builder->build();
|
return $builder->build();
|
||||||
@ -355,7 +355,7 @@ class ClassMetadataBuilder
|
|||||||
public function addInverseOneToOne($name, $targetEntity, $mappedBy)
|
public function addInverseOneToOne($name, $targetEntity, $mappedBy)
|
||||||
{
|
{
|
||||||
$builder = $this->createOneToOne($name, $targetEntity);
|
$builder = $this->createOneToOne($name, $targetEntity);
|
||||||
$builder->setMappedBy($mappedBy);
|
$builder->mappedBy($mappedBy);
|
||||||
|
|
||||||
return $builder->build();
|
return $builder->build();
|
||||||
}
|
}
|
||||||
@ -373,7 +373,7 @@ class ClassMetadataBuilder
|
|||||||
$builder = $this->createOneToOne($name, $targetEntity);
|
$builder = $this->createOneToOne($name, $targetEntity);
|
||||||
|
|
||||||
if ($inversedBy) {
|
if ($inversedBy) {
|
||||||
$builder->setInversedBy($inversedBy);
|
$builder->inversedBy($inversedBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $builder->build();
|
return $builder->build();
|
||||||
@ -411,7 +411,7 @@ class ClassMetadataBuilder
|
|||||||
$builder = $this->createManyToMany($name, $targetEntity);
|
$builder = $this->createManyToMany($name, $targetEntity);
|
||||||
|
|
||||||
if ($inversedBy) {
|
if ($inversedBy) {
|
||||||
$builder->setInversedBy($inversedBy);
|
$builder->inversedBy($inversedBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $builder->build();
|
return $builder->build();
|
||||||
@ -428,7 +428,7 @@ class ClassMetadataBuilder
|
|||||||
public function addInverseManyToMany($name, $targetEntity, $mappedBy)
|
public function addInverseManyToMany($name, $targetEntity, $mappedBy)
|
||||||
{
|
{
|
||||||
$builder = $this->createManyToMany($name, $targetEntity);
|
$builder = $this->createManyToMany($name, $targetEntity);
|
||||||
$builder->setMappedBy($mappedBy);
|
$builder->mappedBy($mappedBy);
|
||||||
|
|
||||||
return $builder->build();
|
return $builder->build();
|
||||||
}
|
}
|
||||||
@ -463,7 +463,7 @@ class ClassMetadataBuilder
|
|||||||
public function addOneToMany($name, $targetEntity, $mappedBy)
|
public function addOneToMany($name, $targetEntity, $mappedBy)
|
||||||
{
|
{
|
||||||
$builder = $this->createOneToMany($name, $targetEntity);
|
$builder = $this->createOneToMany($name, $targetEntity);
|
||||||
$builder->setMappedBy($mappedBy);
|
$builder->mappedBy($mappedBy);
|
||||||
|
|
||||||
return $builder->build();
|
return $builder->build();
|
||||||
}
|
}
|
||||||
|
@ -371,6 +371,10 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
|
|||||||
// second condition is necessary for mapped superclasses in the middle of an inheritance hierachy
|
// second condition is necessary for mapped superclasses in the middle of an inheritance hierachy
|
||||||
throw MappingException::noInheritanceOnMappedSuperClass($class->name);
|
throw MappingException::noInheritanceOnMappedSuperClass($class->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($class->usesIdGenerator() && $class->isIdentifierComposite) {
|
||||||
|
throw MappingException::compositeKeyAssignedIdGeneratorRequired($class->name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1849,21 +1849,6 @@ class ClassMetadataInfo implements ClassMetadata
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Refine an association targetEntity class pointer to be consumed through loadMetadata event.
|
|
||||||
*
|
|
||||||
* @param string $assoc
|
|
||||||
* @param string $class
|
|
||||||
*/
|
|
||||||
public function setAssociationTargetClass($assocName, $class)
|
|
||||||
{
|
|
||||||
if ( ! isset($this->associationMappings[$assocName])) {
|
|
||||||
throw new \InvalidArgumentException("Association name expected, '" . $assocName ."' is not an association.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->associationMappings[$assocName]['targetEntity'] = $class;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether this class is to be versioned for optimistic locking.
|
* Sets whether this class is to be versioned for optimistic locking.
|
||||||
*
|
*
|
||||||
|
@ -118,6 +118,9 @@ abstract class AbstractFileDriver implements Driver
|
|||||||
{
|
{
|
||||||
$result = $this->_loadMappingFile($this->_findMappingFile($className));
|
$result = $this->_loadMappingFile($this->_findMappingFile($className));
|
||||||
|
|
||||||
|
if(!isset($result[$className])){
|
||||||
|
throw MappingException::invalidMappingFile($className, str_replace('\\', '.', $className) . $this->_fileExtension);
|
||||||
|
}
|
||||||
return $result[$className];
|
return $result[$className];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +192,14 @@ class AnnotationDriver implements Driver
|
|||||||
if (isset($classAnnotations['Doctrine\ORM\Mapping\NamedQueries'])) {
|
if (isset($classAnnotations['Doctrine\ORM\Mapping\NamedQueries'])) {
|
||||||
$namedQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedQueries'];
|
$namedQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedQueries'];
|
||||||
|
|
||||||
|
if (!is_array($namedQueriesAnnot->value)) {
|
||||||
|
throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations.");
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($namedQueriesAnnot->value as $namedQuery) {
|
foreach ($namedQueriesAnnot->value as $namedQuery) {
|
||||||
|
if (!($namedQuery instanceof \Doctrine\ORM\Mapping\NamedQuery)) {
|
||||||
|
throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations.");
|
||||||
|
}
|
||||||
$metadata->addNamedQuery(array(
|
$metadata->addNamedQuery(array(
|
||||||
'name' => $namedQuery->name,
|
'name' => $namedQuery->name,
|
||||||
'query' => $namedQuery->query
|
'query' => $namedQuery->query
|
||||||
|
@ -68,6 +68,11 @@ class MappingException extends \Doctrine\ORM\ORMException
|
|||||||
return new self("No mapping file found named '$fileName' for class '$entityName'.");
|
return new self("No mapping file found named '$fileName' for class '$entityName'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function invalidMappingFile($entityName, $fileName)
|
||||||
|
{
|
||||||
|
return new self("Invalid mapping file '$fileName' for class '$entityName'.");
|
||||||
|
}
|
||||||
|
|
||||||
public static function mappingNotFound($className, $fieldName)
|
public static function mappingNotFound($className, $fieldName)
|
||||||
{
|
{
|
||||||
return new self("No mapping found for field '$fieldName' on class '$className'.");
|
return new self("No mapping found for field '$fieldName' on class '$className'.");
|
||||||
@ -314,4 +319,9 @@ class MappingException extends \Doctrine\ORM\ORMException
|
|||||||
{
|
{
|
||||||
return new self("Entity '" . $className . "' has a mapping with invalid fetch mode '" . $annotation . "'");
|
return new self("Entity '" . $className . "' has a mapping with invalid fetch mode '" . $annotation . "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function compositeKeyAssignedIdGeneratorRequired($className)
|
||||||
|
{
|
||||||
|
return new self("Entity '". $className . "' has a composite identifier but uses an ID generator other than manually assigning (Identity, Sequence). This is not supported.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,7 @@ use PDO,
|
|||||||
* @author Roman Borschel <roman@code-factory.org>
|
* @author Roman Borschel <roman@code-factory.org>
|
||||||
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
|
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
|
||||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||||
|
* @author Alexander <iam.asm89@gmail.com>
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
class BasicEntityPersister
|
class BasicEntityPersister
|
||||||
@ -900,9 +901,19 @@ class BasicEntityPersister
|
|||||||
$lockSql = ' ' . $this->_platform->getWriteLockSql();
|
$lockSql = ' ' . $this->_platform->getWriteLockSql();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$alias = $this->_getSQLTableAlias($this->_class->name);
|
||||||
|
|
||||||
|
if ($filterSql = $this->generateFilterConditionSQL($this->_class, $alias)) {
|
||||||
|
if ($conditionSql) {
|
||||||
|
$conditionSql .= ' AND ';
|
||||||
|
}
|
||||||
|
|
||||||
|
$conditionSql .= $filterSql;
|
||||||
|
}
|
||||||
|
|
||||||
return $this->_platform->modifyLimitQuery('SELECT ' . $this->_getSelectColumnListSQL()
|
return $this->_platform->modifyLimitQuery('SELECT ' . $this->_getSelectColumnListSQL()
|
||||||
. $this->_platform->appendLockHint(' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' '
|
. $this->_platform->appendLockHint(' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' '
|
||||||
. $this->_getSQLTableAlias($this->_class->name), $lockMode)
|
. $alias, $lockMode)
|
||||||
. $this->_selectJoinSql . $joinSql
|
. $this->_selectJoinSql . $joinSql
|
||||||
. ($conditionSql ? ' WHERE ' . $conditionSql : '')
|
. ($conditionSql ? ' WHERE ' . $conditionSql : '')
|
||||||
. $orderBySql, $limit, $offset)
|
. $orderBySql, $limit, $offset)
|
||||||
@ -1014,14 +1025,20 @@ class BasicEntityPersister
|
|||||||
$this->_selectJoinSql .= ' ' . $this->getJoinSQLForJoinColumns($assoc['joinColumns']);
|
$this->_selectJoinSql .= ' ' . $this->getJoinSQLForJoinColumns($assoc['joinColumns']);
|
||||||
$this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) .' ON ';
|
$this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) .' ON ';
|
||||||
|
|
||||||
|
$tableAlias = $this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias);
|
||||||
foreach ($assoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) {
|
foreach ($assoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) {
|
||||||
if ( ! $first) {
|
if ( ! $first) {
|
||||||
$this->_selectJoinSql .= ' AND ';
|
$this->_selectJoinSql .= ' AND ';
|
||||||
}
|
}
|
||||||
$this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.' . $sourceCol . ' = '
|
$this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.' . $sourceCol . ' = '
|
||||||
. $this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias) . '.' . $targetCol;
|
. $tableAlias . '.' . $targetCol;
|
||||||
$first = false;
|
$first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add filter SQL
|
||||||
|
if ($filterSql = $this->generateFilterConditionSQL($eagerEntity, $tableAlias)) {
|
||||||
|
$this->_selectJoinSql .= ' AND ' . $filterSql;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']);
|
$eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']);
|
||||||
$owningAssoc = $eagerEntity->getAssociationMapping($assoc['mappedBy']);
|
$owningAssoc = $eagerEntity->getAssociationMapping($assoc['mappedBy']);
|
||||||
@ -1521,10 +1538,16 @@ class BasicEntityPersister
|
|||||||
$criteria = array_merge($criteria, $extraConditions);
|
$criteria = array_merge($criteria, $extraConditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$alias = $this->_getSQLTableAlias($this->_class->name);
|
||||||
|
|
||||||
$sql = 'SELECT 1 '
|
$sql = 'SELECT 1 '
|
||||||
. $this->getLockTablesSql()
|
. $this->getLockTablesSql()
|
||||||
. ' WHERE ' . $this->_getSelectConditionSQL($criteria);
|
. ' WHERE ' . $this->_getSelectConditionSQL($criteria);
|
||||||
|
|
||||||
|
if ($filterSql = $this->generateFilterConditionSQL($this->_class, $alias)) {
|
||||||
|
$sql .= ' AND ' . $filterSql;
|
||||||
|
}
|
||||||
|
|
||||||
list($params, $types) = $this->expandParameters($criteria);
|
list($params, $types) = $this->expandParameters($criteria);
|
||||||
|
|
||||||
return (bool) $this->_conn->fetchColumn($sql, $params);
|
return (bool) $this->_conn->fetchColumn($sql, $params);
|
||||||
@ -1539,8 +1562,8 @@ class BasicEntityPersister
|
|||||||
protected function getJoinSQLForJoinColumns($joinColumns)
|
protected function getJoinSQLForJoinColumns($joinColumns)
|
||||||
{
|
{
|
||||||
// if one of the join columns is nullable, return left join
|
// if one of the join columns is nullable, return left join
|
||||||
foreach($joinColumns as $joinColumn) {
|
foreach ($joinColumns as $joinColumn) {
|
||||||
if(isset($joinColumn['nullable']) && $joinColumn['nullable']){
|
if (!isset($joinColumn['nullable']) || $joinColumn['nullable']) {
|
||||||
return 'LEFT JOIN';
|
return 'LEFT JOIN';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1562,4 +1585,26 @@ class BasicEntityPersister
|
|||||||
substr($columnName . $this->_sqlAliasCounter++, -$this->_platform->getMaxIdentifierLength())
|
substr($columnName . $this->_sqlAliasCounter++, -$this->_platform->getMaxIdentifierLength())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 . ")" : ""; // Wrap again to avoid "X or Y and FilterConditionSQL"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ use Doctrine\ORM\ORMException,
|
|||||||
*
|
*
|
||||||
* @author Roman Borschel <roman@code-factory.org>
|
* @author Roman Borschel <roman@code-factory.org>
|
||||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||||
|
* @author Alexander <iam.asm89@gmail.com>
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
* @see http://martinfowler.com/eaaCatalog/classTableInheritance.html
|
* @see http://martinfowler.com/eaaCatalog/classTableInheritance.html
|
||||||
*/
|
*/
|
||||||
@ -374,6 +375,15 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
|||||||
|
|
||||||
$conditionSql = $this->_getSelectConditionSQL($criteria, $assoc);
|
$conditionSql = $this->_getSelectConditionSQL($criteria, $assoc);
|
||||||
|
|
||||||
|
// If the current class in the root entity, add the filters
|
||||||
|
if ($filterSql = $this->generateFilterConditionSQL($this->_em->getClassMetadata($this->_class->rootEntityName), $this->_getSQLTableAlias($this->_class->rootEntityName))) {
|
||||||
|
if ($conditionSql) {
|
||||||
|
$conditionSql .= ' AND ';
|
||||||
|
}
|
||||||
|
|
||||||
|
$conditionSql .= $filterSql;
|
||||||
|
}
|
||||||
|
|
||||||
$orderBy = ($assoc !== null && isset($assoc['orderBy'])) ? $assoc['orderBy'] : $orderBy;
|
$orderBy = ($assoc !== null && isset($assoc['orderBy'])) ? $assoc['orderBy'] : $orderBy;
|
||||||
$orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $baseTableAlias) : '';
|
$orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $baseTableAlias) : '';
|
||||||
|
|
||||||
@ -473,4 +483,5 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
|||||||
$value = $this->fetchVersionValue($this->_getVersionedClassMetadata(), $id);
|
$value = $this->fetchVersionValue($this->_getVersionedClassMetadata(), $id);
|
||||||
$this->_class->setFieldValue($entity, $this->_class->versionField, $value);
|
$this->_class->setFieldValue($entity, $this->_class->versionField, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,8 @@
|
|||||||
|
|
||||||
namespace Doctrine\ORM\Persisters;
|
namespace Doctrine\ORM\Persisters;
|
||||||
|
|
||||||
use Doctrine\ORM\PersistentCollection,
|
use Doctrine\ORM\Mapping\ClassMetadata,
|
||||||
|
Doctrine\ORM\PersistentCollection,
|
||||||
Doctrine\ORM\UnitOfWork;
|
Doctrine\ORM\UnitOfWork;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,6 +30,7 @@ use Doctrine\ORM\PersistentCollection,
|
|||||||
*
|
*
|
||||||
* @author Roman Borschel <roman@code-factory.org>
|
* @author Roman Borschel <roman@code-factory.org>
|
||||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||||
|
* @author Alexander <iam.asm89@gmail.com>
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
class ManyToManyPersister extends AbstractCollectionPersister
|
class ManyToManyPersister extends AbstractCollectionPersister
|
||||||
@ -216,8 +218,14 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
|||||||
: $id[$class->fieldNames[$joinColumns[$joinTableColumn]]];
|
: $id[$class->fieldNames[$joinColumns[$joinTableColumn]]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($mapping);
|
||||||
|
if ($filterSql) {
|
||||||
|
$whereClauses[] = $filterSql;
|
||||||
|
}
|
||||||
|
|
||||||
$sql = 'SELECT COUNT(*)'
|
$sql = 'SELECT COUNT(*)'
|
||||||
. ' FROM ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform())
|
. ' FROM ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform()) . ' t'
|
||||||
|
. $joinTargetEntitySQL
|
||||||
. ' WHERE ' . implode(' AND ', $whereClauses);
|
. ' WHERE ' . implode(' AND ', $whereClauses);
|
||||||
|
|
||||||
return $this->_conn->fetchColumn($sql, $params);
|
return $this->_conn->fetchColumn($sql, $params);
|
||||||
@ -250,7 +258,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element);
|
list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element, true);
|
||||||
|
|
||||||
$sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
|
$sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
|
||||||
|
|
||||||
@ -271,7 +279,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element);
|
list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element, false);
|
||||||
|
|
||||||
$sql = 'DELETE FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
|
$sql = 'DELETE FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
|
||||||
|
|
||||||
@ -281,9 +289,10 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
|||||||
/**
|
/**
|
||||||
* @param \Doctrine\ORM\PersistentCollection $coll
|
* @param \Doctrine\ORM\PersistentCollection $coll
|
||||||
* @param object $element
|
* @param object $element
|
||||||
|
* @param boolean $addFilters Whether the filter SQL should be included or not.
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function getJoinTableRestrictions(PersistentCollection $coll, $element)
|
private function getJoinTableRestrictions(PersistentCollection $coll, $element, $addFilters)
|
||||||
{
|
{
|
||||||
$uow = $this->_em->getUnitOfWork();
|
$uow = $this->_em->getUnitOfWork();
|
||||||
$mapping = $coll->getMapping();
|
$mapping = $coll->getMapping();
|
||||||
@ -322,6 +331,72 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
|||||||
: $sourceId[$sourceClass->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]];
|
: $sourceId[$sourceClass->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($addFilters) {
|
||||||
|
list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($mapping);
|
||||||
|
if ($filterSql) {
|
||||||
|
$quotedJoinTable .= ' t ' . $joinTargetEntitySQL;
|
||||||
|
$whereClauses[] = $filterSql;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return array($quotedJoinTable, $whereClauses, $params);
|
return array($quotedJoinTable, $whereClauses, $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the filter SQL for a given mapping.
|
||||||
|
*
|
||||||
|
* This method is not used for actually grabbing the related entities
|
||||||
|
* but when the extra-lazy collection methods are called on a filtered
|
||||||
|
* association. This is why besides the many to many table we also
|
||||||
|
* have to join in the actual entities table leading to additional
|
||||||
|
* JOIN.
|
||||||
|
*
|
||||||
|
* @param array $targetEntity Array containing mapping information.
|
||||||
|
*
|
||||||
|
* @return string The SQL query part to add to a query.
|
||||||
|
*/
|
||||||
|
public function getFilterSql($mapping)
|
||||||
|
{
|
||||||
|
$targetClass = $this->_em->getClassMetadata($mapping['targetEntity']);
|
||||||
|
$targetClass = $this->_em->getClassMetadata($targetClass->rootEntityName);
|
||||||
|
|
||||||
|
// A join is needed if there is filtering on the target entity
|
||||||
|
$joinTargetEntitySQL = '';
|
||||||
|
if ($filterSql = $this->generateFilterConditionSQL($targetClass, 'te')) {
|
||||||
|
$joinTargetEntitySQL = ' JOIN '
|
||||||
|
. $targetClass->getQuotedTableName($this->_conn->getDatabasePlatform()) . ' te'
|
||||||
|
. ' ON';
|
||||||
|
|
||||||
|
$joinTargetEntitySQLClauses = array();
|
||||||
|
foreach ($mapping['relationToTargetKeyColumns'] as $joinTableColumn => $targetTableColumn) {
|
||||||
|
$joinTargetEntitySQLClauses[] = ' t.' . $joinTableColumn . ' = ' . 'te.' . $targetTableColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
$joinTargetEntitySQL .= implode(' AND ', $joinTargetEntitySQLClauses);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array($joinTargetEntitySQL, $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 . ")" : "";
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,7 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
/*
|
/*
|
||||||
* $Id$
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
@ -29,6 +27,7 @@ use Doctrine\ORM\PersistentCollection,
|
|||||||
*
|
*
|
||||||
* @author Roman Borschel <roman@code-factory.org>
|
* @author Roman Borschel <roman@code-factory.org>
|
||||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||||
|
* @author Alexander <iam.asm89@gmail.com>
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
class OneToManyPersister extends AbstractCollectionPersister
|
class OneToManyPersister extends AbstractCollectionPersister
|
||||||
@ -120,8 +119,14 @@ class OneToManyPersister extends AbstractCollectionPersister
|
|||||||
: $id[$sourceClass->fieldNames[$joinColumn['referencedColumnName']]];
|
: $id[$sourceClass->fieldNames[$joinColumn['referencedColumnName']]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($this->_em->getFilters()->getEnabledFilters() as $filter) {
|
||||||
|
if ($filterExpr = $filter->addFilterConstraint($targetClass, 't')) {
|
||||||
|
$whereClauses[] = '(' . $filterExpr . ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$sql = 'SELECT count(*)'
|
$sql = 'SELECT count(*)'
|
||||||
. ' FROM ' . $targetClass->getQuotedTableName($this->_conn->getDatabasePlatform())
|
. ' FROM ' . $targetClass->getQuotedTableName($this->_conn->getDatabasePlatform()) . ' t'
|
||||||
. ' WHERE ' . implode(' AND ', $whereClauses);
|
. ' WHERE ' . implode(' AND ', $whereClauses);
|
||||||
|
|
||||||
return $this->_conn->fetchColumn($sql, $params);
|
return $this->_conn->fetchColumn($sql, $params);
|
||||||
|
@ -27,6 +27,7 @@ use Doctrine\ORM\Mapping\ClassMetadata;
|
|||||||
*
|
*
|
||||||
* @author Roman Borschel <roman@code-factory.org>
|
* @author Roman Borschel <roman@code-factory.org>
|
||||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||||
|
* @author Alexander <iam.asm89@gmail.com>
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
* @link http://martinfowler.com/eaaCatalog/singleTableInheritance.html
|
* @link http://martinfowler.com/eaaCatalog/singleTableInheritance.html
|
||||||
*/
|
*/
|
||||||
@ -131,4 +132,14 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
|
|||||||
|
|
||||||
return $conditionSql;
|
return $conditionSql;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritdoc} */
|
||||||
|
protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
|
||||||
|
{
|
||||||
|
// Ensure that the filters are applied to the root entity of the inheritance tree
|
||||||
|
$targetEntity = $this->_em->getClassMetadata($targetEntity->rootEntityName);
|
||||||
|
// we dont care about the $targetTableAlias, in a STI there is only one table.
|
||||||
|
|
||||||
|
return parent::generateFilterConditionSQL($targetEntity, $targetTableAlias);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,7 +198,10 @@ final class Query extends AbstractQuery
|
|||||||
*/
|
*/
|
||||||
private function _parse()
|
private function _parse()
|
||||||
{
|
{
|
||||||
if ($this->_state === self::STATE_CLEAN) {
|
// Return previous parser result if the query and the filter collection are both clean
|
||||||
|
if ($this->_state === self::STATE_CLEAN
|
||||||
|
&& $this->_em->isFiltersStateClean()
|
||||||
|
) {
|
||||||
return $this->_parserResult;
|
return $this->_parserResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -623,6 +626,7 @@ final class Query extends AbstractQuery
|
|||||||
|
|
||||||
return md5(
|
return md5(
|
||||||
$this->getDql() . var_export($this->_hints, true) .
|
$this->getDql() . var_export($this->_hints, true) .
|
||||||
|
($this->_em->hasFilters() ? $this->_em->getFilters()->getHash() : '') .
|
||||||
'&firstResult=' . $this->_firstResult . '&maxResult=' . $this->_maxResults .
|
'&firstResult=' . $this->_firstResult . '&maxResult=' . $this->_maxResults .
|
||||||
'&hydrationMode='.$this->_hydrationMode.'DOCTRINE_QUERY_CACHE_SALT'
|
'&hydrationMode='.$this->_hydrationMode.'DOCTRINE_QUERY_CACHE_SALT'
|
||||||
);
|
);
|
||||||
|
122
lib/Doctrine/ORM/Query/Filter/SQLFilter.php
Normal file
122
lib/Doctrine/ORM/Query/Filter/SQLFilter.php
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* This software consists of voluntary contributions made by many individuals
|
||||||
|
* and is licensed under the LGPL. For more information, see
|
||||||
|
* <http://www.doctrine-project.org>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Doctrine\ORM\Query\Filter;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager,
|
||||||
|
Doctrine\ORM\Mapping\ClassMetaData,
|
||||||
|
Doctrine\ORM\Query\ParameterTypeInferer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base class that user defined filters should extend.
|
||||||
|
*
|
||||||
|
* Handles the setting and escaping of parameters.
|
||||||
|
*
|
||||||
|
* @author Alexander <iam.asm89@gmail.com>
|
||||||
|
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||||
|
* @abstract
|
||||||
|
*/
|
||||||
|
abstract class SQLFilter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The entity manager.
|
||||||
|
* @var EntityManager
|
||||||
|
*/
|
||||||
|
private $em;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters for the filter.
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $parameters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the SQLFilter object.
|
||||||
|
*
|
||||||
|
* @param EntityManager $em The EM
|
||||||
|
*/
|
||||||
|
final public function __construct(EntityManager $em)
|
||||||
|
{
|
||||||
|
$this->em = $em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a parameter that can be used by the filter.
|
||||||
|
*
|
||||||
|
* @param string $name Name of the parameter.
|
||||||
|
* @param string $value Value of the parameter.
|
||||||
|
* @param string $type The parameter type. If specified, the given value will be run through
|
||||||
|
* the type conversion of this type. This is usually not needed for
|
||||||
|
* strings and numeric types.
|
||||||
|
*
|
||||||
|
* @return SQLFilter The current SQL filter.
|
||||||
|
*/
|
||||||
|
final public function setParameter($name, $value, $type = null)
|
||||||
|
{
|
||||||
|
if (null === $type) {
|
||||||
|
$type = ParameterTypeInferer::inferType($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->parameters[$name] = array('value' => $value, 'type' => $type);
|
||||||
|
|
||||||
|
// Keep the parameters sorted for the hash
|
||||||
|
ksort($this->parameters);
|
||||||
|
|
||||||
|
// The filter collection of the EM is now dirty
|
||||||
|
$this->em->getFilters()->setFiltersStateDirty();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a parameter to use in a query.
|
||||||
|
*
|
||||||
|
* The function is responsible for the right output escaping to use the
|
||||||
|
* value in a query.
|
||||||
|
*
|
||||||
|
* @param string $name Name of the parameter.
|
||||||
|
*
|
||||||
|
* @return string The SQL escaped parameter to use in a query.
|
||||||
|
*/
|
||||||
|
final public function getParameter($name)
|
||||||
|
{
|
||||||
|
if (!isset($this->parameters[$name])) {
|
||||||
|
throw new \InvalidArgumentException("Parameter '" . $name . "' does not exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->em->getConnection()->quote($this->parameters[$name]['value'], $this->parameters[$name]['type']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns as string representation of the SQLFilter parameters (the state).
|
||||||
|
*
|
||||||
|
* @return string String representation of the SQLFilter.
|
||||||
|
*/
|
||||||
|
final public function __toString()
|
||||||
|
{
|
||||||
|
return serialize($this->parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the SQL query part to add to a query.
|
||||||
|
*
|
||||||
|
* @return string The constraint SQL if there is available, empty string otherwise
|
||||||
|
*/
|
||||||
|
abstract public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias);
|
||||||
|
}
|
198
lib/Doctrine/ORM/Query/FilterCollection.php
Normal file
198
lib/Doctrine/ORM/Query/FilterCollection.php
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* This software consists of voluntary contributions made by many individuals
|
||||||
|
* and is licensed under the LGPL. For more information, see
|
||||||
|
* <http://www.doctrine-project.org>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Doctrine\ORM\Query;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Configuration,
|
||||||
|
Doctrine\ORM\EntityManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection class for all the query filters.
|
||||||
|
*
|
||||||
|
* @author Alexander <iam.asm89@gmail.com>
|
||||||
|
*/
|
||||||
|
class FilterCollection
|
||||||
|
{
|
||||||
|
/* Filter STATES */
|
||||||
|
/**
|
||||||
|
* A filter object is in CLEAN state when it has no changed parameters.
|
||||||
|
*/
|
||||||
|
const FILTERS_STATE_CLEAN = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filter object is in DIRTY state when it has changed parameters.
|
||||||
|
*/
|
||||||
|
const FILTERS_STATE_DIRTY = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The used Configuration.
|
||||||
|
*
|
||||||
|
* @var Doctrine\ORM\Configuration
|
||||||
|
*/
|
||||||
|
private $config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EntityManager that "owns" this FilterCollection instance.
|
||||||
|
*
|
||||||
|
* @var Doctrine\ORM\EntityManager
|
||||||
|
*/
|
||||||
|
private $em;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instances of enabled filters.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $enabledFilters = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string The filter hash from the last time the query was parsed.
|
||||||
|
*/
|
||||||
|
private $filterHash;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var integer $state The current state of this filter
|
||||||
|
*/
|
||||||
|
private $filtersState = self::FILTERS_STATE_CLEAN;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param EntityManager $em
|
||||||
|
*/
|
||||||
|
public function __construct(EntityManager $em)
|
||||||
|
{
|
||||||
|
$this->em = $em;
|
||||||
|
$this->config = $em->getConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the enabled filters.
|
||||||
|
*
|
||||||
|
* @return array The enabled filters.
|
||||||
|
*/
|
||||||
|
public function getEnabledFilters()
|
||||||
|
{
|
||||||
|
return $this->enabledFilters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables a filter from the collection.
|
||||||
|
*
|
||||||
|
* @param string $name Name of the filter.
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If the filter does not exist.
|
||||||
|
*
|
||||||
|
* @return SQLFilter The enabled filter.
|
||||||
|
*/
|
||||||
|
public function enable($name)
|
||||||
|
{
|
||||||
|
if (null === $filterClass = $this->config->getFilterClassName($name)) {
|
||||||
|
throw new \InvalidArgumentException("Filter '" . $name . "' does not exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($this->enabledFilters[$name])) {
|
||||||
|
$this->enabledFilters[$name] = new $filterClass($this->em);
|
||||||
|
|
||||||
|
// Keep the enabled filters sorted for the hash
|
||||||
|
ksort($this->enabledFilters);
|
||||||
|
|
||||||
|
// Now the filter collection is dirty
|
||||||
|
$this->filtersState = self::FILTERS_STATE_DIRTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->enabledFilters[$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables a filter.
|
||||||
|
*
|
||||||
|
* @param string $name Name of the filter.
|
||||||
|
*
|
||||||
|
* @return SQLFilter The disabled filter.
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If the filter does not exist.
|
||||||
|
*/
|
||||||
|
public function disable($name)
|
||||||
|
{
|
||||||
|
// Get the filter to return it
|
||||||
|
$filter = $this->getFilter($name);
|
||||||
|
|
||||||
|
unset($this->enabledFilters[$name]);
|
||||||
|
|
||||||
|
// Now the filter collection is dirty
|
||||||
|
$this->filtersState = self::FILTERS_STATE_DIRTY;
|
||||||
|
|
||||||
|
return $filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an enabled filter from the collection.
|
||||||
|
*
|
||||||
|
* @param string $name Name of the filter.
|
||||||
|
*
|
||||||
|
* @return SQLFilter The filter.
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If the filter is not enabled.
|
||||||
|
*/
|
||||||
|
public function getFilter($name)
|
||||||
|
{
|
||||||
|
if (!isset($this->enabledFilters[$name])) {
|
||||||
|
throw new \InvalidArgumentException("Filter '" . $name . "' is not enabled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->enabledFilters[$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return boolean True, if the filter collection is clean.
|
||||||
|
*/
|
||||||
|
public function isClean()
|
||||||
|
{
|
||||||
|
return self::FILTERS_STATE_CLEAN === $this->filtersState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a string of currently enabled filters to use for the cache id.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getHash()
|
||||||
|
{
|
||||||
|
// If there are only clean filters, the previous hash can be returned
|
||||||
|
if (self::FILTERS_STATE_CLEAN === $this->filtersState) {
|
||||||
|
return $this->filterHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
$filterHash = '';
|
||||||
|
foreach ($this->enabledFilters as $name => $filter) {
|
||||||
|
$filterHash .= $name . $filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $filterHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the filter state to dirty.
|
||||||
|
*/
|
||||||
|
public function setFiltersStateDirty()
|
||||||
|
{
|
||||||
|
$this->filtersState = self::FILTERS_STATE_DIRTY;
|
||||||
|
}
|
||||||
|
}
|
@ -33,6 +33,7 @@ use Doctrine\DBAL\LockMode,
|
|||||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||||
* @author Roman Borschel <roman@code-factory.org>
|
* @author Roman Borschel <roman@code-factory.org>
|
||||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||||
|
* @author Alexander <iam.asm89@gmail.com>
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
* @todo Rename: SQLWalker
|
* @todo Rename: SQLWalker
|
||||||
*/
|
*/
|
||||||
@ -267,6 +268,11 @@ class SqlWalker implements TreeWalker
|
|||||||
$sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
|
$sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add filters on the root class
|
||||||
|
if ($filterSql = $this->generateFilterConditionSQL($parentClass, $tableAlias)) {
|
||||||
|
$sqlParts[] = $filterSql;
|
||||||
|
}
|
||||||
|
|
||||||
$sql .= implode(' AND ', $sqlParts);
|
$sql .= implode(' AND ', $sqlParts);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,6 +358,50 @@ class SqlWalker implements TreeWalker
|
|||||||
return (count($sqlParts) > 1) ? '(' . $sql . ')' : $sql;
|
return (count($sqlParts) > 1) ? '(' . $sql . ')' : $sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
|
||||||
|
{
|
||||||
|
if (!$this->_em->hasFilters()) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch($targetEntity->inheritanceType) {
|
||||||
|
case ClassMetadata::INHERITANCE_TYPE_NONE:
|
||||||
|
break;
|
||||||
|
case ClassMetadata::INHERITANCE_TYPE_JOINED:
|
||||||
|
// The classes in the inheritance will be added to the query one by one,
|
||||||
|
// but only the root node is getting filtered
|
||||||
|
if ($targetEntity->name !== $targetEntity->rootEntityName) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE:
|
||||||
|
// With STI the table will only be queried once, make sure that the filters
|
||||||
|
// are added to the root entity
|
||||||
|
$targetEntity = $this->_em->getClassMetadata($targetEntity->rootEntityName);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
//@todo: throw exception?
|
||||||
|
return '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$filterClauses = array();
|
||||||
|
foreach ($this->_em->getFilters()->getEnabledFilters() as $filter) {
|
||||||
|
if ('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
|
||||||
|
$filterClauses[] = '(' . $filterExpr . ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(' AND ', $filterClauses);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Walks down a SelectStatement AST node, thereby generating the appropriate SQL.
|
* Walks down a SelectStatement AST node, thereby generating the appropriate SQL.
|
||||||
*
|
*
|
||||||
@ -802,6 +852,7 @@ class SqlWalker implements TreeWalker
|
|||||||
$sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn;
|
$sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if ($assoc['type'] == ClassMetadata::MANY_TO_MANY) {
|
} else if ($assoc['type'] == ClassMetadata::MANY_TO_MANY) {
|
||||||
// Join relation table
|
// Join relation table
|
||||||
$joinTable = $assoc['joinTable'];
|
$joinTable = $assoc['joinTable'];
|
||||||
@ -867,6 +918,11 @@ class SqlWalker implements TreeWalker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply the filters
|
||||||
|
if ($filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias)) {
|
||||||
|
$sql .= ' AND ' . $filterExpr;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle WITH clause
|
// Handle WITH clause
|
||||||
if (($condExpr = $join->conditionalExpression) !== null) {
|
if (($condExpr = $join->conditionalExpression) !== null) {
|
||||||
// Phase 2 AST optimization: Skip processment of ConditionalExpression
|
// Phase 2 AST optimization: Skip processment of ConditionalExpression
|
||||||
@ -922,17 +978,13 @@ class SqlWalker implements TreeWalker
|
|||||||
*/
|
*/
|
||||||
public function walkCoalesceExpression($coalesceExpression)
|
public function walkCoalesceExpression($coalesceExpression)
|
||||||
{
|
{
|
||||||
$sql = 'COALESCE(';
|
|
||||||
|
|
||||||
$scalarExpressions = array();
|
$scalarExpressions = array();
|
||||||
|
|
||||||
foreach ($coalesceExpression->scalarExpressions as $scalarExpression) {
|
foreach ($coalesceExpression->scalarExpressions as $scalarExpression) {
|
||||||
$scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression);
|
$scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression);
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql .= implode(', ', $scalarExpressions) . ')';
|
return 'COALESCE(' . implode(', ', $scalarExpressions) . ')';
|
||||||
|
|
||||||
return $sql;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1467,6 +1519,26 @@ class SqlWalker implements TreeWalker
|
|||||||
$condSql = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : '';
|
$condSql = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : '';
|
||||||
$discrSql = $this->_generateDiscriminatorColumnConditionSql($this->_rootAliases);
|
$discrSql = $this->_generateDiscriminatorColumnConditionSql($this->_rootAliases);
|
||||||
|
|
||||||
|
if ($this->_em->hasFilters()) {
|
||||||
|
$filterClauses = array();
|
||||||
|
foreach ($this->_rootAliases as $dqlAlias) {
|
||||||
|
$class = $this->_queryComponents[$dqlAlias]['metadata'];
|
||||||
|
$tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
|
||||||
|
|
||||||
|
if ($filterExpr = $this->generateFilterConditionSQL($class, $tableAlias)) {
|
||||||
|
$filterClauses[] = $filterExpr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($filterClauses)) {
|
||||||
|
if ($condSql) {
|
||||||
|
$condSql .= ' AND ';
|
||||||
|
}
|
||||||
|
|
||||||
|
$condSql .= implode(' AND ', $filterClauses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($condSql) {
|
if ($condSql) {
|
||||||
return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
|
return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
|
||||||
}
|
}
|
||||||
|
94
lib/Doctrine/ORM/Tools/ResolveTargetEntityListener.php
Normal file
94
lib/Doctrine/ORM/Tools/ResolveTargetEntityListener.php
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* This software consists of voluntary contributions made by many individuals
|
||||||
|
* and is licensed under the LGPL. For more information, see
|
||||||
|
* <http://www.doctrine-project.org>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Doctrine\ORM\Tools;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
|
||||||
|
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ResolveTargetEntityListener
|
||||||
|
*
|
||||||
|
* Mechanism to overwrite interfaces or classes specified as association
|
||||||
|
* targets.
|
||||||
|
*
|
||||||
|
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||||
|
* @since 2.2
|
||||||
|
*/
|
||||||
|
class ResolveTargetEntityListener
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $resolveTargetEntities = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a target-entity class name to resolve to a new class name.
|
||||||
|
*
|
||||||
|
* @param string $originalEntity
|
||||||
|
* @param string $newEntity
|
||||||
|
* @param array $mapping
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function addResolveTargetEntity($originalEntity, $newEntity, array $mapping)
|
||||||
|
{
|
||||||
|
$mapping['targetEntity'] = ltrim($newEntity, "\\");
|
||||||
|
$this->resolveTargetEntities[ltrim($originalEntity, "\\")] = $mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process event and resolve new target entity names.
|
||||||
|
*
|
||||||
|
* @param LoadClassMetadataEventArgs $args
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function loadClassMetadata(LoadClassMetadataEventArgs $args)
|
||||||
|
{
|
||||||
|
$cm = $args->getClassMetadata();
|
||||||
|
foreach ($cm->associationMappings as $assocName => $mapping) {
|
||||||
|
if (isset($this->resolveTargetEntities[$mapping['targetEntity']])) {
|
||||||
|
$this->remapAssociation($cm, $mapping);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function remapAssociation($classMetadata, $mapping)
|
||||||
|
{
|
||||||
|
$newMapping = $this->resolveTargetEntities[$mapping['targetEntity']];
|
||||||
|
$newMapping = array_replace_recursive($mapping, $newMapping);
|
||||||
|
$newMapping['fieldName'] = $mapping['fieldName'];
|
||||||
|
unset($classMetadata->associationMappings[$mapping['fieldName']]);
|
||||||
|
|
||||||
|
switch ($mapping['type']) {
|
||||||
|
case ClassMetadata::MANY_TO_MANY:
|
||||||
|
$classMetadata->mapManyToMany($newMapping);
|
||||||
|
break;
|
||||||
|
case ClassMetadata::MANY_TO_ONE:
|
||||||
|
$classMetadata->mapManyToOne($newMapping);
|
||||||
|
break;
|
||||||
|
case ClassMetadata::ONE_TO_MANY:
|
||||||
|
$classMetadata->mapOneToMany($newMapping);
|
||||||
|
break;
|
||||||
|
case ClassMetadata::ONE_TO_ONE:
|
||||||
|
$classMetadata->mapOneToOne($newMapping);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ namespace Doctrine\ORM\Tools;
|
|||||||
|
|
||||||
use Doctrine\ORM\EntityManager;
|
use Doctrine\ORM\EntityManager;
|
||||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||||
|
use Doctrine\DBAL\Types\Type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs strict validation of the mapping schema
|
* Performs strict validation of the mapping schema
|
||||||
@ -28,7 +29,6 @@ use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
|||||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||||
* @link www.doctrine-project.com
|
* @link www.doctrine-project.com
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
* @version $Revision$
|
|
||||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||||
* @author Jonathan Wage <jonwage@gmail.com>
|
* @author Jonathan Wage <jonwage@gmail.com>
|
||||||
@ -88,6 +88,12 @@ class SchemaValidator
|
|||||||
$ce = array();
|
$ce = array();
|
||||||
$cmf = $this->em->getMetadataFactory();
|
$cmf = $this->em->getMetadataFactory();
|
||||||
|
|
||||||
|
foreach ($class->fieldMappings as $fieldName => $mapping) {
|
||||||
|
if (!Type::hasType($mapping['type'])) {
|
||||||
|
$ce[] = "The field '" . $class->name . "#" . $fieldName."' uses a non-existant type '" . $mapping['type'] . "'.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($class->associationMappings AS $fieldName => $assoc) {
|
foreach ($class->associationMappings AS $fieldName => $assoc) {
|
||||||
if (!$cmf->hasMetadataFor($assoc['targetEntity'])) {
|
if (!$cmf->hasMetadataFor($assoc['targetEntity'])) {
|
||||||
$ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is unknown.';
|
$ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is unknown.';
|
||||||
@ -139,6 +145,19 @@ class SchemaValidator
|
|||||||
$assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " are ".
|
$assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " are ".
|
||||||
"incosistent with each other.";
|
"incosistent with each other.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify inverse side/owning side match each other
|
||||||
|
$targetAssoc = $targetMetadata->associationMappings[$assoc['inversedBy']];
|
||||||
|
if ($assoc['type'] == ClassMetadataInfo::ONE_TO_ONE && $targetAssoc['type'] !== ClassMetadataInfo::ONE_TO_ONE){
|
||||||
|
$ce[] = "If association " . $class->name . "#" . $fieldName . " is one-to-one, then the inversed " .
|
||||||
|
"side " . $targetMetadata->name . "#" . $assoc['inversedBy'] . " has to be one-to-one as well.";
|
||||||
|
} else if ($assoc['type'] == ClassMetadataInfo::MANY_TO_ONE && $targetAssoc['type'] !== ClassMetadataInfo::ONE_TO_MANY){
|
||||||
|
$ce[] = "If association " . $class->name . "#" . $fieldName . " is many-to-one, then the inversed " .
|
||||||
|
"side " . $targetMetadata->name . "#" . $assoc['inversedBy'] . " has to be one-to-many.";
|
||||||
|
} else if ($assoc['type'] == ClassMetadataInfo::MANY_TO_MANY && $targetAssoc['type'] !== ClassMetadataInfo::MANY_TO_MANY){
|
||||||
|
$ce[] = "If association " . $class->name . "#" . $fieldName . " is many-to-many, then the inversed " .
|
||||||
|
"side " . $targetMetadata->name . "#" . $assoc['inversedBy'] . " has to be many-to-many as well.";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($assoc['isOwningSide']) {
|
if ($assoc['isOwningSide']) {
|
||||||
|
@ -705,7 +705,6 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
|
|
||||||
foreach ($unwrappedValue as $key => $entry) {
|
foreach ($unwrappedValue as $key => $entry) {
|
||||||
$state = $this->getEntityState($entry, self::STATE_NEW);
|
$state = $this->getEntityState($entry, self::STATE_NEW);
|
||||||
$oid = spl_object_hash($entry);
|
|
||||||
|
|
||||||
switch ($state) {
|
switch ($state) {
|
||||||
case self::STATE_NEW:
|
case self::STATE_NEW:
|
||||||
@ -2293,13 +2292,14 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
$id = array($class->identifier[0] => $idHash);
|
$id = array($class->identifier[0] => $idHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$overrideLocalValues = true;
|
||||||
|
|
||||||
if (isset($this->identityMap[$class->rootEntityName][$idHash])) {
|
if (isset($this->identityMap[$class->rootEntityName][$idHash])) {
|
||||||
$entity = $this->identityMap[$class->rootEntityName][$idHash];
|
$entity = $this->identityMap[$class->rootEntityName][$idHash];
|
||||||
$oid = spl_object_hash($entity);
|
$oid = spl_object_hash($entity);
|
||||||
|
|
||||||
if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
|
if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
|
||||||
$entity->__isInitialized__ = true;
|
$entity->__isInitialized__ = true;
|
||||||
$overrideLocalValues = true;
|
|
||||||
|
|
||||||
if ($entity instanceof NotifyPropertyChanged) {
|
if ($entity instanceof NotifyPropertyChanged) {
|
||||||
$entity->addPropertyChangedListener($this);
|
$entity->addPropertyChangedListener($this);
|
||||||
@ -2308,7 +2308,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
$overrideLocalValues = isset($hints[Query::HINT_REFRESH]);
|
$overrideLocalValues = isset($hints[Query::HINT_REFRESH]);
|
||||||
|
|
||||||
// If only a specific entity is set to refresh, check that it's the one
|
// If only a specific entity is set to refresh, check that it's the one
|
||||||
if(isset($hints[Query::HINT_REFRESH_ENTITY])) {
|
if (isset($hints[Query::HINT_REFRESH_ENTITY])) {
|
||||||
$overrideLocalValues = $hints[Query::HINT_REFRESH_ENTITY] === $entity;
|
$overrideLocalValues = $hints[Query::HINT_REFRESH_ENTITY] === $entity;
|
||||||
|
|
||||||
// inject ObjectManager into just loaded proxies.
|
// inject ObjectManager into just loaded proxies.
|
||||||
@ -2333,8 +2333,6 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
if ($entity instanceof NotifyPropertyChanged) {
|
if ($entity instanceof NotifyPropertyChanged) {
|
||||||
$entity->addPropertyChangedListener($this);
|
$entity->addPropertyChangedListener($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
$overrideLocalValues = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! $overrideLocalValues) {
|
if ( ! $overrideLocalValues) {
|
||||||
@ -2362,6 +2360,10 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
foreach ($class->associationMappings as $field => $assoc) {
|
foreach ($class->associationMappings as $field => $assoc) {
|
||||||
// Check if the association is not among the fetch-joined associations already.
|
// Check if the association is not among the fetch-joined associations already.
|
||||||
if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) {
|
if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) {
|
||||||
|
// DDC-1545: Fetched associations must have original entity data set.
|
||||||
|
// Define NULL value right now, since next iteration may fill it with actual value.
|
||||||
|
$this->originalEntityData[$oid][$field] = null;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2382,14 +2384,17 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) {
|
foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) {
|
||||||
$joinColumnValue = isset($data[$srcColumn]) ? $data[$srcColumn] : null;
|
$joinColumnValue = isset($data[$srcColumn]) ? $data[$srcColumn] : null;
|
||||||
|
|
||||||
if ($joinColumnValue !== null) {
|
// Skip is join column value is null
|
||||||
|
if ($joinColumnValue === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ($targetClass->containsForeignIdentifier) {
|
if ($targetClass->containsForeignIdentifier) {
|
||||||
$associatedId[$targetClass->getFieldForColumn($targetColumn)] = $joinColumnValue;
|
$associatedId[$targetClass->getFieldForColumn($targetColumn)] = $joinColumnValue;
|
||||||
} else {
|
} else {
|
||||||
$associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue;
|
$associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! $associatedId) {
|
if ( ! $associatedId) {
|
||||||
// Foreign key is NULL
|
// Foreign key is NULL
|
||||||
|
@ -143,14 +143,27 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
public function testEagerLoadWithNonNullableColumnsGeneratesInnerJoinOnOwningSide()
|
public function testEagerLoadWithNonNullableColumnsGeneratesInnerJoinOnOwningSide()
|
||||||
{
|
{
|
||||||
$waggon = new Waggon();
|
$waggon = new Waggon();
|
||||||
$this->_em->persist($waggon);
|
|
||||||
|
// It should have a train
|
||||||
|
$train = new Train(new TrainOwner("Alexander"));
|
||||||
|
$train->addWaggon($waggon);
|
||||||
|
|
||||||
|
$this->_em->persist($train);
|
||||||
$this->_em->flush();
|
$this->_em->flush();
|
||||||
$this->_em->clear();
|
$this->_em->clear();
|
||||||
|
|
||||||
$waggon = $this->_em->find(get_class($waggon), $waggon->id);
|
$waggon = $this->_em->find(get_class($waggon), $waggon->id);
|
||||||
|
|
||||||
|
// The last query is the eager loading of the owner of the train
|
||||||
|
$this->assertEquals(
|
||||||
|
"SELECT t0.id AS id1, t0.name AS name2, t3.id AS id4, t3.driver_id AS driver_id5, t3.owner_id AS owner_id6 FROM TrainOwner t0 LEFT JOIN Train t3 ON t3.owner_id = t0.id WHERE t0.id IN (?)",
|
||||||
|
$this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql']
|
||||||
|
);
|
||||||
|
|
||||||
|
// The one before is the fetching of the waggon and train
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
"SELECT t0.id AS id1, t0.train_id AS train_id2, t3.id AS id4, t3.driver_id AS driver_id5, t3.owner_id AS owner_id6 FROM Waggon t0 INNER JOIN Train t3 ON t0.train_id = t3.id WHERE t0.id = ?",
|
"SELECT t0.id AS id1, t0.train_id AS train_id2, t3.id AS id4, t3.driver_id AS driver_id5, t3.owner_id AS owner_id6 FROM Waggon t0 INNER JOIN Train t3 ON t0.train_id = t3.id WHERE t0.id = ?",
|
||||||
$this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql']
|
$this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery - 1]['sql']
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,6 +202,7 @@ class Train
|
|||||||
/**
|
/**
|
||||||
* Owning side
|
* Owning side
|
||||||
* @OneToOne(targetEntity="TrainOwner", inversedBy="train", fetch="EAGER", cascade={"persist"})
|
* @OneToOne(targetEntity="TrainOwner", inversedBy="train", fetch="EAGER", cascade={"persist"})
|
||||||
|
* @JoinColumn(nullable=false)
|
||||||
*/
|
*/
|
||||||
public $owner;
|
public $owner;
|
||||||
/**
|
/**
|
||||||
@ -280,7 +294,10 @@ class Waggon
|
|||||||
{
|
{
|
||||||
/** @id @generatedValue @column(type="integer") */
|
/** @id @generatedValue @column(type="integer") */
|
||||||
public $id;
|
public $id;
|
||||||
/** @ManyToOne(targetEntity="Train", inversedBy="waggons", fetch="EAGER") */
|
/**
|
||||||
|
* @ManyToOne(targetEntity="Train", inversedBy="waggons", fetch="EAGER")
|
||||||
|
* @JoinColumn(nullable=false)
|
||||||
|
*/
|
||||||
public $train;
|
public $train;
|
||||||
|
|
||||||
public function setTrain($train)
|
public function setTrain($train)
|
||||||
|
749
tests/Doctrine/Tests/ORM/Functional/SQLFilterTest.php
Normal file
749
tests/Doctrine/Tests/ORM/Functional/SQLFilterTest.php
Normal file
@ -0,0 +1,749 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Doctrine\Tests\ORM\Functional;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Types\Type as DBALType;
|
||||||
|
use Doctrine\ORM\Query\Filter\SQLFilter;
|
||||||
|
use Doctrine\ORM\Mapping\ClassMetaData;
|
||||||
|
use Doctrine\Common\Cache\ArrayCache;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||||
|
|
||||||
|
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||||
|
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
|
||||||
|
use Doctrine\Tests\Models\CMS\CmsAddress;
|
||||||
|
use Doctrine\Tests\Models\CMS\CmsGroup;
|
||||||
|
use Doctrine\Tests\Models\CMS\CmsArticle;
|
||||||
|
use Doctrine\Tests\Models\CMS\CmsComment;
|
||||||
|
|
||||||
|
use Doctrine\Tests\Models\Company\CompanyPerson;
|
||||||
|
use Doctrine\Tests\Models\Company\CompanyManager;
|
||||||
|
use Doctrine\Tests\Models\Company\CompanyEmployee;
|
||||||
|
|
||||||
|
use Doctrine\Tests\Models\Company\CompanyFlexContract;
|
||||||
|
use Doctrine\Tests\Models\Company\CompanyFlexUltraContract;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../TestInit.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests SQLFilter functionality.
|
||||||
|
*
|
||||||
|
* @author Alexander <iam.asm89@gmail.com>
|
||||||
|
*/
|
||||||
|
class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||||
|
{
|
||||||
|
private $userId, $userId2, $articleId, $articleId2;
|
||||||
|
private $groupId, $groupId2;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->useModelSet('cms');
|
||||||
|
$this->useModelSet('company');
|
||||||
|
parent::setUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown()
|
||||||
|
{
|
||||||
|
parent::tearDown();
|
||||||
|
|
||||||
|
$class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
|
||||||
|
$class->associationMappings['groups']['fetch'] = ClassMetadataInfo::FETCH_LAZY;
|
||||||
|
$class->associationMappings['articles']['fetch'] = ClassMetadataInfo::FETCH_LAZY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testConfigureFilter()
|
||||||
|
{
|
||||||
|
$config = new \Doctrine\ORM\Configuration();
|
||||||
|
|
||||||
|
$config->addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter");
|
||||||
|
|
||||||
|
$this->assertEquals("\Doctrine\Tests\ORM\Functional\MyLocaleFilter", $config->getFilterClassName("locale"));
|
||||||
|
$this->assertNull($config->getFilterClassName("foo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEntityManagerEnableFilter()
|
||||||
|
{
|
||||||
|
$em = $this->_getEntityManager();
|
||||||
|
$this->configureFilters($em);
|
||||||
|
|
||||||
|
// Enable an existing filter
|
||||||
|
$filter = $em->getFilters()->enable("locale");
|
||||||
|
$this->assertTrue($filter instanceof \Doctrine\Tests\ORM\Functional\MyLocaleFilter);
|
||||||
|
|
||||||
|
// Enable the filter again
|
||||||
|
$filter2 = $em->getFilters()->enable("locale");
|
||||||
|
$this->assertEquals($filter, $filter2);
|
||||||
|
|
||||||
|
// Enable a non-existing filter
|
||||||
|
$exceptionThrown = false;
|
||||||
|
try {
|
||||||
|
$filter = $em->getFilters()->enable("foo");
|
||||||
|
} catch (\InvalidArgumentException $e) {
|
||||||
|
$exceptionThrown = true;
|
||||||
|
}
|
||||||
|
$this->assertTrue($exceptionThrown);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEntityManagerEnabledFilters()
|
||||||
|
{
|
||||||
|
$em = $this->_getEntityManager();
|
||||||
|
|
||||||
|
// No enabled filters
|
||||||
|
$this->assertEquals(array(), $em->getFilters()->getEnabledFilters());
|
||||||
|
|
||||||
|
$this->configureFilters($em);
|
||||||
|
$filter = $em->getFilters()->enable("locale");
|
||||||
|
$filter = $em->getFilters()->enable("soft_delete");
|
||||||
|
|
||||||
|
// Two enabled filters
|
||||||
|
$this->assertEquals(2, count($em->getFilters()->getEnabledFilters()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEntityManagerDisableFilter()
|
||||||
|
{
|
||||||
|
$em = $this->_getEntityManager();
|
||||||
|
$this->configureFilters($em);
|
||||||
|
|
||||||
|
// Enable the filter
|
||||||
|
$filter = $em->getFilters()->enable("locale");
|
||||||
|
|
||||||
|
// Disable it
|
||||||
|
$this->assertEquals($filter, $em->getFilters()->disable("locale"));
|
||||||
|
$this->assertEquals(0, count($em->getFilters()->getEnabledFilters()));
|
||||||
|
|
||||||
|
// Disable a non-existing filter
|
||||||
|
$exceptionThrown = false;
|
||||||
|
try {
|
||||||
|
$filter = $em->getFilters()->disable("foo");
|
||||||
|
} catch (\InvalidArgumentException $e) {
|
||||||
|
$exceptionThrown = true;
|
||||||
|
}
|
||||||
|
$this->assertTrue($exceptionThrown);
|
||||||
|
|
||||||
|
// Disable a non-enabled filter
|
||||||
|
$exceptionThrown = false;
|
||||||
|
try {
|
||||||
|
$filter = $em->getFilters()->disable("locale");
|
||||||
|
} catch (\InvalidArgumentException $e) {
|
||||||
|
$exceptionThrown = true;
|
||||||
|
}
|
||||||
|
$this->assertTrue($exceptionThrown);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEntityManagerGetFilter()
|
||||||
|
{
|
||||||
|
$em = $this->_getEntityManager();
|
||||||
|
$this->configureFilters($em);
|
||||||
|
|
||||||
|
// Enable the filter
|
||||||
|
$filter = $em->getFilters()->enable("locale");
|
||||||
|
|
||||||
|
// Get the filter
|
||||||
|
$this->assertEquals($filter, $em->getFilters()->getFilter("locale"));
|
||||||
|
|
||||||
|
// Get a non-enabled filter
|
||||||
|
$exceptionThrown = false;
|
||||||
|
try {
|
||||||
|
$filter = $em->getFilters()->getFilter("soft_delete");
|
||||||
|
} catch (\InvalidArgumentException $e) {
|
||||||
|
$exceptionThrown = true;
|
||||||
|
}
|
||||||
|
$this->assertTrue($exceptionThrown);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configureFilters($em)
|
||||||
|
{
|
||||||
|
// Add filters to the configuration of the EM
|
||||||
|
$config = $em->getConfiguration();
|
||||||
|
$config->addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter");
|
||||||
|
$config->addFilter("soft_delete", "\Doctrine\Tests\ORM\Functional\MySoftDeleteFilter");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getMockConnection()
|
||||||
|
{
|
||||||
|
// Setup connection mock
|
||||||
|
$conn = $this->getMockBuilder('Doctrine\DBAL\Connection')
|
||||||
|
->disableOriginalConstructor()
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
return $conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getMockEntityManager()
|
||||||
|
{
|
||||||
|
// Setup connection mock
|
||||||
|
$em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
|
||||||
|
->disableOriginalConstructor()
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
return $em;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function addMockFilterCollection($em)
|
||||||
|
{
|
||||||
|
$filterCollection = $this->getMockBuilder('Doctrine\ORM\Query\FilterCollection')
|
||||||
|
->disableOriginalConstructor()
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$em->expects($this->any())
|
||||||
|
->method('getFilters')
|
||||||
|
->will($this->returnValue($filterCollection));
|
||||||
|
|
||||||
|
return $filterCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSQLFilterGetSetParameter()
|
||||||
|
{
|
||||||
|
// Setup mock connection
|
||||||
|
$conn = $this->getMockConnection();
|
||||||
|
$conn->expects($this->once())
|
||||||
|
->method('quote')
|
||||||
|
->with($this->equalTo('en'))
|
||||||
|
->will($this->returnValue("'en'"));
|
||||||
|
|
||||||
|
$em = $this->getMockEntityManager($conn);
|
||||||
|
$em->expects($this->once())
|
||||||
|
->method('getConnection')
|
||||||
|
->will($this->returnValue($conn));
|
||||||
|
|
||||||
|
$filterCollection = $this->addMockFilterCollection($em);
|
||||||
|
$filterCollection
|
||||||
|
->expects($this->once())
|
||||||
|
->method('setFiltersStateDirty');
|
||||||
|
|
||||||
|
$filter = new MyLocaleFilter($em);
|
||||||
|
|
||||||
|
$filter->setParameter('locale', 'en', DBALType::STRING);
|
||||||
|
|
||||||
|
$this->assertEquals("'en'", $filter->getParameter('locale'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSQLFilterSetParameterInfersType()
|
||||||
|
{
|
||||||
|
// Setup mock connection
|
||||||
|
$conn = $this->getMockConnection();
|
||||||
|
$conn->expects($this->once())
|
||||||
|
->method('quote')
|
||||||
|
->with($this->equalTo('en'))
|
||||||
|
->will($this->returnValue("'en'"));
|
||||||
|
|
||||||
|
$em = $this->getMockEntityManager($conn);
|
||||||
|
$em->expects($this->once())
|
||||||
|
->method('getConnection')
|
||||||
|
->will($this->returnValue($conn));
|
||||||
|
|
||||||
|
$filterCollection = $this->addMockFilterCollection($em);
|
||||||
|
$filterCollection
|
||||||
|
->expects($this->once())
|
||||||
|
->method('setFiltersStateDirty');
|
||||||
|
|
||||||
|
$filter = new MyLocaleFilter($em);
|
||||||
|
|
||||||
|
$filter->setParameter('locale', 'en');
|
||||||
|
|
||||||
|
$this->assertEquals("'en'", $filter->getParameter('locale'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSQLFilterAddConstraint()
|
||||||
|
{
|
||||||
|
// Set up metadata mock
|
||||||
|
$targetEntity = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata')
|
||||||
|
->disableOriginalConstructor()
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$filter = new MySoftDeleteFilter($this->getMockEntityManager());
|
||||||
|
|
||||||
|
// Test for an entity that gets extra filter data
|
||||||
|
$targetEntity->name = 'MyEntity\SoftDeleteNewsItem';
|
||||||
|
$this->assertEquals('t1_.deleted = 0', $filter->addFilterConstraint($targetEntity, 't1_'));
|
||||||
|
|
||||||
|
// Test for an entity that doesn't get extra filter data
|
||||||
|
$targetEntity->name = 'MyEntity\NoSoftDeleteNewsItem';
|
||||||
|
$this->assertEquals('', $filter->addFilterConstraint($targetEntity, 't1_'));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSQLFilterToString()
|
||||||
|
{
|
||||||
|
$em = $this->getMockEntityManager();
|
||||||
|
$filterCollection = $this->addMockFilterCollection($em);
|
||||||
|
|
||||||
|
$filter = new MyLocaleFilter($em);
|
||||||
|
$filter->setParameter('locale', 'en', DBALType::STRING);
|
||||||
|
$filter->setParameter('foo', 'bar', DBALType::STRING);
|
||||||
|
|
||||||
|
$filter2 = new MyLocaleFilter($em);
|
||||||
|
$filter2->setParameter('foo', 'bar', DBALType::STRING);
|
||||||
|
$filter2->setParameter('locale', 'en', DBALType::STRING);
|
||||||
|
|
||||||
|
$parameters = array(
|
||||||
|
'foo' => array('value' => 'bar', 'type' => DBALType::STRING),
|
||||||
|
'locale' => array('value' => 'en', 'type' => DBALType::STRING),
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(serialize($parameters), ''.$filter);
|
||||||
|
$this->assertEquals(''.$filter, ''.$filter2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testQueryCache_DependsOnFilters()
|
||||||
|
{
|
||||||
|
$cacheDataReflection = new \ReflectionProperty("Doctrine\Common\Cache\ArrayCache", "data");
|
||||||
|
$cacheDataReflection->setAccessible(true);
|
||||||
|
|
||||||
|
$query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux');
|
||||||
|
|
||||||
|
$cache = new ArrayCache();
|
||||||
|
$query->setQueryCacheDriver($cache);
|
||||||
|
|
||||||
|
$query->getResult();
|
||||||
|
$this->assertEquals(1, sizeof($cacheDataReflection->getValue($cache)));
|
||||||
|
|
||||||
|
$conf = $this->_em->getConfiguration();
|
||||||
|
$conf->addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter");
|
||||||
|
$this->_em->getFilters()->enable("locale");
|
||||||
|
|
||||||
|
$query->getResult();
|
||||||
|
$this->assertEquals(2, sizeof($cacheDataReflection->getValue($cache)));
|
||||||
|
|
||||||
|
// Another time doesn't add another cache entry
|
||||||
|
$query->getResult();
|
||||||
|
$this->assertEquals(2, sizeof($cacheDataReflection->getValue($cache)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testQueryGeneration_DependsOnFilters()
|
||||||
|
{
|
||||||
|
$query = $this->_em->createQuery('select a from Doctrine\Tests\Models\CMS\CmsAddress a');
|
||||||
|
$firstSQLQuery = $query->getSQL();
|
||||||
|
|
||||||
|
$conf = $this->_em->getConfiguration();
|
||||||
|
$conf->addFilter("country", "\Doctrine\Tests\ORM\Functional\CMSCountryFilter");
|
||||||
|
$this->_em->getFilters()->enable("country")
|
||||||
|
->setParameter("country", "en", DBALType::STRING);
|
||||||
|
|
||||||
|
$this->assertNotEquals($firstSQLQuery, $query->getSQL());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testToOneFilter()
|
||||||
|
{
|
||||||
|
//$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger);
|
||||||
|
$this->loadFixtureData();
|
||||||
|
|
||||||
|
$query = $this->_em->createQuery('select ux, ua from Doctrine\Tests\Models\CMS\CmsUser ux JOIN ux.address ua');
|
||||||
|
|
||||||
|
// We get two users before enabling the filter
|
||||||
|
$this->assertEquals(2, count($query->getResult()));
|
||||||
|
|
||||||
|
$conf = $this->_em->getConfiguration();
|
||||||
|
$conf->addFilter("country", "\Doctrine\Tests\ORM\Functional\CMSCountryFilter");
|
||||||
|
$this->_em->getFilters()->enable("country")->setParameter("country", "Germany", DBALType::STRING);
|
||||||
|
|
||||||
|
// We get one user after enabling the filter
|
||||||
|
$this->assertEquals(1, count($query->getResult()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testManyToManyFilter()
|
||||||
|
{
|
||||||
|
$this->loadFixtureData();
|
||||||
|
$query = $this->_em->createQuery('select ux, ug from Doctrine\Tests\Models\CMS\CmsUser ux JOIN ux.groups ug');
|
||||||
|
|
||||||
|
// We get two users before enabling the filter
|
||||||
|
$this->assertEquals(2, count($query->getResult()));
|
||||||
|
|
||||||
|
$conf = $this->_em->getConfiguration();
|
||||||
|
$conf->addFilter("group_prefix", "\Doctrine\Tests\ORM\Functional\CMSGroupPrefixFilter");
|
||||||
|
$this->_em->getFilters()->enable("group_prefix")->setParameter("prefix", "bar_%", DBALType::STRING);
|
||||||
|
|
||||||
|
// We get one user after enabling the filter
|
||||||
|
$this->assertEquals(1, count($query->getResult()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWhereFilter()
|
||||||
|
{
|
||||||
|
$this->loadFixtureData();
|
||||||
|
$query = $this->_em->createQuery('select ug from Doctrine\Tests\Models\CMS\CmsGroup ug WHERE 1=1');
|
||||||
|
|
||||||
|
// We get two users before enabling the filter
|
||||||
|
$this->assertEquals(2, count($query->getResult()));
|
||||||
|
|
||||||
|
$conf = $this->_em->getConfiguration();
|
||||||
|
$conf->addFilter("group_prefix", "\Doctrine\Tests\ORM\Functional\CMSGroupPrefixFilter");
|
||||||
|
$this->_em->getFilters()->enable("group_prefix")->setParameter("prefix", "bar_%", DBALType::STRING);
|
||||||
|
|
||||||
|
// We get one user after enabling the filter
|
||||||
|
$this->assertEquals(1, count($query->getResult()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function loadLazyFixtureData()
|
||||||
|
{
|
||||||
|
$class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
|
||||||
|
$class->associationMappings['articles']['fetch'] = ClassMetadataInfo::FETCH_EXTRA_LAZY;
|
||||||
|
$class->associationMappings['groups']['fetch'] = ClassMetadataInfo::FETCH_EXTRA_LAZY;
|
||||||
|
$this->loadFixtureData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function useCMSArticleTopicFilter()
|
||||||
|
{
|
||||||
|
$conf = $this->_em->getConfiguration();
|
||||||
|
$conf->addFilter("article_topic", "\Doctrine\Tests\ORM\Functional\CMSArticleTopicFilter");
|
||||||
|
$this->_em->getFilters()->enable("article_topic")->setParameter("topic", "Test1", DBALType::STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOneToMany_ExtraLazyCountWithFilter()
|
||||||
|
{
|
||||||
|
$this->loadLazyFixtureData();
|
||||||
|
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
|
||||||
|
|
||||||
|
$this->assertFalse($user->articles->isInitialized());
|
||||||
|
$this->assertEquals(2, count($user->articles));
|
||||||
|
|
||||||
|
$this->useCMSArticleTopicFilter();
|
||||||
|
|
||||||
|
$this->assertEquals(1, count($user->articles));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOneToMany_ExtraLazyContainsWithFilter()
|
||||||
|
{
|
||||||
|
$this->loadLazyFixtureData();
|
||||||
|
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
|
||||||
|
$filteredArticle = $this->_em->find('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId2);
|
||||||
|
|
||||||
|
$this->assertFalse($user->articles->isInitialized());
|
||||||
|
$this->assertTrue($user->articles->contains($filteredArticle));
|
||||||
|
|
||||||
|
$this->useCMSArticleTopicFilter();
|
||||||
|
|
||||||
|
$this->assertFalse($user->articles->contains($filteredArticle));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOneToMany_ExtraLazySliceWithFilter()
|
||||||
|
{
|
||||||
|
$this->loadLazyFixtureData();
|
||||||
|
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
|
||||||
|
|
||||||
|
$this->assertFalse($user->articles->isInitialized());
|
||||||
|
$this->assertEquals(2, count($user->articles->slice(0,10)));
|
||||||
|
|
||||||
|
$this->useCMSArticleTopicFilter();
|
||||||
|
|
||||||
|
$this->assertEquals(1, count($user->articles->slice(0,10)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function useCMSGroupPrefixFilter()
|
||||||
|
{
|
||||||
|
$conf = $this->_em->getConfiguration();
|
||||||
|
$conf->addFilter("group_prefix", "\Doctrine\Tests\ORM\Functional\CMSGroupPrefixFilter");
|
||||||
|
$this->_em->getFilters()->enable("group_prefix")->setParameter("prefix", "foo%", DBALType::STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testManyToMany_ExtraLazyCountWithFilter()
|
||||||
|
{
|
||||||
|
$this->loadLazyFixtureData();
|
||||||
|
|
||||||
|
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId2);
|
||||||
|
|
||||||
|
$this->assertFalse($user->groups->isInitialized());
|
||||||
|
$this->assertEquals(2, count($user->groups));
|
||||||
|
|
||||||
|
$this->useCMSGroupPrefixFilter();
|
||||||
|
|
||||||
|
$this->assertEquals(1, count($user->groups));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testManyToMany_ExtraLazyContainsWithFilter()
|
||||||
|
{
|
||||||
|
$this->loadLazyFixtureData();
|
||||||
|
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId2);
|
||||||
|
$filteredArticle = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId2);
|
||||||
|
|
||||||
|
$this->assertFalse($user->groups->isInitialized());
|
||||||
|
$this->assertTrue($user->groups->contains($filteredArticle));
|
||||||
|
|
||||||
|
$this->useCMSGroupPrefixFilter();
|
||||||
|
|
||||||
|
$this->assertFalse($user->groups->contains($filteredArticle));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testManyToMany_ExtraLazySliceWithFilter()
|
||||||
|
{
|
||||||
|
$this->loadLazyFixtureData();
|
||||||
|
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId2);
|
||||||
|
|
||||||
|
$this->assertFalse($user->groups->isInitialized());
|
||||||
|
$this->assertEquals(2, count($user->groups->slice(0,10)));
|
||||||
|
|
||||||
|
$this->useCMSGroupPrefixFilter();
|
||||||
|
|
||||||
|
$this->assertEquals(1, count($user->groups->slice(0,10)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadFixtureData()
|
||||||
|
{
|
||||||
|
$user = new CmsUser;
|
||||||
|
$user->name = 'Roman';
|
||||||
|
$user->username = 'romanb';
|
||||||
|
$user->status = 'developer';
|
||||||
|
|
||||||
|
$address = new CmsAddress;
|
||||||
|
$address->country = 'Germany';
|
||||||
|
$address->city = 'Berlin';
|
||||||
|
$address->zip = '12345';
|
||||||
|
|
||||||
|
$user->address = $address; // inverse side
|
||||||
|
$address->user = $user; // owning side!
|
||||||
|
|
||||||
|
$group = new CmsGroup;
|
||||||
|
$group->name = 'foo_group';
|
||||||
|
$user->addGroup($group);
|
||||||
|
|
||||||
|
$article1 = new CmsArticle;
|
||||||
|
$article1->topic = "Test1";
|
||||||
|
$article1->text = "Test";
|
||||||
|
$article1->setAuthor($user);
|
||||||
|
|
||||||
|
$article2 = new CmsArticle;
|
||||||
|
$article2->topic = "Test2";
|
||||||
|
$article2->text = "Test";
|
||||||
|
$article2->setAuthor($user);
|
||||||
|
|
||||||
|
$this->_em->persist($article1);
|
||||||
|
$this->_em->persist($article2);
|
||||||
|
|
||||||
|
$this->_em->persist($user);
|
||||||
|
|
||||||
|
$user2 = new CmsUser;
|
||||||
|
$user2->name = 'Guilherme';
|
||||||
|
$user2->username = 'gblanco';
|
||||||
|
$user2->status = 'developer';
|
||||||
|
|
||||||
|
$address2 = new CmsAddress;
|
||||||
|
$address2->country = 'France';
|
||||||
|
$address2->city = 'Paris';
|
||||||
|
$address2->zip = '12345';
|
||||||
|
|
||||||
|
$user->address = $address2; // inverse side
|
||||||
|
$address2->user = $user2; // owning side!
|
||||||
|
|
||||||
|
$user2->addGroup($group);
|
||||||
|
$group2 = new CmsGroup;
|
||||||
|
$group2->name = 'bar_group';
|
||||||
|
$user2->addGroup($group2);
|
||||||
|
|
||||||
|
$this->_em->persist($user2);
|
||||||
|
$this->_em->flush();
|
||||||
|
$this->_em->clear();
|
||||||
|
|
||||||
|
$this->userId = $user->getId();
|
||||||
|
$this->userId2 = $user2->getId();
|
||||||
|
$this->articleId = $article1->id;
|
||||||
|
$this->articleId2 = $article2->id;
|
||||||
|
$this->groupId = $group->id;
|
||||||
|
$this->groupId2 = $group2->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testJoinSubclassPersister_FilterOnlyOnRootTableWhenFetchingSubEntity()
|
||||||
|
{
|
||||||
|
$this->loadCompanyJoinedSubclassFixtureData();
|
||||||
|
// Persister
|
||||||
|
$this->assertEquals(2, count($this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyManager')->findAll()));
|
||||||
|
// SQLWalker
|
||||||
|
$this->assertEquals(2, count($this->_em->createQuery("SELECT cm FROM Doctrine\Tests\Models\Company\CompanyManager cm")->getResult()));
|
||||||
|
|
||||||
|
// Enable the filter
|
||||||
|
$conf = $this->_em->getConfiguration();
|
||||||
|
$conf->addFilter("person_name", "\Doctrine\Tests\ORM\Functional\CompanyPersonNameFilter");
|
||||||
|
$this->_em->getFilters()
|
||||||
|
->enable("person_name")
|
||||||
|
->setParameter("name", "Guilh%", DBALType::STRING);
|
||||||
|
|
||||||
|
$managers = $this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyManager')->findAll();
|
||||||
|
$this->assertEquals(1, count($managers));
|
||||||
|
$this->assertEquals("Guilherme", $managers[0]->getName());
|
||||||
|
|
||||||
|
$this->assertEquals(1, count($this->_em->createQuery("SELECT cm FROM Doctrine\Tests\Models\Company\CompanyManager cm")->getResult()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testJoinSubclassPersister_FilterOnlyOnRootTableWhenFetchingRootEntity()
|
||||||
|
{
|
||||||
|
$this->loadCompanyJoinedSubclassFixtureData();
|
||||||
|
$this->assertEquals(3, count($this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyPerson')->findAll()));
|
||||||
|
$this->assertEquals(3, count($this->_em->createQuery("SELECT cp FROM Doctrine\Tests\Models\Company\CompanyPerson cp")->getResult()));
|
||||||
|
|
||||||
|
// Enable the filter
|
||||||
|
$conf = $this->_em->getConfiguration();
|
||||||
|
$conf->addFilter("person_name", "\Doctrine\Tests\ORM\Functional\CompanyPersonNameFilter");
|
||||||
|
$this->_em->getFilters()
|
||||||
|
->enable("person_name")
|
||||||
|
->setParameter("name", "Guilh%", DBALType::STRING);
|
||||||
|
|
||||||
|
$persons = $this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyPerson')->findAll();
|
||||||
|
$this->assertEquals(1, count($persons));
|
||||||
|
$this->assertEquals("Guilherme", $persons[0]->getName());
|
||||||
|
|
||||||
|
$this->assertEquals(1, count($this->_em->createQuery("SELECT cp FROM Doctrine\Tests\Models\Company\CompanyPerson cp")->getResult()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadCompanyJoinedSubclassFixtureData()
|
||||||
|
{
|
||||||
|
$manager = new CompanyManager;
|
||||||
|
$manager->setName('Roman');
|
||||||
|
$manager->setTitle('testlead');
|
||||||
|
$manager->setSalary(42);
|
||||||
|
$manager->setDepartment('persisters');
|
||||||
|
|
||||||
|
$manager2 = new CompanyManager;
|
||||||
|
$manager2->setName('Guilherme');
|
||||||
|
$manager2->setTitle('devlead');
|
||||||
|
$manager2->setSalary(42);
|
||||||
|
$manager2->setDepartment('parsers');
|
||||||
|
|
||||||
|
$person = new CompanyPerson;
|
||||||
|
$person->setName('Benjamin');
|
||||||
|
|
||||||
|
$this->_em->persist($manager);
|
||||||
|
$this->_em->persist($manager2);
|
||||||
|
$this->_em->persist($person);
|
||||||
|
$this->_em->flush();
|
||||||
|
$this->_em->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSingleTableInheritance_FilterOnlyOnRootTableWhenFetchingSubEntity()
|
||||||
|
{
|
||||||
|
$this->loadCompanySingleTableInheritanceFixtureData();
|
||||||
|
// Persister
|
||||||
|
$this->assertEquals(2, count($this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyFlexUltraContract')->findAll()));
|
||||||
|
// SQLWalker
|
||||||
|
$this->assertEquals(2, count($this->_em->createQuery("SELECT cfc FROM Doctrine\Tests\Models\Company\CompanyFlexUltraContract cfc")->getResult()));
|
||||||
|
|
||||||
|
// Enable the filter
|
||||||
|
$conf = $this->_em->getConfiguration();
|
||||||
|
$conf->addFilter("completed_contract", "\Doctrine\Tests\ORM\Functional\CompletedContractFilter");
|
||||||
|
$this->_em->getFilters()
|
||||||
|
->enable("completed_contract")
|
||||||
|
->setParameter("completed", true, DBALType::BOOLEAN);
|
||||||
|
|
||||||
|
$this->assertEquals(1, count($this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyFlexUltraContract')->findAll()));
|
||||||
|
$this->assertEquals(1, count($this->_em->createQuery("SELECT cfc FROM Doctrine\Tests\Models\Company\CompanyFlexUltraContract cfc")->getResult()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSingleTableInheritance_FilterOnlyOnRootTableWhenFetchingRootEntity()
|
||||||
|
{
|
||||||
|
$this->loadCompanySingleTableInheritanceFixtureData();
|
||||||
|
$this->assertEquals(4, count($this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyFlexContract')->findAll()));
|
||||||
|
$this->assertEquals(4, count($this->_em->createQuery("SELECT cfc FROM Doctrine\Tests\Models\Company\CompanyFlexContract cfc")->getResult()));
|
||||||
|
|
||||||
|
// Enable the filter
|
||||||
|
$conf = $this->_em->getConfiguration();
|
||||||
|
$conf->addFilter("completed_contract", "\Doctrine\Tests\ORM\Functional\CompletedContractFilter");
|
||||||
|
$this->_em->getFilters()
|
||||||
|
->enable("completed_contract")
|
||||||
|
->setParameter("completed", true, DBALType::BOOLEAN);
|
||||||
|
|
||||||
|
$this->assertEquals(2, count($this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyFlexContract')->findAll()));
|
||||||
|
$this->assertEquals(2, count($this->_em->createQuery("SELECT cfc FROM Doctrine\Tests\Models\Company\CompanyFlexContract cfc")->getResult()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadCompanySingleTableInheritanceFixtureData()
|
||||||
|
{
|
||||||
|
$contract1 = new CompanyFlexUltraContract;
|
||||||
|
$contract2 = new CompanyFlexUltraContract;
|
||||||
|
$contract2->markCompleted();
|
||||||
|
|
||||||
|
$contract3 = new CompanyFlexContract;
|
||||||
|
$contract4 = new CompanyFlexContract;
|
||||||
|
$contract4->markCompleted();
|
||||||
|
|
||||||
|
$this->_em->persist($contract1);
|
||||||
|
$this->_em->persist($contract2);
|
||||||
|
$this->_em->persist($contract3);
|
||||||
|
$this->_em->persist($contract4);
|
||||||
|
$this->_em->flush();
|
||||||
|
$this->_em->clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MySoftDeleteFilter extends SQLFilter
|
||||||
|
{
|
||||||
|
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
|
||||||
|
{
|
||||||
|
if ($targetEntity->name != "MyEntity\SoftDeleteNewsItem") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $targetTableAlias.'.deleted = 0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyLocaleFilter extends SQLFilter
|
||||||
|
{
|
||||||
|
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
|
||||||
|
{
|
||||||
|
if (!in_array("LocaleAware", $targetEntity->reflClass->getInterfaceNames())) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $targetTableAlias.'.locale = ' . $this->getParameter('locale'); // getParam uses connection to quote the value.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CMSCountryFilter extends SQLFilter
|
||||||
|
{
|
||||||
|
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
|
||||||
|
{
|
||||||
|
if ($targetEntity->name != "Doctrine\Tests\Models\CMS\CmsAddress") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $targetTableAlias.'.country = ' . $this->getParameter('country'); // getParam uses connection to quote the value.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CMSGroupPrefixFilter extends SQLFilter
|
||||||
|
{
|
||||||
|
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
|
||||||
|
{
|
||||||
|
if ($targetEntity->name != "Doctrine\Tests\Models\CMS\CmsGroup") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $targetTableAlias.'.name LIKE ' . $this->getParameter('prefix'); // getParam uses connection to quote the value.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CMSArticleTopicFilter extends SQLFilter
|
||||||
|
{
|
||||||
|
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
|
||||||
|
{
|
||||||
|
if ($targetEntity->name != "Doctrine\Tests\Models\CMS\CmsArticle") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $targetTableAlias.'.topic = ' . $this->getParameter('topic'); // getParam uses connection to quote the value.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CompanyPersonNameFilter extends SQLFilter
|
||||||
|
{
|
||||||
|
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias, $targetTable = '')
|
||||||
|
{
|
||||||
|
if ($targetEntity->name != "Doctrine\Tests\Models\Company\CompanyPerson") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $targetTableAlias.'.name LIKE ' . $this->getParameter('name');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CompletedContractFilter extends SQLFilter
|
||||||
|
{
|
||||||
|
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias, $targetTable = '')
|
||||||
|
{
|
||||||
|
if ($targetEntity->name != "Doctrine\Tests\Models\Company\CompanyContract") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $targetTableAlias.'.completed = ' . $this->getParameter('completed');
|
||||||
|
}
|
||||||
|
}
|
203
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1545Test.php
Normal file
203
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1545Test.php
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||||
|
|
||||||
|
use Doctrine\Tests\Models\Qelista\User;
|
||||||
|
|
||||||
|
use Doctrine\Tests\Models\Qelista\ShoppingList;
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Tests\Models\CMS\CmsComment;
|
||||||
|
use Doctrine\Tests\Models\CMS\CmsArticle;
|
||||||
|
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||||
|
require_once __DIR__ . '/../../../TestInit.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group DDC-1545
|
||||||
|
*/
|
||||||
|
class DDC1545Test extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||||
|
{
|
||||||
|
private $articleId;
|
||||||
|
|
||||||
|
private $userId;
|
||||||
|
|
||||||
|
private $user2Id;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->useModelSet('cms');
|
||||||
|
parent::setUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function initDb($link)
|
||||||
|
{
|
||||||
|
$article = new CmsArticle();
|
||||||
|
$article->topic = 'foo';
|
||||||
|
$article->text = 'foo';
|
||||||
|
|
||||||
|
$user = new CmsUser();
|
||||||
|
$user->status = 'foo';
|
||||||
|
$user->username = 'foo';
|
||||||
|
$user->name = 'foo';
|
||||||
|
|
||||||
|
$user2 = new CmsUser();
|
||||||
|
$user2->status = 'bar';
|
||||||
|
$user2->username = 'bar';
|
||||||
|
$user2->name = 'bar';
|
||||||
|
|
||||||
|
if ($link) {
|
||||||
|
$article->user = $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_em->persist($article);
|
||||||
|
$this->_em->persist($user);
|
||||||
|
$this->_em->persist($user2);
|
||||||
|
$this->_em->flush();
|
||||||
|
$this->_em->clear();
|
||||||
|
|
||||||
|
$this->articleId = $article->id;
|
||||||
|
$this->userId = $user->id;
|
||||||
|
$this->user2Id = $user2->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLinkObjects()
|
||||||
|
{
|
||||||
|
$this->initDb(false);
|
||||||
|
|
||||||
|
// don't join association
|
||||||
|
$article = $this->_em->find('Doctrine\Tests\Models\Cms\CmsArticle', $this->articleId);
|
||||||
|
|
||||||
|
$user = $this->_em->find('Doctrine\Tests\Models\Cms\CmsUser', $this->userId);
|
||||||
|
|
||||||
|
$article->user = $user;
|
||||||
|
|
||||||
|
$this->_em->flush();
|
||||||
|
$this->_em->clear();
|
||||||
|
|
||||||
|
$article = $this->_em
|
||||||
|
->createQuery('SELECT a, u FROM Doctrine\Tests\Models\Cms\CmsArticle a LEFT JOIN a.user u WHERE a.id = :id')
|
||||||
|
->setParameter('id', $this->articleId)
|
||||||
|
->getOneOrNullResult();
|
||||||
|
|
||||||
|
$this->assertNotNull($article->user);
|
||||||
|
$this->assertEquals($user->id, $article->user->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLinkObjectsWithAssociationLoaded()
|
||||||
|
{
|
||||||
|
$this->initDb(false);
|
||||||
|
|
||||||
|
// join association
|
||||||
|
$article = $this->_em
|
||||||
|
->createQuery('SELECT a, u FROM Doctrine\Tests\Models\Cms\CmsArticle a LEFT JOIN a.user u WHERE a.id = :id')
|
||||||
|
->setParameter('id', $this->articleId)
|
||||||
|
->getOneOrNullResult();
|
||||||
|
|
||||||
|
$user = $this->_em->find('Doctrine\Tests\Models\Cms\CmsUser', $this->userId);
|
||||||
|
|
||||||
|
$article->user = $user;
|
||||||
|
|
||||||
|
$this->_em->flush();
|
||||||
|
$this->_em->clear();
|
||||||
|
|
||||||
|
$article = $this->_em
|
||||||
|
->createQuery('SELECT a, u FROM Doctrine\Tests\Models\Cms\CmsArticle a LEFT JOIN a.user u WHERE a.id = :id')
|
||||||
|
->setParameter('id', $this->articleId)
|
||||||
|
->getOneOrNullResult();
|
||||||
|
|
||||||
|
$this->assertNotNull($article->user);
|
||||||
|
$this->assertEquals($user->id, $article->user->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnlinkObjects()
|
||||||
|
{
|
||||||
|
$this->initDb(true);
|
||||||
|
|
||||||
|
// don't join association
|
||||||
|
$article = $this->_em->find('Doctrine\Tests\Models\Cms\CmsArticle', $this->articleId);
|
||||||
|
|
||||||
|
$article->user = null;
|
||||||
|
|
||||||
|
$this->_em->flush();
|
||||||
|
$this->_em->clear();
|
||||||
|
|
||||||
|
$article = $this->_em
|
||||||
|
->createQuery('SELECT a, u FROM Doctrine\Tests\Models\Cms\CmsArticle a LEFT JOIN a.user u WHERE a.id = :id')
|
||||||
|
->setParameter('id', $this->articleId)
|
||||||
|
->getOneOrNullResult();
|
||||||
|
|
||||||
|
$this->assertNull($article->user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnlinkObjectsWithAssociationLoaded()
|
||||||
|
{
|
||||||
|
$this->initDb(true);
|
||||||
|
|
||||||
|
// join association
|
||||||
|
$article = $this->_em
|
||||||
|
->createQuery('SELECT a, u FROM Doctrine\Tests\Models\Cms\CmsArticle a LEFT JOIN a.user u WHERE a.id = :id')
|
||||||
|
->setParameter('id', $this->articleId)
|
||||||
|
->getOneOrNullResult();
|
||||||
|
|
||||||
|
$article->user = null;
|
||||||
|
|
||||||
|
$this->_em->flush();
|
||||||
|
$this->_em->clear();
|
||||||
|
|
||||||
|
$article = $this->_em
|
||||||
|
->createQuery('SELECT a, u FROM Doctrine\Tests\Models\Cms\CmsArticle a LEFT JOIN a.user u WHERE a.id = :id')
|
||||||
|
->setParameter('id', $this->articleId)
|
||||||
|
->getOneOrNullResult();
|
||||||
|
|
||||||
|
$this->assertNull($article->user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testChangeLink()
|
||||||
|
{
|
||||||
|
$this->initDb(false);
|
||||||
|
|
||||||
|
// don't join association
|
||||||
|
$article = $this->_em->find('Doctrine\Tests\Models\Cms\CmsArticle', $this->articleId);
|
||||||
|
|
||||||
|
$user2 = $this->_em->find('Doctrine\Tests\Models\Cms\CmsUser', $this->user2Id);
|
||||||
|
|
||||||
|
$article->user = $user2;
|
||||||
|
|
||||||
|
$this->_em->flush();
|
||||||
|
$this->_em->clear();
|
||||||
|
|
||||||
|
$article = $this->_em
|
||||||
|
->createQuery('SELECT a, u FROM Doctrine\Tests\Models\Cms\CmsArticle a LEFT JOIN a.user u WHERE a.id = :id')
|
||||||
|
->setParameter('id', $this->articleId)
|
||||||
|
->getOneOrNullResult();
|
||||||
|
|
||||||
|
$this->assertNotNull($article->user);
|
||||||
|
$this->assertEquals($user2->id, $article->user->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testChangeLinkWithAssociationLoaded()
|
||||||
|
{
|
||||||
|
$this->initDb(false);
|
||||||
|
|
||||||
|
// join association
|
||||||
|
$article = $this->_em
|
||||||
|
->createQuery('SELECT a, u FROM Doctrine\Tests\Models\Cms\CmsArticle a LEFT JOIN a.user u WHERE a.id = :id')
|
||||||
|
->setParameter('id', $this->articleId)
|
||||||
|
->getOneOrNullResult();
|
||||||
|
|
||||||
|
$user2 = $this->_em->find('Doctrine\Tests\Models\Cms\CmsUser', $this->user2Id);
|
||||||
|
|
||||||
|
$article->user = $user2;
|
||||||
|
|
||||||
|
$this->_em->flush();
|
||||||
|
$this->_em->clear();
|
||||||
|
|
||||||
|
$article = $this->_em
|
||||||
|
->createQuery('SELECT a, u FROM Doctrine\Tests\Models\Cms\CmsArticle a LEFT JOIN a.user u WHERE a.id = :id')
|
||||||
|
->setParameter('id', $this->articleId)
|
||||||
|
->getOneOrNullResult();
|
||||||
|
|
||||||
|
$this->assertNotNull($article->user);
|
||||||
|
$this->assertEquals($user2->id, $article->user->id);
|
||||||
|
}
|
||||||
|
}
|
@ -51,6 +51,17 @@ class XmlMappingDriverTest extends AbstractMappingDriverTest
|
|||||||
$this->assertTrue($class->associationMappings['article']['id']);
|
$this->assertTrue($class->associationMappings['article']['id']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group DDC-1468
|
||||||
|
*
|
||||||
|
* @expectedException Doctrine\ORM\Mapping\MappingException
|
||||||
|
* @expectedExceptionMessage Invalid mapping file 'Doctrine.Tests.Models.Generic.SerializationModel.dcm.xml' for class 'Doctrine\Tests\Models\Generic\SerializationModel'.
|
||||||
|
*/
|
||||||
|
public function testInvalidMappingFileException()
|
||||||
|
{
|
||||||
|
$this->createClassMetadata('Doctrine\Tests\Models\Generic\SerializationModel');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $xmlMappingFile
|
* @param string $xmlMappingFile
|
||||||
* @dataProvider dataValidSchema
|
* @dataProvider dataValidSchema
|
||||||
|
@ -43,4 +43,15 @@ class YamlMappingDriverTest extends AbstractMappingDriverTest
|
|||||||
$this->assertEquals('Doctrine\Tests\Models\DirectoryTree\Directory', $classDirectory->associationMappings['parentDirectory']['sourceEntity']);
|
$this->assertEquals('Doctrine\Tests\Models\DirectoryTree\Directory', $classDirectory->associationMappings['parentDirectory']['sourceEntity']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group DDC-1468
|
||||||
|
*
|
||||||
|
* @expectedException Doctrine\ORM\Mapping\MappingException
|
||||||
|
* @expectedExceptionMessage Invalid mapping file 'Doctrine.Tests.Models.Generic.SerializationModel.dcm.yml' for class 'Doctrine\Tests\Models\Generic\SerializationModel'.
|
||||||
|
*/
|
||||||
|
public function testInvalidMappingFileException()
|
||||||
|
{
|
||||||
|
$this->createClassMetadata('Doctrine\Tests\Models\Generic\SerializationModel');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
|
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
|
|
||||||
|
<entity name="\stdClass">
|
||||||
|
<id name="id" type="integer" column="id">
|
||||||
|
<generator strategy="AUTO"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<field name="array" column="array" type="array"/>
|
||||||
|
|
||||||
|
<field name="object" column="object" type="object"/>
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
</doctrine-mapping>
|
@ -0,0 +1,13 @@
|
|||||||
|
\stdClass:
|
||||||
|
type: entity
|
||||||
|
id:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
unsigned: true
|
||||||
|
generator:
|
||||||
|
strategy: AUTO
|
||||||
|
fields:
|
||||||
|
array:
|
||||||
|
type: array
|
||||||
|
object:
|
||||||
|
type: object
|
@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Doctrine\Tests\ORM\Tools;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Events;
|
||||||
|
use Doctrine\ORM\Mapping\ClassMetadataFactory;
|
||||||
|
use Doctrine\ORM\Tools\ResolveTargetEntityListener;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../TestInit.php';
|
||||||
|
|
||||||
|
class ResolveTargetEntityListenerTest extends \Doctrine\Tests\OrmTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var EntityManager
|
||||||
|
*/
|
||||||
|
private $em = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ResolveTargetEntityListener
|
||||||
|
*/
|
||||||
|
private $listener = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ClassMetadataFactory
|
||||||
|
*/
|
||||||
|
private $factory = null;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$annotationDriver = $this->createAnnotationDriver();
|
||||||
|
|
||||||
|
$this->em = $this->_getTestEntityManager();
|
||||||
|
$this->em->getConfiguration()->setMetadataDriverImpl($annotationDriver);
|
||||||
|
$this->factory = new ClassMetadataFactory;
|
||||||
|
$this->factory->setEntityManager($this->em);
|
||||||
|
$this->listener = new ResolveTargetEntityListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group DDC-1544
|
||||||
|
*/
|
||||||
|
public function testResolveTargetEntityListenerCanResolveTargetEntity()
|
||||||
|
{
|
||||||
|
$evm = $this->em->getEventManager();
|
||||||
|
$this->listener->addResolveTargetEntity(
|
||||||
|
'Doctrine\Tests\ORM\Tools\ResolveTargetInterface',
|
||||||
|
'Doctrine\Tests\ORM\Tools\ResolveTargetEntity',
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
$this->listener->addResolveTargetEntity(
|
||||||
|
'Doctrine\Tests\ORM\Tools\TargetInterface',
|
||||||
|
'Doctrine\Tests\ORM\Tools\TargetEntity',
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
$evm->addEventListener(Events::loadClassMetadata, $this->listener);
|
||||||
|
$cm = $this->factory->getMetadataFor('Doctrine\Tests\ORM\Tools\ResolveTargetEntity');
|
||||||
|
$meta = $cm->associationMappings;
|
||||||
|
$this->assertSame('Doctrine\Tests\ORM\Tools\TargetEntity', $meta['manyToMany']['targetEntity']);
|
||||||
|
$this->assertSame('Doctrine\Tests\ORM\Tools\ResolveTargetEntity', $meta['manyToOne']['targetEntity']);
|
||||||
|
$this->assertSame('Doctrine\Tests\ORM\Tools\ResolveTargetEntity', $meta['oneToMany']['targetEntity']);
|
||||||
|
$this->assertSame('Doctrine\Tests\ORM\Tools\TargetEntity', $meta['oneToOne']['targetEntity']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResolveTargetInterface
|
||||||
|
{
|
||||||
|
public function getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TargetInterface extends ResolveTargetInterface
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
*/
|
||||||
|
class ResolveTargetEntity implements ResolveTargetInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Id
|
||||||
|
* @Column(type="integer")
|
||||||
|
* @GeneratedValue(strategy="AUTO")
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ManyToMany(targetEntity="Doctrine\Tests\ORM\Tools\TargetInterface")
|
||||||
|
*/
|
||||||
|
private $manyToMany;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ManyToOne(targetEntity="Doctrine\Tests\ORM\Tools\ResolveTargetInterface", inversedBy="oneToMany")
|
||||||
|
*/
|
||||||
|
private $manyToOne;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OneToMany(targetEntity="Doctrine\Tests\ORM\Tools\ResolveTargetInterface", mappedBy="manyToOne")
|
||||||
|
*/
|
||||||
|
private $oneToMany;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OneToOne(targetEntity="Doctrine\Tests\ORM\Tools\TargetInterface")
|
||||||
|
* @JoinColumn(name="target_entity_id", referencedColumnName="id")
|
||||||
|
*/
|
||||||
|
private $oneToOne;
|
||||||
|
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
*/
|
||||||
|
class TargetEntity implements TargetInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Id
|
||||||
|
* @Column(type="integer")
|
||||||
|
* @GeneratedValue(strategy="AUTO")
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
}
|
@ -141,13 +141,11 @@ class InvalidEntity2
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @Id @Column
|
* @Id @Column
|
||||||
* @GeneratedValue(strategy="AUTO")
|
|
||||||
*/
|
*/
|
||||||
protected $key3;
|
protected $key3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Id @Column
|
* @Id @Column
|
||||||
* @GeneratedValue(strategy="AUTO")
|
|
||||||
*/
|
*/
|
||||||
protected $key4;
|
protected $key4;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user