Asi nechápu flow, basePresenter vs. other Presenters

MW
Člen | 626
+
0
-

Zdravím a prosím o pomoc.

Presenter pro načítání dat „nastavení“ kde data v pohodě načtu (SettingsModel $settingsModel):

Final class SettingsPresenter extends BasePresenter {

    /** @persistent */
    public $backlink = '';
    private Forms\SettingsFormFactory **$settingsFactory**;
    private $settingsModel;

    public function __construct(Forms\SettingsFormFactory $settingsFormFactory, SettingsModel $settingsModel) {
	$this->settingsFactory = $settingsFormFactory;
	$this->settingsModel = $settingsModel;
    }

    public function renderDefault(): void {

	$this->template->title = "Nastavení";
    }

    /**
     * Settings form factory.
     */
    public function createComponentSettingsForm(): Form {

	$form = $this->settingsFactory->create();
	$form->setDefaults($this->settingsModel->getSettingsTable()->fetch());

	$form->onSuccess[] = function ($form) {

	$this->settingsModel->update($form->values);

	$this->flashMessage("Nastavení uloženo.");
	$this->redirect('Settings:');

	};
	return $form;
    }
}php

rád bych toto delegoval do BasePresenteru, kde jsem se o to pokusil:

/**
 * Base presenter for all application presenters.
 */
abstract class BasePresenter extends Nette\Application\UI\Presenter {

    private $settingsModel;
    private $settings;

    public function __construct(SettingsModel $settingsModel) {

	$this->settingsModel = $settingsModel;
	$this->settings = $settingsModel->getSettingsTable()->fetch();
    }

    protected function startup() {
	parent::startup();

	if (!$this->getUser()->isLoggedIn()) {
	    $this->flashMessage('Přihlašte se prosím');
	    $this->redirect('Sign:in');
	}

	If (($this->getUser()->isLoggedIn()) AND (!$this->getUser()->isInRole('admin') AND !$this->getUser()->isInRole('user') AND !$this->getUser()->isInRole('person'))) {
	    $this->flashMessage("Váš účet ještě nebyl aktivován", 'danger');
	    $this->redirect('Sign:in');
	}
    }
}

Problém je, že v Base presenteru mám pořád NULL.. a to už od „settingsModel“ a to nechápu proč..

Netuší prosím někdo, co bych mohl dělat blbě?

Editoval MW (2. 7. 23:24)

Marek Bartoš
Nette Blogger | 1260
+
+2
-

Přetěžuješ konstruktor, ale konstruktor rodiče v tom přetíženém nevoláš.

Editoval Marek Bartoš (2. 7. 23:34)

m.brecher
Generous Backer | 863
+
+5
-

@MW

abstract class BasePresenter extends Nette\Application\UI\Presenter {

    protected $settingsModel;

    protected $settings;

    public function injectSettingsModel(SettingsModel $settingsModel)
    {
        $this->settingsModel = $settingsModel;
    }

    protected function startup(): void
    {
        parent::startup();
        $this->settings = $this->settingsModel->getSettingsTable()->fetch();

       // .......

    }
}
  • v abstraktních presenterech ze kterých dědí finální presentery nikdy nepoužívej konstruktor, místo něj předávej služby metodou inject<*>()
  • naopak ve finálních presenterech nepoužívej inject<*>(), ale konstruktor, tím předejdeš problémům jak píše @MarekBartoš
  • v metodě injectSettingsModel() bych pouze předal službu a dotaz do databáze bych dal raději do metody startup() – je to přehlednější (ale ne nutné)
  • property $settings i $settingsModel se budou používat ve finálních presenterech a musí být tedy protected
MW
Člen | 626
+
0
-

Díky moc všem!

Chápu tedy, že inject*<> zajistí předání všem potomkům. Je to tak?

Mimochodem v tomto případě mě nepomohlo volání

parent::__construct();

což mě nebylo moc jasné, že se nepředalo. Bylo to tím, že byl kontruktor v abstrakní třídě?

