Problém při dědění presenterů – registrace služeb v config

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

Dobrý den,
při injektování jsem narazil na následující problém. Mám model Base, kde je připojení k databázi a pár SQL dotazů, které se mají dostat do společné šablony. Z tohoto moelu dědí modely SezonniAkce a Cenik. Pak mám BasePresenter, který injectuje služby z Base modelu. A teď chci dědit z BasePresenteru pro další Presentery (SezonniAkce a CenikPresenter), jenže při zaregistrování služeb v configu mi to hlásí výjimku: Nette\DI\MissingServiceException → Multiple services of type \App\Base found: 22_App_Cenik, 24_App_SezonniAkce.

A teď jedna zvláštnost: Když z configu odmažu registraci jedné ze služeb, tak to funguje, ale tím pádem nemohu používat daný presenter. Proč můžu z BasePresenteru dědit „pouze jednou“?

Base model:

abstract class Base extends Nette\Object
{

	/** @var Nette\Database\Connection */
	protected $connection;

	public function __construct(Nette\Database\Connection $connection)
	{
    $this->connection = $connection;
	}

  public function vratEmaily(){
    return $this->connection->query('SELECT kontakty.hodnota FROM kontakty NATURAL JOIN typy_kontaktu WHERE typy_kontaktu.nazev = "E-mail"');
  }

  public function vratTelefony(){
    return $this->connection->query('SELECT kontakty.hodnota FROM kontakty NATURAL JOIN typy_kontaktu WHERE typy_kontaktu.nazev = "Telefon"');
  }

  public function vratSouradnice(){
    return $this->connection->query('SELECT kontakty.hodnota FROM kontakty NATURAL JOIN typy_kontaktu WHERE typy_kontaktu.nazev = "GPS souřadnice"');
  }

}

BasePresenter:

abstract class BasePresenter extends Nette\Application\UI\Presenter
{
    /** @var \App\Base @inject */
    public $base;

    public function beforeRender()
    {
      $this->template->emaily = $this->base->vratEmaily()->fetchAll();
      $this->template->telefony = $this->base->vratTelefony()->fetchAll();
      $this->template->souradnice = $this->base->vratSouradnice()->fetchAll();
    }
}

SezonniAkcePresenter

final class SezonniAkcePresenter extends BasePresenter
{
   /** @var \App\SezonniAkce @inject */
    public $sezonniAkce;

    public function renderDefault()
    {
      $this->template->akce = $this->sezonniAkce->vratAkce();
    }
}

config.neon: Config.neon

matto
Člen | 55
+
0
-

Skus v configu pouzit "autowired: no" pre tie sluzby

Editoval matto (19. 2. 2014 9:22)

Vojtěch Dobeš
Gold Partner | 1316
+
0
-

Celá ta architektura je taková pofidérní. Nelze autowirovat abstraktní třídu, stejně jako ji nelze registrovat jako službu. Abstraktní třída má ze své podstaty více implementací, a tudíž logicky nedává smysl ji autowirovat – kterou implementaci?

Udělej si pro ty ošklivě pojmenované vrat* metody separátní třídy, a ty si naautowiruj. Tohle mi přijde jako typické zneužití dědičnosti oproti kompozici.

Jarek92
Člen | 91
+
0
-

vojtech.dobes: Abstraktní třídu nelze registrovat, to vím. Jenže i kdybych to přepsal tak jak říkáš, tak stejně budu potřebovat z nějaké třídy dědit.

matto: Zkouším to jak se dá, ale zatím na mě hází špatnou syntaxi configu. Nevíš jak přesně se to zapisuje?

Vojtěch Dobeš
Gold Partner | 1316
+
0
-

Klidně děď, ale ne ze špatných důvodů :). autowired: off nic nevyřeší, pak si ti zase nenainjectují ty reálně zaregistrované služby. Prostě bych pro každou z těch metod vytvořil samostatnou službu a tu injectnul.

On koneckonců i BasePresenter je prý antipattern :).

Jarek92
Člen | 91
+
0
-

Jasně máš pravdu, ale já potřebuju dostat ty výsledky ze třech metod do každého Presenteru který budu vytvářet, tzn. SezonniAkcePresenter, CenikPresenter… atd, protože ty věci se vykreslují v šabloně @layout.latte. Čili bych potřeboval nějak vysvětlit, jak udělat, abych měl dejme tomu Model, který bude vykreslovat základní věci a z něho budou dědit další modely a tím pádem i presentery pro vnitřek stránky (další layouty). Díky.

David Matějka
Moderator | 6445
+
0
-

do konkretniho presenteru si injectnes konkretni modelove tridy a base presenter si injectne vse, co potrebuje na vsech strankach – zadna dedicnost modelovych trid neni potreba.

Jarek92
Člen | 91
+
0
-

A připojení k databázi se pak řeší jak? V každém modelu?

MartinitCZ
Člen | 580
+
0
-

