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

Як правильно реалізувати додавання і редагування запису в 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() і налаштувати форму відповідним чином, або ми можемо залишити це безпосередньо 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
	{
		// перевірте, щоб дія була "додати" або "редагувати
		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('...');
	}
}