Příklad použítí Kdyby\DoctrineForms

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

Zdravím,

naučil jsem se používat Doctrine díky Kdyby\Doctrine, narazil jsem ještě na DoctrineForms, pochopil jsem že půjde o nějakou validaci nad Entitami, bohužel jsem už jsem nepochopil ani z testů jak to použít v praxi. Mohl by mi někdo dát konkrétní příklad?

Děkuji.

Filip Procházka
Moderator | 4668
+
0
-

Předně mysli na to že je to vývojová verze.

  • Umí to mapovat entity na formuláře a naopak
  • Mělo by to umět validovat entity
  • Mělo by to umět buildit formuláře

Work in progress je generování js validací ze symfony validací.

Mělo by stačit do formuláře přidat traitu EntityForm a zavolat metodu bindEntity

Pokud najdeš chybu tak ji prosím reportuj na github issues, díky!

Tomáš Votruba
Moderator | 1114
+
0
-

Jak vytvořit select pro *To* vazby? Jsou implementované?

Editoval Tomáš Votruba (31. 3. 2014 14:15)

Filip Procházka
Moderator | 4668
+
0
-

Viz testy Tomáši

Tomáš Votruba
Moderator | 1114
+
0
-

Díky Filipe. Mám teď problém s tím, že bindEntity() mi háže exception.
Jak by vypadala komponenta i s formulářem, na který bych navázal entitu?

Edit: Promazán bordel.

Editoval Tomáš Votruba (28. 11. 2014 10:31)

Filip Procházka
Moderator | 4668
+
0
-

Udělal bych si něco takového

namespace MyApp\UI;

class Form extends \Nette\Application\UI\Form
{
	use \Kdyby\DoctrineForms\EntityForm;
}

interface IBaseFormFactory
{
	/** @return \MyApp\UI\Form */
	function create($class = 'MyApp\UI\Form');
}

class Control extends \Nette\Application\UI\BaseControl
{
	use \Kdyby\Autowired\AutowireComponentFactories;
}

konfigurace:

services:
	formFactory:
		class: MyApp\UI\Form(%class%)
		create: "$this->createInstance(?)"(%class%)
		implement: MyApp\UI\IBaseFormFactory
		parameters: [class = "MyApp\UI\Form"]
		inject: yes

použití:

class SisterControl extends MyApp\UI\Control
{
    private $sister;

    public function setSister(App\Sister $sister)
    {
        $this->sister = $sister;
    }

    protected function createComponentForm(MyApp\UI\IBaseFormFactory $factory)
    {
        if (!$this->sister) {
            throw new \LogicException('Missing call ' . get_called_class()  . '::setSister($entity)');
        }

        $form = $factory->create();
        $form->bindEntity($this->sister);
        $form->add('name');

        $form->addSubmit('send', 'Uložit');
        $form->onSuccess[] = $this->processForm;

        return $form;
    }


    public function processForm($form)
    {
        $this->sisters->save($form->entity);
        $this->presenter->redirect('default');
    }

}

interface ISisterControlFactory
{
	/** @return SisterControl */
	function create();
}

Použití komponenty

protected function createComponentSister(ISisterControlFactory $factory)
{
	$control = $factory->create();
	$control->setSister(new App\Sister);

	return $control;
}
Tomáš Votruba
Moderator | 1114
+
0
-

Díky moc Filipe, vyzkouším.

Tak u $createInstance() nastane chyba Multiple services of type Nette\ComponentModel\IContainer found: a za ní jsou vyjmenovány všechny presentery registrované jako služby. Screen laděnky

Presentery tam potřebuji kvůli eventům. Netuším, co s tím.

Editoval Tomáš Votruba (25. 3. 2014 12:39)

Filip Procházka
Moderator | 4668
+
0
-

Nemáš vyžadovat presenter ale Application ze kterého si vytáhneš instanci aktivního presenteru.

Nehledě na to že presenter se ti sám zpřístupní po připojení do presenteru :)

Filip Procházka
Moderator | 4668
+
0
-

Pořád nechápu naco potřebuješ presenter v konstruktoru komponenty. Vždyť naprosto jasně ho dostáváš v attached.

Btw, víš že tenhle problém nijak nesouvisí s doctrine? :)

Tharos
Člen | 1030
+
0
-

