From 3702a710dafd2cf59e51075a7f8699c361b6057d Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 16 Dec 2018 16:20:26 +0100 Subject: [PATCH] Note: this will still lead to the `UnitOfWork#getSingleIdentifierValue()` still being called when not specifying the type of a DQL parameter being bound via `Doctrine\ORM\Query#setParameter()`: ```php $query->setParameter('foo', $theValue, $theType); ``` A full parameter bind is required in order to gain back performance: ```php $query->setParameter('foo', $theValue, $theType); ``` This is up for discussion with patch reviewers. --- lib/Doctrine/ORM/AbstractQuery.php | 6 +- lib/Doctrine/ORM/Query.php | 58 +++++++++++++------ lib/Doctrine/ORM/Query/Parameter.php | 17 +++++- tests/Doctrine/Tests/ORM/QueryBuilderTest.php | 5 +- 4 files changed, 63 insertions(+), 23 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 25a284ceb4c..6ff603228b5 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -72,7 +72,7 @@ abstract class AbstractQuery /** * The parameter map of this query. * - * @var \Doctrine\Common\Collections\ArrayCollection + * @var ArrayCollection|Parameter[] */ protected $parameters; @@ -306,7 +306,7 @@ public function free() /** * Get all defined parameters. * - * @return \Doctrine\Common\Collections\ArrayCollection The defined query parameters. + * @return ArrayCollection The defined query parameters. */ public function getParameters() { @@ -336,7 +336,7 @@ function (Query\Parameter $parameter) use ($key) : bool { /** * Sets a collection of query parameters. * - * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters + * @param ArrayCollection|array $parameters * * @return static This query instance. */ diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index 525aa7a5081..2bc480a9b07 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -19,15 +19,18 @@ namespace Doctrine\ORM; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\DBAL\LockMode; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\Exec\AbstractSqlExecutor; +use Doctrine\ORM\Query\Parameter; +use Doctrine\ORM\Query\ParameterTypeInferer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\ParserResult; use Doctrine\ORM\Query\QueryException; -use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Query\ParameterTypeInferer; -use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver; +use function array_keys; +use function assert; /** * A Query object represents a DQL query. @@ -387,26 +390,13 @@ private function processParameterMappings($paramMappings) $types = []; foreach ($this->parameters as $parameter) { - $key = $parameter->getName(); - $value = $parameter->getValue(); - $rsm = $this->getResultSetMapping(); + $key = $parameter->getName(); if ( ! isset($paramMappings[$key])) { throw QueryException::unknownParameter($key); } - if (isset($rsm->metadataParameterMapping[$key]) && $value instanceof ClassMetadata) { - $value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]); - } - - if (isset($rsm->discriminatorParameters[$key]) && $value instanceof ClassMetadata) { - $value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->_em)); - } - - $value = $this->processParameterValue($value); - $type = ($parameter->getValue() === $value) - ? $parameter->getType() - : ParameterTypeInferer::inferType($value); + [$value, $type] = $this->resolveParameterValue($parameter); foreach ($paramMappings[$key] as $position) { $types[$position] = $type; @@ -439,6 +429,38 @@ private function processParameterMappings($paramMappings) return [$sqlParams, $types]; } + /** @return mixed[] tuple of (value, type) */ + private function resolveParameterValue(Parameter $parameter) : array + { + if ($parameter->typeWasSpecified()) { + return [$parameter->getValue(), $parameter->getType()]; + } + + $key = $parameter->getName(); + $originalValue = $parameter->getValue(); + $value = $originalValue; + $rsm = $this->getResultSetMapping(); + + assert($rsm !== null); + + if ($value instanceof ClassMetadata && isset($rsm->metadataParameterMapping[$key])) { + $value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]); + } + + if ($value instanceof ClassMetadata && isset($rsm->discriminatorParameters[$key])) { + $value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->_em)); + } + + $processedValue = $this->processParameterValue($value); + + return [ + $processedValue, + $originalValue === $processedValue + ? $parameter->getType() + : ParameterTypeInferer::inferType($processedValue), + ]; + } + /** * Defines a cache driver to be used for caching queries. * diff --git a/lib/Doctrine/ORM/Query/Parameter.php b/lib/Doctrine/ORM/Query/Parameter.php index 39e2a7a4f21..6e968a1a9a7 100644 --- a/lib/Doctrine/ORM/Query/Parameter.php +++ b/lib/Doctrine/ORM/Query/Parameter.php @@ -19,6 +19,8 @@ namespace Doctrine\ORM\Query; +use function trim; + /** * Defines a Query Parameter. * @@ -49,6 +51,13 @@ class Parameter */ private $type; + /** + * Whether the parameter type was explicitly specified or not + * + * @var bool + */ + private $typeSpecified; + /** * Constructor. * @@ -58,7 +67,8 @@ class Parameter */ public function __construct($name, $value, $type = null) { - $this->name = trim($name, ':'); + $this->name = trim($name, ':'); + $this->typeSpecified = $type !== null; $this->setValue($value, $type); } @@ -104,4 +114,9 @@ public function setValue($value, $type = null) $this->value = $value; $this->type = $type ?: ParameterTypeInferer::inferType($value); } + + public function typeWasSpecified() : bool + { + return $this->typeSpecified; + } } diff --git a/tests/Doctrine/Tests/ORM/QueryBuilderTest.php b/tests/Doctrine/Tests/ORM/QueryBuilderTest.php index 1cf8ab1646f..17d10f4b2dc 100644 --- a/tests/Doctrine/Tests/ORM/QueryBuilderTest.php +++ b/tests/Doctrine/Tests/ORM/QueryBuilderTest.php @@ -609,8 +609,11 @@ public function testSetParameter() ->setParameter('id', 1); $parameter = new Parameter('id', 1, ParameterTypeInferer::inferType(1)); + $inferred = $qb->getParameter('id'); - $this->assertEquals($parameter, $qb->getParameter('id')); + self::assertSame($parameter->getValue(), $inferred->getValue()); + self::assertSame($parameter->getType(), $inferred->getType()); + self::assertFalse($inferred->typeWasSpecified()); } public function testSetParameters()