Best practise: formuláře jako komponenty

Vytvářet znovupoužitelné formuláře je snadné, máte na výběr několik různých způsobů a každý se hodí na jiný případ.

Továrna na UI\Form

Uděláme si factory třídu, kterou zaregistrujeme do DI Containeru a použijeme v našich presenterech k vytváření instancí formulářů.

use Nette\Application\UI;

class CategoryForm
{
	private $database;

	public function __construct(Nette\Database\Connection $database)
	{
		$this->database = $database;
	}

	public function create()
	{
		$form = new UI\Form;
		// mohu použít $this->database

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

		return $form;
	}

	public function processForm($form)
	{
		// mohu použít $this->database
		// zpracovani formulare
	}
}

Zaregistrujeme továrnu jako službu

services:
	- CategoryForm

Injectneme si ho do presenteru

use Nette\Application\UI;

class MyPresenter extends BasePresenter
{
	/** @var \CategoryForm @inject */
	public $categoryFormFactory;

	protected function createComponentCategoryForm()
	{
		$form = $this->categoryFormFactory->create();
		$form->onSuccess[] = function (UI\Form $form) {
			$this->redirect('this');
		};

		return $form;
	}
}

Aby byla komponenta skutečně znovupoužitelná, je nejlepší přesměrování po úspěšném odeslání mít až v konkrétní továrničce. Takto mohu komponentu použít na více místech a na každém můžu bez odporného ifování přesměrovat vždy jinam.

Formulář v UI\Control a generovaná továrnička

Pokud chceme, aby se formulář renderoval specifickým způsobem a opět ho chceme použít na více místech aplikace, je vhodné obalit ho do UI\Control, který si nese vlastní šablonu.

class CategoryForm extends UI\Control
{
	private $database;

	public $onCategorySave;

	public function __construct(Nette\Database\Connection $database)
	{
		parent::__construct();
		$this->database = $database;
	}

	protected function createComponentForm()
	{
		$form = new UI\Form;
		// mohu použít $this->database

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

		return $form;
	}

	public function processForm($form)
	{
		// mohu použít $this->database
		// zpracování formuláře, např. změním údaje upravované kategorie
		// $category je nějaký řádek tabulky (entita), kterou zpracováváme
		$this->onCategorySave($this, $category);
	}
}

/** rozhranní pro generovanou továrničku */
interface ICategoryFormFactory
{
	/** @return \CategoryForm */
	function create();
}

Samozřejmostí je použití generovaných továrniček, díky kterým opět můžeme komponentu vytvářet pomocí DI Containeru a o předávání služeb se nestaráme.

Zaregistrujeme rozhranní pro generovanou továrničku jako službu

services:
	- ICategoryFormFactory

Toto rozhraní si necháme injectnout do presenteru.

use Nette\Application\UI;

class MyPresenter extends BasePresenter
{
	/** @var \ICategoryFormFactory @inject */
	public $categoryFormFactory;

	protected function createComponentCategoryForm()
	{
		$control = $this->categoryFormFactory->create();
		$control->onCategorySave[] = function (CategoryControl $control, $category) {
			$this->redirect('this');
			// nebo například přesměrujeme na detail dané kategorie
			// $this->redirect('detail', ['id' => $category->id]);
		};

		return $control;
	}
}

Jako bonus je možné velice snadno využít signály na pokročilou ajaxovou komunikaci a donačítání informací do formuláře (například napovídání).