Formular zum Erstellen und Bearbeiten von Datensätzen

Wie implementiert man in Nette das Hinzufügen und Bearbeiten von Datensätzen korrekt, sodass für beides dasselbe Formular verwendet wird?

In vielen Fällen sind die Formulare zum Hinzufügen und Bearbeiten von Datensätzen identisch, sie unterscheiden sich vielleicht nur durch die Beschriftung auf dem Button. Wir zeigen Beispiele für einfache Presenter, in denen wir das Formular zunächst zum Hinzufügen eines Datensatzes verwenden, dann zum Bearbeiten und schließlich beide Lösungen zusammenführen.

Hinzufügen eines Datensatzes

Beispiel eines Presenters zum Hinzufügen eines Datensatzes. Die eigentliche Arbeit mit der Datenbank überlassen wir der Klasse Facade, deren Code für das Beispiel nicht wesentlich 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

Nun zeigen wir, 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 // Überprüfung der Existenz des Datensatzes
			|| !$this->facade->isEditAllowed(/*...*/) // Berechtigungsprüfung
		) {
			$this->error(); // Fehler 404
		}

		$this->record = $record;
	}

	protected function createComponentRecordForm(): Form
	{
		// Überprüfen, ob die Aktion 'edit' 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 actionEdit()-Methode, die gleich zu Beginn des Presenter-Lebenszyklus ausgeführt wird, überprüfen wir die Existenz des Datensatzes und die Berechtigung des Benutzers, ihn zu bearbeiten.

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

	public function actionEdit(int $id): void
	{
		$record = $this->facade->get($id);
		if (
			// Überprüfung der Existenz und Berechtigungsprüfung
		) {
			$this->error();
		}

		// Standardwerte des Formulars setzen
		$this->getComponent('recordForm')
			->setDefaults($record);
	}

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

Jedoch, und das sollte die wichtigste Erkenntnis des gesamten Codes sein, müssen wir beim Erstellen des Formulars sicherstellen, dass die Aktion tatsächlich edit ist. Denn andernfalls würde die Überprüfung in der Methode actionEdit() überhaupt nicht stattfinden!

Dasselbe Formular zum Hinzufügen und Bearbeiten

Und nun führen wir beide Presenter zu einem zusammen. Entweder könnten wir in der Methode createComponentRecordForm() unterscheiden, um welche Aktion es sich handelt und das Formular entsprechend konfigurieren, oder wir können dies direkt den Action-Methoden überlassen und die Bedingung entfernen:

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 // Überprüfung der Existenz des Datensatzes
			|| !$this->facade->isEditAllowed(/*...*/) // Berechtigungsprüfung
		) {
			$this->error(); // Fehler 404
		}

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

	protected function createComponentRecordForm(): Form
	{
		// Überprüfen, ob die Aktion 'add' oder 'edit' 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('...');
	}
}