Jak správně vyřešit problém s tzv. „circular reference“?

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
fary
Člen | 155
+
0
-

Ahoj, chtěl bych se zeptat, jak správně řešit problém, když máte svě service třídy a stane se vám, že první injektuje druhou a druhá injektuje první?

např.:

class A
{
    private $b;

    public function __construct(B $b)
    {
        $this->b = $b;
    }
}

class B
{
    private $a;

    public function __construct(A $a)
    {
        $this->a = $a;
    }
}
CZechBoY
Člen | 3608
+
0
-

Neinjectovat jednu druhou a druhou první…
Co konkrétně řešíš?
Případně si udělej factory, která ti tu třídu vytvoří.

$instance = $factory->create($this);

Editoval CZechBoY (12. 5. 2016 0:11)

mkoubik
Člen | 728
+
0
-

Ideálně cyklické závislosti nevytvářet. Jak to obejít v php najdeš tady, proč to nedělat najdeš tady.

GEpic
Člen | 566
+
0
-

fary napsal(a):

Ahoj, chtěl bych se zeptat, jak správně řešit problém, když máte svě service třídy a stane se vám, že první injektuje druhou a druhá injektuje první?

např.:

class A
{
    private $b;

    public function __construct(B $b)
    {
        $this->b = $b;
    }
}

class B
{
    private $a;

    public function __construct(A $a)
    {
        $this->a = $a;
    }
}

Proboha, proč a kde se tohle dá využít? :-O

Šaman
Člen | 2665
+
+2
-

Tak občas se na to narazí, i když to není přímo zamýšlený závrhový vzor. Typicky se to občas řešilo když se někdo pokoušel o chytřejí autentizaci, která potřebovala entitu uživatele. A Nette\User zase vyžaduje autentizátor. Předáš autentizátoru objekt User a bum, hlásí to kruhovou závislost.

fary
Člen | 155
+
0
-

Konkrétně mám v úmyslu vytvořit cache pro menu. Mám několik hlavních sekcí v tom menu (články, videa, obrázky…). Aplikace obsahuje tagy a tyto tagy se přidávají záznamům, které můžou být tedy článek, obrázek nebo viedo. Ke každému záznamu se přiřadí vždy jeden tag. Takže pak, když generuji to menu, potřebuju nejdříve získat seznam všech existujících tagů a poté iterovat napříč všemi články, videi a obrázky (vždycky zvlášť pro každou sekci) a zjistit, jestli se pro daný tag v dané sekci vyskytuje nějaký záznam. Pokud ano, tak ten tag do menu přidám.

Ten popis výše je spíše jen jako menší doplnění, ale jde o to, že mám třídu pro cache, která injektuje repozitáře, které obsahují články, obrázky a videa, abych mohl získat data z db a uložit do cache. Pak do každého z těchto repozitářů injektuji tuhle cache třídu a zde je ten problém. Injektuji ji tam kvůli tomu, aby se invalidovala konkrétní menu sekce v cache, pokud někdo v této sekci změní záznamy (např. přidá článek).

class RepoA
{
    private $menuCache;

    public function __construct(MenuCache $menuCache)
    {
        $this->menuCache = $menuCache;
    }

    public function create()
    {
        // smazat sekci pro 'sekci A' v menu cache
    }
}

class RepoB
{
    private $menuCache;

    public function __construct(MenuCache $menuCache)
    {
        $this->menuCache = $menuCache;
    }

    public function create()
    {
        // smazat sekci pro 'sekci B' v menu cache
    }
}

class RepoC
{
    private $menuCache;

    public function __construct(MenuCache $menuCache)
    {
        $this->menuCache = $menuCache;
    }

    public function create()
    {
        // smazat sekci pro 'sekci C' v menu cache
    }
}

class MenuCache
{
    private $a;
    private $b;
    private $c;

    public function __construct(RepoA $a, RepoB $b, RepoC $c)
    {
        $this->a = $a;
        $this->b = $b;
        $this->c = $c;
    }

    public function getAll()
    {
        // získat data z cache, pokud nejsou v cache, získat data z db a uložit do cache
    }
}
CZechBoY
Člen | 3608
+
0
-

Ja si cachuju vysledky v modelu v metodach samotnych. Je to dobry, protoze na venek nepoznas jestli to je z cache nebo cerstve nagenerovany.
Takze do modelu si injektuju tridu, ktera mi zajisti nejakou storage, namespace podle vnejsich podminek atd..

greeny
Člen | 405
+
0
-

Nebo si udělej MenuFactory, co si requirne RepoABC a Nette\Caching\IStorage a tam naimplementuj getAll :)

CZechBoY
Člen | 3608
+
0
-

To asi neni uplne optimalni. Melo by menu vedet o tom, ze nekdo zmenit napr nazev clanku? Ze nekdo ten clanek presunul do jiny kategorie?

mkoubik
Člen | 728
+
+1
-

Spíš bych vyhodil závislost na cache z těch repozitářů a invalidoval to událostí.

class RepoA
{
	public $onItemCreated = [];

	public function create()
	{
		// ...
		$this->onItemCreated($item);
	}
}

class MenuCache
{
	public function __construct(RepoA $repoA, RepoB $repoB, RepoC $repoC)
	{
		//
	}

	public function getAll()
	{
		// ...
	}

	public function invalidateItem($item)
	{
		// ...
	}
}

$repoA->onItemCreated[] = [$menuCache, 'invalidateItem'];
GEpic
Člen | 566
+
0
-

Není lepší udělat si nějaké BaseRepo s předáním FileStorage a poté upravovat v potomcích pouze namespace uložiště? Poté není potřeba vůbec Repozitáře předávat, ale stačí FileStorage a tahat si data z daných namespace

fary
Člen | 155
+
0
-

Díky za rady, nakonec jsem to vyřešil tak, že jsem si v MenuCache vytvořil pro repozitáře settery.

class RepoA
{
    private $menuCache;

    public function __construct(MenuCache $menuCache)
    {
        $this->menuCache = $menuCache->setRepoA($this);
    }

    public function create()
    {
        // smazat sekci pro 'sekci A' v menu cache
    }
}

class RepoB
{
    private $menuCache;

    public function __construct(MenuCache $menuCache)
    {
        $this->menuCache = $menuCache->setRepoB($this);
    }

    public function create()
    {
        // smazat sekci pro 'sekci B' v menu cache
    }
}

class RepoC
{
    private $menuCache;

    public function __construct(MenuCache $menuCache)
    {
        $this->menuCache = $menuCache->setRepoC($this);
    }

    public function create()
    {
        // smazat sekci pro 'sekci C' v menu cache
    }
}

class MenuCache
{
    private $a;
    private $b;
    private $c;

    public function setRepoA(RepoA $a)
    {
        $this->a = $a;
        return this;
    }

    public function setRepoB(RepoB $b)
    {
        $this->b = $b;
        return this;
    }

    public function setRepoC(RepoC $c)
    {
        $this->c = $c;
        return this;
    }

    public function getAll()
    {
        // získat data z cache, pokud nejsou v cache, získat data z db a uložit do cache
    }
}