Formanyomtatványok újrafelhasználása több helyen
A Nette-ben több lehetőséged is van arra, hogy ugyanazt az űrlapot több helyen újra felhasználd anélkül, hogy a kódot duplikálnád. Ebben a cikkben áttekintjük a különböző megoldásokat, beleértve azokat is, amelyeket érdemes elkerülni.
Form Factory
Egy komponens több helyen történő használatának egyik alapvető megközelítése, hogy létrehozunk egy metódust vagy osztályt, amely létrehozza a komponenst, majd ezt a metódust az alkalmazás különböző helyein hívjuk meg. Az ilyen metódust vagy osztályt gyárnak nevezzük. Kérjük, ne keverjük össze a factory method tervezési mintával, amely a gyárak használatának egy speciális módját írja le, és nem kapcsolódik ehhez a témához.
Példaként hozzunk létre egy gyárat, amely egy szerkesztési űrlapot készít:
use Nette\Application\UI\Form;
class FormFactory
{
public function createEditForm(): Form
{
$form = new Form;
$form->addText('title', 'Title:');
// itt további űrlapmezők kerülnek hozzáadásra
$form->addSubmit('send', 'Save');
return $form;
}
}
Ezt a gyárat az alkalmazás különböző helyein használhatja, például prezenterekben vagy komponensekben. Ezt pedig úgy tesszük, hogy függőségként kérjük be. Tehát először írjuk be az osztályt a konfigurációs fájlba:
services:
- FormFactory
És aztán használjuk a prezenterben:
class MyPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private FormFactory $formFactory,
) {
}
protected function createComponentEditForm(): Form
{
$form = $this->formFactory->createEditForm();
$form->onSuccess[] = function () {
// a küldött adatok feldolgozása
};
return $form;
}
}
A form factory-t további metódusokkal bővíthetjük, hogy más típusú űrlapokat hozzunk létre az alkalmazásunknak megfelelően. És természetesen hozzáadhat egy olyan metódust is, amely egy elemek nélküli alap űrlapot hoz létre, amelyet a többi metódus használni fog:
class FormFactory
{
public function createForm(): Form
{
$form = new Form;
return $form;
}
public function createEditForm(): Form
{
$form = $this->createForm();
$form->addText('title', 'Title:');
// itt további űrlapmezők kerülnek hozzáadásra
$form->addSubmit('send', 'Save');
return $form;
}
}
A createForm()
metódus egyelőre semmi hasznosat nem csinál, de ez hamarosan megváltozik.
Gyári függőségek
Idővel nyilvánvalóvá válik, hogy az űrlapoknak többnyelvűnek kell lenniük. Ez azt jelenti, hogy minden űrlaphoz be
kell állítanunk egy fordítót. Ehhez módosítjuk a
FormFactory
osztályt, hogy a konstruktorban függőségként elfogadja a Translator
objektumot, és
átadja azt az űrlapnak:
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;
}
//...
}
Mivel a createForm()
metódust más, konkrét űrlapokat létrehozó metódusok is meghívják, a fordítót csak
ebben a metódusban kell beállítanunk. És kész is vagyunk. Nem kell semmilyen prezenter vagy komponens kódot módosítani,
ami nagyszerű.
További gyári osztályok
Alternatívaként több osztályt is létrehozhat minden egyes űrlaphoz, amelyet az alkalmazásban használni szeretne. Ez a
megközelítés növelheti a kód olvashatóságát és megkönnyítheti az űrlapok kezelését. Hagyja meg az eredeti
FormFactory
címet, hogy csak egy tiszta űrlapot hozzon létre alapvető konfigurációval (például
fordítástámogatással), és hozzon létre egy új gyári EditFormFactory
címet a szerkesztési űrlaphoz.
class FormFactory
{
public function __construct(
private Translator $translator,
) {
}
public function create(): Form
{
$form = new Form;
$form->setTranslator($this->translator);
return $form;
}
}
// ✅ a kompozíció használata
class EditFormFactory
{
public function __construct(
private FormFactory $formFactory,
) {
}
public function create(): Form
{
$form = $this->formFactory->create();
// itt további űrlapmezők kerülnek hozzáadásra
$form->addSubmit('send', 'Save');
return $form;
}
}
Nagyon fontos, hogy a FormFactory
és a EditFormFactory
osztályok közötti kötés kompozícióval, nem pedig objektumörökléssel
valósul meg:
// ⛔ NO! AZ ÖRÖKSÉG NEM TARTOZIK IDE
class EditFormFactory extends FormFactory
{
public function create(): Form
{
$form = parent::create();
$form->addText('title', 'Title:');
// további űrlapmezők kerülnek ide
$form->addSubmit('send', 'Save');
return $form;
}
}
Az öröklés használata ebben az esetben teljesen kontraproduktív lenne. Nagyon gyorsan problémákba ütközne. Például,
ha paramétereket akarnánk hozzáadni a create()
metódushoz; a PHP hibát jelezne, hogy a metódus aláírása
eltér a szülőétől. Vagy ha a konstruktoron keresztül átadnánk egy függőséget a EditFormFactory
osztálynak. Ez azt okozná, amit mi konstruktor
pokolnak hívunk.
Általában jobb, ha a kompozíciót előnyben részesítjük az örökléssel szemben.
Form kezelés
A sikeres elküldés után meghívott űrlapkezelő lehet egy gyári osztály része is. Ez úgy fog működni, hogy a
beküldött adatokat átadja a modellnek feldolgozásra. Az esetleges hibákat visszaadja az űrlapnak. A következő példában a
modellt a Facade
osztály képviseli:
class EditFormFactory
{
public function __construct(
private FormFactory $formFactory,
private Facade $facade,
) {
}
public function create(): Form
{
$form = $this->formFactory->create();
$form->addText('title', 'Title:');
// itt további űrlapmezők kerülnek hozzáadásra
$form->addSubmit('send', 'Save');
$form->onSuccess[] = [$this, 'processForm'];
return $form;
}
public function processForm(Form $form, array $data): void
{
try {
// a beküldött adatok feldolgozása
$this->facade->process($data);
} catch (AnyModelException $e) {
$form->addError('...');
}
}
}
Hagyjuk, hogy a prezenter maga kezelje az átirányítást. A onSuccess
eseményhez hozzáad egy másik kezelőt,
amely elvégzi az átirányítást. Ez lehetővé teszi, hogy az űrlapot különböző prezenterekben lehessen használni, és
mindegyik más helyre irányíthasson át.
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;
}
}
Ez a megoldás kihasználja az űrlapok azon tulajdonságát, hogy amikor a addError()
meghívásra kerül egy
űrlapon vagy annak elemén, a következő onSuccess
kezelő nem hívódik meg.
Öröklés a Form osztályból
Egy épített űrlap nem lehet egy űrlap gyermeke. Más szóval, ne használja ezt a megoldást:
// ⛔ NO! AZ ÖRÖKSÉG NEM TARTOZIK IDE
class EditForm extends Form
{
public function __construct(Translator $translator)
{
parent::__construct();
$form->addText('title', 'Title:');
// további űrlapmezők kerülnek ide
$form->addSubmit('send', 'Save');
$form->setTranslator($translator);
}
}
Ahelyett, hogy az űrlapot a konstruktorban építenéd fel, használd a gyárat.
Fontos tisztában lenni azzal, hogy a Form
osztály elsősorban egy űrlap összeállításának eszköze, azaz
egy űrlapépítő. Az összerakott űrlap pedig a termékének tekinthető. A termék azonban nem az építő speciális esete;
nincs köztük is a kapcsolat, ami az öröklés alapját képezi.
Form komponens
Egy teljesen más megközelítés egy olyan komponens létrehozása, amely egy űrlapot tartalmaz. Ez új lehetőségeket ad, például az űrlap meghatározott módon történő megjelenítésére, mivel a komponens tartalmaz egy sablont. Vagy jeleket lehet használni az AJAX-kommunikációhoz és az információk betöltéséhez az űrlapba, például a hintinghez stb.
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:');
// itt további űrlapmezők kerülnek hozzáadásra
$form->addSubmit('send', 'Save');
$form->onSuccess[] = [$this, 'processForm'];
return $form;
}
public function processForm(Form $form, array $data): void
{
try {
// a beküldött adatok feldolgozása
$this->facade->process($data);
} catch (AnyModelException $e) {
$form->addError('...');
return;
}
// eseményhívás
$this->onSave($this, $data);
}
}
Hozzunk létre egy gyárat, amely ezt a komponenst fogja előállítani. Elég, ha megírjuk az interfészét:
interface EditControlFactory
{
function create(): EditControl;
}
És adjuk hozzá a konfigurációs fájlhoz:
services:
- EditControlFactory
És most már kérhetjük a gyárat, és használhatjuk a prezenterben:
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');
// vagy átirányítás a szerkesztés eredményére, pl.:
// $this->redirect('részlet', ['id' => $data->id]);
};
return $control;
}
}