Reza Saadati
Reza Saadati

Reputation: 1274

Circular reference detected for service

I have a simple class which looks like this:

<?php
namespace App\Algorithm;

use App\Dao\MatchDao;
use App\Service\MatchService;

class Calculator {
    private $users;
    private $matchDao;

    function __construct(MatchService $matchService, MatchDao $matchDao) {
        $this->users = $matchService->users;
        $this->matchDao = $matchDao;
    }

    public function hourlyRate() {
        $query = $this->matchDao->getSingleColumn('Payment', 'hourly_rate', 32);
        var_dump($query);
    }
}

But I get the following error message:

Circular reference detected for service "App\Algorithm\Calculator", path: "App\Algorithm\Calculator -> App\Service\MatchService -> App\Algorithm\Calculator".

MatchService.php

<?php
namespace App\Service;

use App\Algorithm\Calculator;
use App\Algorithm\Collection;

class MatchService {
    public $users;
    private $collection;
    private $calculator;

    function __construct(Collection $collection, Calculator $calculator) {
        $this->collection = $collection;
        $this->calculator = $calculator;
    }

    public function getMatch($data) {
        $this->users = $this->collection->getAllUsers($data);
        $this->calculator->hourlyRate();
        return 1;
    }

}

The problem would be MatchService but what exactly am I doing wrong?

Upvotes: 7

Views: 13794

Answers (3)

Diego Favero
Diego Favero

Reputation: 2125

It is kind of obvious that you are injecting service A into Service B, and, also, Service B into Service A . Seems kind of not logical to do so, but sometimes is needed. In my case, I have two services :

_MySesion -> Which prototypes Symfony Session

_MyClient -> Responsible for identify the client and get its DB Credentials

I use the MySession to store those credentials, as so, it will be available to the whole system, but, to get those credentials using MyClient, I need some info stored into MySession .... See, two services that need each other to work ...

I start to see this same

Circular reference detected for service

just after upgrade to Symfony 5. And, sfy5 itself suggested the solution :

composer require symfony/proxy-manager-bridge

Remember that the services may be set with

lazy : true

More info on Symfony Docs

Upvotes: 3

Cerad
Cerad

Reputation: 48865

As several people have pointed out, the circular dependency comes from that fact that you are trying to inject the Calculator into MatchService and at the same time, injecting MatchService into the Calculator. No way to create one before creating the other.

Looking a bit more deeply, it appears that Calculator is using the MatchService to get list of users. As a second problem, Calculator is trying to get the users before MatchService has generated them.

Here is one possible refactoring:

class Calculator
{
    private $matchDao;

    public function __construct(MatchDao $matchDao)
    {
        $this->matchDao = $matchDao;
    }
    public function getHourlyRate($users) // Added argument
    {
        $query = $this->matchDao->getSingleColumn('Payment', 'hourly_rate', 32);
    }
}
class MatchService
{
    private $collection;
    private $calculator;

    public function __construct(Collection $collection, Calculator $calculator)
    {
        $this->calculator = $calculator;
        $this->collection = $collection;
    }
    public function getMatch($data)
    {
        $users = $this->collection->getAllUsers($data);
        $this->calculator->getHourlyRate($users);
    }
}

Removing MatchService from the Calculator's constructor solves the circular dependency problem. Passing $users to getHourlyRate solves the problem of trying to get users before they are available.

This is course is just one possible solution. It's not clear from your posted code if Calculator really needs $users or not.

Upvotes: 6

MylesK
MylesK

Reputation: 4389

This usually occurs when classes are dependency injecting each other, hence the circular reference.

Given you above example, your class MatchService injects Collection and Calculator. One of these (would assume calculator as collection is probably a doctrine class) dependency injects your MatchService.

Here is how I imagine your classes are supt:

class MatchService 
{
    public $users;
    private $collection;
    private $calculator;

    public function __construct(Collection $collection, Calculator $calculator) {
        $this->collection = $collection;
        $this->calculator = $calculator;
    }
}

class Calculator
{
    private $matchService;

    public function __construct(MatchService $matchService)
    {
        $this->matchService = $matchService;
    }
}

You have a couple of options:

  • More services with fewer dependencies
  • Using statics

It's hard for us to solve for you as it's dependent on how you architect your application.

Upvotes: 3

Related Questions