Reutilizarea formularelor în mai multe locuri
În Nette aveți la dispoziție mai multe opțiuni pentru a utiliza același formular în mai multe locuri și a nu duplica codul. În acest articol vom prezenta diverse soluții, inclusiv cele pe care ar trebui să le evitați.
Fabrica de formulare
Una dintre abordările de bază pentru utilizarea aceleiași componente în mai multe locuri este crearea unei metode sau clase care generează această componentă și apoi apelarea acestei metode în diferite locuri ale aplicației. O astfel de metodă sau clasă se numește fabrică. Vă rugăm să nu confundați cu modelul de proiectare factory method, care descrie un mod specific de utilizare a fabricilor și nu are legătură cu acest subiect.
Ca exemplu, vom crea o fabrică care va construi un formular de editare:
use Nette\Application\UI\Form;
class FormFactory
{
public function createEditForm(): Form
{
$form = new Form;
$form->addText('title', 'Titlu:');
// aici se adaugă alte câmpuri de formular
$form->addSubmit('send', 'Trimite');
return $form;
}
}
Acum puteți utiliza această fabrică în diferite locuri din aplicația dvs., de exemplu în presenteri sau componente. Și asta prin solicitarea ei ca dependență. Mai întâi, vom înregistra clasa în fișierul de configurare:
services:
- FormFactory
Și apoi o vom folosi într-un presenter:
class MyPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private FormFactory $formFactory,
) {
}
protected function createComponentEditForm(): Form
{
$form = $this->formFactory->createEditForm();
$form->onSuccess[] = function () {
// procesarea datelor trimise
};
return $form;
}
}
Puteți extinde fabrica de formulare cu alte metode pentru crearea altor tipuri de formulare, în funcție de nevoile aplicației dvs. Și, desigur, putem adăuga și o metodă care creează un formular de bază fără elemente, pe care celelalte metode o vor utiliza:
class FormFactory
{
public function createForm(): Form
{
$form = new Form;
return $form;
}
public function createEditForm(): Form
{
$form = $this->createForm();
$form->addText('title', 'Titlu:');
// aici se adaugă alte câmpuri de formular
$form->addSubmit('send', 'Trimite');
return $form;
}
}
Metoda createForm()
nu face încă nimic util, dar acest lucru se va schimba rapid.
Dependențele fabricii
Cu timpul, se va dovedi că avem nevoie ca formularele să fie multilingve. Acest lucru înseamnă că trebuie să setăm un
așa-numit translator pentru toate formularele. În acest
scop, vom modifica clasa FormFactory
astfel încât să accepte obiectul Translator
ca dependență în
constructor și să-l transmitem formularului:
use Nette\Localization\Translator;
class FormFactory
{
public function __construct(
private Translator $translator,
) {
}
public function createForm(): Form
{
$form = new Form;
$form->setTranslator($this->translator);
return $form;
}
// ...
}
Deoarece metoda createForm()
este apelată și de celelalte metode care creează formulare specifice, este
suficient să setăm translatorul doar în ea. Și am terminat. Nu este nevoie să modificăm codul niciunui presenter sau
componente, ceea ce este grozav.
Mai multe clase de fabrici
Alternativ, puteți crea mai multe clase pentru fiecare formular pe care doriți să-l utilizați în aplicația dvs. Această
abordare poate crește lizibilitatea codului și facilita gestionarea formularelor. Vom lăsa FormFactory
originală
să creeze doar un formular curat cu configurația de bază (de exemplu, cu suport pentru traduceri) și vom crea o nouă
fabrică EditFormFactory
pentru formularul de editare.
class FormFactory
{
public function __construct(
private Translator $translator,
) {
}
public function create(): Form
{
$form = new Form;
$form->setTranslator($this->translator);
return $form;
}
}
// ✅ utilizarea compoziției
class EditFormFactory
{
public function __construct(
private FormFactory $formFactory,
) {
}
public function create(): Form
{
$form = $this->formFactory->create();
// aici se adaugă alte câmpuri de formular
$form->addSubmit('send', 'Trimite');
return $form;
}
}
Este foarte important ca legătura dintre clasele FormFactory
și EditFormFactory
să fie realizată
prin compoziție, nu prin moștenire de obiecte:
// ⛔ NU AȘA! MOȘTENIREA NU APARȚINE AICI
class EditFormFactory extends FormFactory
{
public function create(): Form
{
$form = parent::create();
$form->addText('title', 'Titlu:');
// aici se adaugă alte câmpuri de formular
$form->addSubmit('send', 'Trimite');
return $form;
}
}
Utilizarea moștenirii ar fi complet contraproductivă în acest caz. Ați întâmpina probleme foarte rapid. De exemplu, în
momentul în care ați dori să adăugați parametri metodei create()
; PHP ar raporta o eroare că semnătura sa
diferă de cea a părintelui. Sau la transmiterea dependențelor către clasa EditFormFactory
prin constructor. Ar
apărea o situație pe care o numim constructor hell.
În general, este mai bine să preferăm compoziția în detrimentul moștenirii.
Gestionarea formularului
Gestionarea formularului, care este apelată după trimiterea cu succes, poate fi, de asemenea, parte a clasei fabricii. Va
funcționa prin transmiterea datelor trimise către model pentru procesare. Eventualele erori le va transmite înapoi formularului. Modelul din exemplul
următor este reprezentat de clasa Facade
:
class EditFormFactory
{
public function __construct(
private FormFactory $formFactory,
private Facade $facade,
) {
}
public function create(): Form
{
$form = $this->formFactory->create();
$form->addText('title', 'Titlu:');
// aici se adaugă alte câmpuri de formular
$form->addSubmit('send', 'Trimite');
$form->onSuccess[] = [$this, 'processForm'];
return $form;
}
public function processForm(Form $form, array $data): void
{
try {
// procesarea datelor trimise
$this->facade->process($data);
} catch (AnyModelException $e) {
$form->addError('...');
}
}
}
Redirecționarea în sine o vom lăsa însă pe seama presenterului. Acesta va adăuga evenimentului onSuccess
un
alt handler care va efectua redirecționarea. Datorită acestui fapt, va fi posibilă utilizarea formularului în diferiți
presenteri și redirecționarea către locuri diferite în fiecare.
class MyPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private EditFormFactory $formFactory,
) {
}
protected function createComponentEditForm(): Form
{
$form = $this->formFactory->create();
$form->onSuccess[] = function () {
$this->flashMessage('Înregistrarea a fost salvată');
$this->redirect('Homepage:');
};
return $form;
}
}
Această soluție utilizează proprietatea formularelor că, atunci când se apelează addError()
pe formular sau
pe elementele sale, următorul handler onSuccess
nu mai este apelat.
Moștenirea de la clasa Form
Formularul construit nu trebuie să fie un descendent al formularului. Cu alte cuvinte, nu utilizați această soluție:
// ⛔ NU AȘA! MOȘTENIREA NU APARȚINE AICI
class EditForm extends Form
{
public function __construct(Translator $translator)
{
parent::__construct();
$this->addText('title', 'Titlu:');
// aici se adaugă alte câmpuri de formular
$this->addSubmit('send', 'Trimite');
$this->setTranslator($translator);
}
}
În loc să construiți formularul în constructor, utilizați o fabrică.
Este necesar să realizăm că clasa Form
este în primul rând un instrument pentru construirea unui formular,
adică un form builder. Iar formularul construit poate fi considerat produsul său. Însă produsul nu este un caz
specific al builder-ului, nu există între ele o legătură is a care stă la baza moștenirii.
Componenta cu formular
O abordare complet diferită este crearea unei componente, care include un formular. Acest lucru oferă noi posibilități, de exemplu, redarea formularului într-un mod specific, deoarece componenta include și un șablon. Sau se pot utiliza semnale pentru comunicarea AJAX și încărcarea suplimentară a informațiilor în formular, de exemplu pentru sugestii, etc.
use Nette\Application\UI\Form;
class EditControl extends Nette\Application\UI\Control
{
public array $onSave = [];
public function __construct(
private Facade $facade,
) {
}
protected function createComponentForm(): Form
{
$form = new Form;
$form->addText('title', 'Titlu:');
// aici se adaugă alte câmpuri de formular
$form->addSubmit('send', 'Trimite');
$form->onSuccess[] = [$this, 'processForm'];
return $form;
}
public function processForm(Form $form, array $data): void
{
try {
// procesarea datelor trimise
$this->facade->process($data);
} catch (AnyModelException $e) {
$form->addError('...');
return;
}
// declanșarea evenimentului
$this->onSave($this, $data);
}
}
Vom crea și o fabrică care va produce această componentă. Este suficient să înregistrăm interfața sa:
interface EditControlFactory
{
function create(): EditControl;
}
Și să o adăugăm în fișierul de configurare:
services:
- EditControlFactory
Și acum putem solicita fabrica și o putem utiliza în presenter:
class MyPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private EditControlFactory $controlFactory,
) {
}
protected function createComponentEditForm(): EditControl
{
$control = $this->controlFactory->create();
$control->onSave[] = function (EditControl $control, $data) {
$this->redirect('this');
// sau redirecționăm către rezultatul editării, de ex.:
// $this->redirect('detail', ['id' => $data->id]);
};
return $control;
}
}