Nette Documentation Preview

syntax
Wiederverwendung von Formularen an mehreren Stellen
***************************************************

.[perex]
In Nette haben Sie mehrere Möglichkeiten, dasselbe Formular an mehreren Stellen zu verwenden und Code nicht zu duplizieren. In diesem Artikel zeigen wir Ihnen verschiedene Lösungen, einschließlich solcher, die Sie vermeiden sollten.


Formular-Factory
================

Eine der grundlegenden Herangehensweisen, dieselbe Komponente an mehreren Stellen zu verwenden, ist die Erstellung einer Methode oder Klasse, die diese Komponente generiert, und der anschließende Aufruf dieser Methode an verschiedenen Stellen der Anwendung. Eine solche Methode oder Klasse wird *Factory* genannt. Bitte nicht verwechseln mit dem Entwurfsmuster *Factory Method*, das eine spezifische Verwendung von Factories beschreibt und nichts mit diesem Thema zu tun hat.

Als Beispiel erstellen wir eine Factory, die ein Bearbeitungsformular erstellt:

```php
use Nette\Application\UI\Form;

class FormFactory
{
	public function createEditForm(): Form
	{
		$form = new Form;
		$form->addText('title', 'Titel:');
		// hier werden weitere Formularfelder hinzugefügt
		$form->addSubmit('send', 'Senden');
		return $form;
	}
}
```

Nun können Sie diese Factory an verschiedenen Stellen in Ihrer Anwendung verwenden, beispielsweise in Presentern oder Komponenten. Und zwar, indem Sie sie [als Abhängigkeit anfordern|dependency-injection:passing-dependencies]. Zuerst schreiben wir also die Klasse in die Konfigurationsdatei:

```neon
services:
	- FormFactory
```

Und dann verwenden wir sie im Presenter:


```php
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 Formular-Factory um weitere Methoden zur Erstellung anderer Formulartypen erweitern, je nach Bedarf Ihrer Anwendung. Und natürlich können wir auch eine Methode hinzufügen, die ein Basisformular ohne Elemente erstellt, und diese werden die anderen Methoden nutzen:

```php
class FormFactory
{
	public function createForm(): Form
	{
		$form = new Form;
		return $form;
	}

	public function createEditForm(): Form
	{
		$form = $this->createForm();
		$form->addText('title', 'Titel:');
		// hier werden weitere Formularfelder hinzugefügt
		$form->addSubmit('send', 'Senden');
		return $form;
	}
}
```

Die Methode `createForm()` tut bisher nichts Nützliches, aber das wird sich schnell ändern.


Abhängigkeiten der Factory
==========================

Mit der Zeit stellt sich heraus, dass wir Formulare mehrsprachig benötigen. Das bedeutet, dass wir allen Formularen einen sogenannten [Translator|forms:rendering#translation] zuweisen müssen. Zu diesem Zweck passen wir die Klasse `FormFactory` so an, dass sie das `Translator`-Objekt als Abhängigkeit im Konstruktor akzeptiert und es an das Formular übergibt:

```php
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 spezifische Formulare erstellen, genügt es, den Translator nur in ihr zu setzen. Und wir sind fertig. Es ist nicht nötig, den Code irgendeines Presenters oder einer Komponente zu ändern, was großartig ist.


Mehrere Factory-Klassen
=======================

Alternativ können Sie mehrere Klassen für jedes Formular erstellen, das Sie in Ihrer Anwendung verwenden möchten. Dieser Ansatz kann die Lesbarkeit des Codes erhöhen und die Verwaltung von Formularen erleichtern. Die ursprüngliche `FormFactory` lassen wir nur ein reines Formular mit Grundkonfiguration erstellen (z.B. mit Übersetzungsunterstützung) und für das Bearbeitungsformular erstellen wir eine neue Factory `EditFormFactory`.

