Ponovna uporaba obrazcev na več mestih
V Nette imate več možnosti za ponovno uporabo istega obrazca na več mestih brez podvajanja kode. V tem članku bomo pregledali različne rešitve, vključno s tistimi, ki se jim morate izogniti.
Tovarna obrazcev
Osnovni pristop k uporabi iste komponente na več mestih je ustvarjanje metode ali razreda, ki generira komponento, in nato klicanje te metode na različnih mestih v aplikaciji. Takšna metoda ali razred se imenuje factory. Ne zamenjujte z načrtovalskim vzorcem tovarniška metoda, ki opisuje poseben način uporabe tovarn in ni povezan s to temo.
Kot primer ustvarimo tovarno, ki bo ustvarila obrazec za urejanje:
use Nette\Application\UI\Form;
class FormFactory
{
public function createEditForm(): Form
{
$form = new Form;
$form->addText('title', 'Title:');
// tukaj so dodana dodatna polja obrazca
$form->addSubmit('send', 'Save');
return $form;
}
}
Zdaj lahko to tovarno uporabite na različnih mestih v aplikaciji, na primer v predstavitvah ali komponentah. To storimo tako, da jo zahtevamo kot odvisnost. Najprej bomo razred zapisali v konfiguracijsko datoteko:
services:
- FormFactory
Nato ga bomo uporabili v predstavitvi:
class MyPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private FormFactory $formFactory,
) {
}
protected function createComponentEditForm(): Form
{
$form = $this->formFactory->createEditForm();
$form->onSuccess[] = function () {
// obdelava poslanih podatkov
};
return $form;
}
}
Tovarno obrazcev lahko razširite z dodatnimi metodami in tako ustvarite druge vrste obrazcev, ki ustrezajo vaši aplikaciji. Seveda lahko dodate tudi metodo, ki ustvari osnovni obrazec brez elementov, ki ga bodo uporabljale druge metode:
class FormFactory
{
public function createForm(): Form
{
$form = new Form;
return $form;
}
public function createEditForm(): Form
{
$form = $this->createForm();
$form->addText('title', 'Title:');
// tukaj so dodana dodatna polja obrazca
$form->addSubmit('send', 'Save');
return $form;
}
}
Metoda createForm()
še ne počne ničesar uporabnega, vendar se bo to hitro spremenilo.
Tovarniške odvisnosti
Sčasoma se bo izkazalo, da potrebujemo večjezične obrazce. To pomeni, da moramo za vse obrazce vzpostaviti prevajalnik. V ta namen spremenimo razred
FormFactory
tako, da v konstruktorju sprejme objekt Translator
kot odvisnost in ga posreduje
obrazcu:
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;
}
//...
}
Ker metodo createForm()
kličejo tudi druge metode, ki ustvarjajo določene obrazce, moramo prevajalnik nastaviti
le v tej metodi. In končali smo. Ni treba spreminjati kode predstavnika ali komponente, kar je odlično.
Več tovarniških razredov
Ustvarite lahko tudi več razredov za vsak obrazec, ki ga želite uporabiti v svoji aplikaciji. Ta pristop lahko poveča
berljivost kode in olajša upravljanje obrazcev. Pustite prvotni FormFactory
za ustvarjanje samo čistega obrazca
z osnovno konfiguracijo (na primer s podporo za prevajanje) in ustvarite novo tovarno EditFormFactory
za obrazec za
urejanje.
class FormFactory
{
public function __construct(
private Translator $translator,
) {
}
public function create(): Form
{
$form = new Form;
$form->setTranslator($this->translator);
return $form;
}
}
// ✅ uporaba sestave
class EditFormFactory
{
public function __construct(
private FormFactory $formFactory,
) {
}
public function create(): Form
{
$form = $this->formFactory->create();
// tu so dodana dodatna polja obrazca
$form->addSubmit('send', 'Save');
return $form;
}
}
Zelo pomembno je, da se povezava med razredoma FormFactory
in EditFormFactory
izvaja s kompozicijo in ne z dedovanjem objektov:
// ⛔ NE! DEDIŠČINA NE SPADA SEM
class EditFormFactory extends FormFactory
{
public function create(): Form
{
$form = parent::create();
$form->addText('title', 'Title:');
// tu so dodana dodatna polja obrazca
$form->addSubmit('send', 'Save');
return $form;
}
}
Uporaba dedovanja bi bila v tem primeru popolnoma neproduktivna. Zelo hitro bi naleteli na težave. Na primer, če bi želeli
metodi create()
dodati parametre; PHP bi sporočil napako, ker se njen podpis razlikuje od podpisa nadrejene metode.
Ali pa pri posredovanju odvisnosti razredu EditFormFactory
prek konstruktorja. To bi povzročilo tako imenovani konstruktorski pekel.
Na splošno je bolje dati prednost sestavi pred dedovanjem.
Ravnanje z obrazci
Obvladovalnik obrazca, ki se pokliče po uspešni oddaji, je lahko tudi del tovarniškega razreda. Deloval bo tako, da bo
predložene podatke posredoval modelu v obdelavo. Morebitne napake bo posredoval nazaj v obrazec. Model v naslednjem primeru
predstavlja razred Facade
:
class EditFormFactory
{
public function __construct(
private FormFactory $formFactory,
private Facade $facade,
) {
}
public function create(): Form
{
$form = $this->formFactory->create();
$form->addText('title', 'Title:');
// tukaj so dodana dodatna polja obrazca
$form->addSubmit('send', 'Save');
$form->onSuccess[] = [$this, 'processForm'];
return $form;
}
public function processForm(Form $form, array $data): void
{
try {
// obdelava posredovanih podatkov
$this->facade->process($data);
} catch (AnyModelException $e) {
$form->addError('...');
}
}
}
Naj predstavnik sam poskrbi za preusmeritev. Dogodku onSuccess
bo dodal še eno izvajalko, ki bo izvedla
preusmeritev. To bo omogočilo uporabo obrazca v različnih predstavitvenih programih, pri čemer lahko vsak od njih preusmeri na
drugo mesto.
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('Záznam byl uložen');
$this->redirect('Homepage:');
};
return $form;
}
}
Ta rešitev izkorišča lastnost obrazcev, da se ob klicu addError()
na obrazcu ali njegovem elementu ne prikliče
naslednji izvajalec onSuccess
.
Dedovanje iz razreda Form
Vgrajeni obrazec naj ne bi bil otrok obrazca. Z drugimi besedami, ne uporabljajte te rešitve:
// ⛔ NE! DEDIŠČINA NE SPADA SEM
class EditForm extends Form
{
public function __construct(Translator $translator)
{
parent::__construct();
$form->addText('title', 'Title:');
// tu so dodana dodatna polja obrazca
$form->addSubmit('send', 'Save');
$form->setTranslator($translator);
}
}
Namesto da bi obrazec zgradili v konstruktorju, uporabite tovarno.
Pomembno se je zavedati, da je razred Form
predvsem orodje za sestavljanje obrazca, tj. gradnik obrazca.
Sestavljeni obrazec pa lahko štejemo za njegov izdelek. Vendar izdelek ni poseben primer gradnika; med njima ni razmerja is
a, ki je osnova dedovanja.
Komponenta obrazca
Povsem drugačen pristop je ustvarjanje komponente, ki vključuje obrazec. To daje nove možnosti, na primer prikazovanje obrazca na poseben način, saj komponenta vključuje predlogo. Ali pa se lahko signali uporabijo za komunikacijo AJAX in nalaganje informacij v obrazec, na primer za namige itd.
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', 'Title:');
// tukaj so dodana dodatna polja obrazca
$form->addSubmit('send', 'Save');
$form->onSuccess[] = [$this, 'processForm'];
return $form;
}
public function processForm(Form $form, array $data): void
{
try {
// obdelava posredovanih podatkov
$this->facade->process($data);
} catch (AnyModelException $e) {
$form->addError('...');
return;
}
// priklic dogodka
$this->onSave($this, $data);
}
}
Ustvarimo tovarno, ki bo izdelala to komponento. Dovolj je, če napišemo njen vmesnik:
interface EditControlFactory
{
function create(): EditControl;
}
in ga dodamo v konfiguracijsko datoteko:
services:
- EditControlFactory
Zdaj lahko zahtevamo tovarno in jo uporabimo v predstavitvenem programu:
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');
// ali preusmerite na rezultat urejanja, npr.:
// $this->reirect('detail', ['id' => $data->id]);
};
return $control;
}
}