Handling Circular/Cyclic dependency ZF3 services


#1

I recently ran into circular dependency with 2 of my services, basically they are referencing each other. Since I have dependency injection in service factories, it ends up in a loop.

I would want to discuss this topic, and share experiences.

Thank you,
Have a great day !!


#2

Can you post some example code?


#3

Hey so also had to deal with circular dependency in one of the project I was working on. I ended up lazy loading the dependency I needed. Not something I’m proud of, because it felt like a cheat (and many more… :see_no_evil:). It went like this:

    <?php

    class MyClass 
    {
    	/**
    	 *
    	 * @var ServiceInterface $service
    	 */
    	private $service;

    	/**
     	 * @return ServiceInterface
     	 */
    	public function getService(): ServiceInterface
    	{
    	    if ($this->service instanceof \Closure) {
    	        $this->service = call_user_func($this->service, $this);
    	    }
    	    return $this->service;
    	}

    	/**
    	 * @param ServiceInterface|\Closure $service
    	 * @return self
    	 */
    	public function setService(ServiceInterface $service): self
    	{
    	    $this->service = $service;
    	    return $this;
    	}
    }

And the factory would look like this:

    <?php

    class MyClassFactory implements FactoryInterface
    {
    	public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    	{
        	$myClass = new MyClass();
        	
        	$myClass->setService($this->lazyInjectService($container));

        	return $myClass;
    	}

        /**
         * @param $container
         * @return \Closure
         */
        protected function lazyInjectService($container): \Closure
        {
            return function () use ($container) {
                return $container->get(Service::class);
            };
        }

    }

#4

There’s a better way to lazy-load stuff, but in general (and also to avoid people mis-using it), circular dependencies should be avoided. If there’s common behavior that you want to share, create another interactor that contains the minimum common denominator functionality.


#5

What I tended to do in cases like this… besides the overall architecture problem that led to the circular dependency…
I create something I call “repository”…
In other words I normally went for “Controller -> Service -> Model”
IF I run into a problem where I need more than 1 one service, I went for “Controller -> Repository -> Model” or “Controller -> Repository -> Service -> Model”
What it means, instead of creating a circular dependency, rethink what you really need for an action to happen.
In your case you might need 2 (or more) totally different things to happen in sequence. That’s where I would got the “repository” route, where you inject (factory) all the services you need and then go from there.
Example:

class doSomethingRepository
{
    public function __constructor(ServiceA $serviceA, ServiceB $serviceB, ...)
{
    /* ... */
}

Just my 2 cents

#6

To solve the circular dependency issue, I used the zf3 LazyServiceFactory along with ocramius/proxy-manager module.

// module.config.php
        'lazy_services' => [
                'class_map' => [
                    Service\UserService::class => Service\UserService::class,
                ],
            ],
            'delegators' => [
                Service\UserService::class => [
                    LazyServiceFactory::class,
                ],
            ],

That’s it, no other changes need, it worked.

Although I am still skeptical about its performance, is there any benchmarking done, not sure.
Please share your views

Thanks !!


#7

Hi, yes I totally agree with you. Especially the mis-using part of your comment.