Formulare an mehreren Stellen wiederverwenden
In Nette haben Sie mehrere Möglichkeiten, dasselbe Formular an mehreren Stellen wiederzuverwenden, ohne Code zu duplizieren. In diesem Artikel gehen wir auf die verschiedenen Lösungen ein, einschließlich derer, die Sie vermeiden sollten.
Formular-Fabrik
Ein grundlegender Ansatz für die Verwendung derselben Komponente an mehreren Stellen besteht darin, eine Methode oder Klasse zu erstellen, die die Komponente erzeugt, und diese Methode dann an verschiedenen Stellen in der Anwendung aufzurufen. Eine solche Methode oder Klasse wird als Factory bezeichnet. Bitte nicht mit dem Entwurfsmuster Fabrikmethode verwechseln, das eine spezielle Art der Verwendung von Fabriken beschreibt und nicht mit diesem Thema zusammenhängt.
Lassen Sie uns als Beispiel eine Fabrik erstellen, die ein Bearbeitungsformular erstellt:
use Nette\Application\UI\Form;
class FormFactory
{
public function createEditForm(): Form
{
$form = new Form;
$form->addText('title', 'Title:');
// zusätzliche Formularfelder werden hier hinzugefügt
$form->addSubmit('send', 'Save');
return $form;
}
}
Jetzt können Sie diese Fabrik an verschiedenen Stellen in Ihrer Anwendung verwenden, zum Beispiel in Presentern oder Komponenten. Und wir tun dies, indem wir sie als Abhängigkeit anfordern. Zuerst schreiben wir also die Klasse in die Konfigurationsdatei:
services:
- FormFactory
Und dann verwenden wir sie im Präsentator:
class MyPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private FormFactory $formFactory,
) {
}
protected function createComponentEditForm(): Form
{
$form = $this->formFactory->createEditForm();
$form->onSuccess[] = function () {
// Verarbeitung der gesendeten Daten
};
return $form;
}
}
Sie können die Formularfabrik mit zusätzlichen Methoden erweitern, um andere Arten von Formularen für Ihre Anwendung zu erstellen. Und natürlich können Sie auch eine Methode hinzufügen, die ein Basisformular ohne Elemente erstellt, das die anderen Methoden verwenden werden:
class FormFactory
{
public function createForm(): Form
{
$form = new Form;
return $form;
}
public function createEditForm(): Form
{
$form = $this->createForm();
$form->addText('title', 'Title:');
// zusätzliche Formularfelder werden hier hinzugefügt
$form->addSubmit('send', 'Save');
return $form;
}
}
Die Methode createForm()
tut noch nichts Nützliches, aber das wird sich schnell ändern.
Abhängigkeiten von der Fabrik
Mit der Zeit wird sich herausstellen, dass die Formulare mehrsprachig sein müssen. Das bedeutet, dass wir einen Übersetzer für alle Formulare einrichten müssen. Zu diesem
Zweck ändern wir die Klasse FormFactory
so, dass sie das Objekt Translator
als Abhängigkeit im
Konstruktor akzeptiert und an das Formular übergibt:
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;
}
//...
}
Da die Methode createForm()
auch von anderen Methoden aufgerufen wird, die bestimmte Formulare erstellen, müssen
wir den Übersetzer nur in dieser Methode festlegen. Und schon sind wir fertig. Es ist nicht nötig, den Code des Presenters oder
der Komponente zu ändern, was großartig ist.
Weitere Factory-Klassen
Alternativ können Sie für jedes Formular, das Sie in Ihrer Anwendung verwenden möchten, mehrere Klassen erstellen. Dieser
Ansatz kann die Lesbarkeit des Codes erhöhen und die Verwaltung der Formulare erleichtern. Belassen Sie das Original
FormFactory
, um nur ein reines Formular mit Grundkonfiguration zu erstellen (z. B. mit Übersetzungsunterstützung),
und erstellen Sie eine neue Fabrik EditFormFactory
für das Bearbeitungsformular.
class FormFactory
{
public function __construct(
private Translator $translator,
) {
}
public function create(): Form
{
$form = new Form;
$form->setTranslator($this->translator);
return $form;
}
}
// ✅ Verwendung der Zusammensetzung
class EditFormFactory
{
public function __construct(
private FormFactory $formFactory,
) {
}
public function create(): Form
{
$form = $this->formFactory->create();
// hier werden zusätzliche Formularfelder hinzugefügt
$form->addSubmit('send', 'Save');
return $form;
}
}
Es ist sehr wichtig, dass die Bindung zwischen den Klassen FormFactory
und EditFormFactory
durch Komposition und nicht durch Objektvererbung
implementiert wird:
// ⛔ NEIN! VERERBUNG GEHÖRT HIER NICHT HIN
class EditFormFactory extends FormFactory
{
public function create(): Form
{
$form = parent::create();
$form->addText('title', 'Title:');
// zusätzliche Formularfelder werden hier hinzugefügt
$form->addSubmit('send', 'Save');
return $form;
}
}
Die Verwendung von Vererbung wäre in diesem Fall völlig kontraproduktiv. Sie würden sehr schnell auf Probleme stoßen. Wenn
Sie z.B. der Methode create()
Parameter hinzufügen wollten, würde PHP einen Fehler melden, dass sich die Signatur
der Methode von der des Elternteils unterscheidet. Oder bei der Übergabe einer Abhängigkeit an die Klasse
EditFormFactory
über den Konstruktor. Dies würde zu dem führen, was wir Konstruktorhölle nennen.
Im Allgemeinen ist es besser, die Komposition der Vererbung vorzuziehen.
Handhabung von Formularen
Der Formular-Handler, der nach einer erfolgreichen Übermittlung aufgerufen wird, kann auch Teil einer Fabrikklasse sein. Er
arbeitet, indem er die übermittelten Daten zur Verarbeitung an das Modell weitergibt. Eventuelle Fehler werden an das Formular zurückgegeben. Das Modell im folgenden Beispiel wird
durch die Klasse Facade
repräsentiert:
class EditFormFactory
{
public function __construct(
private FormFactory $formFactory,
private Facade $facade,
) {
}
public function create(): Form
{
$form = $this->formFactory->create();
$form->addText('title', 'Title:');
// zusätzliche Formularfelder werden hier hinzugefügt
$form->addSubmit('send', 'Save');
$form->onSuccess[] = [$this, 'processForm'];
return $form;
}
public function processForm(Form $form, array $data): void
{
try {
// Verarbeitung der übermittelten Daten
$this->facade->process($data);
} catch (AnyModelException $e) {
$form->addError('...');
}
}
}
Der Präsentator soll die Umleitung selbst durchführen. Er fügt dem Ereignis onSuccess
einen weiteren Handler
hinzu, der die Umleitung durchführt. Auf diese Weise kann das Formular in verschiedenen Präsentatoren verwendet werden, und
jeder kann an eine andere Stelle weiterleiten.
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;
}
}
Diese Lösung macht sich die Eigenschaft von Formularen zunutze, dass beim Aufruf von addError()
auf einem
Formular oder seinem Element der nächste onSuccess
-Handler nicht aufgerufen wird.
Vererbung von der Formularklasse
Ein erstelltes Formular sollte nicht das Kind eines Formulars sein. Mit anderen Worten: Verwenden Sie diese Lösung nicht:
// ⛔ NEIN! VERERBUNG GEHÖRT HIER NICHT HIN
class EditForm extends Form
{
public function __construct(Translator $translator)
{
parent::__construct();
$form->addText('title', 'Title:');
// zusätzliche Formularfelder werden hier hinzugefügt
$form->addSubmit('send', 'Save');
$form->setTranslator($translator);
}
}
Anstatt das Formular im Konstruktor zu erstellen, verwenden Sie die Fabrik.
Es ist wichtig zu erkennen, dass die Klasse Form
in erster Linie ein Werkzeug zum Zusammenstellen eines Formulars
ist, d.h. ein Formularersteller. Und das zusammengesetzte Formular kann als ihr Produkt betrachtet werden. Das Produkt ist jedoch
kein Sonderfall des Builders; es gibt keine ist eine Beziehung zwischen ihnen, die die Grundlage der Vererbung
bildet.
Formular-Komponente
Ein völlig anderer Ansatz besteht darin, eine Komponente zu erstellen, die ein Formular enthält. Dadurch ergeben sich neue Möglichkeiten, z. B. um das Formular auf eine bestimmte Art und Weise zu rendern, da die Komponente eine Vorlage enthält. Oder es können Signale für die AJAX-Kommunikation und das Laden von Informationen in das Formular verwendet werden, z. B. für Hinting usw.
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:');
// zusätzliche Formularfelder werden hier hinzugefügt
$form->addSubmit('send', 'Save');
$form->onSuccess[] = [$this, 'processForm'];
return $form;
}
public function processForm(Form $form, array $data): void
{
try {
// Verarbeitung der übermittelten Daten
$this->facade->process($data);
} catch (AnyModelException $e) {
$form->addError('...');
return;
}
// Ereignisaufruf
$this->onSave($this, $data);
}
}
Lassen Sie uns eine Fabrik erstellen, die diese Komponente produzieren wird. Es genügt, ihre Schnittstelle zu schreiben:
interface EditControlFactory
{
function create(): EditControl;
}
Und fügen Sie sie der Konfigurationsdatei hinzu:
services:
- EditControlFactory
Jetzt können wir die Fabrik anfordern und sie im Präsentator verwenden:
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');
// oder auf das Ergebnis der Bearbeitung umleiten, z. B:
// $this->redirect('detail', ['id' => $data->id]);
};
return $control;
}
}