DI v komponentách (formuláře)

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

Zdravím,

chci se zeptat na optimální postup při předávání závislostí komponentám, které jsou vytvářené v továrničkách presenteru. Pochopil jsem, že nově se v presenterech používají inject metody. Dá se používat předávání závislostí definovaných v configu i komponentám?

Mám zhruba následující kód (zkráceno):

namespace MTS\App\AclModule\Presenters;

class AclPresenter extends BasePresenter
{
	private $aclFacade;

	public function injectAclFacade(\MTS\App\AclModule\Models\Facades\AclFacade $aclFacade)
	{
		if ($this->aclFacade !== NULL)
			throw new \Nette\InvalidStateException('AclFacade has already been set.');

		$this->aclFacade = $aclFacade;
	}

	protected function createComponentStoreAclRuleForm()
	{
		$form = new \MTS\App\AclModule\Components\Forms\AclRuleForm();

		$form->injectAclFacade($this->aclFacade);
		$form->create();

		$form->onSuccess[] = callback($form, 'storeAclRuleFormSubmitted');

		return $form;
	}
}
namespace MTS\App\AclModule\Components\Forms;

use Nette\Application\UI\Form;

class AclRuleForm extends \Nette\Application\UI\Form
{
	private $aclFacade;

	public function __construct(\Nette\ComponentModel\IContainer $parent = NULL, $name = NULL)
	{
		parent::__construct($parent, $name);
	}

	public function injectAclFacade(\MTS\App\AclModule\Models\Facades\AclFacade $aclFacade)
	{
		if ($this->aclFacade !== NULL)
			throw new \Nette\InvalidStateException('AclFacade has already been set.');

		$this->aclFacade = $aclFacade;
	}

	public function create()
	{
		if ($this->aclFacade === NULL)
			throw new \Nette\InvalidStateException('Dependencies have not been set.');

		$this->addMultiSelect('roles', 'Role', NULL, 15)
			->setRequired('Zvolte si role.');

		$this->addMultiSelect('resources', 'Zdroje', NULL, 15)
			->setRequired('Zvolte si zdroje.');

		$this->addMultiSelect('privileges', 'Privilegia', NULL, 15)
			->setRequired('Zvolte si privilegia.');

		$this->addRadioList('permission', 'Přístup', array(1 => 'Povolit', 0 => 'Zakázat'))
			->setRequired('Zvolte si typ pravidla.');

		$this->addProtection('Bezpečnostní token vypršel. Ujistěte se, že máte povolené cookies, a odešlete formulář znovu.');

		$this->addSubmit('send', 'Přidat');
	}

	public function storeAclRuleFormSubmitted(\Nette\Application\UI\Form $form)
	{
		if ($this->aclFacade === NULL)
			throw new \Nette\InvalidStateException('Dependencies have not been set.');

		$values = $form->getValues();

		$this->getPresenter()->flashMessage('Pravidla byla přidána.', 'success');
		$this->getPresenter()->redirect('default');
	}
}

Předpokládám, že použití new v továrničce nesplňuje jakési striktní DI, takže bych tuto třídu měl předpokládám také předat jako závislost do presenteru?

Nelíbí se mi v té továrničce ani použití mých metod inject a pak následně create. Jde o to, že v create nebo obsluze odeslaného formuláře se závislosti někdy využívají. V ukázce zrovna ne, ale chtěl bych to obecně. V konstruktoru to využívat nechci, jelikož bych mohl narazit na problém s děděním a parametry konstruktoru předka.

Lze nějak do této komponenty vkládat závislosti podobně jako do presenteru?

Také bych se chtěl zeptat, jak správně vyřešit redirect a flashMessage bez getPresenter. Někde jsem četl, že to není optimální řešení.

Za odpověď děkuji.

Editoval darthcz (3. 2. 2013 6:23)

Šaman
Člen | 2665
+
0
-

Takže postupně:

  • v Nette 2.0.x není možné používat injectXxx() metody mimo presentery. (Resp. nefunguje na nich autowire. Je nutné je volat ručně jako obyčejný setter, čímž tyto metody defacto jsou.) V dev verzi tato možnost je.
  • na povinné závislosti je vhodné používat konstruktor, na nepovinné setter (jen u presenterů je tolerované se konstruktoru vyhnout, protože presentery mají závislostí moc a zápis by byl nepřehledný). Tzn. že pokud pro formulář AclFacade není životně důležitá služba a formulář se zvládne zpracovat bez ní, používáš správně setter. Pokud bez ní form nemůže existovat, nech si ji předat hned v konstruktoru.
  • vytváření instance v továrničce pomocí new je podle mě naprosto v pořádku. DI ti injectuje hotové třídy a služby, které si máš nechat PŘEDAT a nelovit je svépomocí někde v kontextu nebo čertví kde. Továrna slouží k VYTVOŘENÍ nové instance třídy. Teoreticky můžeš mít tuto továrničku jako službu v configu, pak presenteru předáváš pomocí DI službu, která ti ale opět vytvoří form pomocí new.
  • flashMessage() je metoda presenteru a bez něho se zavolat nedá. Resp. dá se zavolat „malá“ flashMessage přímo nad komponentou (https://doc.nette.org/…n/components), pokud chceš ale volat flashMessage která by se vykreslila v pohledu presenteru, musíš ten presenter mít.

Editoval Šaman (3. 2. 2013 6:12)

enumag
Člen | 2118
+
0
-

Ještě bych upozornil, že inject* metody na komponentách nejsou automaticky volány ani v dev verzi.

vvoody
Člen | 910
+
0
-

Naimplementovať si inject autowire nad komponentami nieje až taký problém ak každú komponentu vytvárame továrnou triedou. Stačí opajcovať kúsok kódu z presenter factory :)

abstract class BaseFactory extends \Nette\Object
{
	/** @var \Nette\DI\Container */
	protected $container;

	public function __construct(\Nette\DI\Container $c)
	{
		$this->container = $c;
	}

	protected function inject($object)
	{
		if(is_object($object)) {
			foreach (array_reverse(get_class_methods($object)) as $method) {
				if (substr($method, 0, 6) === 'inject') {
					$this->container->callMethod(array($object, $method));
				}
			}
		}

		return $object;
	}
}
class LoginFormFactory extends BaseFactory
{
	public function create()
	{
		return $this->inject(new \App\Components\LoginForm);
	}
}
enumag
Člen | 2118
+
0
-

V Nette 2.1 stačí $container->callInjects($object);.

darthcz
Člen | 113
+
0
-

Díky moc za užitečné informace :-)