@Filip Procházka: Nemyslím to nijak zle, ale Tebou uvedené konstrukce mi prostě přijdou hrozně složité (hlavně pak ta konstrukce v neonu…). Jako ukázka možností Nette DI a komponent je to super :), ale pro použití v praxi bych asi osobně volil něco trochu jednoduššího.

Plus to dědění Form… Proč to neřešit jednoduše cca takhle?

class SampleFormFactory
{

	public function createForm($entity = null)
	{
		$form = new Form;
		$form->addText('name', 'Name');

		if ($entity !== null) {
			$form->setDefaults((array) $entity); // tohle je jenom takové zjednoduššení…
		}

		return $form;
	}

}

class SampleControl extends Control
{

	private $sampleFormFactory;

	private $entity;


	public function __construct(SampleFormFactory $sampleFormFactory)
	{
		$this->sampleFormFactory = $sampleFormFactory;
	}

	public function setEntity($entity)
	{
		$this->entity = $entity;
	}

	public function render()
	{
		echo $this['form'];
	}

	protected function createComponentForm()
	{
		$form = $this->sampleFormFactory->createForm($this->entity);
		$form->onSuccess[] = function (Form $form) {
			$this->processForm($form);
		};
		return $form;
	}

	private function processForm(Form $form)
	{
		// form processing
	}

}

interface ISampleControlFactory
{

	/**
	 * @return SampleControl
	 */
	function create();

}
services:
	- SampleFormFactory
	- ISampleControlFactory

Nějaké fiktivní použití v presenteru:

class DefaultPresenter extends Presenter
{

	private $entity;

	private $sampleControlFactory;


	public function __construct(ISampleControlFactory $sampleControlFactory)
	{
		$this->sampleControlFactory = $sampleControlFactory;
	}

	public function startup()
	{
		parent::startup();
		$this->entity = $entity = new stdClass;
		$entity->name = 'This is my name';
	}

	protected function createComponentSampleControl()
	{
		$control = $this->sampleControlFactory->create();
		$control->setEntity($this->entity);

		return $control;
	}

}

Výsledek dost podobný, kód jednoduší, přímočařejší.

Editoval Tharos (25. 3. 2014 23:04)

akadlec
Člen | 1326
+
0
-

@Tharos: proč to v presenteru předáváš konstruktorem? Nestačilo by to takto?

class DefaultPresenter extends Presenter
{
    private $entity;

    public function startup()
    {
        parent::startup();
        $this->entity = $entity = new stdClass;
        $entity->name = 'This is my name';
    }

    protected function createComponentSampleControl(ISampleControlFactory $sampleControlFactory)
    {
        $control = $sampleControlFactory->create();
        $control->setEntity($this->entity);

        return $control;
    }
}
Tharos
Člen | 1030
+
0
-

@akadlec: Za předpokladu, že bych v tom presenteru použil AutowireComponentFactories, tak samozřejmě ano. :) Pozor na to, že tenhle auto-wiring není vestavěnou fičurou Nette, ale je z Kdyby. A já jsem tu ukázku chtěl mít co nejútlejší…

To je ale pořád vedlejší. Hlavní pointou mého zamyšlení je, zda přece jenom není namísto poděděného formuláře s automaticky vygenerovanou továrnou a trochu hacky definicí v neonu výhodnější formulář nedědit a tu továrnu si třeba napsat ručně.

Editoval Tharos (26. 3. 2014 10:00)

akadlec
Člen | 1326
+
0
-

@Tharos: joo vlastně, já už na to zapomněl že používám autowired ;)

Já už několikrát měnil vytváření formů atd. a nakonec jsem dospěl k závěru, že použiju create:

interface IStepOneFormFactory
{
	/**
	 * @return StepOneForm
	 */
	public function create();
}

a samotný form pak je nějak takto:

class StepOneForm extends Form
{
	protected $formProcessor;

	public function __construct(StepOneFormProcessor $processor IContainer $parent = NULL,  $name = NULL)
	{
		parent::__construct($parent, $name);

		// Main form processor
		$this->formProcessor = $processor;
	}

	public function createForm()
	{
		// Create form object
		$this
			->addProcessor($this->formProcessor)
			->setRenderer(new BootstrapRenderer());

		$this->addText('title', 'texts.items.form.title')
			->addRule($this::FILLED);
	}
}

