Vánoční nadílka Vánoční nadílka
Až 80% zdarma! Předvánoční BLACK FRIDAY akce. Více informací

Lekce 4 - Dokončení kalkulačky v Nette

PHP Nette Framework Základy Dokončení kalkulačky v Nette

ONEbit hosting Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, První aplikace v Nette, jsme dokončili model a nyní se můžeme přesunout na presenter. Takže bez dalšího otálení jdeme na to! :)

Presenter

app/presenter­s/CalculatorPre­senter.php

V šabloně Nette webového projektu máme v složce app/presenters/ již vytvořený soubor HomepagePresener.php. My ho však smažeme nebo přejmenujeme a vytvoříme si zde náš vlastní CalculatorPresenter.php a v něm třídu CalculatorPresenter, která dědí z Nette\Application\UI\Presenter. Tím jsme si vytvořili základní kostru presenteru, do které ještě doplníme výchozí metodu pro vykreslování šablony, aby výsledku vypadala nějak takto:

<?php

namespace App\Presenters;

use Nette\Application\UI\Presenter;
use App\Model\CalculatorManager;
use Nette\Application\UI\Form;
use Nette\Utils\ArrayHash;

/**
 * Presenter kalkulačky.
 * @package App\Presenters
 */
class CalculatorPresenter extends Presenter
{
        /** @var int|null výsledek operace nebo null */
        private $result = null;

        /** Výchozí vykreslovací metoda tohoto presenteru. */
        public function renderDefault()
        {
                // Předání výsledku do šablony.
                $this->template->result = $this->result;
        }
}

Jak vidíte, předáváme zde výsledek do šablony, abychom ho mohli zobrazit. Výsledek je zatím pouze null, ale to za chvíli napravíme. ;)

Získání modelu z presenteru

K tomu, abychom získali výsledek, budeme potřebovat přístup k modelu. Ten se v Nette standardně zajišťuje pomocí Dependency Injection (zkráceně DI) a to tak, že v konfiguračním souboru config.neon zaregistrujeme náš model jako anonymní službu (service) a pak si ho necháme injektovat do našeho presenteru.

app/config/con­gif.neon

Nejdříve tedy najdeme tento soubor a upravíme přidáním služby:

...
services:
        router: App\RouterFactory::createRouter
        - App\Model\CalculatorManager

Nyní máme několik možností, jak a kde si nechat službu injektovat. Já si zvolím standardní a asi nejpoužívanější postup a to přes konstruktor presenteru. Do naší třídy CalculatorPresenter tdy přidáme následující kód:

// ...
/** @var CalculatorManager Instance třídy modelu pro práci s operacemi kalkulačky. */
private $calculatorManager;

/**
 * Konstruktor s injektovaným modelem pro práci s operacemi kalkulačky.
 * @param CalculatorManager $calculatorManager automaticky injektovaná třída modelu pro práci s operacemi kalkulačky
 */
public function __construct(CalculatorManager $calculatorManager)
{
        parent::__construct();
        $this->calculatorManager = $calculatorManager;
}
// ...

Nyní máme přístup k našemu modelu přes $this->calculatorManager.

Nette formuláře v Presenterech

Máme již vše potřebné pro výpočet a předání výsledku do šablony. Je tedy potřeba se zamyslet nad tím, jak budeme získávat data pro výpočet od uživatele. Odpověď je jednoduchá, přes formulář. To v Nette znamená, že si potřebujeme v presenteru zadefinovat formulář, který pak předáme do šablony. Z něj získáme data, která předáme modelu pro výpočet a výsledek uložíme do $this->result. Nette nám pro tento účel přináší systém komponent a speciálně pro formuláře v presenterech má již připravenou třídu Nette\Application\UI\Form. Nám již jen stačí pomocí ní zadefinovat podobu formuláře a funkci pro jeho zpracování:

/** Definice konstant pro zprávy formuláře. */
// ...
const
    FORM_MSG_REQUIRED = 'Tohle pole je povinné.',
    FORM_MSG_RULE = 'Tohle pole má neplatný formát.';
// ...
/**
 * Vrátí formulář kalkulačky.
 * @return Form formulář kalkulačky
 */