```php
class FormFactory
{
	public function __construct(
		private Translator $translator,
	) {
	}

	public function create(): Form
	{
		$form = new Form;
		$form->setTranslator($this->translator);
		return $form;
	}
}


// ✅ Verwendung von Komposition
class EditFormFactory
{
	public function __construct(
		private FormFactory $formFactory,
	) {
	}

	public function create(): Form
	{
		$form = $this->formFactory->create();
		// hier werden weitere Formularfelder hinzugefügt
		$form->addSubmit('send', 'Senden');
		return $form;
	}
}
```

Sehr wichtig ist, dass die Bindung zwischen den Klassen `FormFactory` und `EditFormFactory` durch [Komposition|nette:introduction-to-object-oriented-programming#composition] realisiert wird, nicht durch [objektorientierte Vererbung|nette:introduction-to-object-oriented-programming#inheritance]:

```php
// ⛔ SO NICHT! HIER GEHÖRT KEINE VERERBUNG HIN
class EditFormFactory extends FormFactory
{
	public function create(): Form
	{
		$form = parent::create();
		$form->addText('title', 'Titel:');
		// hier werden weitere Formularfelder hinzugefügt
		$form->addSubmit('send', 'Senden');
		return $form;
	}
}
```

Die Verwendung von Vererbung wäre in diesem Fall völlig kontraproduktiv. Sie würden sehr schnell auf Probleme stoßen. Zum Beispiel, wenn Sie der Methode `create()` Parameter hinzufügen möchten; PHP würde einen Fehler melden, dass ihre Signatur von der der Elternklasse abweicht. Oder bei der Übergabe von Abhängigkeiten an die Klasse `EditFormFactory` über den Konstruktor. Es würde eine Situation entstehen, die wir [Constructor Hell |dependency-injection:passing-dependencies#Constructor hell] nennen.

Im Allgemeinen ist es besser, [Komposition der Vererbung vorzuziehen|dependency-injection:faq#composition-vs-inheritance].


Formularverarbeitung
====================

Die Verarbeitung des Formulars, die nach erfolgreichem Absenden aufgerufen wird, kann auch Teil der Factory-Klasse sein. Sie wird so funktionieren, dass sie die gesendeten Daten zur Verarbeitung an das Modell übergibt. Eventuelle Fehler [gibt sie zurück |forms:validation#errors-during-processing] an das Formular. Das Modell wird im folgenden Beispiel durch die Klasse `Facade` repräsentiert:

```php
class EditFormFactory
{
	public function __construct(
		private FormFactory $formFactory,
		private Facade $facade,
	) {
	}

	public function create(): Form
	{
		$form = $this->formFactory->create();
		$form->addText('title', 'Titel:');
		// hier werden weitere Formularfelder hinzugefügt
		$form->addSubmit('send', 'Senden');
		$form->onSuccess[] = [$this, 'processForm'];
		return $form;
	}

	public function processForm(Form $form, array $data): void
	{
		try {
			// Verarbeitung der gesendeten Daten
			$this->facade->process($data);

		} catch (AnyModelException $e) {
			$form->addError('...');
		}
	}
}
```

Die eigentliche Weiterleitung überlassen wir jedoch dem Presenter. Dieser fügt dem `onSuccess`-Ereignis einen weiteren Handler hinzu, der die Weiterleitung durchführt. Dadurch wird es möglich, das Formular in verschiedenen Presentern zu verwenden und in jedem woandershin weiterzuleiten.

```php
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('Der Datensatz wurde gespeichert');
			$this->redirect('Homepage:');
		};
		return $form;
	}
}
```

Diese Lösung nutzt die Eigenschaft von Formularen, dass, wenn `addError()` über dem Formular oder einem seiner Elemente aufgerufen wird, der nächste `onSuccess`-Handler nicht mehr aufgerufen wird.


Vererbung von der Klasse Form
=============================

Das erstellte Formular soll kein Nachkomme der Klasse `Form` sein. Mit anderen Worten, verwenden Sie nicht diese Lösung:

```php
// ⛔ SO NICHT! HIER GEHÖRT KEINE VERERBUNG HIN
class EditForm extends Form
{
	public function __construct(Translator $translator)
	{
		parent::__construct();
		$this->addText('title', 'Titel:');
		// hier werden weitere Formularfelder hinzugefügt
		$this->addSubmit('send', 'Senden');
		$this->setTranslator($translator);
	}
}
```

Anstatt das Formular im Konstruktor zu erstellen, verwenden Sie eine Factory.

Es ist wichtig zu erkennen, dass die Klasse `Form` in erster Linie ein Werkzeug zur Erstellung eines Formulars ist, also ein *Form Builder*. Und das erstellte Formular kann als ihr Produkt betrachtet werden. Aber ein Produkt ist kein spezifischer Fall eines Builders, es gibt keine *is a*-Beziehung zwischen ihnen, die die Grundlage der Vererbung bildet.


Komponente mit Formular
=======================

Ein völlig anderer Ansatz ist die Erstellung einer [Komponente|application:components], die ein Formular enthält. Dies eröffnet neue Möglichkeiten, zum Beispiel das Formular auf spezifische Weise zu rendern, da die Komponente auch ein Template enthält. Oder man kann Signale für die AJAX-Kommunikation und das Nachladen von Informationen in das Formular nutzen, zum Beispiel für Vorschläge usw.


```php
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', 'Titel:');
		// hier werden weitere Formularfelder hinzugefügt
		$form->addSubmit('send', 'Senden');
		$form->onSuccess[] = [$this, 'processForm'];

		return $form;
	}

	public function processForm(Form $form, array $data): void
	{
		try {
			// Verarbeitung der gesendeten Daten
			$this->facade->process($data);

		} catch (AnyModelException $e) {
			$form->addError('...');
			return;
		}

		// Ereignis auslösen
		$this->onSave($this, $data);
	}
}
```

Wir erstellen noch eine Factory, die diese Komponente herstellt. Es genügt, [ihr Interface zu definieren|application:components#components-with-dependencies]:

```php
interface EditControlFactory
{
	function create(): EditControl;
}
```

Und in die Konfigurationsdatei hinzufügen:

```neon
services:
	- EditControlFactory
```

Und nun können wir die Factory anfordern und im Presenter verwenden:

```php
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 wir leiten zum Ergebnis der Bearbeitung weiter, z.B.:
			// $this->redirect('detail', ['id' => $data->id]);
		};

		return $control;
	}
}
```

{{sitename: Best Practices}}

Wiederverwendung von Formularen an mehreren Stellen

In Nette haben Sie mehrere Möglichkeiten, dasselbe Formular an mehreren Stellen zu verwenden und Code nicht zu duplizieren. In diesem Artikel zeigen wir Ihnen verschiedene Lösungen, einschließlich solcher, die Sie vermeiden sollten.

Formular-Factory

Eine der grundlegenden Herangehensweisen, dieselbe Komponente an mehreren Stellen zu verwenden, ist die Erstellung einer Methode oder Klasse, die diese Komponente generiert, und der anschließende Aufruf dieser Methode an verschiedenen Stellen der Anwendung. Eine solche Methode oder Klasse wird Factory genannt. Bitte nicht verwechseln mit dem Entwurfsmuster Factory Method, das eine spezifische Verwendung von Factories beschreibt und nichts mit diesem Thema zu tun hat.

Als Beispiel erstellen wir eine Factory, die ein Bearbeitungsformular erstellt:

use Nette\Application\UI\Form;

class FormFactory
{
	public function createEditForm(): Form
	{
		$form = new Form;
		$form->addText('title', 'Titel:');
		// hier werden weitere Formularfelder hinzugefügt
		$form->addSubmit('send', 'Senden');
		return $form;
	}
}

Nun können Sie diese Factory an verschiedenen Stellen in Ihrer Anwendung verwenden, beispielsweise in Presentern oder Komponenten. Und zwar, indem Sie sie als Abhängigkeit anfordern. Zuerst schreiben wir also die Klasse in die Konfigurationsdatei:

services:
	- FormFactory

Und dann verwenden wir sie im 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 () {
			// Verarbeitung der gesendeten Daten
		};
		return $form;
	}
}

Sie können die Formular-Factory um weitere Methoden zur Erstellung anderer Formulartypen erweitern, je nach Bedarf Ihrer Anwendung. Und natürlich können wir auch eine Methode hinzufügen, die ein Basisformular ohne Elemente erstellt, und diese werden die anderen Methoden nutzen:

class FormFactory
{
	public function createForm(): Form
	{
		$form = new Form;
		return $form;
	}

	public function createEditForm(): Form
	{
		$form = $this->createForm();
		$form->addText('title', 'Titel:');
		// hier werden weitere Formularfelder hinzugefügt
		$form->addSubmit('send', 'Senden');
		return $form;
	}
}

Die Methode createForm() tut bisher nichts Nützliches, aber das wird sich schnell ändern.

Abhängigkeiten der Factory

Mit der Zeit stellt sich heraus, dass wir Formulare mehrsprachig benötigen. Das bedeutet, dass wir allen Formularen einen sogenannten Translator zuweisen müssen. Zu diesem Zweck passen wir die Klasse FormFactory so an, dass sie das Translator-Objekt als Abhängigkeit im Konstruktor akzeptiert und es 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 spezifische Formulare erstellen, genügt es, den Translator nur in ihr zu setzen. Und wir sind fertig. Es ist nicht nötig, den Code irgendeines Presenters oder einer Komponente zu ändern, was großartig ist.

Mehrere Factory-Klassen

Alternativ können Sie mehrere Klassen für jedes Formular erstellen, das Sie in Ihrer Anwendung verwenden möchten. Dieser Ansatz kann die Lesbarkeit des Codes erhöhen und die Verwaltung von Formularen erleichtern. Die ursprüngliche FormFactory lassen wir nur ein reines Formular mit Grundkonfiguration erstellen (z.B. mit Übersetzungsunterstützung) und für das Bearbeitungsformular erstellen wir eine neue Factory EditFormFactory.

class FormFactory
{
	public function __construct(
		private Translator $translator,
	) {
	}

	public function create(): Form
	{
		$form = new Form;
		$form->setTranslator($this->translator);
		return $form;
	}
}


// ✅ Verwendung von Komposition
class EditFormFactory
{
	public function __construct(
		private FormFactory $formFactory,
	) {
	}

	public function create(): Form
	{
		$form = $this->formFactory->create();
		// hier werden weitere Formularfelder hinzugefügt
		$form->addSubmit('send', 'Senden');
		return $form;
	}
}

Sehr wichtig ist, dass die Bindung zwischen den Klassen FormFactory und EditFormFactory durch Komposition realisiert wird, nicht durch objektorientierte Vererbung:

// ⛔ SO NICHT! HIER GEHÖRT KEINE VERERBUNG HIN
class EditFormFactory extends FormFactory
{
	public function create(): Form
	{
		$form = parent::create();
		$form->addText('title', 'Titel:');
		// hier werden weitere Formularfelder hinzugefügt
		$form->addSubmit('send', 'Senden');
		return $form;
	}
}

Die Verwendung von Vererbung wäre in diesem Fall völlig kontraproduktiv. Sie würden sehr schnell auf Probleme stoßen. Zum Beispiel, wenn Sie der Methode create() Parameter hinzufügen möchten; PHP würde einen Fehler melden, dass ihre Signatur von der der Elternklasse abweicht. Oder bei der Übergabe von Abhängigkeiten an die Klasse EditFormFactory über den Konstruktor. Es würde eine Situation entstehen, die wir Constructor Hell nennen.

Im Allgemeinen ist es besser, Komposition der Vererbung vorzuziehen.

Formularverarbeitung

Die Verarbeitung des Formulars, die nach erfolgreichem Absenden aufgerufen wird, kann auch Teil der Factory-Klasse sein. Sie wird so funktionieren, dass sie die gesendeten Daten zur Verarbeitung an das Modell übergibt. Eventuelle Fehler gibt sie zurück an das Formular. Das Modell wird im folgenden Beispiel 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', 'Titel:');
		// hier werden weitere Formularfelder hinzugefügt
		$form->addSubmit('send', 'Senden');
		$form->onSuccess[] = [$this, 'processForm'];
		return $form;
	}

	public function processForm(Form $form, array $data): void
	{
		try {
			// Verarbeitung der gesendeten Daten
			$this->facade->process($data);

		} catch (AnyModelException $e) {
			$form->addError('...');
		}
	}
}

Die eigentliche Weiterleitung überlassen wir jedoch dem Presenter. Dieser fügt dem onSuccess-Ereignis einen weiteren Handler hinzu, der die Weiterleitung durchführt. Dadurch wird es möglich, das Formular in verschiedenen Presentern zu verwenden und in jedem woandershin weiterzuleiten.

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('Der Datensatz wurde gespeichert');
			$this->redirect('Homepage:');
		};
		return $form;
	}
}