A to proto abych si mohl na jednotlivé formy pak vázat eventy pro kdyby\events

A když na to tak koukám, tak to mám skoro jako filip akorát nemám BaseFactory ale každý form má svou factory, takže pomocí BaseFactory bych si ušetřil trochu psaní a nějaké ty soubory ;)

Editoval akadlec (26. 3. 2014 9:29)

Tharos
Člen | 1030
+
0
-

@akadlec: Stejně bych ale v tomhle případě to dědění nahradil kompozicí. :)

Mít třídu reprezentující konkrétní formulář dává velký smysl. Kvůli type hintům, navazování událostí… Prostě je to nějaká konkrétní třída s jasným API. Taková třída ale nemusí dědit z Form. Já bych za tímhle účelem klidně použil celou tu svou SampleControl.

Ta Tvá metoda createForm mi přijde trochu zvláštní. :) Je veřejná, a tak by mě zajímalo, co vrací a jak se vůbec používá?

Tharos
Člen | 1030
+
0
-

Pokusil jsem se tu svou ukázku ještě více zjednodušit, aby šla ještě více k jádru věci (de facto dědičnost versus kompozice):

class SampleForm extends Control
{

	private $entity;


	public function setEntity($entity)
	{
		$this->entity = $entity;
	}

	public function render()
	{
		echo $this['form'];
	}

	protected function createComponentForm()
	{
		$form = new Form;
		$form->addText('name', 'Name');

		if ($this->entity !== null) {
			$form->setDefaults((array) $this->entity);
		}

		$form->onSuccess[] = function (Form $form) {
			$this->processForm($form);
		};

		return $form;
	}

	private function processForm(Form $form)
	{
		// form processing
	}

}

interface ISampleFormFactory
{

	/**
	 * @return SampleForm
	 */
	function create();

}
services:
	- ISampleFormFactory

Použití v presenteru (pro názornost bez Kdyby\Autowired):

class DefaultPresenter extends Presenter
{

	private $entity;

	private $sampleFormFactory;


	public function __construct(ISampleFormFactory $sampleFormFactory)
	{
		$this->sampleFormFactory = $sampleFormFactory;
	}

	public function startup()
	{
		parent::startup();
		$this->entity = $entity = new stdClass;
		$entity->name = 'This is my name';
	}

	protected function createComponentSampleForm()
	{
		$sampleForm = $this->sampleFormFactory->create();
		$sampleForm->setEntity($this->entity);

		return $sampleForm;
	}

}

Takovýto přístup mi prostě přijde ve všem výhodnější, než SampleForm extends Form. :)

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

@Tharos Tleskám! Že by samostatné (na úrovni logiky aplikace: formulář na články, formulář na…) formuláře neměly dědit od Form tvrdím už nějakou dobu :).

akadlec
Člen | 1326
+
0
-

@Tharos: jop vypadlo mi tam return $this. V podstatě v presenteru či komponentě ti to vrátí onen form objekt.

Jakou výhodu má to tvé poslední řešení? Já svuj form neextenduji o nette form ale mám tam ještě svou implementaci kde si určím jakou šablonou se má form vykreslit atd. Vykreslovače nette jsou pro mě maximálně nevhodné, zatím jsem nenarazil nikde kde bych je mohl použít a hodit do template jen {control nejakyForm} vždy ten form potřebuju ručně doladit :(

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

@akadlec Právě protože se manuální renderování formu tak často hodí, je dědění od UI\Control nejlepší, protože ta už má k dispozici metodu render(), ve které lze snadno přistoupit k šabloně. Nemá smysl logiku ohledně šablony přenášet do jakéhokoliv potomka UI\Form.

Jan Suchánek
Člen | 404
+
0
-

@vojtech.dobes: processForm(Form $form) pak deleguješ na udalosti a přiřazuješ je v presenteru nebo přímo zpracováváš v Controlu?

private function processForm(Form $form)
{
    $this->onProcess($form); // Nebo přímo zpracováváš?
}
Vojtěch Dobeš
Gold Partner | 1316
+
+1
-

@jenicek Přiznám se, že nedokážu kvalifikovaně odpovědět. Buď je formulář zpracován Controlem, nebo se třeba na Control zavěsí jiná (zpracovatelská :) ) služba. Ve formuláři V presenteru spíš ne, tam se snažím mít jen flash messages, redirecty apod., které zavěsím do custom událostí toho Controlu (onArticleSaved apod.).