protected function createComponentCalculatorForm()
{
    $form = new Form;
        // Získáme existující operace kalkulačky a dáme je do výběru operací.
    $form->addRadioList('operation', 'Operace:', $this->calculatorManager->getOperations())
         ->setDefaultValue(CalculatorManager::ADD)
         ->setRequired(self::FORM_MSG_REQUIRED);
    $form->addText('x', 'První číslo:')
        ->setType('number')
        ->setDefaultValue(0)
        ->setRequired(self::FORM_MSG_REQUIRED)
        ->addRule(Form::INTEGER, self::FORM_MSG_RULE);
    $form->addText('y', 'Druhé číslo:')
        ->setType('number')
        ->setDefaultValue(0)
        ->setRequired(self::FORM_MSG_REQUIRED)
        ->addRule(Form::INTEGER, self::FORM_MSG_RULE)
        // Ošetříme dělení nulou.
        ->addConditionOn($form['operation'], Form::EQUAL, CalculatorManager::DIVIDE)
        ->addRule(Form::PATTERN, 'Nelze dělit nulou.', '^[^0].*');
    $form->addSubmit('calculate', 'Spočítej výsledek');
    $form->onSuccess[] = [$this, 'calculatorFormSucceeded'];
    return $form;
}

/**
 * Funkce se vykonaná při úspěšném odeslání formuláře kalkulačky a zpracuje odeslané hodnoty.
 * @param Form $form        formulář kalkulačky
 * @param ArrayHash $values odeslané hodnoty formuláře
 */
public function calculatorFormSucceeded($form, $values)
{
    // Necháme si vypočítat výsledek podle zvolené operace a zadaných hodnot.
    $this->result = $this->calculatorManager->calculate($values->operation, $values->x, $values->y);
}

Je toho více, takže to vezmeme hezky popořadě. První metoda je speciální metoda pro tvorbu komponent, která se definuje jako createComponent<název_komponenty>() a vrací požadovanou komponentu. Jen upozorňuji, že u této metody stačí, aby byla protected. Pokud metoda splňuje tyto podmínky, můžeme jí poté zavolat přímo ze šablony a to pomocí speciálního Latte makra {control}, což si za chvíli ukážeme.

Dále pár slov k samotnému formuláři. Definujeme zde celkem 4 prvky:

  1. Pole radio inputů, které slouží pro výběr operace výpočtu. Zde si můžete všimnout volání metody z modelu, která nám vrátí univerzálně všechny dostupné operace. Dále nastavujeme výchozí hodnotu na sčítání a že je povinné toho pole vyplnit.
  2. Text input pro zadání prvního čísla, kde nastavíme, že se jedná o typ inputu number, což platí pro prohlížeče podporující HTML5. Dále určíme výchozí hodnotu 0 a validační pravidlo, že toto pole musí být vyplněno a pouze hodnotou celého čísla.
  3. Text input pro zadání druhého čísla, který je identický s tím prvním, pouze obsahuje navíc pravidlo, kde ošetříme dělení nulou, což jsem slíbil. Teď by asi byla na místě diskuze, proč jsem to ošetřil zde a ne již v modelu. Samozřejmě to lze udělat oběma způsoby. Já jsem se ale rozhodl demonstrovat, že presenter by měl zpracovat všechna data z šablony a do modelu už by měla jít jen ta data, se kterými tento model umí pracovat. Navíc zde může dojít k validaci již na straně uživatele a celkově je tento způsob efektivnější, než například vyhazovat nějaké výjimky z modelu. :)
  4. Submit button jako jednoduché tlačítko pro odeslání formuláře.

Nakonec se formuláři ještě předá funkce, která se volá při jeho úspěšném odeslání a do parametru dostane uživatelem zadané hodnoty. Chtěl bych zde upozornit na to, že úspěšné odeslání formuláře znamená jeho kompletní validaci na straně klienta i serveru podle zadaných pravidel, tzn. že do metody calculatorFormSucceeded() se dostanou už jen hodnoty, se kterými umí náš model pracovat. V této metodě pak stačí jen zavolat univerzální výpočetní funkci z modelu, předat jí potřebné hodnoty od uživatele a výsledek uložit do $this->result. Nyní je náš presenter již kompletní, ovšem cesta k němu není úplně jasná. Pojďme to napravit.

Routování v Nette

