Formularz do tworzenia i edycji rekordu

Jak w Nette poprawnie zrealizować dodawanie i edycję rekordu, używając do obu tego samego formularza?

W wielu przypadkach formularze do dodawania i edycji rekordu są takie same, różnią się może tylko etykietą przycisku. Pokażemy przykłady prostych presenterów, w których użyjemy formularza najpierw do dodania rekordu, potem do jego edycji, a na koniec połączymy oba rozwiązania.

Dodawanie rekordu

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

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;
	}

	private 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

Zobaczmy teraz, 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 // weryfikujemy istnienie rekordu
			|| !$this->facade->isEditAllowed(/*...*/) // sprawdzamy uprawnienia
		) {
			$this->error(); // błąd 404
		}

		$this->record = $record;
	}

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

		$form = new Form;

		// ... dodajemy pola formularza ...

		$form->setDefaults($this->record); // ustawiamy wartości domyślne
		$form->onSuccess[] = $this->recordFormSucceeded(...);
		return $form;
	}

	private 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 action, wywoływanej na początku cyklu życia presentera, weryfikujemy istnienie rekordu i uprawnienia użytkownika do jego edycji.

Rekord zapisujemy do właściwości $record, dzięki czemu jest dostępny w metodzie createComponentRecordForm() do ustawienia wartości domyślnych oraz w recordFormSucceeded() do odczytu ID. Alternatywnym rozwiązaniem jest ustawienie wartości domyślnych bezpośrednio w actionEdit() i odczytanie wartości ID (będącej częścią URL) za pomocą getParameter('id'):

	public function actionEdit(int $id): void
	{
		$record = $this->facade->get($id);
		if (
			// weryfikujemy istnienie i sprawdzamy uprawnienia
		) {
			$this->error();
		}

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

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

Ale, i to powinno być najważniejszym wnioskiem z całego kodu, przy tworzeniu formularza musimy upewnić się, że akcja rzeczywiście jest edit. W przeciwnym razie weryfikacja w metodzie actionEdit() w ogóle by się nie odbyła!

Ten sam formularz do dodawania i edycji

Połączmy teraz oba presentery w jeden. Moglibyśmy albo rozróżniać akcję w metodzie createComponentRecordForm() i odpowiednio konfigurować formularz, albo możemy to zostawić bezpośrednio metodom action 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 // weryfikujemy istnienie rekordu
			|| !$this->facade->isEditAllowed(/*...*/) // sprawdzamy uprawnienia
		) {
			$this->error(); // błąd 404
		}

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

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

		$form = new Form;

		// ... dodajemy pola formularza ...

		return $form;
	}

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

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