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í).