diff --git a/docs/en/reference/query-builder.rst b/docs/en/reference/query-builder.rst index 41ef31420a..b99ca43a2e 100644 --- a/docs/en/reference/query-builder.rst +++ b/docs/en/reference/query-builder.rst @@ -555,6 +555,24 @@ using ``addCriteria``: $qb->addCriteria($criteria); // then execute your query like normal +Adding hints to a Query +^^^^^^^^^^^^^^^^^^^^^^^ + +You can also set query hints to a QueryBuilder by using ``setHint``: + +.. code-block:: php + + setHint('hintName', 'hintValue'); + // then execute your query like normal + +The query hint can hold anything the usual query hints can hold +except null. Those hints will be applied to the query when the +query is created. + Low Level API ^^^^^^^^^^^^^ diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 02dc28956b..dd5975b603 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -117,6 +117,13 @@ class QueryBuilder implements Stringable */ private int $boundCounter = 0; + /** + * The hints to set on the query. + * + * @var array|object> + */ + private array $hints = []; + /** * Initializes a new QueryBuilder that uses the given EntityManager. * @@ -208,6 +215,39 @@ public function setLifetime(int $lifetime): static return $this; } + /** @return array|object> */ + public function getHints(): array + { + return $this->hints; + } + + /** + * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned. + * + * @return mixed The value of the hint or FALSE, if the hint name is not recognized. + */ + public function getHint(string $name): mixed + { + return $this->hints[$name] ?? false; + } + + public function hasHint(string $name): bool + { + return isset($this->hints[$name]); + } + + /** + * Adds hints for the query. + * + * @return $this + */ + public function setHint(string $name, mixed $value): static + { + $this->hints[$name] = $value; + + return $this; + } + /** @phpstan-return Cache::MODE_*|null */ public function getCacheMode(): int|null { @@ -288,6 +328,10 @@ public function getQuery(): Query $query->setCacheRegion($this->cacheRegion); } + foreach ($this->hints as $name => $value) { + $query->setHint($name, $value); + } + return $query; } diff --git a/tests/Tests/ORM/QueryBuilderTest.php b/tests/Tests/ORM/QueryBuilderTest.php index 274d4bde20..91a083e241 100644 --- a/tests/Tests/ORM/QueryBuilderTest.php +++ b/tests/Tests/ORM/QueryBuilderTest.php @@ -24,6 +24,7 @@ use Doctrine\Tests\Models\CMS\CmsUser; use Doctrine\Tests\OrmTestCase; use InvalidArgumentException; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\WithoutErrorHandler; use PHPUnit\Framework\TestCase; @@ -1375,4 +1376,99 @@ public function test(): void $qb->test(); } + + #[DataProvider('provideHint')] + public function testSingleHint(mixed $expected): void + { + $qb = $this->entityManager->createQueryBuilder() + ->delete(CmsUser::class, 'u') + ->select('u.id', 'u.username') + ->setHint('foo', $expected); + + $this->assertValidQueryBuilder($qb, 'SELECT u.id, u.username FROM Doctrine\Tests\Models\CMS\CmsUser u'); + + $query = $qb->getQuery(); + self::assertTrue($query->hasHint('foo')); + self::assertEquals($expected, $query->getHint('foo')); + } + + public static function provideHint(): array + { + return [ + ['bar'], + [new CmsUser()], + [['a','b','c']], + [1], + [true], + ]; + } + + public function testMultipleHints(): void + { + $object = new CmsUser(); + $qb = $this->entityManager->createQueryBuilder() + ->delete(CmsUser::class, 'u') + ->select('u.id', 'u.username') + ->setHint('string', 'bar') + ->setHint('object', $object) + ->setHint('array', ['a', 'b', 'c']) + ->setHint('int', 5) + ->setHint('bool', true); + + $this->assertValidQueryBuilder($qb, 'SELECT u.id, u.username FROM Doctrine\Tests\Models\CMS\CmsUser u'); + + $query = $qb->getQuery(); + self::assertTrue($query->hasHint('string')); + self::assertTrue($query->hasHint('object')); + self::assertTrue($query->hasHint('array')); + self::assertTrue($query->hasHint('int')); + self::assertTrue($query->hasHint('bool')); + + self::assertEquals('bar', $query->getHint('string')); + self::assertInstanceOf(CmsUser::class, $query->getHint('object')); + self::assertEquals(['a', 'b', 'c'], $query->getHint('array')); + self::assertEquals(5, $query->getHint('int')); + self::assertTrue($query->getHint('bool')); + } + + public function testHasHint(): void + { + $qb = $this->entityManager->createQueryBuilder() + ->delete(CmsUser::class, 'u') + ->select('u.id', 'u.username') + ->setHint('foo', 'bar'); + + $this->assertValidQueryBuilder($qb, 'SELECT u.id, u.username FROM Doctrine\Tests\Models\CMS\CmsUser u'); + + self::assertTrue($qb->hasHint('foo')); + } + + public function testGetHint(): void + { + $qb = $this->entityManager->createQueryBuilder() + ->delete(CmsUser::class, 'u') + ->select('u.id', 'u.username') + ->setHint('foo', 'bar'); + + $this->assertValidQueryBuilder($qb, 'SELECT u.id, u.username FROM Doctrine\Tests\Models\CMS\CmsUser u'); + + self::assertEquals('bar', $qb->getHint('foo')); + } + + public function testGetHints(): void + { + $object = new CmsUser(); + $qb = $this->entityManager->createQueryBuilder() + ->delete(CmsUser::class, 'u') + ->select('u.id', 'u.username') + ->setHint('string', 'bar') + ->setHint('object', $object) + ->setHint('array', ['a', 'b', 'c']) + ->setHint('int', 5) + ->setHint('bool', true); + + $this->assertValidQueryBuilder($qb, 'SELECT u.id, u.username FROM Doctrine\Tests\Models\CMS\CmsUser u'); + + self::assertCount(5, $qb->getHints('foo')); + } }