Diese Lösung nutzt die Eigenschaft von Formularen, dass, wenn addError() über dem Formular oder einem seiner Elemente aufgerufen wird, der nächste onSuccess-Handler nicht mehr aufgerufen wird.

Vererbung von der Klasse Form

Das erstellte Formular soll kein Nachkomme der Klasse Form sein. Mit anderen Worten, verwenden Sie nicht diese Lösung:

// ⛔ SO NICHT! HIER GEHÖRT KEINE VERERBUNG HIN
class EditForm extends Form
{
	public function __construct(Translator $translator)
	{
		parent::__construct();
		$this->addText('title', 'Titel:');
		// hier werden weitere Formularfelder hinzugefügt
		$this->addSubmit('send', 'Senden');
		$this->setTranslator($translator);
	}
}

Anstatt das Formular im Konstruktor zu erstellen, verwenden Sie eine Factory.

Es ist wichtig zu erkennen, dass die Klasse Form in erster Linie ein Werkzeug zur Erstellung eines Formulars ist, also ein Form Builder. Und das erstellte Formular kann als ihr Produkt betrachtet werden. Aber ein Produkt ist kein spezifischer Fall eines Builders, es gibt keine is a-Beziehung zwischen ihnen, die die Grundlage der Vererbung bildet.

Komponente mit Formular