Editoval vojtech.dobes (26. 3. 2014 11:48)

Tharos
Člen | 1030
+
0
-

@akadlec:

jop vypadlo mi tam return $this. V podstatě v presenteru či komponentě ti to vrátí onen form objekt.

Co se stane, když tu metodu zavolám vícekrát? :) (Je součástí veřejného API, nic mi v tom nebrání a mohu to klidně udělat v dobré víře.)

Jakou výhodu má to tvé poslední řešení?

Z jazyka mi vzal odpověď @vojtech.dobes :), a tak já už se to pokusím jenom trochu doplnit…

Nechi opakovat, co už bylo řečeno jinde, takže určitě nevynech vlákno Vícenásobné použití formuláře: dědičnost nebo továrna? a taky vřele doporučuji to vklákno od Vojty, které mi kdysi nějak uniklo a moc mě to mrzí, protože jsem si sám mohl ušetřit spoustu nervů a tápání. ;)


Použití kompozice Ti v tomto případě dává mnohem větší volnost (vede k volnějším vazbám). U dědičnosti bys postupně mohl narazit na to, že ji využíváš k vyčlenění nějaké společné funkcionality a nikoliv čistě ke specializaci. Plus má tendenci být „neohrabaná“ (viz rodinná linie Nette presenterů).

Formuláře v Nette mají povahu, jakou mají: formulář v aplikaci není instancí unikátní třídy nadefinované pro ten konkrétní formulář, ale speciálně nakonfigurovanou instancí nějaké obecné třídy. To je velmi výhodné, pružné, ale je zapotřební k tomu patřičně přistupovat. Podědění formuláře a použití konstruktoru ke konfiguraci té třídy je, jak píše i David, taková docela bad practice. Factory se na tohle hodí mnohem lépe.

Editoval Tharos (26. 3. 2014 11:30)

Jan Suchánek
Člen | 404
+
0
-

@vojtech.dobes: Ok, hmm custom události pěkné.

Tzn nějak takto?

private function processForm(Form $form)
{
	try {
		 // … ukládání
		$this->onArticleSaved($entity);
	} catch(MyApp\Exception $e){
		$this->onArticleError($entity);
	}
}

Nebo přez Kdyby\Events?

Editoval jenicek (26. 3. 2014 11:31)

Tharos
Člen | 1030
+
0
-

vojtech.dobes napsal(a):

… zavěsím do custom událostí toho Controlu (onArticleSaved apod.)

Super nápad!

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

@jenicek Rozdělil bych potencionální „úspěšné uložení článku“ na reakci Controlu a reakci modelu. Reakcí modelu může být třeba invalidace nějaké cache nebo odeslání emailu nebo něco podobného. Na to se výborně hodí globální eventový systém, jakým jsou Kdyby\Events, které by v tomto konkrétním případě měly reagovat na nějakou modelovou událost (ne událost Controlu).

Reakce Controlu by měla být čistě věcí UI (koneckonců viz namespace této třídy :) – UI\Control). Tudíž její onArticleSaved by mělo vést k těm flash messages, redirectům apod., na které obvykle Kdyby\Events nejsou nijak potřeba, presenter má instanci Controlu v továrničce k dispozici, a může se do jejích eventů zavěsit přímým voláním.

Jan Suchánek
Člen | 404
+
0
-

@vojtech.dobes: vyzkouším :)

Tomáš Votruba
Moderator | 1114
+
0
-

Pocitate v teto diskusi s traitou v Kdyby\DoctrineForms? Tam formular vyzaduje jiny pristup nez obvykle.

Pokud ano, ktery ze vsech zminenych kodu jste vyzkouseli a je fcni? Diky.

akadlec
Člen | 1326
+
0
-

@Tomáš Votruba: muj je, protože tuto traitu využívám.

Jan Suchánek
Člen | 404
+
0
-

vojtech.dobes: Ještě se v tom plácám, tzn. Form komponenty nedědit na hodně stejné akce formulářovích komponent využít traity?

Filip Procházka
Moderator | 4668
+
0
-

Tharos napsal(a):

