Best Practice: Mají mít komponenty parenta? Být připojeny k presenteru?

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

Z diskuze zde mi vyplynula jedna otázka: https://forum.nette.org/…at-formulare

Všiml jsem si (tu na foru, v docách), že strašmě mockrát se dělají komponenty bez parenta. Je to správně? Důsledkem totiž je, že komponenta není připojena k presenteru a to přináší různé problémy.

Mám zvyk, že všechny presentery v aplikaci dědí o jednoho BasePresenteru, všechny komponenty od BaseControlu a všechny formuláře dědí nebo jsou přímo instancí nějuakého mého App\Formu. Navíc důsledně všechny komponenty a formuláře připojují k presenteru.

Je to správně? Proč případně nění a jaký je best practise?

Zde je ukázka kódu jak ± vypadá můj BaseControl.

abstract class BaseControl extends Control {

    /** @var Translator  */
    public $translator;

    public function __construct(IContainer $parent = NULL, $name = NULL) {
        if (!$parent) {
            throw new \Exception("Component must have \$parent.", NULL);
        }
        if (!$parent instanceof \App\Controls\BaseControl AND !$parent instanceof BasePresenter) {
            throw new \Exception("\$parent must be instance of \App\Controls\BaseControl or \BasePresenter.");
        }
        $this->translator = $parent->translator;
        parent::__construct($parent, $name);
    }

    public function setLocalTemplateFile($name) {
        $this->template->setFile(dirname($this->reflection->getFileName()) . '/' . $name);
    }

    protected function registerHelper($helper, $template) {
        if (method_exists ($helper , 'injectTranslator')) {
            $helper->injectTranslator($this->translator);
        }
        $template->getLatte()->addFilter(NULL, array($helper, 'loader'));
    }

    public function createTemplate($class = NULL) {
        $template = parent::createTemplate($class);
        $template->setTranslator($this->translator);

        // HELPERS
        $this->registerHelper(new Helpers(), $template);
        $this->registerHelper(new \App\Helpers\Helpers(), $template);
        return $template;
    }

    /**
     * @param bool $need
     * @return \BasePresenter
     */
    public function getPresenter($need = TRUE) {
        return parent::getPresenter($need);
    }

    /**
     * NOTE: render() can't be declared here, breakes child controls with render with arguments
     */

}

Editoval Achse (16. 6. 2014 13:26)

mkoubik
Člen | 728
+
+3
-

Komponentu můžeš připojit k předkovi dvěma způsoby:

  1. Přímo: $parent->addComponent($component, 'nazev') nebo $parent['nazev'] = $component. Tohle dělá automaticky metoda getComponent(), takže pokud použiješ továrničku createComponentNazev() tak už nic předávat nemusíš. To je doporučený způsob.
  2. V konstruktoru (to je to co děláš ty). Tenhle způsob je v nette hlavně kvůli zpětné kompatibilitě se starým kódem.

Výhoda první varianty je, že máš volný konstruktor. Komponenta je ale funkční (připojená) až v meodě attached(), takže bys s ní v konstruktoru neměl dělat nic složitějšího, než nastavení závislostí.

MartinitCZ
Člen | 580
+
+1
-

Jak do komponent předáváš závislosti? Tímto řešením __constructor-u si to jen stížíš.
Osobně taky používám nějaký BaseControl, BasePresenter, BaseForm např. k nastavení vzhledu, jazyka translátoru …, ale __constructor používám jedině k předání vlastních závislostí.
Tzv. žádný __construct(IContainer $parent = NULL, $name = NULL) mě nezajímá (+ si myslím, že je to dokonce deprecated). Funguje to i bez nich :)

Snad ti to nějak pomůže.

Editoval martinit (16. 6. 2014 13:05)

Achse
Člen | 44
+
0
-

Jak do komponenty dostaneš translator aniž by jsi ho musel posílat do každé instance komponenty kontruktorem / nastavoval ho inject*?

mystik
Člen | 308
+
0
-

Achse: Já si o něj řeknu presenteru v attached(), ale nevím jestli je to úplně doporučované řešení. Další (čistší) možnost je si to tam předávat přes settery v továrničkách, ale to už znamená mít ručně psané továrničky.

Editoval mystik (16. 6. 2014 14:06)

Achse
Člen | 44
+
0
-

Výsledkem by byly všechny komponenty s ručně psanými továrničkami v neonu, kde by se autowiringem řešila DI. Ano to by asi bylo nejlepší. Ale na tohle sem strašně línej.

mystik
Člen | 308
+
0
-

Já taky :-) Proto to mám vyřešeno takhle:

<?php
  protected function attached($component) {
    parent::attached($component);
    if($component instanceof TranslatorAware) {
      $this->setTranslator($component->getTranslator());
    }
  }
?>

TranslatorAware je rozhraní definující jenom getTranslator() a mají ho všechny moje komponenty a presentery.

Quinix
Člen | 108
+
+3
-

Lepší je konstruktor komponenty použít pro předání závislostí z kontejneru. Komponenta je automaticky připojená k předkovi (a presenteru) pokud jí vrátíš z metody createComponentXXX(). Pokud teď v konstruktoru komponenty potřebuješ presenter, přesuň takový kód do metody attached.

Komponentu která má závislosti v konstruktoru si pak zaregistruješ v configu a injectneš do presenteru kde ji potřebuješ. Best practice je pak imho ještě použít generované továrničky.

Achse
Člen | 44
+
0
-

Díky, udělám to podobně. :)

Editoval Achse (17. 6. 2014 10:17)