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

Handling embedded entity collections #23

Closed
npetrovski opened this Issue Feb 28, 2014 · 11 comments

Comments

Projects
None yet
4 participants
@npetrovski

npetrovski commented Feb 28, 2014

Recently I was playing with the lib and I faced a problem when an entity has an embedded collection. To be more specific I did play with Apigility and Doctrine, and the problem came when I received an entity with ManyToMany relation. I did a quick fix https://github.com/npetrovski/zf-hal/commit/d58145cb372e0c62dbcbcd3e9e448c07f7aa0a40 but I wondering if I miss something?

@weierophinney

This comment has been minimized.

Member

weierophinney commented Feb 28, 2014

Can you provide more details on the problem you were facing? Hard to evaluate if the code is correct, as I don't know exactly what it's trying to solve. (hint: write a unit test to demonstrate the issue; ping me or somebody else in #apigility-dev if you need assistance)

@npetrovski

This comment has been minimized.

npetrovski commented Feb 28, 2014

Okay. I will try to explain, when I have more time I will try to put some unit tests into account.

As I already mentioned I was playing with Apigility & Doctrine and more specific a project called Roll'n API (https://github.com/StukiOrg/RollnApi).

The problem came when I put a Doctrine entity with 2 ManyToOne relations and one ManyToMany. Both ManyToOne were properly fetched and returned into json response inside _embedded, but the one that is ManyToMany aint. First I thought that the problem is in Roll'n Api project (I did even post a bug into this project), but then after some investigation I found that Doctrine entities are properly fetched and passed to the Hal plugin. After more investigation I found that Doctrine Entity is converted into Hal entity, but the embedded collection inside this entity stays a Doctrine ArrayCollection and not a Hal collection, which causes a wrong json response for the entity containing a filed with an empty object instead of array of objects.

In the solution I proposed I am using a new function parameter called $renderEmbeddedCollections in order to prevent endless recursion caused by the Doctrine's entity nature.

@weierophinney

This comment has been minimized.

Member

weierophinney commented Feb 28, 2014

You might want to try https://github.com/soliantconsulting/zf-apigility-doctrine-server and https://github.com/soliantconsulting/zf-apigility-doctrine-admin -- I think these things are largely solved in those modules, and they will be ported to zfcampus officially in the not-too-distant future.

Let me know if they do not solve this issue, though!

@npetrovski

This comment has been minimized.

npetrovski commented Feb 28, 2014

Roll'N API is using both modules and they don't seem to modify the Doctrine Entity and/or Doctrine Collection before it is passed to Hal plugin.

Keeping Doctrine aside, what I am trying to figure out (and maybe solve) is the case when an entity contains a filed with value of traversable object, which should be translated to a Hal entity with Hal collection inside and currently it is not.

@npetrovski npetrovski closed this Mar 1, 2014

@weierophinney

This comment has been minimized.

Member

weierophinney commented Mar 4, 2014

@npetrovski Just curious why you closed this -- did you find a solution? If so, perhaps you can share, in case others come across this issue with similar problems?

@npetrovski

This comment has been minimized.

npetrovski commented Mar 4, 2014

In fact I am still working in order to find better solution on this problem and I guess you are right that the issue might be related to DoctrineModule and not HAL plugin.

The solution I suggested "works around" it, but doesn't solve the problem in the right manner.

What I tease now is the DoctrineModule\Stdlib\Hydrator\DoctrineObject and more specific the AbstractCollectionStrategy where my ManyToMany relation returns a Doctrine\ORM\PersistentCollection and I think it has not been extracted properly into array, which I believe would solve the problem.

Apparently I opened a bug in the wrong place that's why I closed it. I will proceed with the investigation.

Thank you for being responsive Matthew.

-Nick

@npetrovski

This comment has been minimized.

npetrovski commented Mar 4, 2014

Just to be clear enough about what is the problem, here is the "wrong" response I receive now:

{
    "id": 1,
    "sn": "CA9922AB",
    "total": 1,
    "fueltype": "Petrol",
    "available": true,
    "tsCreated": {
        "date": "2014-02-02 00:56:15",
        "timezone_type": 3,
        "timezone": "Europe/Sofia"
    },
    "feature": {},
    "_embedded": {
        "type": {
            "id": 3,
            "name": "Luxury",
            "_links": {
                "self": {
                    "href": "http://localhost:8000/api/types/3"
                }
            }
        },
        "vendor": {
            "id": 3,
            "name": "Opel",
            "_links": {
                "self": {
                    "href": "http://localhost:8000/api/vendors/3"
                }
            }
        }
    },
    "_links": {
        "self": {
            "href": "http://localhost:8000/api/cars/1"
        }
    }
}

and here is the changed response after my changes in HAL plugin:

{
    "id": 1,
    "sn": "CA9922AB",
    "total": 1,
    "fueltype": "Petrol",
    "available": true,
    "tsCreated": {
        "date": "2014-02-02 00:56:15",
        "timezone_type": 3,
        "timezone": "Europe/Sofia"
    },
    "_embedded": {
        "type": {
            "id": 3,
            "name": "Luxury",
            "_links": {
                "self": {
                    "href": "http://localhost:8000/api/types/3"
                }
            }
        },
        "vendor": {
            "id": 3,
            "name": "Opel",
            "_links": {
                "self": {
                    "href": "http://localhost:8000/api/vendors/3"
                }
            }
        },
        "feature": [
            {
                "id": 3,
                "name": "CD Player",
                "car": {},
                "_links": {
                    "self": {
                        "href": "http://localhost:8000/api/features/3"
                    }
                }
            },
            {
                "id": 9,
                "name": "5 Door",
                "car": {},
                "_links": {
                    "self": {
                        "href": "http://localhost:8000/api/features/9"
                    }
                }
            }
        ]
    },
    "_links": {
        "self": {
            "href": "http://localhost:8000/api/cars/1"
        }
    }
}

@npetrovski npetrovski reopened this Mar 4, 2014

@npetrovski npetrovski closed this Mar 4, 2014

@npetrovski

This comment has been minimized.

npetrovski commented Mar 6, 2014

Okay - I think the whole problem comes from the omitted metadata_map for the "Doctrine\ORM\PersistentCollection" key. I believe I fixed that issue by adding :

            'Doctrine\ORM\PersistentCollection' => array(
                'hydrator'        => 'ArraySerializable',
                'entity_identifier_name' => 'id',
                'route_identifier_name'      => 'id',
                'route_name'      => 'doctrine-orm-persistent-collection',
                'isCollection' => true,
            ),

btw. can I offer one small performance upgrade in ZF\Hal\Metadata\Metadata.php - I know it is small, but saves alot of CPU cycles:

@@ -117,10 +117,6 @@
      */
     public function __construct($class, array $options = array(), HydratorPluginManager $hydrators = null)
     {
-        $filter = new FilterChain();
-        $filter->attachByName('WordUnderscoreToCamelCase')
-               ->attachByName('StringToLower');
-
         if (!class_exists($class)) {
             throw new Exception\InvalidArgumentException(sprintf(
                 'Class provided to %s must exist; received "%s"',
@@ -137,7 +133,7 @@
         $legacyIdentifierName = false;

         foreach ($options as $key => $value) {
-            $filteredKey = $filter($key);
+            $filteredKey = strtolower(str_replace('_', '', $key));

             if ($filteredKey === 'class') {
                 continue;

@rrisse

This comment has been minimized.

rrisse commented Jan 14, 2015

Hi I have the same issue using Apigility and Doctrine but I did not understand your fix. Could please help me.

@rrisse

This comment has been minimized.

rrisse commented Jan 14, 2015

Hi
by modifying the Metadata_map I get
Maximum function nesting level of '100' reached, aborting!

@PowerKiKi

This comment has been minimized.

PowerKiKi commented Jul 22, 2015

@rrisse an alternative solution is to add hydrator strategy as described there: zfcampus/zf-apigility-doctrine#205 (comment)

I believe in both case you will have to add maxdepth key in zf-hall config, something like:

'zf-hal' => array(
    'metadata_map' => array(
        'Application\\Model\\User' => array(
            'maxdepth' => 2,
            // more things...
        ),
    ),
),
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment