Formulario para crear y editar un registro

¿Cómo implementar correctamente la creación y edición de un registro en Nette, utilizando el mismo formulario para ambas cosas?

En muchos casos, los formularios para añadir y editar un registro son iguales, diferenciándose únicamente por la etiqueta del botón. Mostraremos ejemplos de presentadores sencillos en los que utilizamos el formulario primero para añadir un registro, luego para editarlo y finalmente combinamos las dos soluciones.

Añadir un registro

Ejemplo de presentador utilizado para añadir un registro. Dejaremos el trabajo real de la base de datos a la clase Facade, cuyo código no es relevante para el 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 campos de formulario ...

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

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

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

Editar un registro

Veamos ahora cómo sería un presentador utilizado 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 // verificar la existencia del registro
			|| !$this->facade->isEditAllowed(/*...*/) // comprobar permisos
		) {
			$this->error(); // error 404
		}

		$this->record = $record;
	}

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

		$form = new Form;

		// ... añadir campos de formulario ...

		$form->setDefaults($this->record); // establecer valores por defecto
		$form->onSuccess[] = [$this, 'recordFormSucceeded'];
		return $form;
	}

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

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

Almacenamos el registro en la propiedad $record para que esté disponible en el método createComponentRecordForm() para establecer los valores por defecto, y recordFormSucceeded() para el ID. Una solución alternativa sería establecer los valores por defecto directamente en actionEdit() y el valor del ID, que forma parte de la URL, se recupera utilizando getParameter('id'):

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

		// establecer valores de formulario por defecto
		$this->getComponent('recordForm')
			->setDefaults($record);
	}

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

Sin embargo, y esto debería ser lo más importante de todo el código, necesitamos asegurarnos de que la acción es efectivamente edit cuando creamos el formulario. De lo contrario, la validación en el método actionEdit() no se realizaría.

El mismo formulario para añadir y editar

Y ahora combinaremos ambos presentadores en uno solo. Podríamos distinguir qué acción está implicada en el método createComponentRecordForm() y configurar el formulario en consecuencia, o podemos dejarlo directamente en manos de los métodos de acción y deshacernos de 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 // verificar la existencia del registro
			|| !$this->facade->isEditAllowed(/*...*/) // comprobar permisos
		) {
			$this->error(); // error 404
		}

		$form = $this->getComponent('recordForm');
		$form->setDefaults($record); // establecer valores por defecto
		$form->onSuccess[] = [$this, 'editingFormSucceeded'];
	}

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

		$form = new Form;

		// ... añadir campos de formulario ...

		return $form;
	}

	public function addingFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // añadir registro a la base de datos
		$this->flashMessage('Añadido con éxito');
		$this->redirect('...');
	}

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