Nette Documentation Preview

syntax
Formulář pro vytvoření i editaci záznamu
****************************************

.[perex]
Jak správně v Nette implementovat přidání a editaci záznamu, s tím, že pro obojí využijeme tentýž formulář?

V mnoha případech bývají formuláře pro přidání i editaci záznamu stejné, liší se třeba jen popiskou na tlačítku. Ukážeme příklady jednoduchých presenterů, kde formulář použijeme nejprve pro přidání záznamu, poté pro editaci a nakonec obě řešení spojíme.


Přidání záznamu
---------------

Příklad presenteru sloužícího k přidání záznamu. Samotnou práci s databází necháme na třídě `Facade`, jejíž kód není pro ukázku podstatný.


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

		// ... přidáme políčka formuláře ...

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

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // přidání záznamu do databáze
		$this->flashMessage('Successfully added');
		$this->redirect('...');
	}

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


Editace záznamu
---------------

Nyní si ukážeme, jak by vypadal presenter sloužící k editaci záznamu:


```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 // oveření existence záznamu
			|| !$this->facade->isEditAllowed(/*...*/) // kontrola oprávnění
		) {
			$this->error(); // chyba 404
		}

		$this->record = $record;
	}

	protected function createComponentRecordForm(): Form
	{
		// ověříme, že akce je 'edit'
		if ($this->getAction() !== 'edit') {
			$this->error();
		}

		$form = new Form;

		// ... přidáme políčka formuláře ...

		$form->setDefaults($this->record); // nastavení výchozích hodnot
		$form->onSuccess[] = [$this, 'recordFormSucceeded'];
		return $form;
	}

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->update($this->record->id, $data); // aktualizace záznamu
		$this->flashMessage('Successfully updated');
		$this->redirect('...');
	}
}
```

V metodě *action*, která se spouští hned na začátku [životního cyklu presenteru|application:presenters#zivotni-cyklus-presenteru], ověříme existenci záznamu a oprávnění uživatele jej editovat.

Záznam si uložíme do property `$record`, abychom jej měli k dispozici v metodě `createComponentRecordForm()` kvůli nastavení výchozích hodnot, a `recordFormSucceeded()` kvůli ID. Alternativním řešením by bylo nastavit výchozí hodnoty přímo v `actionEdit()` a hodnotu ID, která je součástí URL, získat pomocí `getParameter('id')`:


```php
	public function actionEdit(int $id): void
	{
		$record = $this->facade->get($id);
		if (
			// oveření existence a kontrola oprávnění
		) {
			$this->error();
		}

		// nastavení výchozích hodnot formuláře
		$this->getComponent('recordForm')
			->setDefaults($record);
	}

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

Nicméně, a to by mělo být **nejdůležitejším poznatkem celého kódu**, musíme se při tvorbě formuláře ujistit, že akce je skutečně `edit`. Protože jinak by ověření v metodě `actionEdit()` vůbec neproběhlo!


Stejný formulář pro přidání i editaci
-------------------------------------

A nyní oba presentery spojíme do jednoho. Buď bychom mohli v metodě `createComponentRecordForm()` rozlišit, o kterou akci jde a podle toho formulář nakonfigurovat, nebo to můžeme nechat přímo na action-metodách a zbavit se podmínky:


```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 // oveření existence záznamu
			|| !$this->facade->isEditAllowed(/*...*/) // kontrola oprávnění
		) {
			$this->error(); // chyba 404
		}

		$form = $this->getComponent('recordForm');
		$form->setDefaults($record); // nastavení výchozích hodnot
		$form->onSuccess[] = [$this, 'editingFormSucceeded'];
	}

	protected function createComponentRecordForm(): Form
	{
		// ověříme, že akce je 'add' nebo 'edit'
		if (!in_array($this->getAction(), ['add', 'edit'])) {
			$this->error();
		}

		$form = new Form;

		// ... přidáme políčka formuláře ...

		return $form;
	}

	public function addingFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // přidání záznamu do databáze
		$this->flashMessage('Successfully added');
		$this->redirect('...');
	}

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

