3

I'm working on a reasonably large Laravel project and am using Repositories.

I have a user repository which injects its dependencies like so:

public function __construct(CartRepository $cartRepo...)

This causes the following error:

Maximum function nesting level of '100' reached, aborting!

I think this is because the CartRepo injects an ItemRepo which in turn injects the UserRepo, causing an infinite nesting loop.

What I don't get is how to find away around this, the ItemRepo needs the UserRepo as items are tied to a user?

Has anyone come across this before? If so how'd you get around it?

I know I can increase xdebug.max_nesting_level but even with a value of 750 it's still throwing an error, I'd also rather fix the underlying problem than just bury it.

Matthieu Napoli
  • 48,448
  • 45
  • 173
  • 261
Tom Green
  • 373
  • 4
  • 18
  • My `xdebug.max_nesting_level=999999`. It's ugly but works. – rernesto Nov 11 '13 at 14:58
  • Are you using App::bind or App::singleton to bind your dependencies to the container? – SixteenStudio Nov 11 '13 at 15:06
  • Neither, these are standalone repositories which are injected via the `__construct` methods – Tom Green Nov 11 '13 at 15:19
  • The issue seems to be that you are re-instantiating your repository classes every time a repository is injected, and the use of App::singleton to bind these to interfaces would be a solution, as this will make sure that it only calls your repository __construct methods once. App::singleton only instantiates your repository once and injects that same instance in every construct that requests it. – SixteenStudio Nov 11 '13 at 15:46

3 Answers3

5

You have a cycle in your dependency graph:

UserRepo -> CartRepo -> ItemRepo -> UserRepo -> …

You can't resolve that. It's an infinite loop, xdebug.max_nesting_level won't help you.

I'm just surprised that Laravel DI container doesn't throw an explicit exception.

You have to rethink your dependencies between services/repositories, maybe by splitting some classes into smaller, less coupled objects.


Update: Woops, I forgot about a couple of solutions!

  • Setter injection

Rather than injecting a dependency in the constructor, you can have it injected in a setter, which would be called after the object is constructed. In pseudo-code, that would look like that:

$userRepo = new UserRepository();
$cartRepo = new CartRepository($userRepo);
$userRepo->setCartRepo($userRepo);
  • Lazy injection

I don't know if Laravel does support lazy injection, but that's also a solution: the container will inject a proxy object instead of the actual dependency. That proxy-object will load the dependency only when it is accessed, thus removing the need to build the dependency when the constructor is called.

Matthieu Napoli
  • 48,448
  • 45
  • 173
  • 261
  • The problem with using Setters is that it (I think) makes testing quiet a bit more tricky. I can't see anything regarding lazy injection, but I think using App::singleton will do it. I've submitted [an issue to the Laravel Framework](https://github.com/laravel/framework/issues/2717) inquiring about having the DI container look for these loops. – Tom Green Nov 13 '13 at 11:09
  • @TomGreen Yes, I prefer constructor injection too, but in this case there are not a lot of solutions. Maybe try lazy injection if your DI container supports it. – Matthieu Napoli Nov 13 '13 at 11:10
0

For those who reach this answer and still are a little unsure of what to do like myself, I just wanted to share my solution. Matthieu's solution is correct but for a beginner like myself it still didn't give me a concrete answer to actually solving cyclical dependency. In the end I came to the conclusion that my classes were too large and that breaking them up into smaller classes even classes with just one method was the answer. For example if you have a User class that contains a login method and registration method and then a some other class, say a Social Class that uses the login method of the user class and for whatever reason the User class has a dependency on the Social class then my solution would be to move the login method into its own class which wouldn't have any dependencies. This way the Social class now uses the Login class, which doesn't have any dependencies. Overall I went from about 3 classes to 9 and this completely solved the issue for me. I think this type of thinking is intuitive for non-beginners but if you don't know what you don't know, it can be tough.

maplater
  • 171
  • 2
  • 12
-2

The cause of your error could be a bug on Laravel, but i'm currently working with symfony2, and symfony2 did the same thing (on entity classes for example) without problems. Whatever set your php.ini setting max_nesting_level (64 by default) to higher value, or if you're using xdebug check xdebug.max_nesting_level setting. Try the last suggestion first...

rernesto
  • 554
  • 4
  • 11