symfony-jsonapi
symfony-jsonapi copied to clipboard
Related Doctrine Entities fail with empty document?
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?
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
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.
If you or anyone reading this finds a solution, please post here
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.
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 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.
Any news on this front? Or any orkarounds to make actual practical use of this? Because I really want to implement this
@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 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 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 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 !
@nilportugues Has anyone got a temporary fix of some way to work around this issue expect creating additional object?
@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 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.
Can anybody post an example or gist of a hack on how to get around this? Would be greatly appreciated :)
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