Formulario para crear y editar un registro

¿Cómo implementar correctamente en Nette añadir y editar un registro usando el mismo formulario para ambas cosas?

En muchos casos, los formularios para añadir y editar registros son idénticos y se diferencian, como mucho, en el texto del botón. Mostraremos ejemplos de presenters sencillos en los que usaremos el formulario primero para añadir un registro, después para editarlo y, por último, combinaremos ambas soluciones.

Añadir un registro

Ejemplo de un presenter para añadir un registro. El trabajo real con la base de datos se lo dejaremos a la clase Facade, cuyo código no es esencial para este ejemplo.

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;

	// ... añadir los campos del formulario ...

		$form->onSuccess[] = $this->recordFormSucceeded(...);
		return $form;
	}

	private function recordFormSucceeded(Form $form, array $data): void
	{
	$this->facade->add($data); // añade el registro a la base de datos
		$this->flashMessage('Successfully added');
		$this->redirect('...');
	}

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

Editar un registro

Veamos ahora qué aspecto tendría un presenter para editar un 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 // comprueba que el registro existe
		|| !$this->facade->isEditAllowed(/*...*/) // comprueba los permisos
		) {
		$this->error(); // error 404
		}

		$this->record = $record;
	}

	protected function createComponentRecordForm(): Form
	{
	// comprueba que la acción es 'edit'
		if ($this->getAction() !== 'edit') {
			$this->error();
		}

		$form = new Form;

	// ... añadir los campos del formulario ...

	$form->setDefaults($this->record); // establece los valores predeterminados
		$form->onSuccess[] = $this->recordFormSucceeded(...);
		return $form;
	}

	private function recordFormSucceeded(Form $form, array $data): void
	{
	$this->facade->update($this->record->id, $data); // actualiza el registro
		$this->flashMessage('Successfully updated');
		$this->redirect('...');
	}
}

En el método action, que se invoca al principio del ciclo de vida del presenter, comprobamos la existencia del registro y el permiso del usuario para editarlo.

Guardamos el registro en la propiedad $record, con lo que queda disponible en el método createComponentRecordForm() para establecer los valores predeterminados y en recordFormSucceeded() para acceder al ID. Una solución alternativa es establecer los valores predeterminados directamente en actionEdit() y obtener el valor del ID (que forma parte de la URL) con getParameter('id'):

	public function actionEdit(int $id): void
	{
		$record = $this->facade->get($id);
		if (
		// comprueba la existencia y los permisos
		) {
			$this->error();
		}

	// establece los valores predeterminados del formulario
		$this->getComponent('recordForm')
			->setDefaults($record);
	}

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

Ahora bien, y esto debería ser lo más importante que se lleve de todo este código, al crear el formulario tenemos que asegurarnos de que la acción es realmente edit. ¡De lo contrario, la comprobación del método actionEdit() no se realizaría en absoluto!

El mismo formulario para añadir y editar

Combinemos ahora ambos presenters en uno. Podríamos distinguir la acción dentro del método createComponentRecordForm() y configurar el formulario en consecuencia, o bien podemos delegarlo directamente en los métodos de acción y eliminar la condición:

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 // comprueba que el registro existe
		|| !$this->facade->isEditAllowed(/*...*/) // comprueba los permisos
		) {
		$this->error(); // error 404
		}

		$form = $this->getComponent('recordForm');
	$form->setDefaults($record); // establece los valores predeterminados
		$form->onSuccess[] = $this->editingFormSucceeded(...);
	}

	protected function createComponentRecordForm(): Form
	{
	// comprueba que la acción es 'add' o 'edit'
		if (!in_array($this->getAction(), ['add', 'edit'])) {
			$this->error();
		}

		$form = new Form;

	// ... añadir los campos del formulario ...

		return $form;
	}

	private function addingFormSucceeded(Form $form, array $data): void
	{
	$this->facade->add($data); // añade el registro a la base de datos
		$this->flashMessage('Successfully added');
		$this->redirect('...');
	}

	private function editingFormSucceeded(Form $form, array $data): void
	{
		$id = (int) $this->getParameter('id');
	$this->facade->update($id, $data); // actualiza el registro
		$this->flashMessage('Successfully updated');
		$this->redirect('...');
	}
}