Повторное использование форм в нескольких местах

Как повторно использовать одну и ту же форму в нескольких местах и не дублировать код? Это очень легко сделать в Nette, и у вас есть несколько способов на выбор.

Фабрика форм

Давайте создадим класс, который может создавать форму. Такой класс называется фабрикой. В том месте, где мы хотим использовать форму (например, в презентере), мы запрашиваем фабрику как зависимость.

Часть фабрики — это код, который передает данные для дальнейшей обработки, когда форма успешно отправлена. Обычно на слой модели. Он также проверяет, всё ли прошло успешно, и передает обратно любые ошибки в форму. Модель в следующем примере представлена классом Facade:

use Nette\Application\UI\Form;

class EditFormFactory
{
	public function __construct(
		private Facade $facade,
	) {
	}

	public function create(/* parameters */): Form
	{
		$form = new Form;

		// добавляем элементы к форме

		$form->addSubmit('send', 'Submit');
		$form->onSuccess[] = [$this, 'processForm'];

		return $form;
	}

	public function processForm(Form $form, array $values): void
	{
		try {
			// обработка формы
			$this->facade->process($values);

		} catch (AnyModelException $e) {
			$form->addError('...');
		}
	}
}

Конечно, фабрика может быть параметрической, т. е. он может принимать параметры, которые будут влиять на внешний вид создаваемой формы.

Теперь мы продемонстрируем передачу фабрики презентеру. Сначала мы записываем её в конфигурационный файл:

services:
	- EditFormFactory

А затем запрашиваем в презентере. Последний шаг обработки отправленной формы — перенаправление на следующую страницу:

class MyPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private EditFormFactory $formFactory,
	) {
	}

	protected function createComponentEditForm(): Form
	{
		$form = $this->formFactory->create();

		$form->onSuccess[] = function (Form $form) {
			$this->redirect('this');
		};

		return $form;
	}
}

Поскольку перенаправление обрабатывается обработчиком презентера, компонент можно использовать в нескольких местах и в каждом из них перенаправлять в другое место.

Компонент с формой

Другой способ — создать новый компонент, содержащий форму. Это дает нам возможность визуализировать форму определенным образом, если компонент включает в себя шаблон. Или мы можем использовать сигналы для связи AJAX и загрузки информации в форму, например, для автозаполнения и т. д.

use Nette\Application\UI\Form;

class EditControl extends Nette\Application\UI\Control
{
	public $onSave;

	public function __construct(
		private Facade $facade,
	) {
	}

	protected function createComponentForm(): Form
	{
		$form = new Form;

		// добавляем элементы к форме

		$form->addSubmit('send', 'Submit');
		$form->onSuccess[] = [$this, 'processForm'];

		return $form;
	}

	public function processForm(Form $form, array $values): void
	{
		try {
			// обработка формы
			$this->facade->process($values);

		} catch (AnyModelException $e) {
			$form->addError('...');
			return;
		}

		// вызов события
		$this->onSave($this, $values);
	}
}

Далее мы создадим фабрику, которая будет производить этот компонент. Просто напишите его интерфейс:

interface EditControlFactory
{
	function create(): EditControl;
}

И добавьте в файл конфигурации:

services:
	- EditControlFactory

И теперь мы можем потребовать фабрику и использовать её в презентере:

class MyPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private EditControlFactory $controlFactory,
	) {
	}

	protected function createComponentEditForm(): Form
	{
		$control = $this->controlFactory->create();

		$control->onSave[] = function (EditControl $control, $data) {
			$this->redirect('this');
			// или перенаправить на результат редактирования, например:
			// $this->redirect('detail', ['id' => $data->id]);
		};

		return $control;
	}
}