From 1635e0af4b06ef3015205563b59b505ae3fac69d Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Mon, 28 May 2012 12:16:42 -0400 Subject: [PATCH 1/6] [DDC-1840] Implemented parameters as a collection. --- lib/Doctrine/ORM/AbstractQuery.php | 167 ++++++++---------- lib/Doctrine/ORM/NativeQuery.php | 25 ++- lib/Doctrine/ORM/Query.php | 37 ++-- .../Query/Exec/MultiTableUpdateExecutor.php | 21 ++- lib/Doctrine/ORM/Query/Parameter.php | 101 +++++++++++ lib/Doctrine/ORM/Query/SqlWalker.php | 7 +- lib/Doctrine/ORM/QueryBuilder.php | 48 ++--- .../Tests/ORM/Functional/NativeQueryTest.php | 20 ++- .../Tests/ORM/Functional/QueryCacheTest.php | 5 +- .../Tests/ORM/Functional/QueryTest.php | 33 ++-- tests/Doctrine/Tests/ORM/Query/QueryTest.php | 37 ++-- tests/Doctrine/Tests/ORM/QueryBuilderTest.php | 35 +++- 12 files changed, 357 insertions(+), 179 deletions(-) create mode 100644 lib/Doctrine/ORM/Query/Parameter.php diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 77ff57a08cd..f402c743af4 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -19,15 +19,17 @@ namespace Doctrine\ORM; -use Doctrine\DBAL\Types\Type, - Doctrine\DBAL\Cache\QueryCacheProfile, - Doctrine\ORM\Query\QueryException, - Doctrine\Common\Util\ClassUtils; +use Doctrine\Common\Util\ClassUtils; +use Doctrine\Common\Collections\ArrayCollection; + +use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Cache\QueryCacheProfile; + +use Doctrine\ORM\Query\QueryException; /** * Base contract for ORM queries. Base class for Query and NativeQuery. * - * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei @@ -62,14 +64,9 @@ abstract class AbstractQuery const HYDRATE_SIMPLEOBJECT = 5; /** - * @var array The parameter map of this query. - */ - protected $_params = array(); - - /** - * @var array The parameter type map of this query. + * @var \Doctrine\Common\Collections\ArrayCollection The parameter map of this query. */ - protected $_paramTypes = array(); + protected $parameters; /** * @var ResultSetMapping The user-specified ResultSetMapping to use. @@ -114,8 +111,18 @@ abstract class AbstractQuery public function __construct(EntityManager $em) { $this->_em = $em; + $this->parameters = new ArrayCollection(); } + /** + * Gets the SQL query that corresponds to this query object. + * The returned SQL syntax depends on the connection driver that is used + * by this query object at the time of this method call. + * + * @return string SQL query + */ + abstract public function getSQL(); + /** * Retrieves the associated EntityManager of this Query instance. * @@ -135,8 +142,8 @@ public function getEntityManager() */ public function free() { - $this->_params = array(); - $this->_paramTypes = array(); + $this->parameters = new ArrayCollection(); + $this->_hints = array(); } @@ -147,58 +154,42 @@ public function free() */ public function getParameters() { - return $this->_params; - } - - /** - * Get all defined parameter types. - * - * @return array The defined query parameter types. - */ - public function getParameterTypes() - { - return $this->_paramTypes; + return $this->parameters; } /** * Gets a query parameter. * * @param mixed $key The key (index or name) of the bound parameter. + * * @return mixed The value of the bound parameter. */ public function getParameter($key) { - if (isset($this->_params[$key])) { - return $this->_params[$key]; + foreach ($this->parameters->getIterator() as $parameter) { + // Must not be identical because of string to integer conversion + if ($parameter->getName() == $key) { + return $parameter; + } } return null; } /** - * Gets a query parameter type. + * Sets a collection of query parameters. * - * @param mixed $key The key (index or name) of the bound parameter. - * @return mixed The parameter type of the bound parameter. + * @param \Doctrine\Common\Collections\ArrayCollection $parameters + * + * @return \Doctrine\ORM\AbstractQuery This query instance. */ - public function getParameterType($key) + public function setParameters(ArrayCollection $parameters) { - if (isset($this->_paramTypes[$key])) { - return $this->_paramTypes[$key]; - } + $this->parameters = $parameters; - return null; + return $this; } - /** - * Gets the SQL query that corresponds to this query object. - * The returned SQL syntax depends on the connection driver that is used - * by this query object at the time of this method call. - * - * @return string SQL query - */ - abstract public function getSQL(); - /** * Sets a query parameter. * @@ -207,19 +198,29 @@ abstract public function getSQL(); * @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 \Doctrine\ORM\AbstractQuery This query instance. */ public function setParameter($key, $value, $type = null) { - $key = trim($key, ':'); - $value = $this->processParameterValue($value); + $filteredParameters = $this->parameters->filter( + function ($parameter) use ($key) + { + // Must not be identical because of string to integer conversion + return ($key == $parameter->getName()); + } + ); - if ($type === null) { - $type = Query\ParameterTypeInferer::inferType($value); + if (count($filteredParameters)) { + $parameter = $filteredParameters->first(); + $parameter->setValue($value, $type); + + return $this; } - $this->_paramTypes[$key] = $type; - $this->_params[$key] = $value; + $parameter = new Query\Parameter($key, $value, $type); + + $this->parameters->add($parameter); return $this; } @@ -230,7 +231,7 @@ public function setParameter($key, $value, $type = null) * @param mixed $value * @return array */ - private function processParameterValue($value) + public function processParameterValue($value) { switch (true) { case is_array($value): @@ -249,7 +250,7 @@ private function processParameterValue($value) } } - protected function convertObjectParameterToScalarValue($value) + private function convertObjectParameterToScalarValue($value) { $class = $this->_em->getClassMetadata(get_class($value)); @@ -275,22 +276,6 @@ protected function convertObjectParameterToScalarValue($value) return $value; } - /** - * Sets a collection of query parameters. - * - * @param array $params - * @param array $types - * @return \Doctrine\ORM\AbstractQuery This query instance. - */ - public function setParameters(array $params, array $types = array()) - { - foreach ($params as $key => $value) { - $this->setParameter($key, $value, isset($types[$key]) ? $types[$key] : null); - } - - return $this; - } - /** * Sets the ResultSetMapping that should be used for hydration. * @@ -530,37 +515,37 @@ public function getHydrationMode() /** * Gets the list of results for the query. * - * Alias for execute(array(), $hydrationMode = HYDRATE_OBJECT). + * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT). * * @return array */ public function getResult($hydrationMode = self::HYDRATE_OBJECT) { - return $this->execute(array(), $hydrationMode); + return $this->execute(null, $hydrationMode); } /** * Gets the array of results for the query. * - * Alias for execute(array(), HYDRATE_ARRAY). + * Alias for execute(null, HYDRATE_ARRAY). * * @return array */ public function getArrayResult() { - return $this->execute(array(), self::HYDRATE_ARRAY); + return $this->execute(null, self::HYDRATE_ARRAY); } /** * Gets the scalar results for the query. * - * Alias for execute(array(), HYDRATE_SCALAR). + * Alias for execute(null, HYDRATE_SCALAR). * * @return array */ public function getScalarResult() { - return $this->execute(array(), self::HYDRATE_SCALAR); + return $this->execute(null, self::HYDRATE_SCALAR); } /** @@ -572,7 +557,7 @@ public function getScalarResult() */ public function getOneOrNullResult($hydrationMode = null) { - $result = $this->execute(array(), $hydrationMode); + $result = $this->execute(null, $hydrationMode); if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) { return null; @@ -604,7 +589,7 @@ public function getOneOrNullResult($hydrationMode = null) */ public function getSingleResult($hydrationMode = null) { - $result = $this->execute(array(), $hydrationMode); + $result = $this->execute(null, $hydrationMode); if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) { throw new NoResultException; @@ -673,18 +658,18 @@ public function getHints() * Executes the query and returns an IterableResult that can be used to incrementally * iterate over the result. * - * @param array $params The query parameters. + * @param \Doctrine\Common\Collections\ArrayCollection $parameters The query parameters. * @param integer $hydrationMode The hydration mode to use. * @return \Doctrine\ORM\Internal\Hydration\IterableResult */ - public function iterate(array $params = array(), $hydrationMode = null) + public function iterate(ArrayCollection $parameters = null, $hydrationMode = null) { if ($hydrationMode !== null) { $this->setHydrationMode($hydrationMode); } - if ($params) { - $this->setParameters($params); + if ($parameters) { + $this->setParameters($parameters); } $stmt = $this->_doExecute(); @@ -697,18 +682,18 @@ public function iterate(array $params = array(), $hydrationMode = null) /** * Executes the query. * - * @param array $params Any additional query parameters. + * @param \Doctrine\Common\Collections\ArrayCollection $parameters Query parameters. * @param integer $hydrationMode Processing mode to be used during the hydration process. * @return mixed */ - public function execute($params = array(), $hydrationMode = null) + public function execute(ArrayCollection $parameters = null, $hydrationMode = null) { if ($hydrationMode !== null) { $this->setHydrationMode($hydrationMode); } - if ($params) { - $this->setParameters($params); + if ($parameters) { + $this->setParameters($parameters); } $setCacheEntry = function() {}; @@ -730,6 +715,7 @@ public function execute($params = array(), $hydrationMode = null) $setCacheEntry = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) { $result[$realCacheKey] = $data; + $cache->save($cacheKey, $result, $queryCacheProfile->getLifetime()); }; } @@ -760,19 +746,20 @@ public function execute($params = array(), $hydrationMode = null) */ protected function getHydrationCacheId() { - $params = $this->getParameters(); + $parameters = array(); - foreach ($params AS $key => $value) { - $params[$key] = $this->processParameterValue($value); + foreach ($this->getParameters()->getIterator() as $parameter) { + $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue()); } $sql = $this->getSQL(); $queryCacheProfile = $this->getHydrationCacheProfile(); $hints = $this->getHints(); $hints['hydrationMode'] = $this->getHydrationMode(); + ksort($hints); - return $queryCacheProfile->generateCacheKeys($sql, $params, $hints); + return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints); } /** @@ -817,8 +804,8 @@ abstract protected function _doExecute(); */ public function __clone() { - $this->_params = array(); - $this->_paramTypes = array(); + $this->parameters = new ArrayCollection(); + $this->_hints = array(); } } diff --git a/lib/Doctrine/ORM/NativeQuery.php b/lib/Doctrine/ORM/NativeQuery.php index 5e76cc52079..72232f24185 100644 --- a/lib/Doctrine/ORM/NativeQuery.php +++ b/lib/Doctrine/ORM/NativeQuery.php @@ -58,19 +58,30 @@ public function getSQL() */ protected function _doExecute() { - $params = $this->_params; - $types = $this->_paramTypes; + $parameters = array(); + $types = array(); - if ($params && is_int(key($params))) { - ksort($params); + foreach ($this->getParameters()->getIterator() as $parameter) { + $name = $parameter->getName(); + $value = $this->processParameterValue($parameter->getValue()); + $type = ($parameter->getValue() === $value) + ? $parameter->getType() + : Query\ParameterTypeInferer::inferType($value); + + $parameters[$name] = $value; + $types[$name] = $type; + } + + if ($parameters && is_int(key($parameters))) { + ksort($parameters); ksort($types); - $params = array_values($params); - $types = array_values($types); + $parameters = array_values($parameters); + $types = array_values($types); } return $this->_em->getConnection()->executeQuery( - $this->_sql, $params, $types, $this->_queryCacheProfile + $this->_sql, $parameters, $types, $this->_queryCacheProfile ); } } diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index fba4d757934..1238a1f7cd6 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM; -use Doctrine\DBAL\LockMode, - Doctrine\ORM\Query\Parser, - Doctrine\ORM\Query\ParserResult, - Doctrine\ORM\Query\QueryException; +use Doctrine\Common\Collections\ArrayCollection; + +use Doctrine\DBAL\LockMode; + +use Doctrine\ORM\Query\Parser; +use Doctrine\ORM\Query\ParserResult; +use Doctrine\ORM\Query\QueryException; /** * A Query object represents a DQL query. @@ -248,7 +251,7 @@ protected function _doExecute() // Prepare parameters $paramMappings = $this->_parserResult->getParameterMappings(); - if (count($paramMappings) != count($this->_params)) { + if (count($paramMappings) != count($this->parameters)) { throw QueryException::invalidParameterNumber(); } @@ -269,17 +272,23 @@ protected function _doExecute() */ private function processParameterMappings($paramMappings) { - $sqlParams = $types = array(); + $sqlParams = array(); + $types = array(); + + foreach ($this->parameters->getIterator() as $parameter) { + $key = $parameter->getName(); - foreach ($this->_params as $key => $value) { if ( ! isset($paramMappings[$key])) { throw QueryException::unknownParameter($key); } - if (isset($this->_paramTypes[$key])) { - foreach ($paramMappings[$key] as $position) { - $types[$position] = $this->_paramTypes[$key]; - } + $value = $this->processParameterValue($parameter->getValue()); + $type = ($parameter->getValue() === $value) + ? $parameter->getType() + : Query\ParameterTypeInferer::inferType($value); + + foreach ($paramMappings[$key] as $position) { + $types[$position] = $type; } $sqlPositions = $paramMappings[$key]; @@ -517,15 +526,15 @@ public function getMaxResults() * Executes the query and returns an IterableResult that can be used to incrementally * iterated over the result. * - * @param array $params The query parameters. + * @param \Doctrine\Common\Collections\ArrayCollection $parameters The query parameters. * @param integer $hydrationMode The hydration mode to use. * @return \Doctrine\ORM\Internal\Hydration\IterableResult */ - public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT) + public function iterate(ArrayCollection $parameters = null, $hydrationMode = self::HYDRATE_OBJECT) { $this->setHint(self::HINT_INTERNAL_ITERATION, true); - return parent::iterate($params, $hydrationMode); + return parent::iterate($parameters, $hydrationMode); } /** diff --git a/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php b/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php index 2978d8302ab..575e6dfb8a6 100644 --- a/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php @@ -19,9 +19,11 @@ namespace Doctrine\ORM\Query\Exec; -use Doctrine\DBAL\Connection, - Doctrine\DBAL\Types\Type, - Doctrine\ORM\Query\AST; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Types\Type; + +use Doctrine\ORM\Query\ParameterTypeInferer; +use Doctrine\ORM\Query\AST; /** * Executes the SQL statements for bulk DQL UPDATE statements on classes in @@ -105,9 +107,16 @@ public function __construct(AST\Node $AST, $sqlWalker) //FIXME: parameters can be more deeply nested. traverse the tree. //FIXME (URGENT): With query cache the parameter is out of date. Move to execute() stage. if ($newValue instanceof AST\InputParameter) { - $paramKey = $newValue->name; - $this->_sqlParameters[$i]['parameters'][] = $sqlWalker->getQuery()->getParameter($paramKey); - $this->_sqlParameters[$i]['types'][] = $sqlWalker->getQuery()->getParameterType($paramKey); + $parameterName = $newValue->name; + $parameter = $sqlWalker->getQuery()->getParameter($parameterName); + + $value = $sqlWalker->getQuery()->processParameterValue($parameter->getValue()); + $type = ($parameter->getValue() === $value) + ? $parameter->getType() + : ParameterTypeInferer::inferType($value); + + $this->_sqlParameters[$i]['parameters'][] = $value; + $this->_sqlParameters[$i]['types'][] = $type; ++$this->_numParametersInUpdateClause; } diff --git a/lib/Doctrine/ORM/Query/Parameter.php b/lib/Doctrine/ORM/Query/Parameter.php new file mode 100644 index 00000000000..6593866657e --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parameter.php @@ -0,0 +1,101 @@ +. + */ + +namespace Doctrine\ORM\Query; + +/** + * Define a Query Parameter + * + * @link www.doctrine-project.org + * @since 2.3 + * @author Guilherme Blanco + */ +class Parameter +{ + /** + * @var string Parameter name + */ + private $name; + + /** + * @var mixed Parameter value + */ + private $value; + + /** + * @var mixed Parameter type + */ + private $type; + + /** + * Constructor. + * + * @param string $name Parameter name + * @param mixed $value Parameter value + * @param mixed $type Parameter type + */ + public function __construct($name, $value, $type = null) + { + $this->name = trim($name, ':'); + + $this->setValue($value); + } + + /** + * Retrieve the Parameter name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Retrieve the Parameter value. + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * Retrieve the Parameter type. + * + * @return mixed + */ + public function getType() + { + return $this->type; + } + + /** + * Define the Parameter value. + * + * @param mixed $value Parameter value + * @param mixed $type Parameter type + */ + public function setValue($value, $type = null) + { + $this->value = $value; + $this->type = $type ?: ParameterTypeInferer::inferType($value); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 3b524da6940..2bef079ae17 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -1687,7 +1687,6 @@ public function walkCollectionMemberExpression($collMemberExpr) // InputParameter case ($entityExpr instanceof AST\InputParameter): $dqlParamKey = $entityExpr->name; - $entity = $this->_query->getParameter($dqlParamKey); $entitySql = '?'; break; @@ -1853,8 +1852,7 @@ public function walkInstanceOfExpression($instanceOfExpr) $dqlAlias = $instanceOfExpr->identificationVariable; $discrClass = $class = $this->_queryComponents[$dqlAlias]['metadata']; - $fieldName = null; - + if ($class->discriminatorColumn) { $discrClass = $this->_em->getClassMetadata($class->rootEntityName); } @@ -1871,7 +1869,8 @@ public function walkInstanceOfExpression($instanceOfExpr) if ($parameter instanceof AST\InputParameter) { // We need to modify the parameter value to be its correspondent mapped value $dqlParamKey = $parameter->name; - $paramValue = $this->_query->getParameter($dqlParamKey); + $dqlParam = $this->_query->getParameter($dqlParamKey); + $paramValue = $this->_query->processParameterValue($dqlParam->getValue()); if ( ! ($paramValue instanceof \Doctrine\ORM\Mapping\ClassMetadata)) { throw QueryException::invalidParameterType('ClassMetadata', get_class($paramValue)); diff --git a/lib/Doctrine/ORM/QueryBuilder.php b/lib/Doctrine/ORM/QueryBuilder.php index 436805f1502..0db6e297e40 100644 --- a/lib/Doctrine/ORM/QueryBuilder.php +++ b/lib/Doctrine/ORM/QueryBuilder.php @@ -19,6 +19,8 @@ namespace Doctrine\ORM; +use Doctrine\Common\Collections\ArrayCollection; + use Doctrine\ORM\Query\Expr; /** @@ -77,14 +79,9 @@ class QueryBuilder private $_dql; /** - * @var array The query parameters. - */ - private $_params = array(); - - /** - * @var array The parameter type map of this query. + * @var \Doctrine\Common\Collections\ArrayCollection The query parameters. */ - private $_paramTypes = array(); + private $parameters = array(); /** * @var integer The index of the first result to retrieve. @@ -109,6 +106,7 @@ class QueryBuilder public function __construct(EntityManager $em) { $this->_em = $em; + $this->parameters = new ArrayCollection(); } /** @@ -218,8 +216,10 @@ public function getDQL() */ public function getQuery() { + $parameters = clone $this->parameters; + return $this->_em->createQuery($this->getDQL()) - ->setParameters($this->_params, $this->_paramTypes) + ->setParameters($parameters) ->setFirstResult($this->_firstResult) ->setMaxResults($this->_maxResults); } @@ -355,10 +355,7 @@ public function getRootEntities() */ public function setParameter($key, $value, $type = null) { - $key = trim($key, ':'); - - $this->_paramTypes[$key] = $type ?: Query\ParameterTypeInferer::inferType($value); - $this->_params[$key] = $value; + $this->parameters->add(new Query\Parameter($key, $value, $type)); return $this; } @@ -371,20 +368,18 @@ public function setParameter($key, $value, $type = null) * ->select('u') * ->from('User', 'u') * ->where('u.id = :user_id1 OR u.id = :user_id2') - * ->setParameters(array( - * 'user_id1' => 1, - * 'user_id2' => 2 - )); + * ->setParameters(new ArrayCollection(array( + * new Parameter('user_id1', 1), + * new Parameter('user_id2', 2) + ))); * * - * @param array $params The query parameters to set. + * @param \Doctrine\Common\Collections\ArrayCollections $params The query parameters to set. * @return QueryBuilder This QueryBuilder instance. */ - public function setParameters(array $params, array $types = array()) + public function setParameters(ArrayCollection $parameters) { - foreach ($params as $key => $value) { - $this->setParameter($key, $value, isset($types[$key]) ? $types[$key] : null); - } + $this->parameters = $parameters; return $this; } @@ -396,18 +391,25 @@ public function setParameters(array $params, array $types = array()) */ public function getParameters() { - return $this->_params; + return $this->parameters; } /** * Gets a (previously set) query parameter of the query being constructed. * * @param mixed $key The key (index or name) of the bound parameter. + * * @return mixed The value of the bound parameter. */ public function getParameter($key) { - return isset($this->_params[$key]) ? $this->_params[$key] : null; + foreach ($this->parameters->getIterator() as $parameter) { + if ($parameter->getName() === $key) { + return $parameter; + } + } + + return null; } /** diff --git a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php index a6a9016a469..3436a74f4f8 100644 --- a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php @@ -2,8 +2,12 @@ namespace Doctrine\Tests\ORM\Functional; +use Doctrine\Common\Collections\ArrayCollection; + use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\Query\ResultSetMappingBuilder; +use Doctrine\ORM\Query\Parameter; + use Doctrine\Tests\Models\CMS\CmsUser; use Doctrine\Tests\Models\CMS\CmsPhonenumber; use Doctrine\Tests\Models\CMS\CmsAddress; @@ -191,6 +195,10 @@ public function testJoinedOneToOneNativeQuery() public function testFluentInterface() { + $parameters = new ArrayCollection; + $parameters->add(new Parameter(1, 'foo')); + $parameters->add(new Parameter(2, 'bar')); + $rsm = new ResultSetMapping; $q = $this->_em->createNativeQuery('SELECT id, name, status, phonenumber FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username = ?', $rsm); @@ -199,7 +207,7 @@ public function testFluentInterface() ->expireResultCache(true) ->setHint('foo', 'bar') ->setParameter(1, 'foo') - ->setParameters(array(2 => 'bar')) + ->setParameters($parameters) ->setResultCacheDriver(null) ->setResultCacheLifetime(3500); @@ -362,7 +370,7 @@ public function testBasicNativeNamedQueryWithSqlResultSetMapping() $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsAddress'); $query = $repository->createNativeNamedQuery('find-all'); $result = $query->getResult(); - + $this->assertCount(1, $result); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress', $result[0]); $this->assertEquals($addr->id, $result[0]->id); @@ -396,7 +404,7 @@ public function testBasicNativeNamedQueryWithResultClass() $result = $repository->createNativeNamedQuery('fetchIdAndUsernameWithResultClass') ->setParameter(1, 'FabioBatSilva')->getResult(); - + $this->assertEquals(1, count($result)); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]); $this->assertNull($result[0]->name); @@ -511,7 +519,7 @@ public function testMixedNativeNamedQueryNormalJoin() $user2->name = 'test tester'; $user2->username = 'test'; $user2->status = 'tester'; - + $phone1 = new CmsPhonenumber; $phone2 = new CmsPhonenumber; $phone3 = new CmsPhonenumber; @@ -528,7 +536,7 @@ public function testMixedNativeNamedQueryNormalJoin() $this->_em->flush(); $this->_em->clear(); - + $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); $result = $repository->createNativeNamedQuery('fetchUserPhonenumberCount') @@ -583,7 +591,7 @@ public function testNativeNamedQueryInheritance() $this->_em->clear(); - + $result = $repository->createNativeNamedQuery('fetchAllWithResultClass') ->getResult(); diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php index e4d567db4d7..faffdd7bc16 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php @@ -19,10 +19,13 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase */ private $cacheDataReflection; - protected function setUp() { + protected function setUp() + { $this->cacheDataReflection = new \ReflectionProperty("Doctrine\Common\Cache\ArrayCache", "data"); $this->cacheDataReflection->setAccessible(true); + $this->useModelSet('cms'); + parent::setUp(); } diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php index 74824ff15bd..19a804b4d55 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php @@ -2,11 +2,16 @@ namespace Doctrine\Tests\ORM\Functional; +use Doctrine\Common\Collections\ArrayCollection; + use Doctrine\DBAL\Connection; -use Doctrine\Tests\Models\CMS\CmsUser, - Doctrine\Tests\Models\CMS\CmsArticle; + use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query; +use Doctrine\ORM\Query\Parameter; + +use Doctrine\Tests\Models\CMS\CmsUser; +use Doctrine\Tests\Models\CMS\CmsArticle; require_once __DIR__ . '/../../TestInit.php'; @@ -151,7 +156,12 @@ public function testInvalidInputParameterThrowsException() public function testSetParameters() { $q = $this->_em->createQuery('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name = ?1 AND u.status = ?2'); - $q->setParameters(array(1 => 'jwage', 2 => 'active')); + + $parameters = new ArrayCollection(); + $parameters->add(new Parameter(1, 'jwage')); + $parameters->add(new Parameter(2, 'active')); + + $q->setParameters($parameters); $users = $q->getResult(); } @@ -176,7 +186,7 @@ public function testIterateResultAsArrayAndParams() $articleId = $article1->id; $query = $this->_em->createQuery("select a from Doctrine\Tests\Models\CMS\CmsArticle a WHERE a.topic = ?1"); - $articles = $query->iterate(array(1 => 'Doctrine 2'), Query::HYDRATE_ARRAY); + $articles = $query->iterate(new ArrayCollection(array(new Parameter(1, 'Doctrine 2'))), Query::HYDRATE_ARRAY); $found = array(); foreach ($articles AS $article) { @@ -520,10 +530,10 @@ public function testParameterOrder() $this->_em->clear(); $query = $this->_em->createQuery("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.status = :a AND u.id IN (:b)"); - $query->setParameters(array( - 'b' => array($user1->id, $user2->id, $user3->id), - 'a' => 'developer', - )); + $query->setParameters(new ArrayCollection(array( + new Parameter('b', array($user1->id, $user2->id, $user3->id)), + new Parameter('a', 'developer') + ))); $result = $query->getResult(); $this->assertEquals(3, count($result)); @@ -639,7 +649,7 @@ public function testQueryWithHiddenAsSelectExpression() /** * @group DDC-1651 */ - public function testSetParameterBindingSingleIdentifierObjectConverted() + public function testSetParameterBindingSingleIdentifierObject() { $userC = new CmsUser; $userC->name = 'Jonathan'; @@ -653,7 +663,10 @@ public function testSetParameterBindingSingleIdentifierObjectConverted() $q = $this->_em->createQuery("SELECT DISTINCT u from Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = ?1"); $q->setParameter(1, $userC); - $this->assertEquals($userC->id, $q->getParameter(1)); + $this->assertEquals($userC, $q->getParameter(1)->getValue()); + + // Parameter is not converted before, but it should be converted during execution. Test should not fail here + $q->getResult(); } diff --git a/tests/Doctrine/Tests/ORM/Query/QueryTest.php b/tests/Doctrine/Tests/ORM/Query/QueryTest.php index 24267c5c53f..de160d09664 100644 --- a/tests/Doctrine/Tests/ORM/Query/QueryTest.php +++ b/tests/Doctrine/Tests/ORM/Query/QueryTest.php @@ -3,6 +3,9 @@ namespace Doctrine\Tests\ORM\Query; use Doctrine\Common\Cache\ArrayCache; +use Doctrine\Common\Collections\ArrayCollection; + +use Doctrine\ORM\Query\Parameter; class QueryTest extends \Doctrine\Tests\OrmTestCase { @@ -16,21 +19,34 @@ protected function setUp() public function testGetParameters() { $query = $this->_em->createQuery("select u from Doctrine\Tests\Models\CMS\CmsUser u where u.username = ?1"); - $this->assertEquals(array(), $query->getParameters()); + + $parameters = new ArrayCollection(); + + $this->assertEquals($parameters, $query->getParameters()); } public function testGetParameters_HasSomeAlready() { $query = $this->_em->createQuery("select u from Doctrine\Tests\Models\CMS\CmsUser u where u.username = ?1"); $query->setParameter(2, 84); - $this->assertEquals(array(2 => 84), $query->getParameters()); + + $parameters = new ArrayCollection(); + $parameters->add(new Parameter(2, 84)); + + $this->assertEquals($parameters, $query->getParameters()); } public function testSetParameters() { $query = $this->_em->createQuery("select u from Doctrine\Tests\Models\CMS\CmsUser u where u.username = ?1"); - $query->setParameters(array(1 => 'foo', 2 => 'bar')); - $this->assertEquals(array(1 => 'foo', 2 => 'bar'), $query->getParameters()); + + $parameters = new ArrayCollection(); + $parameters->add(new Parameter(1, 'foo')); + $parameters->add(new Parameter(2, 'bar')); + + $query->setParameters($parameters); + + $this->assertEquals($parameters, $query->getParameters()); } public function testFree() @@ -40,7 +56,7 @@ public function testFree() $query->free(); - $this->assertEquals(array(), $query->getParameters()); + $this->assertEquals(0, count($query->getParameters())); } public function testClone() @@ -54,7 +70,7 @@ public function testClone() $cloned = clone $query; $this->assertEquals($dql, $cloned->getDql()); - $this->assertEquals(array(), $cloned->getParameters()); + $this->assertEquals(0, count($cloned->getParameters())); $this->assertFalse($cloned->getHint('foo')); } @@ -68,7 +84,7 @@ public function testFluentQueryInterface() ->setHint('foo', 'bar') ->setHint('bar', 'baz') ->setParameter(1, 'bar') - ->setParameters(array(2 => 'baz')) + ->setParameters(new ArrayCollection(array(new Parameter(2, 'baz')))) ->setResultCacheDriver(null) ->setResultCacheId('foo') ->setDql('foo') @@ -129,7 +145,7 @@ public function testIterateWithDistinct() /** * @group DDC-1697 */ - public function testKeyValueParameters() + public function testCollectionParameters() { $cities = array( 0 => "Paris", @@ -142,8 +158,9 @@ public function testKeyValueParameters() ->setParameter('cities', $cities); $parameters = $query->getParameters(); + $parameter = $parameters->first(); - $this->assertArrayHasKey('cities', $parameters); - $this->assertEquals($cities, $parameters['cities']); + $this->assertEquals('cities', $parameter->getName()); + $this->assertEquals($cities, $parameter->getValue()); } } diff --git a/tests/Doctrine/Tests/ORM/QueryBuilderTest.php b/tests/Doctrine/Tests/ORM/QueryBuilderTest.php index f078b899596..60a0e3dfa6f 100644 --- a/tests/Doctrine/Tests/ORM/QueryBuilderTest.php +++ b/tests/Doctrine/Tests/ORM/QueryBuilderTest.php @@ -19,8 +19,12 @@ namespace Doctrine\Tests\ORM; +use Doctrine\Common\Collections\ArrayCollection; + use Doctrine\ORM\QueryBuilder, - Doctrine\ORM\Query\Expr; + Doctrine\ORM\Query\Expr, + Doctrine\ORM\Query\Parameter, + Doctrine\ORM\Query\ParameterTypeInferer; require_once __DIR__ . '/../TestInit.php'; @@ -385,7 +389,9 @@ public function testSetParameter() ->where('u.id = :id') ->setParameter('id', 1); - $this->assertEquals(array('id' => 1), $qb->getParameters()); + $parameter = new Parameter('id', 1, ParameterTypeInferer::inferType(1)); + + $this->assertEquals($parameter, $qb->getParameter('id')); } public function testSetParameters() @@ -395,9 +401,13 @@ public function testSetParameters() ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') ->where($qb->expr()->orx('u.username = :username', 'u.username = :username2')); - $qb->setParameters(array('username' => 'jwage', 'username2' => 'jonwage')); + $parameters = new ArrayCollection(); + $parameters->add(new Parameter('username', 'jwage')); + $parameters->add(new Parameter('username2', 'jonwage')); - $this->assertEquals(array('username' => 'jwage', 'username2' => 'jonwage'), $qb->getQuery()->getParameters()); + $qb->setParameters($parameters); + + $this->assertEquals($parameters, $qb->getQuery()->getParameters()); } @@ -408,8 +418,12 @@ public function testGetParameters() ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') ->where('u.id = :id'); - $qb->setParameters(array('id' => 1)); - $this->assertEquals(array('id' => 1), $qb->getParameters()); + $parameters = new ArrayCollection(); + $parameters->add(new Parameter('id', 1)); + + $qb->setParameters($parameters); + + $this->assertEquals($parameters, $qb->getParameters()); } public function testGetParameter() @@ -419,8 +433,12 @@ public function testGetParameter() ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') ->where('u.id = :id'); - $qb->setParameters(array('id' => 1)); - $this->assertEquals(1, $qb->getParameter('id')); + $parameters = new ArrayCollection(); + $parameters->add(new Parameter('id', 1)); + + $qb->setParameters($parameters); + + $this->assertEquals($parameters->first(), $qb->getParameter('id')); } public function testMultipleWhere() @@ -536,6 +554,7 @@ public function testMultipleIsolatedQueryConstruction() $qb->setParameter('name', 'romanb'); $q1 = $qb->getQuery(); + $this->assertEquals('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name = :name', $q1->getDql()); $this->assertEquals(1, count($q1->getParameters())); From 79ff1f10d2d6553d0a73203c8267e1acd9e8d98c Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Mon, 28 May 2012 12:20:35 -0400 Subject: [PATCH 2/6] Optimized getParameter. --- lib/Doctrine/ORM/AbstractQuery.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index f402c743af4..b0c07e12911 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -166,14 +166,15 @@ public function getParameters() */ public function getParameter($key) { - foreach ($this->parameters->getIterator() as $parameter) { - // Must not be identical because of string to integer conversion - if ($parameter->getName() == $key) { - return $parameter; + $filteredParameters = $this->parameters->filter( + function ($parameter) use ($key) + { + // Must not be identical because of string to integer conversion + return ($key == $parameter->getName()); } - } + ); - return null; + return count($filteredParameters) ? $filteredParameters->first() : null; } /** From d8e165da8d5e7b63d628bb7e79f5fd90965b3808 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Mon, 28 May 2012 12:28:54 -0400 Subject: [PATCH 3/6] Added 2.3 BC break information. --- UPGRADE_TO_2_3 | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/UPGRADE_TO_2_3 b/UPGRADE_TO_2_3 index 7f860be93e6..c0b451f22e4 100644 --- a/UPGRADE_TO_2_3 +++ b/UPGRADE_TO_2_3 @@ -7,3 +7,21 @@ and strip a trailing "s" character if there is one. # Merge copies non persisted properties too When merging an entity in UoW not only mapped properties are copied, but also others. + +# Query, QueryBuilder and NativeQuery parameters *BC break* + +From now on, parameters in queries is an ArrayCollection instead of a simple array. +This affects heavily the usage of setParameters(), because it will not append anymore +parameters to query, but will actually override the already defined ones. +Whenever you are retrieving a parameter (ie. $query->getParameter(1)), you will +receive an instance of Query\Parameter, which contains the methods "getName", +"getValue" and "getType". Parameters are also only converted to when necessary, and +not when they are set. + +Also, related functions were affected: + +* execute(ArrayCollection $parameters, $hydrationMode) +* iterate(ArrayCollection $parameters, $hydrationMode) +* setParameters(ArrayCollection $parameters) +* getParameters() +* getParameter($key) \ No newline at end of file From b3e7493278ce4915d7b741a4ea3cd390030b82bb Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Tue, 29 May 2012 14:25:54 -0400 Subject: [PATCH 4/6] Made setParameters()/excute()/iterate() BC compatible. --- lib/Doctrine/ORM/AbstractQuery.php | 20 +++++++++++-------- lib/Doctrine/ORM/Query.php | 4 ++-- .../ORM/Functional/HydrationCacheTest.php | 10 +++++++--- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index b0c07e12911..94a76f64136 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -180,12 +180,16 @@ function ($parameter) use ($key) /** * Sets a collection of query parameters. * - * @param \Doctrine\Common\Collections\ArrayCollection $parameters + * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters * * @return \Doctrine\ORM\AbstractQuery This query instance. */ - public function setParameters(ArrayCollection $parameters) + public function setParameters($parameters) { + if (is_array($parameters)) { + $parameters = new ArrayCollection($parameters); + } + $this->parameters = $parameters; return $this; @@ -659,17 +663,17 @@ public function getHints() * Executes the query and returns an IterableResult that can be used to incrementally * iterate over the result. * - * @param \Doctrine\Common\Collections\ArrayCollection $parameters The query parameters. + * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters The query parameters. * @param integer $hydrationMode The hydration mode to use. * @return \Doctrine\ORM\Internal\Hydration\IterableResult */ - public function iterate(ArrayCollection $parameters = null, $hydrationMode = null) + public function iterate($parameters = null, $hydrationMode = null) { if ($hydrationMode !== null) { $this->setHydrationMode($hydrationMode); } - if ($parameters) { + if ( ! empty($parameters)) { $this->setParameters($parameters); } @@ -683,17 +687,17 @@ public function iterate(ArrayCollection $parameters = null, $hydrationMode = nul /** * Executes the query. * - * @param \Doctrine\Common\Collections\ArrayCollection $parameters Query parameters. + * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters Query parameters. * @param integer $hydrationMode Processing mode to be used during the hydration process. * @return mixed */ - public function execute(ArrayCollection $parameters = null, $hydrationMode = null) + public function execute($parameters = null, $hydrationMode = null) { if ($hydrationMode !== null) { $this->setHydrationMode($hydrationMode); } - if ($parameters) { + if ( ! empty($parameters)) { $this->setParameters($parameters); } diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index 1238a1f7cd6..5ff7f96ebc2 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -526,11 +526,11 @@ public function getMaxResults() * Executes the query and returns an IterableResult that can be used to incrementally * iterated over the result. * - * @param \Doctrine\Common\Collections\ArrayCollection $parameters The query parameters. + * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters The query parameters. * @param integer $hydrationMode The hydration mode to use. * @return \Doctrine\ORM\Internal\Hydration\IterableResult */ - public function iterate(ArrayCollection $parameters = null, $hydrationMode = self::HYDRATE_OBJECT) + public function iterate($parameters = null, $hydrationMode = self::HYDRATE_OBJECT) { $this->setHint(self::HINT_INTERNAL_ITERATION, true); diff --git a/tests/Doctrine/Tests/ORM/Functional/HydrationCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/HydrationCacheTest.php index 897421164d9..821c5dff438 100644 --- a/tests/Doctrine/Tests/ORM/Functional/HydrationCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/HydrationCacheTest.php @@ -14,6 +14,7 @@ class HydrationCacheTest extends OrmFunctionalTestCase public function setUp() { $this->useModelSet('cms'); + parent::setUp(); $user = new CmsUser; @@ -72,14 +73,17 @@ public function testHydrationParametersSerialization() $user = new CmsUser(); $user->id = 1; - $dql = "SELECT u FROM Doctrine\Tests\Models\Cms\CmsUser u WHERE u.id = ?1"; + $dql = "SELECT u FROM Doctrine\Tests\Models\Cms\CmsUser u WHERE u.id = ?1"; $query = $this->_em->createQuery($dql) - ->setParameter(1, $user) - ->setHydrationCacheProfile(new QueryCacheProfile(null, null, $cache)); + ->setParameter(1, $user) + ->setHydrationCacheProfile(new QueryCacheProfile(null, null, $cache)); $query->getResult(); + $c = $this->getCurrentQueryCount(); + $query->getResult(); + $this->assertEquals($c, $this->getCurrentQueryCount(), "Should not execute query. Its cached!"); } } From 161ae31a7e044a7f0dda357ed271215f2317e9a5 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Tue, 29 May 2012 14:41:32 -0400 Subject: [PATCH 5/6] Adde more BC compatibility in setParameters. --- lib/Doctrine/ORM/AbstractQuery.php | 11 ++++++++++- tests/Doctrine/Tests/ORM/Functional/QueryTest.php | 8 ++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 94a76f64136..d3e661071af 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -186,8 +186,17 @@ function ($parameter) use ($key) */ public function setParameters($parameters) { + // BC compatibility with 2.3- if (is_array($parameters)) { - $parameters = new ArrayCollection($parameters); + $parameterCollection = new ArrayCollection(); + + foreach ($parameters as $key => $value) { + $parameter = new Query\Parameter($key, $value); + + $parameterCollection->add($parameter); + } + + $parameters = $parameterCollection; } $this->parameters = $parameters; diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php index 19a804b4d55..fd1e1470315 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php @@ -165,6 +165,14 @@ public function testSetParameters() $users = $q->getResult(); } + public function testSetParametersBackwardsCompatible() + { + $q = $this->_em->createQuery('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name = ?1 AND u.status = ?2'); + $q->setParameters(array(1 => 'jwage', 2 => 'active')); + + $users = $q->getResult(); + } + /** * @group DDC-1070 */ From 15f76c62bb9a826939173f07f5fded7283e9f75b Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Tue, 29 May 2012 15:40:27 -0300 Subject: [PATCH 6/6] Update UPGRADE_TO_2_3 --- UPGRADE_TO_2_3 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/UPGRADE_TO_2_3 b/UPGRADE_TO_2_3 index c0b451f22e4..28e734a841e 100644 --- a/UPGRADE_TO_2_3 +++ b/UPGRADE_TO_2_3 @@ -20,8 +20,8 @@ not when they are set. Also, related functions were affected: -* execute(ArrayCollection $parameters, $hydrationMode) -* iterate(ArrayCollection $parameters, $hydrationMode) -* setParameters(ArrayCollection $parameters) -* getParameters() -* getParameter($key) \ No newline at end of file +* execute($parameters, $hydrationMode) the argument $parameters can be either an key=>value array or an ArrayCollection instance +* iterate($parameters, $hydrationMode) the argument $parameters can be either an key=>value array or an ArrayCollection instance +* setParameters($parameters) the argument $parameters can be either an key=>value array or an ArrayCollection instance +* getParameters() now returns ArrayCollection instead of array +* getParameter($key) now returns Parameter instance instead of parameter value \ No newline at end of file