Nette Documentation Preview

syntax
Formularz tworzenia i edycji rekordu
************************************

.[perex]
Jak poprawnie zaimplementować dodawanie i edycję rekordu w Nette, używając tego samego formularza dla obu?

W wielu przypadkach formularze dodawania i edycji rekordu są takie same, różniąc się jedynie etykietą na przycisku. Pokażemy przykłady prostych prezenterów, w których wykorzystujemy formularz najpierw do dodania rekordu, potem do jego edycji, a na koniec łączymy te dwa rozwiązania.


Dodanie rekordu .[#toc-adding-a-record]
---------------------------------------

Przykład prezentera używanego do dodania rekordu. Faktyczną pracę z bazą danych pozostawimy klasie `Facade`, której kod nie jest istotny dla tego przykładu.


```php
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;

		// ... dodać pola formularza ...

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

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // dodaj rekord do bazy danych
		$this->flashMessage('Successfully added');
		$this->redirect('...');
	}

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


Edycja zapisu .[#toc-editing-a-record]
--------------------------------------

Teraz zobaczmy jak wyglądałby prezenter używany do edycji nagrania:


```php
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 // sprawdzić istnienie rekordu
			|| !$this->facade->isEditAllowed(/*...*/) // sprawdzić uprawnienia
		) {
			$this->error(); // błąd 404
		}

		$this->record = $record;
	}

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

		$form = new Form;

		// dodaj pola formularza ...

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

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

W metodzie *action*, która jest wywoływana zaraz na początku [cyklu życia prezentera |application:presenters#Life Cycle of Presenter], sprawdzamy istnienie rekordu oraz uprawnienia użytkownika do jego edycji.

Rekord przechowujemy we właściwości `$record`, tak aby był dostępny w metodzie `createComponentRecordForm()` do ustawiania wartości domyślnych, oraz `recordFormSucceeded()` do identyfikatora. Alternatywnym rozwiązaniem byłoby ustawienie wartości domyślnych bezpośrednio w `actionEdit()`, a wartość ID, która jest częścią adresu URL, jest pobierana za pomocą `getParameter('id')`:


```php
	public function actionEdit(int $id): void
	{
		$record = $this->facade->get($id);
		if (
			// zweryfikować istnienie i sprawdzić uprawnienia
		) {
			$this->error();
		}

		// ustawić domyślne wartości 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żniejsze spostrzeżenie z całego kodu**, musimy upewnić się, kiedy tworzymy formularz, że akcja jest faktycznie `edit`. Ponieważ w przeciwnym razie walidacja w metodzie `actionEdit()` nie miałaby w ogóle miejsca!


Ten sam formularz do dodawania i edycji .[#toc-same-form-for-adding-and-editing]
--------------------------------------------------------------------------------

A teraz połączymy obu prezenterów w jednego. Moglibyśmy albo rozróżnić, która akcja jest tą w metodzie `createComponentRecordForm()` i odpowiednio skonfigurować formularz, albo zostawić to bezpośrednio w metodach akcji i pozbyć się warunku:


```php
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 // sprawdzić istnienie rekordu
			|| !$this->facade->isEditAllowed(/*...*/) // sprawdzić uprawnienia
		) {
			$this->error(); // błąd 404
		}

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

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

		$form = new Form;

		// dodaj pola formularza ...

		return $form;
	}

	public function addingFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // dodać rekord do bazy danych
		$this->flashMessage('Successfully added');
		$this->redirect('...');
	}

	public function editingFormSucceeded(Form $form, array $data): void
	{
		$id = (int) $this->getParameter('id');
		$this->facade->update($id, $data); // aktualizować rekord
		$this->flashMessage('Successfully updated');
		$this->redirect('...');
	}
}
```

{{priority: -1}}
{{sitename: Najlepsze praktyki}}

Formularz tworzenia i edycji rekordu

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

W wielu przypadkach formularze dodawania i edycji rekordu są takie same, różniąc się jedynie etykietą na przycisku. Pokażemy przykłady prostych prezenterów, w których wykorzystujemy formularz najpierw do dodania rekordu, potem do jego edycji, a na koniec łączymy te dwa rozwiązania.

Dodanie rekordu

Przykład prezentera używanego do dodania rekordu. Faktyczną pracę z bazą danych pozostawimy klasie Facade, której kod nie jest istotny dla tego 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;

		// ... dodać pola formularza ...

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

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // dodaj rekord do bazy danych
		$this->flashMessage('Successfully added');
		$this->redirect('...');
	}

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

Edycja zapisu

Teraz zobaczmy jak wyglądałby prezenter używany do edycji nagrania:

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

		$this->record = $record;
	}

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

		$form = new Form;

		// dodaj pola formularza ...

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

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

W metodzie action, która jest wywoływana zaraz na początku cyklu życia prezentera, sprawdzamy istnienie rekordu oraz uprawnienia użytkownika do jego edycji.

Rekord przechowujemy we właściwości $record, tak aby był dostępny w metodzie createComponentRecordForm() do ustawiania wartości domyślnych, oraz recordFormSucceeded() do identyfikatora. Alternatywnym rozwiązaniem byłoby ustawienie wartości domyślnych bezpośrednio w actionEdit(), a wartość ID, która jest częścią adresu URL, jest pobierana za pomocą getParameter('id'):

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

		// ustawić domyślne wartości 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żniejsze spostrzeżenie z całego kodu, musimy upewnić się, kiedy tworzymy formularz, że akcja jest faktycznie edit. Ponieważ w przeciwnym razie walidacja w metodzie actionEdit() nie miałaby w ogóle miejsca!

Ten sam formularz do dodawania i edycji

A teraz połączymy obu prezenterów w jednego. Moglibyśmy albo rozróżnić, która akcja jest tą w metodzie createComponentRecordForm() i odpowiednio skonfigurować formularz, albo zostawić to bezpośrednio w metodach 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 // sprawdzić istnienie rekordu
			|| !$this->facade->isEditAllowed(/*...*/) // sprawdzić uprawnienia
		) {
			$this->error(); // błąd 404
		}

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

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

		$form = new Form;

		// dodaj pola formularza ...

		return $form;
	}

	public function addingFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // dodać rekord do bazy danych
		$this->flashMessage('Successfully added');
		$this->redirect('...');
	}

	public function editingFormSucceeded(Form $form, array $data): void
	{
		$id = (int) $this->getParameter('id');
		$this->facade->update($id, $data); // aktualizować rekord
		$this->flashMessage('Successfully updated');
		$this->redirect('...');
	}
}