Formulaire pour la création et l'édition d'enregistrements

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

Dans de nombreux cas, les formulaires pour ajouter et éditer des enregistrements sont identiques, ne différant peut-être que par l'étiquette du bouton. Nous montrerons des exemples de presenters simples où nous utiliserons d'abord le formulaire pour ajouter un enregistrement, puis pour l'éditer, et enfin combinerons les deux solutions.

Ajout d'un enregistrement

Exemple de presenter servant à ajouter un enregistrement. Nous laisserons le travail réel avec la base de données à la classe Facade, dont le code n'est pas essentiel pour la démonstration.

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;

		// ... ajouter les champs du formulaire ...

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

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

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

Édition d'un enregistrement

Maintenant, montrons à quoi ressemblerait un presenter servant à éditer un 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érification de l'existence de l'enregistrement
			|| !$this->facade->isEditAllowed(/*...*/) // contrôle des permissions
		) {
			$this->error(); // erreur 404
		}

		$this->record = $record;
	}

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

		$form = new Form;

		// ... ajouter les champs du formulaire ...

		$form->setDefaults($this->record); // définition des valeurs par défaut
		$form->onSuccess[] = [$this, 'recordFormSucceeded'];
		return $form;
	}

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

Dans la méthode action, qui s'exécute au tout début du cycle de vie du presenter, nous vérifions l'existence de l'enregistrement et les permissions de l'utilisateur pour l'éditer.

Nous sauvegardons l'enregistrement dans la propriété $record pour l'avoir disponible dans la méthode createComponentRecordForm() pour définir les valeurs par défaut, et dans recordFormSucceeded() pour l'ID. Une solution alternative serait de définir les valeurs par défaut directement dans actionEdit() et d'obtenir la valeur de l'ID, qui fait partie de l'URL, en utilisant getParameter('id') :

	public function actionEdit(int $id): void
	{
		$record = $this->facade->get($id);
		if (
			// vérification de l'existence et contrôle des permissions
		) {
			$this->error();
		}

		// définition des 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 cela devrait être la leçon la plus importante de tout le code, nous devons nous assurer lors de la création du formulaire que l'action est bien edit. Sinon, la vérification dans la méthode actionEdit() n'aurait pas lieu du tout !

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

Et maintenant, combinons les deux presenters en un seul. Soit nous pourrions distinguer dans la méthode createComponentRecordForm() de quelle action il s'agit et configurer le formulaire en conséquence, soit nous pouvons laisser cela directement aux méthodes d'action et nous débarrasser de 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érification de l'existence de l'enregistrement
			|| !$this->facade->isEditAllowed(/*...*/) // contrôle des permissions
		) {
			$this->error(); // erreur 404
		}

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

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

		$form = new Form;

		// ... ajouter les champs du formulaire ...

		return $form;
	}

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

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