zf-fr / zfr-rest

A module for Zend Framework 2 that aims to simplify RESTful
81 stars 31 forks source link

Recursive extraction #118

Closed danizord closed 9 years ago

danizord commented 10 years ago

What's the best way to support this? Let's create a RecursiveHydrator?

{
    "id": 1,
    "title": "ZfrRest is awesome",
    "author": {
        "id": 50,
        "name": "Michaël Gallego"
    }
}
Ocramius commented 10 years ago

@danizord I'd personally keep it out of ZfrRest - every time we try to implement that thing it bites us back (every. frikken. time.)

bakura10 commented 10 years ago

I already tried recursive hydrators for ZF3. I failed lamentably. REALLY hard thing.

danizord commented 10 years ago

Ok, so what's the best/easy workaround? A custom hardcoded hydrator for each entity? Meh!

Ocramius commented 10 years ago

Right now, yes. What ZfrRest can do is removing fields from submitted input data and from extracted data when building the output.

The "recursive hydrator" thing is something that I'd likely get from https://github.com/leedavis81/drest instead

fabiocarneiro commented 10 years ago

:+1:

bakura10 commented 10 years ago

Hi again,

@danizord , the only solution currently is to hardcode them. I really can't find of a clean way of automating this for various reasons:

Furthermore, the current Doctirne hydrator is WAY too complex. It really should be splitted in smaller, more specialized hydrator (but this will be hard until we get to ZF3 to have separate interfaces for hydrator and extractor... and find a good name for this new object that will combine "Hydrator" and "Extractor" :D).

Of course, we could go the complex way and using something like JMSSerializer, but I'm a bit afraid of the performance, and it means even more configuration, even more annotations... it will really lead to entities that are 90% comments and 10% code and I don't like this.

Ruby On Rails' Active model serializer uses a "Serializer" object for this. An example:

class PostSerializer < ActiveModel::Serializer
  attributes :title, :body
end

So what we could provide are sane defaults:

class MyHydrator
{
    protected $attributes = ['title', 'body'];
}

better than:

class MyHydrator
{
    public function extract($object)
    {
        return ['id' => $object->getId(), 'body' => $object->getBody()];
    }
}

Any better? I dunno.

Orkin commented 10 years ago

@bakura10 @danizord @Ocramius Hi, maybe a solution about to prevent circular dependency by creating a new annotation or params in association's annotation to specifying a level. User's hydrator trying to serialize tweet using TweetHydrator. User's tweet association has one level so TweetHydrator don't serialize user.

Sorry for my very bad english it's not easy to explain my idea :(

bakura10 commented 10 years ago

It does not work. For instance what if the tweet entity contains a user (that you don't want to serialize to avoid circular) but other associations that you indeed want to serialize ?

Envoyé de mon iPhone

Le 10 févr. 2014 à 13:38, Florent Blaison notifications@github.com a écrit :

@bakura10 @danizord @Ocramius Hi, maybe a solution about to prevent circular dependency by creating a new annotation or params in association's annotation to specifying a level. User's hydrator trying to serialize tweet using TweetHydrator. User's tweet association has one level so TweetHydrator don't serialize user.

Sorry for my very bad english it's not easy to explain my idea :(

— Reply to this email directly or view it on GitHub.

Orkin commented 10 years ago

You can specifying the level for each association. User => Tweet : one level but User => Role => Permission : two level

bakura10 commented 10 years ago

What if "Tweet" has another association "Favorited" that you want to serialize too ? ;)....

Orkin commented 10 years ago

Arf :(

primitive-type commented 10 years ago

@bakura10 @Ocramius What is a good way to use the ZF2 HydratorInterface in conjunction with DoctrineORMModule\Paginator\Adapter\DoctrinePaginator? In general, I've run into trouble hydrating / extracting from Doctrine collections using ZF2 hydrators.

I'm referring mostly to this code from the slides at http://marco-pivetta.com/doctrine-orm-zf2-tutorial/

use Doctrine\ORM\Tools\Pagination\Paginator as ORMPaginator;
use Zend\Paginator\Paginator;

// Create a Doctrine Collection
$query = $em->createQuery('SELECT f FROM Foo f JOIN f.bar b');

// Create the paginator itself
$paginator = new Paginator(
    new DoctrinePaginator(new ORMPaginator($query))
);

Is there an easy way I'm missing to use a custom hydrator when returning such a result?

bakura10 commented 10 years ago

Some thoughts: https://github.com/doctrine/DoctrineModule/issues/385

Using your example @danizord:

class TweetHydrator extends DoctrineComposableHydrator
{
    public function __construct()
    {
        $this->addStrategy('author', new SingleAssociationEmbeddedStrategy());
    }
}
bakura10 commented 10 years ago

In turn, author will use the DoctrineComposableHydrator when extracted. This has some sane defaults (output all fields, and each associations using id only). So if "author" has other properties you want recursively extract:

class TweetHydrator extends DoctrineComposableHydrator
{
    public function __construct()
    {
        $this->addStrategy('author', new SingleAssociationEmbeddedStrategy(
            new UserHydrator()
        ));
    }
}

class UserHydrator extends DoctrineComposableHydrator
{
    public function __construct()
    {
        $this->addFilter('password');
        $this->addStrategy('addresses', new CollectionAssociationEmbeddedStrategy());
    }
}
bakura10 commented 10 years ago

Is this enough or you were actually toward even more automatization? If so, what would be your ideal usage?

bakura10 commented 10 years ago

In the context of ZfrRest, this could also help us to add mapping information and build hydrators automatically:

/**
 * @REST\Resource(hydrator="DoctrineModule\Hydrator\DoctrineComposableHydrator")
 */
class Tweet
{
    /**
     * @REST\Association(extractionStrategy="EMBEDDED")
     */
    protected $author;
}

This will automatically create a DoctrineComposableHydrator for the tweet AND adding a strategy for the author using the hydrator bound in the resource of the User entity (associated entity). If you want a custom hydrator:

bakura10 commented 10 years ago

The more I think about it, the more I believe that we actually should separate hydration and extraction, as other framework do.

bakura10 commented 10 years ago

Regarding circular, I think we can use the following workaround. Imagine we extract User, that has an Embedded extraction for tweet, and tweet itself has an embedded for author => we hit circular dependency problem:

class UserHydrator
{
   static protected $circularChecker = [];

   public function extract($object)
   {
       if (self::$circularChecker[spl_object_hash($object)) {
           return $object->getId(); // return id for any association entity that may ask for the user
       }

       self::$circularChecker[spl_object_hash($object)] = true;

       /// when extracting the tweet association... its hydrator will also ask for extracting the user,
       // but to avoid circular dependency... use the id only!

       self::$circularChecker[spl_object_hash($object)] = false;
   }
}

Will result in:

[
    'id' => 5,
    'firstName' => 'Michaël',
    'tweets' => [
         [
              'id' => 5654,
              'content' => 'Foo',
              'author' => 5
         ]
    ]
]
primitive-type commented 10 years ago

Thanks, I was actually trying to use the standard Doctrine AbstractHydrator here: https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php

I like the direction you're heading with this though. My issue stemmed more from the fact that I didn't know how to set a ZF2 HydratorInterface object to be the hydrator for the standard Doctrine paginators (DoctrineORMModule\Paginator\Adapter\DoctrinePaginator).

bakura10 commented 9 years ago

No longer apply with #184. Rendering is now done using template, and ZfrRest will support render sub-resources. So each template is now only responsible to render its own part.