Formulário para a criação e edição de um registro

Como implementar corretamente a adição e edição de um registro em Nette, usando o mesmo formulário para ambos?

Em muitos casos, os formulários para adicionar e editar um registro são os mesmos, diferindo apenas pela etiqueta no botão. Vamos mostrar exemplos de apresentadores simples onde usamos o formulário primeiro para adicionar um registro, depois para editá-lo e, finalmente, combinar as duas soluções.

Adicionando um registro

Um exemplo de um apresentador usado para adicionar um registro. Deixaremos o trabalho real do banco de dados para a classe Facade, cujo código não é relevante para o exemplo.

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;

		// ... acrescentar campos de formulário ...

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

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // adicionar registro ao banco de dados
		$this->flashMessage("Adicionado com sucesso");
		$this->redirect('...');
	}

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

Edição de um registro

Agora vamos ver como seria um apresentador usado para editar um registro:

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 // verificar a existência do registro
			|| !$this->facade->isEditAllowed(/*...*/) // verificar permissões
		) {
			$this->error(); // 404 erro
		}

		$this->record = $record;
	}

	protected function createComponentRecordForm(): Form
	{
		// verificar se a ação é 'editar'
		if ($this->getAction() !== 'edit') {
			$this->error();
		}

		$form = new Form;

		// ... acrescentar campos de formulário ...

		$form->setDefaults($this->record); // definir valores padrão
		$form->onSuccess[] = [$this, 'recordFormSucceeded'];
		return $form;
	}

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->update($this->record->id, $data); // registro de atualização
		$this->flashMessage('Atualizado com sucesso');
		$this->redirect('...');
	}
}

No método ação, que é invocado logo no início do ciclo de vida do apresentador, verificamos a existência do registro e a permissão do usuário para editá-lo.

Armazenamos o registro na propriedade $record para que esteja disponível no método createComponentRecordForm() para definir os padrões, e recordFormSucceeded() para a identificação. Uma solução alternativa seria definir os valores padrão diretamente em actionEdit() e o valor do ID, que faz parte da URL, é recuperado usando getParameter('id'):

	public function actionEdit(int $id): void
	{
		$record = $this->facade->get($id);
		if (
			// verificar a existência e verificar as permissões
		) {
			$this->error();
		}

		// definir valores de forma padrão
		$this->getComponent('recordForm')
			->setDefaults($record);
	}

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

Entretanto, e esta deve ser ** a retirada mais importante de todo o código***, precisamos ter certeza de que a ação é de fato edit quando criamos o formulário. Porque senão a validação no método actionEdit() não aconteceria de forma alguma!

O mesmo formulário para adicionar e editar

E agora vamos combinar os dois apresentadores em um só. Ou podemos distinguir qual ação está envolvida no método createComponentRecordForm() e configurar a forma de acordo, ou podemos deixá-la diretamente com os métodos de ação e nos livrarmos da condição:

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 // verificar a existência do registro
			|| !$this->facade->isEditAllowed(/*...*/) // verificar permissões
		) {
			$this->error(); // 404 error
		}

		$form = $this->getComponent('recordForm');
		$form->setDefaults($record); // definir padrões
		$form->onSuccess[] = [$this, 'editingFormSucceeded'];
	}

	protected function createComponentRecordForm(): Form
	{
		// verificar se a ação é 'adicionar' ou 'editar'.
		if (!in_array($this->getAction(), ['add', 'edit'])) {
			$this->error();
		}

		$form = new Form;

		// ... acrescentar campos de formulário ...

		return $form;
	}

	public function addingFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // adicionar registro ao banco de dados
		$this->flashMessage("Adicionado com sucesso");
		$this->redirect('...');
	}

	public function editingFormSucceeded(Form $form, array $data): void
	{
		$id = (int) $this->getParameter('id');
		$this->facade->update($id, $data); // registro de atualização
		$this->flashMessage('Atualizado com sucesso');
		$this->redirect('...');
	}
}