Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Hidden fields triggering error when using getSingleScalarResult() #8340

10 changes: 6 additions & 4 deletions lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

use Doctrine\ORM\NoResultException;
use Doctrine\ORM\NonUniqueResultException;
use function count;
use function key;

/**
* Hydrator that hydrates a single scalar value from the result set.
Expand All @@ -46,13 +48,13 @@ protected function hydrateAllData()
if ($numRows > 1) {
throw new NonUniqueResultException('The query returned multiple rows. Change the query or use a different result function like getScalarResult().');
}

if (count($data[key($data)]) > 1) {
throw new NonUniqueResultException('The query returned a row containing multiple columns. Change the query or use a different result function like getScalarResult().');
}

$result = $this->gatherScalarRowData($data[key($data)]);

if (count($result) > 1) {
throw new NonUniqueResultException('The query returned a row containing multiple columns. Change the query or use a different result function like getScalarResult().');
}

return array_shift($result);
}
}
170 changes: 117 additions & 53 deletions tests/Doctrine/Tests/ORM/Hydration/SingleScalarHydratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,90 +4,154 @@

use Doctrine\ORM\Internal\Hydration\SingleScalarHydrator;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\Tests\Mocks\HydratorMockStatement;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Tests\Mocks\HydratorMockStatement;
use Doctrine\Tests\Models\CMS\CmsUser;

class SingleScalarHydratorTest extends HydrationTestCase
{
/** Result set provider for the HYDRATE_SINGLE_SCALAR tests */
public static function singleScalarResultSetProvider(): array
public static function validResultSetProvider()
{
return [
// valid
'valid' => [
'name' => 'result1',
'resultSet' => [
[
'u__name' => 'romanb',
],
// SELECT u.name FROM CmsUser u WHERE u.id = 1
yield [
[
['u__name' => 'romanb'],
],
'romanb',
];
// SELECT u.id FROM CmsUser u WHERE u.id = 1
yield [
[
['u__id' => '1'],
],
1,
];
// SELECT
// u.id,
// COUNT(u.postsCount + u.likesCount) AS HIDDEN score
// FROM CmsUser u
// WHERE u.id = 1
yield [
[
[
'u__id' => '1',
'score' => 10, // Ignored since not part of ResultSetMapping (cf. HIDDEN keyword)
],
],
// valid
1,
];
}

/**
* @dataProvider validResultSetProvider
*/
public function testHydrateSingleScalarFromFieldMappingWithValidResultSet(array $resultSet, $expectedResult)
{
$rsm = new ResultSetMapping();
$rsm->addEntityResult(CmsUser::class, 'u');
$rsm->addFieldResult('u', 'u__id', 'id');
$rsm->addFieldResult('u', 'u__name', 'name');

$stmt = new HydratorMockStatement($resultSet);
$hydrator = new SingleScalarHydrator($this->_em);

$result = $hydrator->hydrateAll($stmt, $rsm);
$this->assertEquals($expectedResult, $result);
}

/**
* @dataProvider validResultSetProvider
*/
public function testHydrateSingleScalarFromScalarMappingWithValidResultSet(array $resultSet, $expectedResult)
{
$rsm = new ResultSetMapping();
$rsm->addScalarResult('u__id', 'id', 'string');
$rsm->addScalarResult('u__name', 'name', 'string');

$stmt = new HydratorMockStatement($resultSet);
$hydrator = new SingleScalarHydrator($this->_em);

$result = $hydrator->hydrateAll($stmt, $rsm);
$this->assertEquals($expectedResult, $result);
}

public static function invalidResultSetProvider()
{
// Single row (OK), multiple columns (NOT OK)
yield [
[
'name' => 'result2',
'resultSet' => [
[
'u__id' => '1',
],
[
'u__id' => '1',
'u__name' => 'romanb',
],
],
// invalid
];
// Multiple rows (NOT OK), single column (OK)
yield [
[
['u__id' => '1'],
['u__id' => '2'],
],
];
// Multiple rows (NOT OK), single column with HIDDEN result (OK)
yield [
[
'name' => 'result3',
'resultSet' => [
[
'u__id' => '1',
'u__name' => 'romanb',
],
[
'u__id' => '1',
'score' => 10, // Ignored since not part of ResultSetMapping
],
[
'u__id' => '2',
'score' => 10, // Ignored since not part of ResultSetMapping
],
],
// invalid
1,
];
// Multiple row (NOT OK), multiple columns (NOT OK)
yield [
[
'name' => 'result4',
'resultSet' => [
[
'u__id' => '1',
],
[
'u__id' => '2',
],
[
'u__id' => '1',
'u__name' => 'romanb',
],
[
'u__id' => '2',
'u__name' => 'romanb',
],
],
];
}

/**
* select u.name from CmsUser u where u.id = 1
*
* @dataProvider singleScalarResultSetProvider
* @dataProvider invalidResultSetProvider
*/
public function testHydrateSingleScalar($name, $resultSet)
public function testHydrateSingleScalarFromFieldMappingWithInvalidResultSet(array $resultSet)
{
$rsm = new ResultSetMapping;
$rsm = new ResultSetMapping();
$rsm->addEntityResult(CmsUser::class, 'u');
$rsm->addFieldResult('u', 'u__id', 'id');
$rsm->addFieldResult('u', 'u__name', 'name');

$stmt = new HydratorMockStatement($resultSet);
$stmt = new HydratorMockStatement($resultSet);
$hydrator = new SingleScalarHydrator($this->_em);

if ($name === 'result1') {
$result = $hydrator->hydrateAll($stmt, $rsm);
$this->assertEquals('romanb', $result);
return;
}
$this->expectException(NonUniqueResultException::class);
$hydrator->hydrateAll($stmt, $rsm);
}

if ($name === 'result2') {
$result = $hydrator->hydrateAll($stmt, $rsm);
$this->assertEquals(1, $result);
/**
* @dataProvider invalidResultSetProvider
*/
public function testHydrateSingleScalarFromScalarMappingWithInvalidResultSet(array $resultSet)
{
$rsm = new ResultSetMapping();
$rsm->addScalarResult('u__id', 'id', 'string');
$rsm->addScalarResult('u__name', 'name', 'string');

return;
}
$stmt = new HydratorMockStatement($resultSet);
$hydrator = new SingleScalarHydrator($this->_em);

if (in_array($name, ['result3', 'result4'], true)) {
$this->expectException(NonUniqueResultException::class);
$hydrator->hydrateAll($stmt, $rsm);
}
$this->expectException(NonUniqueResultException::class);
$hydrator->hydrateAll($stmt, $rsm);
}
}