Tebou uvedené konstrukce mi prostě přijdou hrozně složité (hlavně pak ta konstrukce v neonu…). Jako ukázka možností Nette DI a komponent je to super :), ale pro použití v praxi bych asi osobně volil něco trochu jednoduššího.

Ano souhlasím, není vůbec nic špatného na tom napsat si tu factory ručně a dokonce to ve většině případů může být i čistější/praktičtější.

Plus to dědění Form… Proč to neřešit jednoduše cca takhle?

  1. protože tam potřebuju tu traitu
  2. protože v 99% aplikací si chci do formuláře přidat vlastní typy inputů a ikdybych je přidával jako extension method tak stejně si chci nad formulář napsat annotaci že tam ta metoda je

Výsledkem je nějaký BaseForm, který již pokud možno na konkrétní formuláře nedědím ale upřednostňuji obalování komponentou. Což jsem mimochodem přesně takhle udělal hned na začátku, jenom možná zbytečně složitě.


Ta moje IBaseFormFactory je určená k vytváření instance právě tohoto základního formuláře, v naprosté většině případů pak už dále formulář nedědím, ale obaluji do komponenty.

Ta moje traita je navržená tak, aby v ní nebyla žádná kritická logika a formulář se dal používat bez ní, ovšem když ji nepoužiješ musíš psát hromadu boilerplate kódu, který může nakonec být ještě delší než traita samotná.

Když bych to chtěl zjednodušit, tak dostanu něco takového

namespace MyApp\UI;

class Form extends \Nette\Application\UI\Form
{
	use \Kdyby\DoctrineForms\EntityForm;
}

interface IBaseFormFactory
{
	/** @return \MyApp\UI\Form */
	function create();
}

class Control extends \Nette\Application\UI\BaseControl
{
	use \Kdyby\Autowired\AutowireComponentFactories;
}

konfigurace:

services:
	- {implement: MyApp\UI\IBaseFormFactory, inject: yes}

A mám úplně to stejné jako ty, jenom s tou výhodou, že nemusím psát boilerplate kód na propojení formuláře a entity. Je to prostě vylepšená varianta na tohle – dělám to jenom kvůli tomu že ta traita má nějaké závislosti a já je chci předat čistě.

Na druhou stranu uznávám, že jsem tuhle variantu měl napsat rovnou, jenže jsem měl zrovna chuť trošku znásilnit Neon :)


TL;DR:

  • BaseForm je fajn věc, ve většině aplikací je potřeba, nikdo zatím nevymyslel nic lepšího (extension metody nestačí)
  • udělat si na BaseForm generovanou továrničku je taky fajn věc, pokud má nějaké závislosti
  • BaseForm pokud možno dále nedědit ale upřednostňovat obalení komponentou
Tharos
Člen | 1030
+
0
-

@Filip Procházka: Super, díky za reakci. :)

protože v 99% aplikací si chci do formuláře přidat vlastní typy inputů a ikdybych je přidával jako extension method tak stejně si chci nad formulář napsat annotaci že tam ta metoda je

Vlastní typ inputů považuji za legitimní důvod k podědění formuláře. Zároveň to považuji za jeden z mála legitimních důvodů. :)

V případě, že bych vlastní typy inputů hojně potřeboval a nestačily mi extension metody, asi bych také sáhl po dědičnosti. Konfiguraci vstupů bych ale už ponechal na oddělené factory. Tady se Ti musím omluvit, protože jsem přehlédl, že Ty to tak vlastně děláš… :) Přehlédl jsem, kde máš to add a addSubmit.

Metoda createForm ve formuláři tak, jak ji sem psal @akadlec, mi ale přijde trochu nešťastná.

Takže suma sumárum se myslím shodujeme. :)

TL;DR:

  • BaseForm je fajn věc, ve většině aplikací je potřeba, nikdo zatím nevymyslel nic lepšího (extension metody nestačí)
  • udělat si na BaseForm generovanou továrničku je taky fajn věc, pokud má nějaké závislosti
  • BaseForm pokud možno dále nedědit ale upřednostňovat obalení komponentou

Jo, takhle to zní rozumně. :)

akadlec
Člen | 1326
+
0
-

jen tak na okraj, jak pak na formy vytvořené přes komponenty co je obalují navěsit eventy pomocí kdyby? Věšet je na ony komponenty a fajrovat je v nich?

