Űrlap rekord létrehozásához és szerkesztéséhez

Hogyan implementáljuk helyesen a Nette-ben egy rekord hozzáadását és szerkesztését úgy, hogy mindkettőhöz ugyanazt az űrlapot használjuk?

Sok esetben a rekord hozzáadására és szerkesztésére szolgáló űrlapok ugyanazok, legfeljebb a gomb felirata különbözik. Egyszerű presenterek példáit mutatjuk be, ahol az űrlapot először rekord hozzáadására, majd szerkesztésére használjuk, végül pedig egyesítjük a két megoldást.

Rekord hozzáadása

Példa egy presenter-re, amely rekord hozzáadására szolgál. Magát az adatbázis-kezelést a Facade osztályra bízzuk, amelynek kódja a példa szempontjából nem lényeges.

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;

		// ... hozzáadjuk az űrlap mezőit ...

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

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // rekord hozzáadása az adatbázishoz
		$this->flashMessage('Sikeresen hozzáadva');
		$this->redirect('...');
	}

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

Rekord szerkesztése

Most megmutatjuk, hogyan nézne ki egy presenter, amely rekord szerkesztésére szolgál:

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 // rekord létezésének ellenőrzése
			|| !$this->facade->isEditAllowed(/*...*/) // jogosultság ellenőrzése
		) {
			$this->error(); // 404 hiba
		}

		$this->record = $record;
	}

	protected function createComponentRecordForm(): Form
	{
		// ellenőrizzük, hogy az akció 'edit'
		if ($this->getAction() !== 'edit') {
			$this->error();
		}

		$form = new Form;

		// ... hozzáadjuk az űrlap mezőit ...

		$form->setDefaults($this->record); // alapértelmezett értékek beállítása
		$form->onSuccess[] = [$this, 'recordFormSucceeded'];
		return $form;
	}

	public function recordFormSucceeded(Form $form, array $data): void
	{
		$this->facade->update($this->record->id, $data); // rekord frissítése
		$this->flashMessage('Sikeresen frissítve');
		$this->redirect('...');
	}
}

Az action metódusban, amely rögtön a presenter életciklusának elején fut le, ellenőrizzük a rekord létezését és a felhasználó jogosultságát annak szerkesztésére.

A rekordot a $record property-be mentjük, hogy elérhető legyen a createComponentRecordForm() metódusban az alapértelmezett értékek beállításához, és a recordFormSucceeded() metódusban az ID miatt. Alternatív megoldásként beállíthatnánk az alapértelmezett értékeket közvetlenül az actionEdit() metódusban, és az URL részét képező ID értékét a getParameter('id') segítségével szerezhetnénk meg:

	public function actionEdit(int $id): void
	{
		$record = $this->facade->get($id);
		if (
			// létezés ellenőrzése és jogosultság ellenőrzése
		) {
			$this->error();
		}

		// űrlap alapértelmezett értékeinek beállítása
		$this->getComponent('recordForm')
			->setDefaults($record);
	}

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

Azonban, és ez kellene, hogy az egész kód legfontosabb tanulsága legyen, az űrlap létrehozásakor meg kell győződnünk arról, hogy az akció valóban edit. Mert különben az actionEdit() metódusban lévő ellenőrzés egyáltalán nem futna le!

Ugyanaz az űrlap hozzáadáshoz és szerkesztéshez

És most egyesítjük a két presentert egybe. Vagy megkülönböztethetnénk a createComponentRecordForm() metódusban, hogy melyik akcióról van szó, és ennek megfelelően konfigurálhatnánk az űrlapot, vagy ezt közvetlenül az action-metódusokra bízhatnánk, és megszabadulhatnánk a feltételtől:

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 // rekord létezésének ellenőrzése
			|| !$this->facade->isEditAllowed(/*...*/) // jogosultság ellenőrzése
		) {
			$this->error(); // 404 hiba
		}

		$form = $this->getComponent('recordForm');
		$form->setDefaults($record); // alapértelmezett értékek beállítása
		$form->onSuccess[] = [$this, 'editingFormSucceeded'];
	}

	protected function createComponentRecordForm(): Form
	{
		// ellenőrizzük, hogy az akció 'add' vagy 'edit'
		if (!in_array($this->getAction(), ['add', 'edit'])) {
			$this->error();
		}

		$form = new Form;

		// ... hozzáadjuk az űrlap mezőit ...

		return $form;
	}

	public function addingFormSucceeded(Form $form, array $data): void
	{
		$this->facade->add($data); // rekord hozzáadása az adatbázishoz
		$this->flashMessage('Sikeresen hozzáadva');
		$this->redirect('...');
	}

	public function editingFormSucceeded(Form $form, array $data): void
	{
		$id = (int) $this->getParameter('id');
		$this->facade->update($id, $data); // rekord frissítése
		$this->flashMessage('Sikeresen frissítve');
		$this->redirect('...');
	}
}