Formularz do tworzenia i edycji rekordu

Jak poprawnie zaimplementować w Nette dodawanie i edycję rekordu, wykorzystując ten sam formularz do obu operacji?

W wielu przypadkach formularze do dodawania i edycji rekordu są takie same, różnią się np. tylko etykietą na przycisku. Pokażemy przykłady prostych presenterów, gdzie formularz użyjemy najpierw do dodawania rekordu, potem do edycji, a na końcu połączymy oba rozwiązania.

Dodawanie rekordu

Przykład presentera służącego do dodawania rekordu. Samą pracę z bazą danych pozostawimy klasie Facade, której kod nie jest istotny dla przykładu.

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;

		// ... dodajemy pola formularza ...

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

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // dodanie rekordu do bazy danych
		$this->flashMessage('Pomyślnie dodano');
		$this->redirect('...');
	}

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

Edycja rekordu

Teraz pokażemy, jak wyglądałby presenter służący do edycji rekordu:

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 // weryfikacja istnienia rekordu
			|| !$this->facade->isEditAllowed(/*...*/) // kontrola uprawnień
		) {
			$this->error(); // błąd 404
		}

		$this->record = $record;
	}

	protected function createComponentRecordForm(): Form
	{
		// sprawdzamy, czy akcja to 'edit'
		if ($this->getAction() !== 'edit') {
			$this->error();
		}

		$form = new Form;

		// ... dodajemy pola formularza ...

		$form->setDefaults($this->record); // ustawienie wartości domyślnych
		$form->onSuccess[] = [$this, 'recordFormSucceeded'];
		return $form;
	}

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->update($this->record->id, $data); // aktualizacja rekordu
		$this->flashMessage('Pomyślnie zaktualizowano');
		$this->redirect('...');
	}
}

W metodzie actionEdit(), która uruchamia się na samym początku cyklu życia presentera, weryfikujemy istnienie rekordu i uprawnienia użytkownika do jego edycji.

Rekord zapisujemy do właściwości $record, aby mieć go dostępnego w metodzie createComponentRecordForm() w celu ustawienia wartości domyślnych, oraz w recordFormSucceeded() ze względu na ID. Alternatywnym rozwiązaniem byłoby ustawienie wartości domyślnych bezpośrednio w actionEdit() i pobranie wartości ID, która jest częścią URL, za pomocą getParameter('id'):

	public function actionEdit(int $id): void
	{
		$record = $this->facade->get($id);
		if (
			// weryfikacja istnienia i kontrola uprawnień
		) {
			$this->error();
		}

		// ustawienie wartości domyślnych formularza
		$this->getComponent('recordForm')
			->setDefaults($record);
	}

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

Jednakże, i to powinno być najważniejszą lekcją całego kodu, musimy podczas tworzenia formularza upewnić się, że akcja to rzeczywiście edit. W przeciwnym razie weryfikacja w metodzie actionEdit() w ogóle by nie została przeprowadzona!

Ten sam formularz do dodawania i edycji

A teraz połączymy oba presentery w jeden. Albo moglibyśmy w metodzie createComponentRecordForm() rozróżnić, o którą akcję chodzi i na tej podstawie skonfigurować formularz, albo możemy to zostawić bezpośrednio metodom akcji i pozbyć się warunku:

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 // weryfikacja istnienia rekordu
			|| !$this->facade->isEditAllowed(/*...*/) // kontrola uprawnień
		) {
			$this->error(); // błąd 404
		}

		$form = $this->getComponent('recordForm');
		$form->setDefaults($record); // ustawienie wartości domyślnych
		$form->onSuccess[] = [$this, 'editingFormSucceeded'];
	}

	protected function createComponentRecordForm(): Form
	{
		// sprawdzamy, czy akcja to 'add' lub 'edit'
		if (!in_array($this->getAction(), ['add', 'edit'])) {
			$this->error();
		}

		$form = new Form;

		// ... dodajemy pola formularza ...

		return $form;
	}

	public function addingFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // dodanie rekordu do bazy danych
		$this->flashMessage('Pomyślnie dodano');
		$this->redirect('...');
	}

	public function editingFormSucceeded(Form $form, array $data): void
	{
		$id = (int) $this->getParameter('id');
		$this->facade->update($id, $data); // aktualizacja rekordu
		$this->flashMessage('Pomyślnie zaktualizowano');
		$this->redirect('...');
	}
}