a další dotaz. Když udělám formulář obalený komponentou a chci aby byl „uni“ jak se nejlépe k němu dostat a zadefinovat mu třeba redirecty při onSuccess? V případě že chci aby se v presenteru XY zachoval takto a v presenteru XZ zase jinak?


tak na první otázku si odpovím sám, ano ;) zkusil jsem je zavěšovat a je to cajk i když já je budu věšet na samotné form processory

Editoval akadlec (28. 3. 2014 17:31)

Jan Suchánek
Člen | 404
+
0
-

@akadlec: A ty processory formulářů budeš nastavovat, kde v presenteru nebo v té komponentě?

Editoval jenicek (28. 3. 2014 17:54)

akadlec
Člen | 1326
+
0
-

@jenicek: v komponentě, když se vytvoří form tak mu nadefinuji základní eventy onsuccess, onerror atd. ale potřebuji mu dát i custom event onsuccess který bude platit jen pro dané použití v daném místě. Tak mě napadá to dělat tak že si z té komponenty vytáhnu komponentu formuláře.

Jan Suchánek
Člen | 404
+
0
-

akadlec: Takhle nějak?

Zajímá mě zda ty processy definovat přímo v presenteru nebo až v komponentě či jinde?

function createComponentEditForm(){
	$form = $this->exampleEditFormFactory->create();
	$form->onSuccess[] = $this->proccess();
	return $control;
}

a custom event v Presenteru?

function createComponentItemControl(){
	$control = $this->exampleItemControl->create();
	$control->onSave[] = $this->processSave();
	$control->onError[] = $this->processError();
	return $control;
}

Pokud bych chtěl mohl bych vše řešit na urovni presenteru, ale zatím nevím o nejlepší technice, v komponěntě by bylo dobré řešit flash zprávičky a na úrovni presenteru přímo techniku jak co provést? Komponenta by pak nemusela obsahovat servisu, ale samozřejmě ji pomocí DIC mít může. tak nevím.

Editoval jenicek (31. 3. 2014 13:27)

akadlec
Člen | 1326
+
0
-

@jenicek: no ve zkratce jo. Já jsem ty formy teď předělal jak je uvedeno výše. Když si v presenteru zavolám továrničku na form, tak se mě ve skutečnosti vytvoří klasická komponenta která má v sobě onen form, přiřadí mu patřičné callbacky a hotovo, renderuje se šablona komponenty. Ale v okamžiku kdy chcu formu přidat další custom metody tak narážím na problém jak se k tomu formu dostat, protože je uzavřen v komponentě. Tak mě napadá buď:

  1. udělat si getter na form z komponenty a pak s ním pracovat, nastavit onsuccess atd.
  2. Udělat si setter do komponenty a v tom setteru nastavovat konkrétní eventy formu. Výhodou je že samotný form zůstane zapouzdřen v komponentě.
Jan Suchánek
Člen | 404
+
0
-

@akadlec: Práávě, Getr je jednodušší a co mít nějaký onBeforeProccess a onAfterProcces a ty mít navěšené na v těch callbackách v komponentě a tudíž je moci navěšovat z venku na komponentu bez toho že bych přistupoval přimo k formu, nebo jiné vnitřní lumpárně.

V komponentě?

protected function createComponentEditForm(){
    $form = $this->exampleEditFormFactory->create();
    $form["save"]->onSave[] = function($button) {
		$this->proccessSave($button->getForm());
 	};
    return $control;
}

function getEditForm()
{
	// no nevim zda je potřeba?
	return $this["editForm"];
}

function proccessSave($form){
	// $this->startProccess($form);
	try {
			$this->beforeProccess($form);
			// process
			$this->afterProccess($form);
	} catch(MyApp\Exception $e){
		$this->errorProccess($form);
	}
	// $this->endProccess($form);

}

V presenteru:

	protected function createComponentEditContol()
	{
		$control = $this->editControlFactory->create();
		$control->beforeProccess[] = $this->myBeforeProcess;
		...
		return $control;
	}

Vlastně o tom psal vojtech.dobes.

Editoval jenicek (31. 3. 2014 17:47)

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

Getter na podkomponentu z podstaty komponentového modelu není potřeba. Bohatě třeba v presenteru stačí něco jako:

