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 !!
Can you post some example code?
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… ). 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);
};
}
}
1 Like
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.
3 Likes
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
2 Likes
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 !!
Hi, yes I totally agree with you. Especially the mis-using
part of your comment.