Ještě jednou děkuji!!

Marek Bartoš
Nette Blogger | 1260
+
+3
-

Jak nepomohlo? Pokaždé když přetížíš metodu, tak musíš zavolat implementaci z rodiče, jinak se nevykonaná. Volání parent::__construct() v SettingsPresenter zavolá konstruktor z BasePresenter a v BasePresenter volání parent::__construct() zavolá konstruktor z Nette\Application\UI\Presenter

Šaman
Člen | 2658
+
+4
-

Musel bys to volat takhle. Potomek zavolá metodu předka se všemi parametry, které předek potřebuje. A to je přesně důvod, proč to dělat nechceš (googli constructor hell).

abstract class BasePresenter extends Nette\Application\UI\Presenter {

    private $settingsModel;
    private $settings;

    public function __construct(SettingsModel $settingsModel) {
		$this->settingsModel = $settingsModel;
		$this->settings = $settingsModel->getSettingsTable()->fetch();
    }
}

final class SettingsPresenter extends BasePresenter {

    public function __construct(SettingsModel $settingsModel, Forms\SettingsFormFactory $settingsFormFactory) {
		parent::__construct($settingsModel);

		$this->settingsFactory = $settingsFormFactory;
    }
}

Editoval Šaman (3. 7. 19:40)

MW
Člen | 626
+
0
-

constructor hell, to je ono.

Díky za vysvětlení!

m.brecher
Generous Backer | 863
+
+2
-

@MW

Chápu tedy, že inject*<> zajistí předání všem potomkům. Je to tak?

Metodu inject<*>() spouští Nette Framework na objektech, které jsou „služby“ a to takto:

  • v presenterech automaticky
  • v jiných třídách automaticky ne a je potřeba režim inject zapnout v konfiguraci
  • metoda inject<*>() musí být public

Nette Framework předá do parametrů uvedených v metodě inject<*>() služby podle typehintu, nic víc nedělá – tzv. injektování.

Pokud chceš mít předanou službu k dispozici v jiných metodách presenteru, musíš sám ručně injektovanou službu uložit do property. Potom je k dispozici ve všech potomcích abstraktního presenteru, včetně abstraktního presenteru.

Marek Bartoš
Nette Blogger | 1260
+
+5
-

Mimo presentery není dobré inject zapínat. Jen tím pak skrýváš, že má třída hodně závislostí a dělá toho moc

MW
Člen | 626
+
0
-

m.brecher napsal(a):

  • v jiných třídách automaticky ne a je potřeba režim inject zapnout v konfiguraci

čistě ze zvědavosti, o jaké konfiguraci hovoříš?

Díky!

m.brecher
Generous Backer | 863
+
+1
-

@MW

konfigurace služeb DI containeru v souboru services.neon:

decorator:
	MyForms\FormControl:
		inject: true

Používám pro všechny formuláře abstraktního předka FormControl, ze kterého dědí finální formuláře třeba UserForm. Formuláře mám udělané jako potomek Form z nette zabalený do potomka FormControl, který dědí z Nette UI\Control.

Finální formulář konstruktorem přebírá modelovou třídu – stejně jako u presenterů. Do předka FormControl ale potřebuji předat službu FormFactory, která dodá každé instanci finálního formuláře unikátní instanci Form. Na to používám metodu InjectFormFactory() (vyhnu se constructor hellu) a protože FormControl není presenter, musím si režim inject ručně zapnout.

FormControl ale nedodává do finálního formuláře holý Form, nýbrž ho určitým způsobem upraví. Nejedná se tedy o zamlžování závislostí třídy o níž hovoří @MarekBartoš, ale použití inject je v tomto případě OK.

V příkladu konfigurace je použit decorator, který provede operace na všech potomcích uvedené třídy/interface.

Používej inject jenom v presenterech a mimo presentery předávej závislosti konstruktorem – to je ideální pravidlo pro začátek.