Formular zum Erstellen und Bearbeiten eines Datensatzes

Wie kann man das Hinzufügen und Bearbeiten eines Datensatzes in Nette richtig umsetzen, indem man für beides das gleiche Formular verwendet?

In vielen Fällen sind die Formulare zum Hinzufügen und Bearbeiten eines Datensatzes identisch und unterscheiden sich nur durch die Beschriftung der Schaltfläche. Wir werden Beispiele für einfache Präsentationen zeigen, bei denen wir das Formular zuerst zum Hinzufügen eines Datensatzes und dann zum Bearbeiten verwenden und schließlich beide Lösungen kombinieren.

Hinzufügen eines Datensatzes

Ein Beispiel für einen Präsentator, der zum Hinzufügen eines Datensatzes verwendet wird. Die eigentliche Datenbankarbeit überlassen wir der Klasse Facade, deren Code für dieses Beispiel nicht relevant ist.

use Nette\Application\UI\Form;

class RecordPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private Facade $facade,
	) {
	}

	protected function createComponentRecordForm(): Form
	{
		$form = new Form;

		// ... Formularfelder hinzufügen ...

		$form->onSuccess[] = [$this, 'recordFormSucceeded'];
		return $form;
	}

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // Datensatz zur Datenbank hinzufügen
		$this->flashMessage("Erfolgreich hinzugefügt");
		$this->redirect('...');
	}

	public function renderAdd(): void
	{
		// ...
	}
}

Bearbeiten eines Datensatzes

Sehen wir uns nun an, wie ein Presenter zum Bearbeiten eines Datensatzes aussehen würde:

use Nette\Application\UI\Form;

class RecordPresenter extends Nette\Application\UI\Presenter
{
	private $record;

	public function __construct(
		private Facade $facade,
	) {
	}

	public function actionEdit(int $id): void
	{
		$record = $this->facade->get($id);
		if (
			!$record // die Existenz des Datensatzes zu überprüfen
			|| !$this->facade->isEditAllowed(/*...*/) // Berechtigungen prüfen
		) {
			$this->error(); // 404-Fehler
		}

		$this->record = $record;
	}

	protected function createComponentRecordForm(): Form
	{
		// Überprüfen, ob die Aktion "Bearbeiten" ist
		if ($this->getAction() !== 'edit') {
			$this->error();
		}

		$form = new Form;

		// ... Formularfelder hinzufügen ...

		$form->setDefaults($this->record); // Standardwerte setzen
		$form->onSuccess[] = [$this, 'recordFormSucceeded'];
		return $form;
	}

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->update($this->record->id, $data); // Datensatz aktualisieren
		$this->flashMessage("Erfolgreich aktualisiert");
		$this->redirect('...');
	}
}

In der Action-Methode, die gleich zu Beginn des Presenter-Lebenszyklus aufgerufen wird, überprüfen wir die Existenz des Datensatzes und die Berechtigung des Benutzers, ihn zu bearbeiten.

Wir speichern den Datensatz in der Eigenschaft $record, so dass er in der Methode createComponentRecordForm() zum Festlegen von Standardwerten und recordFormSucceeded() für die ID zur Verfügung steht. Eine alternative Lösung wäre, die Standardwerte direkt in actionEdit() zu setzen und den ID-Wert, der Teil der URL ist, mit getParameter('id') abzurufen:

	public function actionEdit(int $id): void
	{
		$record = $this->facade->get($id);
		if (
			// Existenz überprüfen und Berechtigungen prüfen
		) {
			$this->error();
		}

		// Standardwerte für das Formular festlegen
		$this->getComponent('recordForm')
			->setDefaults($record);
	}

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$id = (int) $this->getParameter('id');
		$this->facade->update($id, $data);
		// ...
	}
}

Allerdings, und das sollte die wichtigste Erkenntnis aus dem ganzen Code sein, müssen wir sicherstellen, dass die Aktion tatsächlich edit ist, wenn wir das Formular erstellen. Denn sonst würde die Validierung in der Methode actionEdit() überhaupt nicht stattfinden!

Dasselbe Formular zum Hinzufügen und Bearbeiten

Und nun werden wir beide Präsentatoren in einem kombinieren. Entweder wir unterscheiden, welche Aktion an der Methode createComponentRecordForm() beteiligt ist und konfigurieren das Formular entsprechend, oder wir überlassen es direkt den Aktionsmethoden und lassen die Bedingung weg:

class RecordPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private Facade $facade,
	) {
	}

	public function actionAdd(): void
	{
		$form = $this->getComponent('recordForm');
		$form->onSuccess[] = [$this, 'addingFormSucceeded'];
	}

	public function actionEdit(int $id): void
	{
		$record = $this->facade->get($id);
		if (
			!$record // die Existenz des Datensatzes zu überprüfen
			|| !$this->facade->isEditAllowed(/*...*/) // Berechtigungen prüfen
		) {
			$this->error(); // 404-Fehler
		}

		$form = $this->getComponent('recordForm');
		$form->setDefaults($record); // Standardeinstellungen festlegen
		$form->onSuccess[] = [$this, 'editingFormSucceeded'];
	}

	protected function createComponentRecordForm(): Form
	{
		// Überprüfen, ob die Aktion "Hinzufügen" oder "Bearbeiten" ist
		if (!in_array($this->getAction(), ['add', 'edit'])) {
			$this->error();
		}

		$form = new Form;

		// ... Formularfelder hinzufügen ...

		return $form;
	}

	public function addingFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // Datensatz zur Datenbank hinzufügen
		$this->flashMessage("Erfolgreich hinzugefügt");
		$this->redirect('...');
	}

	public function editingFormSucceeded(Form $form, array $data): void
	{
		$id = (int) $this->getParameter('id');
		$this->facade->update($id, $data); // Datensatz aktualisieren
		$this->flashMessage("Erfolgreich aktualisiert");
		$this->redirect('...');
	}
}