Ein völlig anderer Ansatz ist die Erstellung einer Komponente, die ein Formular enthält. Dies eröffnet neue Möglichkeiten, zum Beispiel das Formular auf spezifische Weise zu rendern, da die Komponente auch ein Template enthält. Oder man kann Signale für die AJAX-Kommunikation und das Nachladen von Informationen in das Formular nutzen, zum Beispiel für Vorschläge 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', 'Titel:');
		// hier werden weitere Formularfelder hinzugefügt
		$form->addSubmit('send', 'Senden');
		$form->onSuccess[] = [$this, 'processForm'];

		return $form;
	}

	public function processForm(Form $form, array $data): void
	{
		try {
			// Verarbeitung der gesendeten Daten
			$this->facade->process($data);

		} catch (AnyModelException $e) {
			$form->addError('...');
			return;
		}

		// Ereignis auslösen
		$this->onSave($this, $data);
	}
}

Wir erstellen noch eine Factory, die diese Komponente herstellt. Es genügt, ihr Interface zu definieren:

interface EditControlFactory
{
	function create(): EditControl;
}

Und in die Konfigurationsdatei hinzufügen:

services:
	- EditControlFactory

Und nun können wir die Factory anfordern und im Presenter 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 wir leiten zum Ergebnis der Bearbeitung weiter, z.B.:
			// $this->redirect('detail', ['id' => $data->id]);
		};

		return $control;
	}
}