Form for Creating and Editing a Record
How to properly implement adding and editing a record in Nette, using the same form for both?
In many cases, the forms for adding and editing records are identical, perhaps differing only in the button label. We will show examples of simple presenters where we use the form first to add a record, then to edit it, and finally combine the two solutions.
Adding a Record
Example of a presenter for adding a record. We'll leave the actual database interaction to a Facade
class, whose
code isn't essential for this example.
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;
// ... add form fields ...
$form->onSuccess[] = [$this, 'recordFormSucceeded'];
return $form;
}
public function recordFormSucceeded(Form $form, array $data): void
{
$this->facade->add($data); // add record to the database
$this->flashMessage('Successfully added');
$this->redirect('...');
}
public function renderAdd(): void
{
// ...
}
}
Editing a Record
Now let's look at what a presenter for editing a record would look like:
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 // verify the existence of the record
|| !$this->facade->isEditAllowed(/*...*/) // check permissions
) {
$this->error(); // 404 error
}
$this->record = $record;
}
protected function createComponentRecordForm(): Form
{
// verify that the action is 'edit'
if ($this->getAction() !== 'edit') {
$this->error();
}
$form = new Form;
// ... add form fields ...
$form->setDefaults($this->record); // set default values
$form->onSuccess[] = [$this, 'recordFormSucceeded'];
return $form;
}
public function recordFormSucceeded(Form $form, array $data): void
{
$this->facade->update($this->record->id, $data); // update record
$this->flashMessage('Successfully updated');
$this->redirect('...');
}
}
In the action method, invoked at the beginning of the presenter lifecycle, we verify the record's existence and the user's permission to edit it.
We store the record in the $record
property, making it available in the createComponentRecordForm()
method for setting default values and in recordFormSucceeded()
for accessing the ID. An alternative solution is to
set the default values directly in actionEdit()
and retrieve the ID value (part of the URL) using
getParameter('id')
:
public function actionEdit(int $id): void
{
$record = $this->facade->get($id);
if (
// verify existence and check permissions
) {
$this->error();
}
// set default form values
$this->getComponent('recordForm')
->setDefaults($record);
}
public function recordFormSucceeded(Form $form, array $data): void
{
$id = (int) $this->getParameter('id');
$this->facade->update($id, $data);
// ...
}
}
However, and this should be the most important takeaway from the entire code, we must ensure the action is indeed
edit
when creating the form. Otherwise, the verification in the actionEdit()
method would not occur
at all!
Same Form for Adding and Editing
Now, let's combine both presenters into one. We could either differentiate the action within the
createComponentRecordForm()
method and configure the form accordingly, or we can delegate this to the action methods
directly and eliminate the conditional check:
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 // verify the existence of the record
|| !$this->facade->isEditAllowed(/*...*/) // check permissions
) {
$this->error(); // 404 error
}
$form = $this->getComponent('recordForm');
$form->setDefaults($record); // set default values
$form->onSuccess[] = [$this, 'editingFormSucceeded'];
}
protected function createComponentRecordForm(): Form
{
// verify that the action is 'add' or 'edit'
if (!in_array($this->getAction(), ['add', 'edit'])) {
$this->error();
}
$form = new Form;
// ... add form fields ...
return $form;
}
public function addingFormSucceeded(Form $form, array $data): void
{
$this->facade->add($data); // add record to the database
$this->flashMessage('Successfully added');
$this->redirect('...');
}
public function editingFormSucceeded(Form $form, array $data): void
{
$id = (int) $this->getParameter('id');
$this->facade->update($id, $data); // update record
$this->flashMessage('Successfully updated');
$this->redirect('...');
}
}