Jelikož jsme nahradili původní třídu HomepagePresenter naší vlastní, musíme upravit i router, který k němu určoval cestu, tj. pod jakou URL ho najdeme. V Nette k tomu slouží tzv. RouterFactory a opět věřím, že ti bystřejší z vás si všimli její definice v config.neon při nastavování DI. Nyní jí potřebujeme upravit tak, aby pod výchozí URL byl právě náš presenter.

app/router/Rou­terFactory.php

Takže si otevřeme příslušný soubor a takto si ho upravíme:

<?php

namespace App;

use Nette\Application\IRouter;
use Nette\Application\Routers\Route;
use Nette\Application\Routers\RouteList;
use Nette\StaticClass;

class RouterFactory
{
        use StaticClass;

        /**
         * @return IRouter
         */
        public static function createRouter()
        {
                $router = new RouteList;
                $router[] = new Route('<presenter>/<action>[/<id>]', 'Calculator:default');
                return $router;
        }
}

Nyní je tedy pod výchozí URL adresou náš presenter a jeho akce default (metoda renderDefault()). Teď už zbývá jen poslední část skládačky a tou je šablona.

Šablona (View)

Jak již bylo řešeno v první lekci, Nette používá vlastní šablonovací systém Latte. V něm si teď vytvoříme šablonu pro naši aplikaci. Jako vzor si necháme původní šablonu, jen ji trochu upravíme.

app/presenter­s/templates/Cal­culator/defau­lt.latte

Nejdříve půjdeme do složky app/presenters/templates/ a zde přejmenujeme složku Homepage/ na Calculator/. V ní se poté nachází soubor default.latte, který poslouží jako šablona pro naši akci default a my ho pouze upravíme:

{block content}
<div id="banner">
        <h1 n:block=title>Kalkulačka</h1>
</div>

<div id="content">
        <h3 n:ifset=$result>Výsledek je: {$result}</h3>
        {control calculatorForm}
</div>

<style>
...

Jak je vidět, <style> zachováme a upravíme pouze kód nad ním.

Nejprve upravíme <h1>, který díky makru n:block=title slouží zároveň i jako html <title> pro celou stránku.

Následně vymažeme obsah <div id="content"> a doplníme naše vypsání výsledku např. do <h3>. Pomocí {$result} se zde zobrazí data předaná z presenteru a pomocí makra n:ifset se výsledek zobrazí pouze pokud není null.

Celý definovaný formulář pak vykreslíme do šablony pomocí již zmíněného makra {control <název_komponenty>} a názvu našeho formuláře (metody createComponent*()). A tím je naše práce na šabloně i na celém projektu hotová. Pokud nyní otevřete URL projektu, uvidíte zde plně funkční kalkulačku a můžete na ní vyzkoušet všechny chytáky, které vás napadnou :)

Pokud vám není cokoli jasné, stáhněte si projekt z přílohy a projeďte si jak se data předávají z modelu do presenteru a odtud do šablony. Kódu v aplikaci tolik nemáme, po chvíli byste se v principu měli zorientovat. Pokud ani to nepomůže, určitě vám MVC/MVP objasní seriál Jednoduchý redakční systém v PHP objektově (MVC).

To je pro dnešní lekci opravdu vše, ale nebojte. V příští lekci, Jednoduchý redakční systém v Nette - Struktura projektu, začneme úplně novou pořádnou aplikaci v Nette ;)


 

Stáhnout

Staženo 775x (1.22 MB)
Aplikace je včetně zdrojových kódů v jazyce PHP

 

 

Článek pro vás napsal Jindřich Máca
Avatar
Jak se ti líbí článek?
19 hlasů
Autor se věnuje převážně webovým technologiím, ale má velkou zálibu ve všem vědeckém, nejen ze světa IT. :-)
Miniatura
Předchozí článek
První aplikace v Nette
Miniatura
Všechny články v sekci
Základy Nette frameworku
Aktivity (8)

 

 

Komentáře
Zobrazit starší komentáře (45)

Avatar
Vít Cigánek:23. srpna 9:46

Mam uplne stejny problem s routerem jak vanek.

 
Odpovědět 23. srpna 9:46
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Odpovídá na Vít Cigánek
Jindřich Máca:23. srpna 23:00