{{priority: -1}}
{{sitename: Best Practices}}

Formulář pro vytvoření i editaci záznamu

Jak správně v Nette implementovat přidání a editaci záznamu, s tím, že pro obojí využijeme tentýž formulář?

V mnoha případech bývají formuláře pro přidání i editaci záznamu stejné, liší se třeba jen popiskou na tlačítku. Ukážeme příklady jednoduchých presenterů, kde formulář použijeme nejprve pro přidání záznamu, poté pro editaci a nakonec obě řešení spojíme.

Přidání záznamu

Příklad presenteru sloužícího k přidání záznamu. Samotnou práci s databází necháme na třídě Facade, jejíž kód není pro ukázku podstatný.

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;

		// ... přidáme políčka formuláře ...

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

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // přidání záznamu do databáze
		$this->flashMessage('Successfully added');
		$this->redirect('...');
	}

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

Editace záznamu

Nyní si ukážeme, jak by vypadal presenter sloužící k editaci záznamu:

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 // oveření existence záznamu
			|| !$this->facade->isEditAllowed(/*...*/) // kontrola oprávnění
		) {
			$this->error(); // chyba 404
		}

		$this->record = $record;
	}

	protected function createComponentRecordForm(): Form
	{
		// ověříme, že akce je 'edit'
		if ($this->getAction() !== 'edit') {
			$this->error();
		}

		$form = new Form;

		// ... přidáme políčka formuláře ...

		$form->setDefaults($this->record); // nastavení výchozích hodnot
		$form->onSuccess[] = [$this, 'recordFormSucceeded'];
		return $form;
	}

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->update($this->record->id, $data); // aktualizace záznamu
		$this->flashMessage('Successfully updated');
		$this->redirect('...');
	}
}

V metodě action, která se spouští hned na začátku životního cyklu presenteru, ověříme existenci záznamu a oprávnění uživatele jej editovat.

Záznam si uložíme do property $record, abychom jej měli k dispozici v metodě createComponentRecordForm() kvůli nastavení výchozích hodnot, a recordFormSucceeded() kvůli ID. Alternativním řešením by bylo nastavit výchozí hodnoty přímo v actionEdit() a hodnotu ID, která je součástí URL, získat pomocí getParameter('id'):

	public function actionEdit(int $id): void
	{
		$record = $this->facade->get($id);
		if (
			// oveření existence a kontrola oprávnění
		) {
			$this->error();
		}

		// nastavení výchozích hodnot formuláře
		$this->getComponent('recordForm')
			->setDefaults($record);
	}

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

Nicméně, a to by mělo být nejdůležitejším poznatkem celého kódu, musíme se při tvorbě formuláře ujistit, že akce je skutečně edit. Protože jinak by ověření v metodě actionEdit() vůbec neproběhlo!

Stejný formulář pro přidání i editaci

A nyní oba presentery spojíme do jednoho. Buď bychom mohli v metodě createComponentRecordForm() rozlišit, o kterou akci jde a podle toho formulář nakonfigurovat, nebo to můžeme nechat přímo na action-metodách a zbavit se podmínky:

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 // oveření existence záznamu
			|| !$this->facade->isEditAllowed(/*...*/) // kontrola oprávnění
		) {
			$this->error(); // chyba 404
		}

		$form = $this->getComponent('recordForm');
		$form->setDefaults($record); // nastavení výchozích hodnot
		$form->onSuccess[] = [$this, 'editingFormSucceeded'];
	}

	protected function createComponentRecordForm(): Form
	{
		// ověříme, že akce je 'add' nebo 'edit'
		if (!in_array($this->getAction(), ['add', 'edit'])) {
			$this->error();
		}

		$form = new Form;

		// ... přidáme políčka formuláře ...

		return $form;
	}

	public function addingFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // přidání záznamu do databáze
		$this->flashMessage('Successfully added');
		$this->redirect('...');
	}

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