Formulaire de création et d'édition d'enregistrements

Comment implémenter correctement dans Nette l'ajout et l'édition d'un enregistrement, en utilisant le même formulaire pour les deux ?

Dans bien des cas, les formulaires d'ajout et d'édition d'un enregistrement sont identiques et ne diffèrent peut-être que par le libellé du bouton. Nous montrerons des exemples de presenters simples où nous utiliserons le formulaire d'abord pour ajouter un enregistrement, puis pour le modifier, et enfin nous combinerons les deux solutions.

Ajouter un enregistrement

Exemple d'un presenter d'ajout d'enregistrement. Nous laisserons le travail réel avec la base de données à une classe Facade dont le code n'est pas essentiel pour cet exemple.

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;

		// ... ajout des champs du formulaire ...

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

	private function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // ajoute l'enregistrement dans la base de données
		$this->flashMessage('Ajouté avec succès');
		$this->redirect('...');
	}

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

Éditer un enregistrement

Voyons maintenant à quoi ressemblerait un presenter d'édition d'enregistrement :

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 // vérifie l'existence de l'enregistrement
			|| !$this->facade->isEditAllowed(/*...*/) // vérifie les permissions
		) {
			$this->error(); // erreur 404
		}

		$this->record = $record;
	}

	protected function createComponentRecordForm(): Form
	{
		// vérifie que l'action est bien 'edit'
		if ($this->getAction() !== 'edit') {
			$this->error();
		}

		$form = new Form;

		// ... ajout des champs du formulaire ...

		$form->setDefaults($this->record); // définit les valeurs par défaut
		$form->onSuccess[] = $this->recordFormSucceeded(...);
		return $form;
	}

	private function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->update($this->record->id, $data); // met à jour l'enregistrement
		$this->flashMessage('Mis à jour avec succès');
		$this->redirect('...');
	}
}

Dans la méthode action, appelée au début du cycle de vie du presenter, nous vérifions l'existence de l'enregistrement et le droit de l'utilisateur à le modifier.

Nous stockons l'enregistrement dans la propriété $record, ce qui le rend disponible dans la méthode createComponentRecordForm() pour définir les valeurs par défaut, et dans recordFormSucceeded() pour accéder à l'ID. Une solution alternative consiste à définir les valeurs par défaut directement dans actionEdit() et à récupérer la valeur de l'ID (qui fait partie de l'URL) à l'aide de getParameter('id') :

	public function actionEdit(int $id): void
	{
		$record = $this->facade->get($id);
		if (
			// vérifie l'existence et les permissions
		) {
			$this->error();
		}

		// définit les valeurs par défaut du formulaire
		$this->getComponent('recordForm')
			->setDefaults($record);
	}

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

Cependant, et ce devrait être l'enseignement le plus important de tout ce code, nous devons nous assurer que l'action est bien edit lors de la création du formulaire. Sinon, la vérification effectuée dans la méthode actionEdit() n'aurait pas lieu du tout !

Même formulaire pour l'ajout et l'édition

Combinons maintenant les deux presenters en un seul. Nous pourrions soit distinguer l'action dans la méthode createComponentRecordForm() et configurer le formulaire en conséquence, soit déléguer cela directement aux méthodes d'action et supprimer la condition :

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 // vérifie l'existence de l'enregistrement
			|| !$this->facade->isEditAllowed(/*...*/) // vérifie les permissions
		) {
			$this->error(); // erreur 404
		}

		$form = $this->getComponent('recordForm');
		$form->setDefaults($record); // définit les valeurs par défaut
		$form->onSuccess[] = $this->editingFormSucceeded(...);
	}

	protected function createComponentRecordForm(): Form
	{
		// vérifie que l'action est 'add' ou 'edit'
		if (!in_array($this->getAction(), ['add', 'edit'])) {
			$this->error();
		}

		$form = new Form;

		// ... ajout des champs du formulaire ...

		return $form;
	}

	private function addingFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // ajoute l'enregistrement dans la base de données
		$this->flashMessage('Ajouté avec succès');
		$this->redirect('...');
	}

	private function editingFormSucceeded(Form $form, array $data): void
	{
		$id = (int) $this->getParameter('id');
		$this->facade->update($id, $data); // met à jour l'enregistrement
		$this->flashMessage('Mis à jour avec succès');
		$this->redirect('...');
	}
}