Taky už jsem na to jednou odpovídal. Chyba je v nesprávném zacházení s namespace a use. Jednoduše třída, kterou chceme a definuje se v rámci use je Nette\Application\IRouter a ta chyba říká, že se pokoušíte místo toho použít třídu App\Nette\Application\IRouter, což je nesmysl. Tohle ale spadá do základní práce v čistém PHP - http://php.net/…s.basics.php, Nette tohoto mechanismu pouze využívá. :-`

 
Odpovědět 23. srpna 23:00
Avatar
Marty
Člen
Avatar
Marty:29. října 17:30

Jen drobné nepodstatné povšimnutí. Proč složky templates a presenters jsou v množném čísle, a složka model jen v jednotném?

 
Odpovědět 29. října 17:30
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Odpovídá na Marty
Jindřich Máca:29. října 17:44

To je úplně jedno. Ale asi by tam mělo být spíš to models. :-`

 
Odpovědět 29. října 17:44
Avatar
Marty
Člen
Avatar
Odpovídá na Jindřich Máca
Marty:29. října 22:20

Ono je to v jednotném čísle normálně při vytvoření jakéhokoli nette projektu. Přemýšlel jsem, jestli to je nějaký záměr, ale nevím. :)

 
Odpovědět 29. října 22:20
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Odpovídá na Marty
Jindřich Máca:29. října 22:33

Právě a já jsem to tak prostě nechal.

A pokud tam nějaký hlubší záměr je, tak se přiznám, že o něm nevím... :-D

 
Odpovědět 29. října 22:33
Avatar
Martin Konečný (pavelco1998):29. října 22:53

Marty Jindřich Máca Řekl bych, že jednotné číslo označuje název vrstvy, ne ve smyslu "kolekce tříd pod touto složkou". Tedy když v MVP je vrstva Presenter, bude složka pojmenována Presenter. Kdy je tam vrstva Model, bude složka pojmenovaná Model. A v ní teprve v množném čísle oddělené složkami "kolekce" tříd, např. Managers, Repositories, Listeners, Exceptions atd.

Odpovědět 29. října 22:53
Aktuálně připravuji browser RPG, FB stránka - https://www.facebook.com/AlteiraCZ
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Odpovídá na Martin Konečný (pavelco1998)
Jindřich Máca:29. října 23:28

No to chápu, ale právě tady řešíme, že v šabloně to takhle není a to je na tom to divné. :-S Když se podíváš přímo na čistý sandbox, tak uvidíš, že je tam vrstva pro Presenter a složka se jmenuje presenters/ a potom je tam vrstva Model, přičemž složka se jmenuje model/. :D

 
Odpovědět 29. října 23:28
Avatar
Odpovídá na Jindřich Máca
Martin Konečný (pavelco1998):29. října 23:35

Těžko říct, no :D Je pravda, že také používám název "Presenters", ale pak mám název "Model". Mám to ale právě kvůli tomu, že složku "Model" označuji spíše jako celou vrstvu, ve které mám určité "typy" modelových tříd (Facades, Listeners atd.), ve složce Presenters mám jen seznam jednotlivých presenterů. Tedy abych nepsal Presenter/Pre­senters/UserPre­senter, tak to píšu rovnou pod jeden název. U modelových tříd pak mám dělení např. Model/Facades/Ar­ticleSearcher, Model/Entities/Ar­ticle :)

Editováno 29. října 23:36
Odpovědět  +1 29. října 23:35
Aktuálně připravuji browser RPG, FB stránka - https://www.facebook.com/AlteiraCZ
Avatar
Jaroslav Patrný:22. listopadu 23:18

Postupoval jsem podle textu, nemám tudíž
- App\Forms\Sig­nInFormFactory
- App\Forms\Sig­nUpFormFactory
v configu a hlásilo mi to tedy výjimku:

Nette\DI\Servi­ceCreationExcep­tion
Service 'application.4' (type of App\Presenter­s\SignPresenter): Service of type App\Forms\Sig­nInFormFactory needed by $signInFactory in App\Presenter­s\SignPresenter::__con­struct() not found. Did you register it in configuration file?

Proto jsem ve třídě SignPresenter jsem zakomentoval konstruktor

public function __construct(Forms\SignInFormFactory $signInFactory, Forms\SignUpFormFactory $signUpFactory)

Kalkulačka nyní už chodí.

 
Odpovědět 22. listopadu 23:18
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 55. Zobrazit vše