$this['editFormControl']['form']

Puristé pak mohou pro klid duše a svědomí použít :) :

$this['editFormControl']->getComponent('form')
Filip Procházka
Moderator | 4668
+
0
-

A minimalisté

$this['editFormControl-form']

:)

Jan Suchánek
Člen | 404
+
0
-

@Filip Procházka: Doporučuješ tedy nastavovat minimalisticky přímo přez

$this['editFormControl-form']

a nekomplikovat si život? Jde mi o tohle.

Editoval jenicek (1. 4. 2014 10:51)

Tomáš Votruba
Moderator | 1114
+
0
-

Ještě bych navázal na bindEntity()

Při připojení entity v továrničce na formulář nastane chyba: Component '' is not attached to ‚Nette\Application\UI\Presenter‘., když se Kdyby\DoctrineForms\EntityForm pokusí zavolat getServiceLocator(). Viz:

protected function createComponentForm(IEntityFormFactory $factory)
{
	$form = $factory->create();
	$form->addText('name', 'Název');
	$form->addMultiSelect('authors', 'Autoři')
		->setOption(IComponentMapper::ITEMS_TITLE, 'name');
	$form->bindEntity($this->project);
	return $form;
}

Lze nějak vytvořit komponentu form až po připojení k presenteru?

Zatím to obcházím takto:

protected function attached($presenter)
{
	parent::attached($presenter);
	$this['form']->bindEntity($this->project);
}

Díky

Editoval Tomáš Votruba (1. 4. 2014 12:35)

akadlec
Člen | 1326
+
0
-

@Filip Procházka as @vojtech.dobes cool chlapci, díky za radu, teď se mi toto řešení začíná líbit čám dál tim více ;)

Tomáš Votruba
Moderator | 1114
+
0
-

Filipe, můžu se zeptat, jak funguje toMany? Z testů jsem to nepochopil.

Tento kód se mi nikde neprojevil:

$form->toMany('values', function ($parameter) {
	$parameter->addText('value');
});

Chtěl bych mít formulář pro úpravu parametru a zároveň u něj možnost přidávat jeho hodnoty (např. parametr velikost trika, hodnoty XL, L…).

Díky.

akadlec
Člen | 1326
+
0
-

Není náhodou ToMany ještě neimpelmentované? Aspoň mě to někde vyhazovalo výjimku s hláškou not implemented.

chap
Člen | 81
+
0
-

Ahoj, snažím se rozchodit DoctrineForms a trochu narážím na problém s připojením entity ke komponentě. Zkoušel jsem dvěma způsoby:

  1. bind v metodě init – zde mi to správně přiřadí defaultní hodnoty do formuláře z entity, ale při uložení je v getValues i getEntity výchozí stav.
  2. zkoušel jsem se inspirovat zde a binduji v metodě attached. Entitu mi to k formuláři připojí, ale nevyplní mi to defaultní hodnoty do formuláře.
  • Komponenty vytvářím trochu nestandarndně (možná to bude tím). Třída MyFormControl dědí od \Nette\Application\UI\Control pak zde mám nějaké metody pro obecnou práci formulářů v metodě createComponent vytvářím formulář a volám metodu init potomka, která jen doplní formulářové prvky.

Nebyla by prosím rada? Díky.

class UserForm extends MyFormControl{

   /** @var  \App\Uzivatel  */
    private $us;

    public function __construct($parent, $name, $user) {
        $this->us = $user;
        parent::__construct($parent, $name);
    }


    public function init(EntityForm $form) {
        $form->addText("jmeno", "Jméno");
        $form->addText("prijmeni", "Příjmení");
        $form->addText("email", "E-mail");
        $form->addSubmit("register", "Uložit");
       // $form->bindEntity($this->us);
    }

	protected function attached($obj)
	{
		parent::attached($obj);
		if (!$obj instanceof \Nette\Application\UI\Presenter) {return;}
		$form = $this['form']->bindEntity($this->us);
 	}

}

V presenteru to vypadá takto:

public function createComponentUserform($name) {
     new \UserForm($this, $name,$this->user);

}

A případně ta MyFormControl:

abstract class MyFormControl extends \Nette\Application\UI\Control{

    use Kdyby\DoctrineForms\EntityForm;