Viz můj přepis podpisu:
V presenterch zepomentě na __constructor(), ale používejte pouze anotace @inject nebo třídu inject<Name>(Name $a) {…}

Jarek92
Člen | 91
+
0
-

Ano, přesně o to se snažím. V presenterech nemám db připojení, to mám v Base modelu. Jenže pak mám model např. Cenik a ten opět potřebuje připojení k databázi. No a když nebude potomkem třídy Base, tak nezbývá nic jiného než ten konstruktor s vytvořením připojení zavolat znovu. Nicméně předpokládám, že existuje nohem lepší řešení.

Etch
Člen | 403
+
0
-

Jarek92 napsal(a):

A připojení k databázi se pak řeší jak? V každém modelu?

Třeba, ale tohle zrovna není ten problém. Podědit si nějaký BaseModel kvůli databázi je v podstatě v pořádku, ale ty si do toho abstraktního BaseModelu dáváš i věci, které tam nemají naprosto co dělat. K čemu by byla například metoda vratSouradnice() v nějakém ActionModelu??

Nic ti nebrání udělat si BaseModel

abstract class Base extends Nette\Object
{

    /** @var Nette\Database\Connection */
    protected $connection;

    public function __construct(Nette\Database\Connection $connection)
    {
 	   $this->connection = $connection;
    }
}

a pak jednotlivé služby:

class ContactModel extends Base
{

  public function vratEmaily(){
    return $this->connection->query('SELECT kontakty.hodnota FROM kontakty NATURAL JOIN typy_kontaktu WHERE typy_kontaktu.nazev = "E-mail"');
  }

  public function vratTelefony(){
    return $this->connection->query('SELECT kontakty.hodnota FROM kontakty NATURAL JOIN typy_kontaktu WHERE typy_kontaktu.nazev = "Telefon"');
  }

  public function vratSouradnice(){
    return $this->connection->query('SELECT kontakty.hodnota FROM kontakty NATURAL JOIN typy_kontaktu WHERE typy_kontaktu.nazev = "GPS souřadnice"');
  }

}

class ActionModel extends Base
{

  public function vratAkce(){
    return ...;
  }

}

a do presenteru si pak injectnout jen ty služby které potřebuješ. To o co se snažíš ty, v tom prvním příspěvku, je prostě znásilnění návrhu.

Editoval Etch (19. 2. 2014 19:56)

Jarek92
Člen | 91
+
0
-

Dobrý nápad, ale asi se nechápeme. To samozřejmě můžu udělat, taky sem tu udělal už, ale nezmizí tím problém. Když registruju služby v configu, tak nesmím zapisovat dvě a více služeb, které dědí ze stejné třídy. Takže stejně, pokud nebudu připojení k db vytvářet v každém modelu, tak musím dědit z Base modelu. Nebo existuje ještě jiné řešení?

David Matějka
Moderator | 6445
+
0
-

problem tim zmizi, nebudes vyzadovat tridu Base, ale ContactModel, ActionModel apod.

a pripojeni se nebude v kazdem modelu vytvaret, pouze ho vyzadas a do vsech modelu se preda stejna instance

Jarek92
Člen | 91
+
0
-

Nette\DI\MissingServiceException → Multiple services of type \App\Base found: 22_App_Cenik, 23_App_Contact, 25_App_SezonniAkce.

Výjimka při následujícím kódu:

Base:

<?php

namespace App;

use Nette;

class Base extends Nette\Object
{
	/** @var Nette\Database\Connection */
	protected $connection;

	public function __construct(Nette\Database\Connection $connection)
	{
    $this->connection = $connection;
	}
}

Contact:

<?php

namespace App;

use Nette;

class Contact extends Base
{
  public function vratEmaily(){
    return $this->connection->query('SELECT kontakty.hodnota FROM kontakty NATURAL JOIN typy_kontaktu WHERE typy_kontaktu.nazev = "E-mail"');
  }

  public function vratTelefony(){
    return $this->connection->query('SELECT kontakty.hodnota FROM kontakty NATURAL JOIN typy_kontaktu WHERE typy_kontaktu.nazev = "Telefon"');
  }

  public function vratSouradnice(){
    return $this->connection->query('SELECT kontakty.hodnota FROM kontakty NATURAL JOIN typy_kontaktu WHERE typy_kontaktu.nazev = "GPS souřadnice"');
  }

}

SezonniAkce:

<?php

namespace App;

use Nette;

class SezonniAkce extends Base
{
   public function vratAkce(){
    return $this->connection->query('SELECT obsah, datum, cas FROM sezonni_akce WHERE zobrazovat = true ORDER BY datum DESC, cas DESC');
  }
}
Jarek92
Člen | 91
+
0
-

Tak to se omlouvám, už jsem na to přišel, chyba byla v BasePresenteru, injektoval jsem tam Base model, i když jsem chtěl injektovat potomky.

Díky moc za rady a pomoc.