Formular zum Erstellen und Bearbeiten von Datensätzen

Wie setzt man in Nette das Hinzufügen und Bearbeiten eines Datensatzes richtig um, wenn man für beides dasselbe Formular verwenden möchte?

In vielen Fällen sind die Formulare zum Hinzufügen und zum Bearbeiten eines Datensatzes identisch und unterscheiden sich vielleicht nur in der Beschriftung der Schaltfläche. Wir zeigen Beispiele einfacher Presenter, in denen wir das Formular zuerst 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 dieses 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;

		// ... wir fügen die Formularfelder hinzu ...

		$form->onSuccess[] = $this->recordFormSucceeded(...);
		return $form;
	}

	private 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 // Existenz des Datensatzes prüfen
			|| !$this->facade->isEditAllowed(/*...*/) // Berechtigungen prüfen
		) {
			$this->error(); // Fehler 404
		}

		$this->record = $record;
	}

	protected function createComponentRecordForm(): Form
	{
		// wir prüfen, dass die Aktion 'edit' ist
		if ($this->getAction() !== 'edit') {
			$this->error();
		}

		$form = new Form;

		// ... wir fügen die Formularfelder hinzu ...

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

	private 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 Lebenszyklus des Presenters ausgeführt wird, prüfen wir die Existenz des Datensatzes und die Berechtigung des Benutzers, ihn zu bearbeiten.

Den Datensatz speichern wir in der Property $record, damit er in der Methode createComponentRecordForm() zum Setzen der Standardwerte und in recordFormSucceeded() wegen der ID zur Verfügung steht. 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') zu ermitteln:

	public function actionEdit(int $id): void
	{
		$record = $this->facade->get($id);
		if (
			// Existenz prüfen und Berechtigungen kontrollieren
		) {
			$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);
		// ...
	}

Allerdings müssen wir uns, und das sollte die wichtigste Erkenntnis aus dem gesamten Code sein, beim Erstellen des Formulars vergewissern, dass die Aktion tatsächlich edit ist. Denn sonst würde die Prü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 überlassen das direkt den action-Methoden und werden die Bedingung los:

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 // Existenz des Datensatzes prüfen
			|| !$this->facade->isEditAllowed(/*...*/) // Berechtigungen prüfen
		) {
			$this->error(); // Fehler 404
		}

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

	protected function createComponentRecordForm(): Form
	{
		// wir prüfen, dass die Aktion 'add' oder 'edit' ist
		if (!in_array($this->getAction(), ['add', 'edit'])) {
			$this->error();
		}

		$form = new Form;

		// ... wir fügen die Formularfelder hinzu ...

		return $form;
	}

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

	private 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('...');
	}
}