Příklad použítí Kdyby\DoctrineForms
- Filip Procházka
- Moderator | 4668
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
Jak vytvořit select pro *To*
vazby? Jsou implementované?
Editoval Tomáš Votruba (31. 3. 2014 14:15)
- Tomáš Votruba
- Moderator | 1114
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
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
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
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
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
@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
@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
@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
@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
@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
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
@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
@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
@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
@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
@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
@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
@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)
- Vojtěch Dobeš
- Gold Partner | 1316
@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.
- Tomáš Votruba
- Moderator | 1114
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.
- Jan Suchánek
- Člen | 404
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
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?
- protože tam potřebuju tu traitu
- 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
@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
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
@akadlec: A ty processory formulářů budeš nastavovat, kde v presenteru nebo v té komponentě?
Editoval jenicek (28. 3. 2014 17:54)
- akadlec
- Člen | 1326
@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
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
@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ď:
- udělat si getter na form z komponenty a pak s ním pracovat, nastavit onsuccess atd.
- 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
@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
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')
- Jan Suchánek
- Člen | 404
@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
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)
- Tomáš Votruba
- Moderator | 1114
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.
- chap
- Člen | 81
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:
- 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.
- 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
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
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
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ístonew 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
@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
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