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

Як правильно реалізувати в 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('Successfully added');
		$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('Successfully updated');
		$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('Successfully added');
		$this->redirect('...');
	}

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