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

Как правильно реализовать в 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
	{
		// проверяем, что действие - 'edit'
		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() для получения ID. Альтернативным решением было бы установить значения по умолчанию прямо в 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() различать, какое действие выполняется, и в соответствии с этим конфигурировать форму, или же мы можем оставить это непосредственно action-методам и избавиться от условия:

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('Успешно добавлено');
		$this->redirect('...');
	}

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