doctrine / orm

Doctrine Object Relational Mapper (ORM)
https://www.doctrine-project.org/projects/orm.html
MIT License
9.93k stars 2.52k forks source link

Querying DTOs with INDEX BY #9101

Open flaushi opened 3 years ago

flaushi commented 3 years ago

Bug Report

Q A
BC Break no
Version 2.8.4

Summary

Results from a DTO-query with INDEX BY are not correctly indexed. I am getting standard arrays instead.

Current behavior

if I query SELECT x.name FROM Entity x INDEX BY x.name the result is array like ['alice' => 'alice', 'bob' => 'bob', ...] however

`SELECT new DTO(x.name) FROM Entity x INDEX BY x.name` 

will return `[0 => Dto('alice'), 1 => DTO('bob'), ...]

Expected behavior

I would expect to get ['alice' =>DTO( 'alice'), 'bob' => DTO('bob'), ...]

In view that partial queries are deprecated and that DTOs should replace them, the index by feature might be important for all developers who used index-by together with partial objects until now and have to migrate. For partial objects, this worked fine.

nCrazed commented 2 years ago

Running into the same problem in version 2.7.3

greg0ire commented 2 years ago

Duplicate of https://github.com/doctrine/orm/issues/4415

greg0ire commented 2 years ago

We've been looking into this with @sir-kain , and it seems that ResultSetMapping::addIndexBy() does nothing, because it does not know how to map A.title to an SQL column. This foreach has nothing to loop on: https://github.com/doctrine/orm/blob/536b65f02b853cc9c8bfb184d23a4cc4feecf002/lib/Doctrine/ORM/Query/ResultSetMapping.php#L222

Not sure if we should have another lookup map, or if one of those should be modified, here maybe:

https://github.com/doctrine/orm/blob/536b65f02b853cc9c8bfb184d23a4cc4feecf002/lib/Doctrine/ORM/Query/SqlWalker.php#L1592-L1604

I tried and failed to do so by using addFieldResult, that resulted in an error during hydration later on: https://github.com/doctrine/orm/blob/536b65f02b853cc9c8bfb184d23a4cc4feecf002/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php#L320-L321

$rowData['data'] became not empty and the aliasMap was missing the required key for the code to continue, which it probably shouldn't.

@sir-kain has written the following test in case anyone wants to continue this:

<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket;

use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Id;
use Doctrine\Tests\OrmFunctionalTestCase;
use Exception;

use function var_dump;

/**
 * @group GH-9101
 */
class GH9101Test extends OrmFunctionalTestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        try {
            $this->_schemaTool->createSchema(
                [
                    $this->_em->getClassMetadata(GH9101Author::class),
                ]
            );

          // Create author 11/Joe
            $author        = new GH9101Author();
            $author->id    = 11;
            $author->name  = 'Joe';
            $author->title = 'Joe';
            $this->_em->persist($author);

          // Create author 12/Sir
            $author1        = new GH9101Author();
            $author1->id    = 12;
            $author1->name  = 'Sir';
            $author1->title = 'Sir';
            $this->_em->persist($author1);

            $this->_em->flush();
            $this->_em->clear();
        } catch (Exception $e) {
        }
    }

    public function testIndexByOk(): void
    {
        /* $dql     = "SELECT 'hello' FROM Doctrine\Tests\ORM\Functional\Ticket\GH9101Author A INDEX BY A.title "; */
        $dql2    = "SELECT new Doctrine\Tests\Models\CMS\CmsUserDTO(A.title)
      FROM Doctrine\Tests\ORM\Functional\Ticket\GH9101Author A INDEX BY A.title ";
        /* $result  = $this->_em->createQuery($dql)->getResult(); */
        $result2 = $this->_em->createQuery($dql2)->getResult();

        /* var_dump($result); */
        var_dump($result2);
      // $joe   = $this->_em->find(GH9101Author::class, 10);
      // $alice = $this->_em->find(GH9101Author::class, 11);

      // self::assertArrayHasKey('Joe', $result, "INDEX BY A.name should return an index by the name of 'Joe'.");
      // self::assertArrayHasKey('Alice', $result, "INDEX BY A.name should return an index by the name of 'Alice'.");
    }
}

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

  /**
   * @var string
   * @Column(type="string")
   */
    public $name;

  /**
   * @var string
   * @Column(type="string")
   */
    public $title;

    public function __construct()
    {
    }
}
Arkemlar commented 11 months ago

Bump it (version 2.15.3)