Fomulář ve factory – uložený záznam v presenteru

motorcb
Člen | 551
+
0
-

Zdravím,

mám fomuláře ve faktory, který uloží záznam do DB. To je OK:

	public function create(callable $onSuccess): Form
	{
		$form = $this->factory->create();
		$form->addText('text', 'Text');
		$form->addSubmit('submit', 'Uložit');
		$form->onSuccess[] = function (Form $form, \stdClass $values) use ($onSuccess): void {
			$textId = $this->repository->insert($values->text);
			$onSuccess();
		};
		return $form;
	}

Jenže já bych potřeboval po uložení přesměrovat v presenteru na detail nově vytvořeného záznamu:

	protected function createComponentSignInForm(): Form
	{
		return $this->textFormFactory->create(function (): void {
           $this->redirect('Text:detail');//@TODO jak tady zjistim ID vytvoreneho zaznamu? $textId z metody create ve factory?
		});
	}

Jak zjistím v presenteru ID vytvořeného záznamu abych přesměroval na detail nově vytvořeného záznamu? Díky

Kamil Valenta
Člen | 752
+
+4
-
public function create(callable $onSuccess): Form
	{
		$form = $this->factory->create();
		$form->addText('text', 'Text');
		$form->addSubmit('submit', 'Uložit');
		$form->onSuccess[] = function (Form $form, \stdClass $values) use ($onSuccess): void {
			$textId = $this->repository->insert($values->text);
			$onSuccess($textId);
		};
		return $form;
	}
protected function createComponentSignInForm(): Form
	{
		return $this->textFormFactory->create(function ($textId): void {
			dump($textId);
           $this->redirect('Text:detail');//@TODO jak tady zjistim ID vytvoreneho zaznamu? $textId z metody create ve factory?
		});
	}

Za předpokladu, že $this->repository->insert() vrací nově vytvořené id.

m.brecher
Generous Backer | 717
+
-3
-

@motorcb

Ahoj, zrovna i já teď zkouším factory pro výrobu nette formulářů. Před pár dny mě @jiripudil, doporučil místo klasické factory, která vrací „holý“ Form z Nette použít „magickou“ factory ve formě „form factory interface“, která vrací Nette\Application\UI\Form obalený komponentou, která dědí z Nette\Application\UI\Control.

Po důkladném vyzkoušení tuto techniku VELMI doporučuji – nejlepší způsob jak s formuláři Nette inteligentně pracovat. Kdyby se to mělo použít ve Tvém příkladu, tak by to vypadalo nějak takto:

1. MyForm jako komponenta presenteru Form je schován uvnitř

use Nette\Application\UI\Control;
use Nette\Application\UI\Form;
use Nette\Utils\ArrayHash;

class MyForm extends Control   			// MyForm je komponenta, nikoliv služba DI
{
    public function __construct(
		private ParamType $param,        	// parametr předaný z presenteru
        private Repository $repository,  	// registrovat Repository jako službu DI
    )
    {}

    public function createComponentBaseForm(): Form  // Control obsahuje Nette\Application\UI\Form
    {
        $form = new Form;

        $form->addText('text', 'Text');

        $form->addSubmit('submit', 'Uložit');

        $form->onSuccess[] = function(ArrayHash $values) {   // ArrayHash nebo \stdClass
            $id = $this->repository->insert($values->text);
            $this->presenter->redirect('Text:detail', ['id' => $id]);   // Control umí presenter
        };

        return $form;
    }

    public function render(): void   // nastavení vykreslení komponenty
    {
		$this['baseForm']->render();							// 1. varianta DefaultRenderer Nette
    	$this->template->render('..../myFormTemplate.latte');	// 2. varianta s vlastní šablonou

    }
}

2. Factory která vytvoří MyForm, s využitím magie Nette přes interface factory

interface MyFormFactory  				// registrovat MyFormFactory jako službu DI
{
    public function create(ParamType $param): MyForm;  // parametr předaný z presenteru do formuláře
}

Poznámka: nelámat si hlavu s tím, že to není class, Nette class pro factory vyrobí automaticky !!

3. registrovat komponentu MyForm v presenteru pomocí MyFormFactory

use Nette\Application\UI\Presenter;

final class MyPresenter extends Presenter
{
    public  function __construct(
		.......
        private MyFormFactory $myFormFactory,		// získání MyFormFactory autowiringem z DI služeb
    )
    {}

    public function createComponentLoginForm(): MyForm  // v presenteru není Form, ale MyForm
    {
        return $this->myFormFactory->create(ParamType $param);  // předání parameru do MyForm
    }
}

3. formulář správně vykreslit – vlastní šablonou, nebo default rendererem

šablona kam vykreslíme formulář

{control myForm}     					// 1. varianta - default renderer

{form baseForm}							// 2. varianta - šablona myFormTemplate.latte
	..... vykreslení prvků formuláře
{/form}

Snad jsem na nic nezapomněl. Funguje to výborně a má to řadu dalších výhod do budoucna – v komponentě lze např. použít persistentní parametry pro uložení stavu formuláře (složitější interaktivní formuláře), oddělené vykreslení od zbytku layoutu, možnost použít presenter uvnitř factory (není nutné předávat callback), a další a další výhody.

Stručně řečeno to spojuje výhody designu Factory s výhodami Nette komponentového konceptu.

Velký dík @jiripudil za propagaci tohoto designu, protože mě jako normálního člověka by nikdy nenapadlo řešit problémy balením formuláře do komponenty :).

Není žádný problém přesměrovat MyForm místo v komponentě v presenteru, ale pokud se má přesměrovat na id nového záznamu, jehož vytváření se volá v komponentě, tak absolutně nedává smysl předávat id do presenteru a teprve tam přesměrovat. A právě s klasickou factory jak zkoušíš tohle nejde :(.

Ale někdy se hodí přesměrovat až v presenteru – velmi snadné:

use Nette\Application\UI\Presenter;

final class MyPresenter extends Presenter
{
    public  function __construct(
		.......
        private MyFormFactory $myFormFactory,
    )
    {}

    public function createComponentLoginForm(): MyForm
    {
        $form = $this->myFormFactory->create(ParamType $param);

		$form->onSuccess[] = fn() => $this->redirect($destination);      // callback arrow funkce
		$form->onSuccess[] = function(){$this->redirect($destination)};  // callback anonymní funkce
     }
}

Magické jsou dvě věci: a) vlastní parametr předaný v presenteru do metody create() se přes magicky vytvořenou factory předá do konstruktoru komponenty MyForm. b) Místo class Factory píšeme interface pro Factory.

Poznámka: do konstruktoru MyForm se vedle vlastních parametrů klasickým autowiringem předají i služby z DI.

Editoval m.brecher (13. 10. 2022 4:14)