Řešení: Circular reference detected for services

quiced
Člen | 85
+
0
-

Zdravím,

vím, že ohledně této chyby je zde již několik vláken, ale zatím jsem z nich moc nevyčetl jak vyřešit můj případ. Abych nejdříve uvedl kontext většina funkcí aplikace je uložena v třídách na což mám složku /app/src/. Třídy z této složky přes konstruktor načítám do presenterů to je jedna úroveň. Ale protože občas potřebuji pracovat s funkcí z jiné třídy také si ji přes konstruktor načtu. Do dnešního dne mi vše fungovalo bez problému až dnes se mi objevila chyba:

Circular reference detected for services: application.3, reservation, fio.

Kterou konkrétně způsobuje tento kód:

<?php

namespace Fio;

use Apartment\Apartment;
use Reservation\Reservation;

class Fio
{

    private $apartment;
    private $reservation;

    public function __construct(Apartment $apartment, Reservation $reservation)
    {
        $this->apartment = $apartment;
        $this->reservation = $reservation;
    }

}

kdy je problém s tímto presenterem:

<?php

namespace AdminModule;

use Apartment\Apartment;
use Nette\Application\Responses\JsonResponse;
use Nette\Application\UI\Form;
use Nette\Utils\DateTime;
use Reservation\Reservation;
use Tracy\Debugger;

class ApartmentPresenter extends BasePresenter{

    private $apartment;
    private $reservation;

    public function __construct(Apartment $apartment, Reservation $reservation)
    {
        $this->apartment = $apartment;
        $this->reservation = $reservation;
    }

Pokud z jedné nebo druhé třídy odstraním v konstrukturu třídu Reservation vše začne fungovat. Co mi ale nejde do hlavy je fakt, že u třídy Apartment je to úplně jedno je jak v Reservation tak i ApartmentPresenter a chybu to nehází. Ty dvě třídy pro mě jsou úplně stejné ale vadí pouze jedna z nich a netuším proč.

Přemýšlel jsem i nad tím jak to celé udělat jinak ale aktuální stav mi přijde ideální. Potřebuji ve třídě Fio použít funkce z Reservation stejně tak jako v presenteru ApartmentPresenter potřebuji použít funkce z třídy Reservation.

Nevím jestli ze zaslaného kódu lze vůbec něco zjistit pokud by bylo třeba můžu zaslat více kódu nebo klidně i celý.

Díky za každou odpověď.

David Matějka
Moderator | 6445
+
0
-

co ma trida Reservation za zavislosti?

quiced
Člen | 85
+
0
-
<?php

namespace Reservation;

use Apartment\Apartment;
use Fio\Fio;
use GoSMS\GoSMSSender;
use Nette\Application\LinkGenerator;
use Nette\Database\Context;
use Nette\Mail\IMailer;
use Nette\Utils\DateTime;
use Nuki\Nuki;
use Setting\Setting;
use Setting\Status;
use Nette\Mail\Message;

class Reservation
{

    private $connection;
    private $apartment;
    private $status;
    private $setting;
    private $fio;
    private $goSMSSender;
    private $nuki;
    private $linkGenerator;
    private $mailer;

    public function __construct(Context $db, IMailer $mailer, Apartment $apartment, Status $status, Setting $setting, Fio $fio, GoSMSSender $goSMSSender, Nuki $nuki, LinkGenerator $linkGenerator)
    {
        $this->connection = $db;
        $this->apartment = $apartment;
        $this->status = $status;
        $this->setting = $setting;
        $this->fio = $fio;
        $this->gosmssender = $goSMSSender;
        $this->nuki = $nuki;
        $this->linkGenerator = $linkGenerator;
        $this->mailer = $mailer;
    }
}
David Matějka
Moderator | 6445
+
0
-

vyzadujes tam Fio a to zas vyzaduje Reservation

quiced
Člen | 85
+
0
-

Není nějaká šance, že by to takhle mohlo fungovat? Jsou určitě oblasti kdy Reservation potřebuji FIO např vytváří platbu. Nadruhou stranu FIO při aktualizaci stavu platby potřebuje Reservation protože tam je změna stavu rezervace.

CZechBoY
Člen | 3608
+
0
-

Tak muzes to obejit tim, ze místo konstruktoru pouzijes setter, ale musis na to davat bacha.

Ondřej Kubíček
Člen | 494
+
+4
-

spíš bych si udělal lepší návrh, třeba další třídu, která by dostala reservation a fio a v ní se prováděly ty společné úkony

quiced
Člen | 85
+
0
-

Asi nejjednodušší bude udělat další třídu. Ale stejně mi to přijde takové divné, že budu mít třídu která bude dělat mezičlánek takže místo toho abych udělal:

$this->fio->createPayment()

budu muset udělat:

$this->neco->createPayment();

class neco
{
	private $fio;

	public function __construct(Fio $fio)
	{
		$this->fio = $fio;
	}

	public function createPayment()
	{
		$this->fio->createPayment();
	}
}

Je to v podstatě jenom opsání kódu.

David Matějka
Moderator | 6445
+
+1
-

uz dle kontruktoru a poctu zavislosti tridy Reservation to vypada, ze toho dela nejak moc, takze drobny refaktoring by urcite neuskodil :)

muzes mi osvetlit, proc fio potrebuje reservation?

CZechBoY
Člen | 3608
+
0
-

Ono všechno dělá všechno, ne jen Reservation :-)

quiced
Člen | 85
+
0
-

Ve FIO je kontrola stavu platby, která potřebuje změnit stav rezervace pokud je zaplaceno. Tím, že funkce pro změnu stavu rezervace není jenom změna v DB ale třeba posílá mail a další věci chci to mít na jednom místě.

Reservation má 12 funkcí určitě by se to dalo rozdělit. Nadruhou stranu si říkám, na co mít třídu která bude mít 1,2 funkce? Není to zase jenom další zbytečný kód? Takhle je v podstatě vše co se stará o rezervace na 1 místě.

quiced
Člen | 85
+
0
-

CZechBoY napsal(a):

Ono všechno dělá všechno, ne jen Reservation :-)

Moc nevím jak to myslíš protože každá třída se stará o svoje. Občas k tomu, ale potřebuje i funkci z jiné třídy to je špatně?

Editoval quiced (29. 11. 2018 9:48)

quiced
Člen | 85
+
0
-

Každopádně díky za odpovědi jdu to přepsat :-)

duke
Člen | 650
+
+1
-

Neříkám, že je to vždycky nejčistší řešení, ale někdy se problém s cyklickou závislostí služeb dá obejít použitím tzv. accessorů, které poskytují předání dané služby až na požádání (líně).
Např. máš služby A a B a obě jsou na sobě navzájem závislé. Tento cyklus můžeš obejít tak, že u jedné z nich nahradíš závislost na té druhé závislostí na accessoru. Např. A bude vyžadovat B a B bude vyžadovat accessor služby A. Rozdíl je pak v tom, že ve službě B budeš muset na accessoru volat navíc metodu get():

	$this->aAccessor->get()->foo();

Nette umí tyto accessory generovat automaticky, takže jediné co potřebuješ je napsat si interface s jedinou metodou get() a zaregistrovat ho do configu podobně jako automaticky generovanou továrnu (jen u továren je místo metody get metoda create).