Ocramius / GeneratedHydrator

:bullettrain_side: Fast Generated Object Hydrator for PHP
MIT License
726 stars 69 forks source link

Doctrine Hydrator implementation? #65

Closed podorozhny closed 7 years ago

podorozhny commented 7 years ago

Hello, @Ocramius.

I'm wondering what you think about implementing Doctrine GeneratedHydrator. I mean something like GeneratedHydrator extends \Doctrine\ORM\Internal\Hydration\AbstractHydrator.

What do you think, is this a good idea? Will it have any advantages over ObjectHydrator?

Or maybe someone has already implemented it? :)

My dream is to learn how to fetch & hydrate & serialize large collections of entities with doctrine for an acceptable time.

For example I have these entities:

class Post {
    /** @ORM\Id() */
    private $id;

    /** @ORM\Column(type="string") */
    private $name;

    /** @ORM\OneToMany(targetEntity="PostTag") */
    private $postTags;

    public function getTags(): Tag[]
    {
        return array_map(
            function (PostTag $postTag) {
                return $postTag->getTag();
            },
            $this->postTags->toArray()
        )
    }
}

class PostTag {
    /** @ORM\ManyToOne(targetEntity="Post") */
    private $post;

    /** @ORM\ManyToOne(targetEntity="Tag") */
    private $tag;
}

class Tag {
    /** @ORM\Id() */
    private $id;

    /** @ORM\Column(type="string") */
    private $name;

    /** @ORM\OneToMany(targetEntity="PostTag") */
    private $postTags;

    ...

    public function getNameProcessedWithSomeKindOfMagic(): string
    {
        return processWithSomeKindOfMagic($this->name);
    }
}

In my JSON API REST controller I want to do something like that:

class PostController {
    public function getAllAction(): Response
    {
        $queryBuilder = $this->postRepository->createQueryBuilder('post')
            ->orderBy('post.id');

        /** @var Post[] $posts */
        $posts = $queryBuilder->getQuery()->getResult();

        // Thx for 2-step hydration trick :)
        // http://ocramius.github.io/blog/doctrine-orm-optimization-hydration/
        $this->postRepository->createQueryBuilder('post')
            ->select('PARTIAL post.{id}')
            ->addSelect('postTag')
            ->addSelect('tag')
            ->leftJoin('post.postTags', 'postTag')
            ->leftJoin('postTag.tag', 'tag')
            ->getQuery()
            ->getResult();

        // In this place I use Symfony Serializer with serialization mappings for each entity
        // Normalized post will look like that example:
        // $post = [
        //     'id' => 1,
        //     'name' => 'Foo Bar',
        //     'tags' => [
        //         [
        //             'id' => 1,
        //             'name' => 'foo',
        //             'name_processed_with_some_kind_of_magic' => 'magic_foo'
        //         ],
        //         [
        //             'id' => 2,
        //             'name' => 'bar',
        //             'name_processed_with_some_kind_of_magic' => 'magic_bar'
        //         ]
        //     ],
        // ];
        $data = $this->serializer->serialize($posts);

        return new JsonResponse($data, 200, [], true);
    }
}

With few thousands posts in database this action will take SECONDS to generate JSON from database data and the only reason here will be complicated doctrine object hydration. Switching to ArrayHydrator will reduce it to a couple of hundreds milliseconds, but I can't use it because entities is not a simple DTOs (look at getTags and getNameProcessedWithSomeKindOfMagic method examples). Database data denormalization and ArrayHydrator usage is also a pretty good solution in big collections API, but I wonder if its not the only solution.

My apologies for the fact that this longread is not directly related to your library, this is a really big problem for me and I will be infinitely glad for your ideas on this issue.

Thanks!

Ocramius commented 7 years ago

Or maybe someone has already implemented it? :)

We started integrating this library with the ORM, but eventually gave up due to the complexity needed for the task. Will likely give it a stab if I have a couple of free months :-\

Meanwhile, closing here. The dependency is Doctrine implementing GeneratedHydrator, not the other way around.