symfony-jsonapi icon indicating copy to clipboard operation
symfony-jsonapi copied to clipboard

Related Doctrine Entities fail with empty document?

Open drewclauson opened this issue 9 years ago • 16 comments

When I try to serialize a doctrine that has related entities, I get a blank 500 error and have to dig through my logs to figure out what happened.

It looks like it's boiling down to a nesting max depth error:

[Thu Nov 19 11:58:08.370490 2015] [:error] [pid 18288] [client 10.0.0.70:50204] PHP Fatal error:  Maximum function nesting level of '256' reached, aborting! in Unknown on line 0

Any ideas on serializing related Doctrine entities?

drewclauson avatar Nov 19 '15 17:11 drewclauson

Here's an additional snippet of the log output (gets really long otherwise)

[Thu Nov 19 12:03:45.622988 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP Fatal error:  Maximum function nesting level of '256' reached, aborting! in /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/Serializer.php on line 176
[Thu Nov 19 12:03:45.623039 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP Stack trace:
[Thu Nov 19 12:03:45.623046 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP   1. {main}() /var/www/WinStreamAPI/web/app_dev.php:0
[Thu Nov 19 12:03:45.623053 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP   2. Symfony\\Component\\HttpKernel\\Kernel->handle($request = *uninitialized*, $type = *uninitialized*, $catch = *uninitialized*) /var/www/WinStreamAPI/web/app_dev.php:33
[Thu Nov 19 12:03:45.623061 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP   3. Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle($request = *uninitialized*, $type = *uninitialized*, $catch = *uninitialized*) /var/www/WinStreamAPI/app/bootstrap.php.cache:2444
[Thu Nov 19 12:03:45.623067 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP   4. Symfony\\Component\\HttpKernel\\HttpKernel->handle($request = *uninitialized*, $type = *uninitialized*, $catch = *uninitialized*) /var/www/WinStreamAPI/app/bootstrap.php.cache:3222
[Thu Nov 19 12:03:45.623073 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP   5. Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw($request = *uninitialized*, $type = *uninitialized*) /var/www/WinStreamAPI/app/bootstrap.php.cache:3071
[Thu Nov 19 12:03:45.623079 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP   6. call_user_func_array:{/var/www/WinStreamAPI/app/bootstrap.php.cache:3109}(*uninitialized*, *uninitialized*) /var/www/WinStreamAPI/app/bootstrap.php.cache:3109
[Thu Nov 19 12:03:45.623084 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP   7. REST\\PlayBundle\\Controller\\PlayController->indexAction() /var/www/WinStreamAPI/app/bootstrap.php.cache:3109
[Thu Nov 19 12:03:45.623090 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP   8. NilPortugues\\Serializer\\Serializer->serialize($value = *uninitialized*) /var/www/WinStreamAPI/src/REST/PlayBundle/Controller/PlayController.php:39
[Thu Nov 19 12:03:45.623096 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP   9. NilPortugues\\Serializer\\Serializer->serializeData($value = *uninitialized*) /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/Serializer.php:117
[Thu Nov 19 12:03:45.623102 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP  10. NilPortugues\\Serializer\\Serializer->serializeArray($value = *uninitialized*) /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/Serializer.php:156
[Thu Nov 19 12:03:45.623107 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP  11. NilPortugues\\Serializer\\Serializer->serializeData($value = *uninitialized*) /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/Serializer.php:420
[Thu Nov 19 12:03:45.623113 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP  12. NilPortugues\\Serializer\\DeepCopySerializer->serializeObject($value = *uninitialized*) /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/Serializer.php:150
[Thu Nov 19 12:03:45.623119 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP  13. NilPortugues\\Serializer\\Serializer->serializeInternalClass($value = *uninitialized*, $className = *uninitialized*, $ref = *uninitialized*) /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/DeepCopySerializer.php:34
[Thu Nov 19 12:03:45.623125 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP  14. array_map(*uninitialized*, *uninitialized*) /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/Serializer.php:458
[Thu Nov 19 12:03:45.623130 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP  15. NilPortugues\\Serializer\\Serializer->serializeData($value = *uninitialized*) /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/Serializer.php:458
[Thu Nov 19 12:03:45.623145 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP  16. NilPortugues\\Serializer\\DeepCopySerializer->serializeObject($value = *uninitialized*) /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/Serializer.php:150
[Thu Nov 19 12:03:45.623151 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP  17. NilPortugues\\Serializer\\Serializer->serializeInternalClass($value = *uninitialized*, $className = *uninitialized*, $ref = *uninitialized*) /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/DeepCopySerializer.php:34
[Thu Nov 19 12:03:45.623156 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP  18. array_map(*uninitialized*, *uninitialized*) /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/Serializer.php:458
[Thu Nov 19 12:03:45.623162 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP  19. NilPortugues\\Serializer\\Serializer->serializeData($value = *uninitialized*) /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/Serializer.php:458
[Thu Nov 19 12:03:45.623179 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP  20. NilPortugues\\Serializer\\DeepCopySerializer->serializeObject($value = *uninitialized*) /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/Serializer.php:150
[Thu Nov 19 12:03:45.623185 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP  21. NilPortugues\\Serializer\\Serializer->serializeInternalClass($value = *uninitialized*, $className = *uninitialized*, $ref = *uninitialized*) /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/DeepCopySerializer.php:34
[Thu Nov 19 12:03:45.623190 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP  22. array_map(*uninitialized*, *uninitialized*) /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/Serializer.php:458
[Thu Nov 19 12:03:45.623195 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP  23. NilPortugues\\Serializer\\Serializer->serializeData($value = *uninitialized*) /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/Serializer.php:458
[Thu Nov 19 12:03:45.623201 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP  24. NilPortugues\\Serializer\\DeepCopySerializer->serializeObject($value = *uninitialized*) /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/Serializer.php:150
[Thu Nov 19 12:03:45.623207 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP  25. NilPortugues\\Serializer\\Serializer->serializeInternalClass($value = *uninitialized*, $className = *uninitialized*, $ref = *uninitialized*) /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/DeepCopySerializer.php:34
[Thu Nov 19 12:03:45.623212 2015] [:error] [pid 20740] [client 10.0.0.70:50764] PHP  26. array_map(*uninitialized*, *uninitialized*) /var/www/WinStreamAPI/vendor/nilportugues/serializer/src/Serializer.php:458

drewclauson avatar Nov 19 '15 17:11 drewclauson

This is a problem for Doctrine. It's design is flawed as it has circular references so it cannot be directly mapped. It will just blow up as you experienced.

I tried solving this issue, but it requires setting a magic nesting number that will vary from case to case, so I decided not to include this solution until I find a way of fixing this.

What I can recommend is creating objects to represent the resource that do not include so many references.

For instance for an object having a reference to a first entity that has another reference, try redefing the "deepest entity" by removing any relationship to its parents or further relationships to other entities.

nilportugues avatar Nov 19 '15 22:11 nilportugues

If you or anyone reading this finds a solution, please post here

nilportugues avatar Nov 19 '15 22:11 nilportugues

This is a known issue: https://github.com/symfony/symfony/issues/15627

And Dunglas tried patching it https://github.com/symfony/serializer/commit/aae6fa7c16deea81e7f14877841802bbdeecbe33. Still doesn't fix the issue.

I've got an idea on how to solve this, but I need some time to code it.

nilportugues avatar Feb 09 '16 16:02 nilportugues

Maybe can you try this trick to avoid circular references with Doctrine and display the entity's id when it is detected more than once: https://github.com/dunglas/DunglasApiBundle/blob/master/src/JsonLd/Serializer/ItemNormalizer.php#L63-L65

        $this->setCircularReferenceHandler(function ($object) {
            return $object->getId(); // It's better to use Doctrien metadata here to guess the id
        });

We use it in API Platform and it works fine.

dunglas avatar Feb 21 '16 16:02 dunglas

@dunglas yeah I remember i send a tweet about it some months ago... just have to port it to my serializer, it's not JMS or Symfony's.

nilportugues avatar Feb 21 '16 22:02 nilportugues

Any news on this front? Or any orkarounds to make actual practical use of this? Because I really want to implement this

AlexandreKilian avatar Apr 02 '16 17:04 AlexandreKilian

@AlexandreKilian for the time being, breaking the cyclic dependency is the only way around, and that can be done by transforming all doctrine entities to objects of your own...

I would need a day or two to dig into this properly, but this is a side project for me.

I'm not writing APIs right now on my daily basis so I cannot spend as much time as I'd like to fix this.

nilportugues avatar Apr 04 '16 14:04 nilportugues

@nilportugues Sorry, that came accross the wrong way... I might have an idea of how to easily work around this issue with a custom repository class, but I'm still working on a few kinks

AlexandreKilian avatar Apr 06 '16 14:04 AlexandreKilian

@AlexandreKilian I'm actually trying to find myself some time to detect the cyclic dependencies and do something about it too.

I'll keep you posted.

nilportugues avatar Apr 06 '16 14:04 nilportugues

@nilportugues Hi while waiting your fixing, i delete manually the closure from the clone of my entity Example : unset($cloneEntity->getContract()->initializer);

Another solution: I tried to add the fetch mode "EAGER" on my entities to avoid the "Proxy class" which add the Closures, but i get a 500 error response from your bundle (with a blank page)

Thank you !

dkwavetech avatar Apr 13 '16 10:04 dkwavetech

@nilportugues Has anyone got a temporary fix of some way to work around this issue expect creating additional object?

svparijs avatar Jul 08 '16 19:07 svparijs

@svparijs you can create your DTOs and link them together manually and serialize that and the problem is gone.

Actually it's a good practise to do so.

nilportugues avatar Jul 08 '16 20:07 nilportugues

@nilportugues it feels like a extra level of complexity and maintenance cost to add DTO's manually wiring them together. To make this package properly usable for Symfony/Doctrine this feature should be supported in my opinion. That being said I've been digging around in the code and it seems not to be easily implemented.

I really would like this to work and would even be willing to sponsor a bit of the work.

svparijs avatar Jul 08 '16 22:07 svparijs

Can anybody post an example or gist of a hack on how to get around this? Would be greatly appreciated :)

The-Don-Himself avatar Oct 15 '16 23:10 The-Don-Himself

maybe using JsonSerializable in your entities you can implements JsonSerializable and define a public function jsonSerialize() that return the entity without the extra relashionship that cause the loop.

something like this gist

c0urg3tt3 avatar Aug 25 '17 12:08 c0urg3tt3