Nette Documentation Preview

syntax
Формуляр за създаване и редактиране на запис
********************************************

.[perex]
Как правилно да въведем добавянето и редактирането на запис в Nette, като използваме една и съща форма и за двете?

В много случаи формулярите за добавяне и редактиране на запис са едни и същи, като се различават само по етикета на бутона. Ще покажем примери за прости презентатори, в които използваме формуляра първо за добавяне на запис, след това за редактирането му и накрая комбинираме двете решения.


Добавяне на запис .[#toc-adding-a-record]
-----------------------------------------

Пример за презентатор, използван за добавяне на запис. Обработката на базата данни ще оставим на класа `Facade`, чийто код не е от значение за този пример.


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

		// ... добавляем поля форма ...

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

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // добавляем запис в базата данни
		$this->flashMessage('Успешно добавено');
		$this->redirect('...');
	}

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


Редактиране на запис .[#toc-editing-a-record]
---------------------------------------------

Сега нека видим как ще изглежда презентаторът, използван за редактиране на записи:


```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 // проверка на съществуването на записа
			|| !$this->facade->isEditAllowed(/*...*/) // проверка на правата за достъп
		) {
			$this->error(); // Грешка 404
		}

		$this->record = $record;
	}

	protected function createComponentRecordForm(): Form
	{
		// проверете дали е избрано действието "редактиране
		if ($this->getAction() !== 'edit') {
			$this->error();
		}

		$form = new Form;

		// ... добавяне на полета за формуляри ...

		$form->setDefaults($this->record); // задаване на настройки по подразбиране
		$form->onSuccess[] = [$this, 'recordFormSucceeded'];
		return $form;
	}

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->update($this->record->id, $data); // актуализиране на записа
		$this->flashMessage('Успешно актуализирано');
		$this->redirect('...');
	}
}
```

В метода *action*, който се извиква в самото начало на жизнения цикъл на [презентатора |application:presenters#Life-Cycle-of-Presenter], се проверява съществуването на записа и разрешението на потребителя да го редактира.

Съхраняваме записа в свойството `$record`, така че той да е достъпен в метода `createComponentRecordForm()` за задаване на стойности по подразбиране и `recordFormSucceeded()` за идентификатор. Алтернативно решение би било да се зададат стойностите по подразбиране директно в `actionEdit()` и стойността на ID, която е част от URL адреса и се извлича с `getParameter('id')`:


```php
	public function actionEdit(int $id): void
	{
		$record = $this->facade->get($id);
		if (
			// проверка на съществуването и правата на достъп
		) {
			$this->error();
		}

		// задаване на стойности по подразбиране за полетата на формуляра
		$this->getComponent('recordForm')
			->setDefaults($record);
	}

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

Въпреки това, и това би трябвало да е **най-важният извод от целия код**, трябва да се уверим, че действието наистина е `edit`, когато създаваме формата. Защото в противен случай валидирането в метода `actionEdit()` изобщо няма да се случи!


Една и съща форма за добавяне и редактиране .[#toc-same-form-for-adding-and-editing]
------------------------------------------------------------------------------------

Сега ще обединим двамата водещи в един. Или можем да разграничим кое действие е включено в метода `createComponentRecordForm()` и да конфигурираме формуляра по съответния начин, или можем да го оставим директно на методите на действие и да се отървем от условието:


```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 // проверка на съществуването на записа
			|| !$this->facade->isEditAllowed(/*...*/) // проверка на правата за достъп
		) {
			$this->error(); // Грешка 404
		}

		$form = $this->getComponent('recordForm');
		$form->setDefaults($record); // задаване на настройки по подразбиране
		$form->onSuccess[] = [$this, 'editingFormSucceeded'];
	}

	protected function createComponentRecordForm(): Form
	{
		// проверява дали текущото действие е 'add' или 'edit'
		if (!in_array($this->getAction(), ['add', 'edit'])) {
			$this->error();
		}

		$form = new Form;

		// ... добавяне на полета за формуляри ...

		return $form;
	}

	public function addingFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // добавете този запис в базата данни
		$this->flashMessage('Successfully added');
		$this->redirect('...');
	}

	public function editingFormSucceeded(Form $form, array $data): void
	{
		$id = (int) $this->getParameter('id');
		$this->facade->update($id, $data); // актуализиране на записа
		$this->flashMessage('Успешно актуализирано');
		$this->redirect('...');
	}
}
```

{{priority: -1}}
{{sitename: Най-добри практики}}

Формуляр за създаване и редактиране на запис

Как правилно да въведем добавянето и редактирането на запис в Nette, като използваме една и съща форма и за двете?

В много случаи формулярите за добавяне и редактиране на запис са едни и същи, като се различават само по етикета на бутона. Ще покажем примери за прости презентатори, в които използваме формуляра първо за добавяне на запис, след това за редактирането му и накрая комбинираме двете решения.

Добавяне на запис

Пример за презентатор, използван за добавяне на запис. Обработката на базата данни ще оставим на класа Facade, чийто код не е от значение за този пример.

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;

		// ... добавляем поля форма ...

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

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // добавляем запис в базата данни
		$this->flashMessage('Успешно добавено');
		$this->redirect('...');
	}

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

Редактиране на запис

Сега нека видим как ще изглежда презентаторът, използван за редактиране на записи:

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 // проверка на съществуването на записа
			|| !$this->facade->isEditAllowed(/*...*/) // проверка на правата за достъп
		) {
			$this->error(); // Грешка 404
		}

		$this->record = $record;
	}

	protected function createComponentRecordForm(): Form
	{
		// проверете дали е избрано действието "редактиране
		if ($this->getAction() !== 'edit') {
			$this->error();
		}

		$form = new Form;

		// ... добавяне на полета за формуляри ...

		$form->setDefaults($this->record); // задаване на настройки по подразбиране
		$form->onSuccess[] = [$this, 'recordFormSucceeded'];
		return $form;
	}

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->update($this->record->id, $data); // актуализиране на записа
		$this->flashMessage('Успешно актуализирано');
		$this->redirect('...');
	}
}

В метода action, който се извиква в самото начало на жизнения цикъл на презентатора, се проверява съществуването на записа и разрешението на потребителя да го редактира.

Съхраняваме записа в свойството $record, така че той да е достъпен в метода createComponentRecordForm() за задаване на стойности по подразбиране и recordFormSucceeded() за идентификатор. Алтернативно решение би било да се зададат стойностите по подразбиране директно в actionEdit() и стойността на ID, която е част от URL адреса и се извлича с getParameter('id'):

	public function actionEdit(int $id): void
	{
		$record = $this->facade->get($id);
		if (
			// проверка на съществуването и правата на достъп
		) {
			$this->error();
		}

		// задаване на стойности по подразбиране за полетата на формуляра
		$this->getComponent('recordForm')
			->setDefaults($record);
	}

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

Въпреки това, и това би трябвало да е най-важният извод от целия код, трябва да се уверим, че действието наистина е edit, когато създаваме формата. Защото в противен случай валидирането в метода actionEdit() изобщо няма да се случи!

Една и съща форма за добавяне и редактиране

Сега ще обединим двамата водещи в един. Или можем да разграничим кое действие е включено в метода createComponentRecordForm() и да конфигурираме формуляра по съответния начин, или можем да го оставим директно на методите на действие и да се отървем от условието:

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 // проверка на съществуването на записа
			|| !$this->facade->isEditAllowed(/*...*/) // проверка на правата за достъп
		) {
			$this->error(); // Грешка 404
		}

		$form = $this->getComponent('recordForm');
		$form->setDefaults($record); // задаване на настройки по подразбиране
		$form->onSuccess[] = [$this, 'editingFormSucceeded'];
	}

	protected function createComponentRecordForm(): Form
	{
		// проверява дали текущото действие е 'add' или 'edit'
		if (!in_array($this->getAction(), ['add', 'edit'])) {
			$this->error();
		}

		$form = new Form;

		// ... добавяне на полета за формуляри ...

		return $form;
	}

	public function addingFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // добавете този запис в базата данни
		$this->flashMessage('Successfully added');
		$this->redirect('...');
	}

	public function editingFormSucceeded(Form $form, array $data): void
	{
		$id = (int) $this->getParameter('id');
		$this->facade->update($id, $data); // актуализиране на записа
		$this->flashMessage('Успешно актуализирано');
		$this->redirect('...');
	}
}