     protected function createComponentForm($name) {
        $form = new EntityForm($this,$name);
        $this->init($form);
        $form->onSuccess[] = $this->succes;
        $form->onValidate[] = $this->validate;
        return $form;
     }

    public function __construct($parent,$name) {
      parent::__construct($parent, $name);
    }

// .... další metody....
    public abstract function succes(EntityForm $form);
    public abstract function validate(EntityForm $form);
    public abstract function init(EntityForm $form);

}
chap
Člen | 81
+
0
-

Ahoj, zase si dovolím doplnit předchozí dotaz. Přepracoval jsem komponentu do následující podoby:

class UserForm extends Nette\Application\UI\Control {

    protected $edit = null;

    public function __construct($userToEdit) {
        parent::__construct();
        $this->edit = $userToEdit;
    }

    public function createComponentForm() {
        $form = new EntityForm();
        $form->addText("jmeno", "Jméno")->setRequired("Jmeno");
        $form->addText("prijmeni", "Příjmení")->setRequired("Příjmení");
        $form->addText("email", "E-mail");
        $form->addSubmit("register", "Uložit");
        $form->onSuccess[] = $this->succes;
        $form->setRenderer(new BootstrapRenderer());
        return $form;
    }

    public function render() {
        $this["form"]->render();
    }

    public function succes(EntityForm $form) {
        Debugger::dump($form->getEntity());die;
    }

    protected function attached($obj) {
        parent::attached($obj);
        if (!$obj instanceof \Nette\Application\UI\Presenter) { return; }
        $this['form']->bindEntity($this->edit);
    }

}

V Presenteru:

protected function createComponentUserform(\IUserFormFactory $uf)
    {
        return $uf->create($this->usersFacade->getById($this->user->id));
    }

Komponentu vložím, natáhne správné info o uživateli z entity do formuláře, udělám úpravy … odešlu a vypíši entitu formuláře. Bohužel neobsahuje změněné hodnoty, ale ty původní – stejně tak v metodě getValues. Už začínám být zoufalý …

jiri.pudil
Nette Blogger | 1029
+
0
-

Já binduju entitu na konci createComponentForm() a aby to nenadávalo kvůli chybějícím závislostem, používám namísto new EntityForm() generovanou továrničku se zapnutými injecty:

interface IEntityFormFactory
{
	/** @return EntityForm */
	function create();
}
- {implement: IEntityFormFactory, inject: on}
chap
Člen | 81
+
0
-

jiri.pudil napsal(a):

Já binduju entitu na konci createComponentForm() a aby to nenadávalo kvůli chybějícím závislostem, používám namísto new EntityForm() generovanou továrničku se zapnutými injecty:

interface IEntityFormFactory
{
	/** @return EntityForm */
	function create();
}
- {implement: IEntityFormFactory, inject: on}

Ahoj, udělal jsem to jak píšeš a takto to funguje. Ale popravdě je mi záhadou proč :) Takže díky a když mi někdo osvětlí proč, tak budu jen rád ;)

jiri.pudil
Nette Blogger | 1029
+
0
-

@chap Ještě jsem neměl čas vystopovat, kde to přesně vzniká, ale zdá se, že se ta entita binduje až potom, co se naplní data z requestu do formuláře, takže se odeslaná vždycky přepíšou tou entitou. Takhle mám zajištěné, že se entita binduje ještě před připojením formuláře do komponenty (protože to se dělá až po zavolání createComponent* metody), a tudíž před naplněním formuláře daty.

chap
Člen | 81
+
0
-

Ahoj, řeším ještě jednu drobnost v doctrine-forms. Vyhazuje mi to výjimku

NotImplementedException("To many relation is not yet implemented");

V jednom formuláři mám přidáno poměrně dost vlastností a jsou zde i mmj M2M relace. Nepotřebuji je nijak automaticky validovat – řeším ručně … ale při uložení mi to vyvolá tuto výjimku. Vyřešil jsem to ošklivě (funkčně a jen do aktualizace, kdy na toto zapomenu) zakomentováním místa, kde je vyvolávána. Jediné co mne napadá, tak Try-catch všude, kde vytvářím instanci daného formuláře (což není úplně šťastný postup). Jak by jste prosím řešili vy?

Chap

akadlec
Člen | 1326
+
-1
-

zakomentováním místa kde se vyjimka vyhazuje ;)