Skip to content

Commit

Permalink
Merge pull request doctrine#9214 from doctrine/2.7
Browse files Browse the repository at this point in the history
Merge 2.7 into 2.10.x
  • Loading branch information
greg0ire authored Dec 28, 2021
2 parents e8275f6 + 66c95a6 commit 40d1e7b
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 64 deletions.
2 changes: 1 addition & 1 deletion docs/en/reference/dql-doctrine-query-language.rst
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,7 @@ clauses:
- ``SQRT(q)`` - Return the square-root of q.
- ``SUBSTRING(str, start [, length])`` - Return substring of given
string.
- ``TRIM([LEADING \| TRAILING \| BOTH] ['trchar' FROM] str)`` - Trim
- ``TRIM([LEADING | TRAILING | BOTH] ['trchar' FROM] str)`` - Trim
the string by the given trim char, defaults to whitespaces.
- ``UPPER(str)`` - Return the upper-case of the given string.
- ``DATE_ADD(date, value, unit)`` - Add the given time to a given date.
Expand Down
6 changes: 3 additions & 3 deletions lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ protected function hydrateAllData()
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) {
$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().');
}

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

return array_shift($result);
}
}
42 changes: 24 additions & 18 deletions lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -1933,36 +1933,42 @@ private function getValues($value): array
return [$newValue];
}

if (is_object($value) && $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) {
$class = $this->em->getClassMetadata(get_class($value));
if ($class->isIdentifierComposite) {
$newValue = [];

foreach ($class->getIdentifierValues($value) as $innerValue) {
$newValue = array_merge($newValue, $this->getValues($innerValue));
}

return $newValue;
}
}

return [$this->getIndividualValue($value)];
return $this->getIndividualValue($value);
}

/**
* Retrieves an individual parameter value.
*
* @param mixed $value
*
* @return mixed
* @return array<mixed>
* @psalm-return list<mixed>
*/
private function getIndividualValue($value)
{
if (! is_object($value) || ! $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) {
return $value;
if (! is_object($value)) {
return [$value];
}

$valueClass = ClassUtils::getClass($value);

if ($this->em->getMetadataFactory()->isTransient($valueClass)) {
return [$value];
}

$class = $this->em->getClassMetadata($valueClass);

if ($class->isIdentifierComposite) {
$newValue = [];

foreach ($class->getIdentifierValues($value) as $innerValue) {
$newValue = array_merge($newValue, $this->getValues($innerValue));
}

return $newValue;
}

return $this->em->getUnitOfWork()->getSingleIdentifierValue($value);
return [$this->em->getUnitOfWork()->getSingleIdentifierValue($value)];
}

/**
Expand Down
97 changes: 97 additions & 0 deletions tests/Doctrine/Tests/ORM/Functional/Ticket/GH7512Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket;

use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\DiscriminatorMap;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\InheritanceType;
use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\Tests\OrmFunctionalTestCase;

class GH7512Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();

$this->setUpEntitySchema([
GH7512EntityA::class,
GH7512EntityB::class,
GH7512EntityC::class,
]);

$this->_em->persist(new GH7512EntityA());
$this->_em->persist(new GH7512EntityC());
$this->_em->flush();
$this->_em->clear();
}

public function testFindEntityByAssociationPropertyJoinedChildWithClearMetadata(): void
{
// unset metadata for entity B as though it hasn't been touched yet in application lifecycle.
$this->_em->getMetadataFactory()->setMetadataFor(GH7512EntityB::class, null);
$result = $this->_em->getRepository(GH7512EntityC::class)->findBy([
'entityA' => new GH7512EntityB(),
]);
$this->assertEmpty($result);
}
}

/**
* @Entity()
* @InheritanceType("JOINED")
* @DiscriminatorMap({
* "entitya"=Doctrine\Tests\ORM\Functional\Ticket\GH7512EntityA::class,
* "entityB"=Doctrine\Tests\ORM\Functional\Ticket\GH7512EntityB::class
* })
*/
class GH7512EntityA
{
/**
* @Column(type="integer")
* @Id()
* @GeneratedValue(strategy="AUTO")
* @var int
*/
public $id;

/**
* @OneToMany(targetEntity="Doctrine\Tests\ORM\Functional\Ticket\GH7512EntityC", mappedBy="entityA")
* @var Collection<int, GH7512EntityC>
*/
public $entityCs;
}

/**
* @Entity()
*/
class GH7512EntityB extends GH7512EntityA
{
}

/**
* @Entity()
*/
class GH7512EntityC
{
/**
* @Column(type="integer")
* @Id()
* @GeneratedValue(strategy="AUTO")
* @var int
*/
public $id;

/**
* @ManyToOne(targetEntity="Doctrine\Tests\ORM\Functional\Ticket\GH7512EntityA", inversedBy="entityCs")
* @var GH7512EntityA
*/
public $entityA;
}
177 changes: 135 additions & 42 deletions tests/Doctrine/Tests/ORM/Hydration/SingleScalarHydratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,56 +9,147 @@
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\Models\CMS\CmsUser;
use Generator;

use function in_array;

class SingleScalarHydratorTest extends HydrationTestCase
{
/** Result set provider for the HYDRATE_SINGLE_SCALAR tests */
public static function singleScalarResultSetProvider(): array
/**
* @return Generator<int, array{list<array<string,mixed>>,mixed}>
*/
public static function validResultSetProvider(): Generator
{
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,
];
}

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

$stmt = ArrayResultFactory::createFromArray($resultSet);
$hydrator = new SingleScalarHydrator($this->entityManager);

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

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

$stmt = ArrayResultFactory::createFromArray($resultSet);
$hydrator = new SingleScalarHydrator($this->entityManager);

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

/**
* @return Generator<int, array{list<array<string,mixed>>}>
*/
public static function invalidResultSetProvider(): Generator
{
// 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
* @param list<array<string, mixed>> $resultSet
*
* @dataProvider singleScalarResultSetProvider
* @dataProvider invalidResultSetProvider
*/
public function testHydrateSingleScalar($name, $resultSet): void
public function testHydrateSingleScalarFromFieldMappingWithInvalidResultSet(array $resultSet): void
{
$rsm = new ResultSetMapping();
$rsm->addEntityResult(CmsUser::class, 'u');
Expand All @@ -68,23 +159,25 @@ public function testHydrateSingleScalar($name, $resultSet): void
$stmt = ArrayResultFactory::createFromArray($resultSet);
$hydrator = new SingleScalarHydrator($this->entityManager);

if ($name === 'result1') {
$result = $hydrator->hydrateAll($stmt, $rsm);
self::assertEquals('romanb', $result);

return;
}
$this->expectException(NonUniqueResultException::class);
$hydrator->hydrateAll($stmt, $rsm);
}

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

return;
}
$stmt = ArrayResultFactory::createFromArray($resultSet);
$hydrator = new SingleScalarHydrator($this->entityManager);

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

0 comments on commit 40d1e7b

Please sign in to comment.