Válí se ti projekty v šuplíku? Dostaň je mezi lidi a získej cool tričko a body na profi IT kurzy v soutěži ITnetwork summer 2017!
Přidej si svou IT školu do profilu a najdi spolužáky zde na síti :)

4. díl - Dokončení kalkulačky v Symfony

PHP Symfony Základy Dokončení kalkulačky v Symfony

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ítejte u další lekce online kurzu o tvorbě webových aplikací v PHP frameworku Symfony. V tomto tutoriálu navážeme na tvorbu kalkulačky z minulé lekce a dokončíme ji. Minule jsme tedy definovali operace modelu, ale to ještě není všechno, takže bez dalšího otálení jdeme na to! :)

Model

Základní operace bychom měli, ale to není všechno. Model by měl být rozšiřitelný a podle MVC bychom při změně modelu, tj. např. při přidání další operace, nejlépe neměli měnit ani kontroler, či šablonu. Abychom toho dosáhli, bude potřeba ještě několik věcí dodělat.

src/AppBundle/En­tity/Operation­.php

Symfony standardně reprezentuje své datové zdroje jako tzv. entity. My si tak pro naše operace kalkulačky také definujeme jednu takovou entitu. I když to na první pohled možná není zřejmé, je to výhodný tah nejen z hlediska modelu, ale tuto entitu můžeme dále využít i v rámci Symfony formulářů. Navíc, jak již bylo výše popsáno, přináší nám možnosti snadného rozšíření, či zkrácení modelu o operaci, ba dokonce i o počet členů v dané operaci. Samotná entita pak bude vypadat takto:

<?php

namespace AppBundle\Entity;

use AppBundle\Model\CalculatorManager;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * Definice operace kalkulačky.
 * @package AppBundle\Entity
 */
class Operation
{
        /**
         * @var int Operace.
         * @Assert\NotBlank(message = "Tohle pole je povinné.")
         */
        protected $operation = CalculatorManager::ADD;

        /**
         * @var int První číslo operace.
         * @Assert\NotBlank(message = "Tohle pole je povinné.")
         * @Assert\Type(
         *     type="int",
         *     message="Hodnota {{ value }} není validní číslo."
         * )
         */
        protected $x = 0;

        /**
         * @var int Druhé číslo operace.
         * @Assert\NotBlank(message = "Tohle pole je povinné.")
         * @Assert\Type(
         *     type="int",
         *     message="Hodnota {{ value }} není validní číslo."
         * )
         */
        protected $y = 0;

        /**
         * Getter pro operaci.
         * @return int operace
         */
        public function getOperation()
        {
                return $this->operation;
        }

        /**
         * Setter pro operaci.
         * @param int $operation operace
         */
        public function setOperation($operation)
        {
                $this->operation = $operation;
        }

        /**
         * Getter pro první číslo operace.
         * @return int první číslo operace
         */
        public function getX()
        {
                return $this->x;
        }

        /**
         * Setter pro první číslo operace.
         * @param int $x první číslo operace
         */
        public function setX($x)
        {
                $this->x = $x;
        }

        /**
         * Getter pro druhé číslo operace.
         * @return int druhé číslo operace
         */
        public function getY()
        {
                return $this->y;
        }

        /**
         * Setter pro druhé číslo operace.
         * @param int $y druhé číslo operace
         */
        public function setY($y)
        {
                $this->y = $y;
        }

        /**
         * Ošetření dělení nulou.
         * @return bool true jestli se dělí nulou, jinak false
         * @Assert\IsFalse(message = "Nelze dělit nulou.")
         */
        public function isDividedByZero()
        {
                return $this->getOperation() == CalculatorManager::DIVIDE && $this->getY() == 0;
        }
}

Vidíte, že v principu se jedná o klasickou třídu s definicí atributů a jejich getterů a setterů. Co je tady opravdu zajímavé jsou anotace. Pomocí nich si totiž tato třída hlídá, jaká data lze do jejích atributů uložit. V neposlední řadě také ošetřuje dělení nulou tak, že nemůže být nastavena operace dělení a zároveň v děliteli uložená nula. Pokud by vás dále do detailu zajímala všechna tato validační pravidla, odkáži vás na oficiální dokumentaci.

src/AppBundle/Mo­del/Calculator­Manager.php

Určitě jste si všimli konstant, které naše entitní třída využívá a pocházejí ze třídy CalculatorManager. My si je tam nyní doplníme a zároveň ještě dodáme metody pro práci s naší entitou operace:

...

/** Definice konstant pro operace. */
const
        ADD = 1,
        SUBTRACT = 2,
        MULTIPLY = 3,
        DIVIDE = 4;

/**
 * Getter pro existující operace.
 * @return array asociativní pole konstant pro operace a jejich slovního pojmenování
 */
public function getOperations()
{
        return array(
                'Sčítání' => self::ADD,
                'Odčítání' => self::SUBTRACT,
                'Násobení' => self::MULTIPLY,
                'Dělení' => self::DIVIDE
        );
}

/**
 * Zavolá zadanou operaci a vrátí její výsledek.
 * @param Operation $operation zadaná operace
 * @return int|null výsledek operace nebo null, pokud zadaná operace neexistuje
 */
public function calculate(Operation $operation)
{
        $x = $operation->getX();
        $y = $operation->getY();

        switch ($operation->getOperation()) {
                case self::ADD:
                        return $this->add($x, $y);
                case self::SUBTRACT:
                        return $this->subtract($x, $y);
                case self::MULTIPLY:
                        return $this->multiply($x, $y);
                case self::DIVIDE:
                        return $this->divide($x, $y);
                default:
                        return null;
        }
}

...

Nyní bychom měli být schopni přidat další operaci do modelu a přitom v kontroleru i v šabloně nemusíme změnit v podstatě nic. A jelikož je to z modelu již opravdu všechno, můžeme se přesunout právě na kontroler.

Získání modelu z presenteru

Když je řeč o kontroleru, je jasné, že v něm budeme potřebovat zajistit přístup k našemu modelu. To se v Symfony standardně řeší pomocí DI (Dependency Injection) a to tak, že v konfiguračním souboru services.yml zaregistrujeme náš model jako službu (service) a pak si ho na požádání necháme injektovat do našeho kontroleru. Najdeme tedy soubor app/config/ser­vices.yml a přidáme náš model:

...

services:
    calculator_manager:
        class: AppBundle\Model\CalculatorManager

Kontroler

Nyní se přesuneme na vytváření kontroleru.

src/AppBundle/Con­troller/Calcu­latorController­.php

V šabloně Symfony projektu máme v našem bundlu ve složce src/AppBundle/Con­troller/ již vytvořený soubor DefaultContro­ller.php. My ho však smažeme nebo přejmenujeme a vytvoříme si zde náš vlastní CalculatorCon­troller.php a v něm třídu CalculatorController, která rozšiřuje Symfony\Bundle\FrameworkBundle\Controller\Controller. Tím jsme si vytvořili základní kostru kontroleru, do které ještě doplníme výchozí metodu pro vykreslování šablony a webový formulář naší kalkulačky. Ve výsledku bude tedy vypadat takto:

<?php

namespace AppBundle\Controller;

use AppBundle\Entity\Operation;
use AppBundle\Model\CalculatorManager;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
 * Kontroler kalkulačky.
 * @package AppBundle\Controller
 */
class CalculatorController extends Controller
{
        /**
         * Výchozí vykreslovací metoda tohoto kontroleru.
         * @param Request $request HTTP požadavek
         * @return Response HTTP odpověď
         * @Route("/", name="homepage")
         */
        public function indexAction(Request $request)
        {
                /** @var CalculatorManager $calculatorManager Model pro práci s operacemi kalkulačky. */
                $calculatorManager = $this->get('calculator_manager');

                /** @var Form $form Formulář kalkulačky. */
                $form = $this->createFormBuilder(new Operation())
                        ->add('operation', ChoiceType::class, [
                                'label' => 'Operace:',
                                'choices' => $calculatorManager->getOperations(),
                                'expanded' => true,
                                'multiple' => false
                        ])
                        ->add('x', null, ['label' => 'První číslo:'])
                        ->add('y', null, ['label' => 'Druhé číslo:'])
                        ->add('calculate', SubmitType::class, ['label' => 'Spočítej výsledek'])
                        ->getForm();

                // Zpracování formuláře kalkulačky.
                $form->handleRequest($request);
                if ($form->isSubmitted() && $form->isValid())
                        $result = $calculatorManager->calculate($form->getData());

                // Vykreslení šablony s předáním formuláře a výsledku.
                return $this->render('AppBundle:Calculator:index.html.twig', [
                        'form' => $form->createView(),
                        'result' => isset($result) ? $result : null
                ]);
        }
}

Zde je potřeba se pozastavit hned nad několika věcmi. První z nich je routování, které je v Symfony standardně řešeno opět pomocí anotací. Rozebírat zde všechny možnosti asi nemá úplně smysl, my se s nimi budeme postupně seznamovat v průběhu seriálu, pokud by byl někdo hodně zvědavý, opět odkáži na oficiální dokumentaci.

Dále zde můžete vidět získání modelu pomocí DI. To asi nevyžaduje další komentář.

Poté zde vidíme vytváření a zpracování formuláře naší kalkulačky. Zpracování je poměrně přímočaré s delegací na náš model. U vytváření bych však poukázal na skutečnost, že celý formulář je vytvářen pomocí tzv. builder, což je další ze standardních Symfony postupů, přímo nad naší entitou operace. To umožňuje automatické doplnění typů a validace polí formuláře právě na základě typů a validace dat v entitě samotné.

Nakonec vidíte vykreslení příslušné šablony s předáním formuláře a případného výsledku, na kterou se hned vzápětí podíváme. :)

Šablona (View)

Z první lekce již víme, že Symfony používá šablonovací systém - Twig. Jako vzor zde můžeme použít původní šablonu z kostry projektu, jen si ji trochu upravíme. Nejdříve je však nutné říci, že tato šablona se nachází volně ve složce app/, nikoliv v bundlu naší aplikace. To vyřešíme jednoduše přesunem složky app/Resources/ do složky našeho bundlu src/AppBundle/. :)

src/AppBundle/Re­sources/views/Cal­culator/index­.html.twig

Konkrétní šablonu poté získáme následovně. Přejdeme do src/AppBundle/Re­sources/views/ a přejmenujeme složku default/ na Calculator/. V ní se pak nachází soubor index.html.twig, který poslouží jako naše šablona a my ho akorát upravíme:

{% extends 'AppBundle::base.html.twig' %}

{% block body %}
    <div id="wrapper">
        <div id="container">
            <div id="welcome">
                <h1>{% block title %}Kalkulačka{% endblock %}</h1>
            </div>

            {% if result %}
                <h2>Výsledek je: {{ result }}</h2>
            {% endif %}

            {{ form(form) }}
        </div>
    </div>
{% endblock %}

...

Jak je vidět, block pro CSS zachováme a upravíme pouze kód nad ním. Nejprve upravíme <h1>, který bude sloužit zároveň i jako html element <title> pro celou stránku. Dále přepíšeme zbylý obsah na naše vypsání výsledku třeba do <h2>. Pomocí {{ result }} se zde zobrazí data předaná z kontroleru a pomocí podmínky se výsledek zobrazí pouze pokud není null. Celý definovaný formulář pak vykreslíme do šablony pomocí funkce form() a názvu našeho formuláře ('form').

A tím je naše práce na šabloně i na celém projektu hotová. Pokud se teď podíváte na 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řiloženého archivu a podívejte na řešení. 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 ještě objasní seriál Jednoduchý redakční systém v PHP objektově (MVC).

To je pro tento díl opravdu vše, ale nebojte, příště začneme úplně novou pořádnou aplikaci v Symfony. A co to bude, to se nechte překvapit. ;)


 

Stáhnout

Staženo 26x (11.6 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?
3 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. :-)
Aktivity (2)

 

 

Komentáře

Avatar
MArtin
Člen
Avatar
MArtin:23. května 11:14

Debug tip: komu to hadze chybovu hlasku ohladne chybajuceho DefaultControllera, tak je potrebne zmazat cache - bud rucne - obsah adresara ./var/cache/ alebo slusne pouzitim konzoloveho prikazu bin/console cache:clear (prip. php bin/console cache:clear)

Editováno 23. května 11:14
 
Odpovědět  +1 23. května 11:14
Avatar
MArtin
Člen
Avatar
MArtin:23. května 12:29

Debug tip 2: pre tych co si kod prepisovali rucne podla clanku treba pridat do suboru CalculatorMana­ger.php na zaciatok:

use AppBundle\Entity\Operation;

...za namespace ...

 
Odpovědět 23. května 12:29
Avatar
MArtin
Člen
Avatar
MArtin:23. května 12:58

Debug tip 3: zmenit v subore .htaccess v adresari web:

- RewriteRule ^ %{ENV:BASE}/app.php [L]
+ RewriteRule ^ %{ENV:BASE}/app_dev.php [L]

...
- RedirectMatch 302 ^/$ /app.php/
+ RedirectMatch 302 ^/$ /app_dev.php/
Editováno 23. května 12:59
 
Odpovědět 23. května 12:58
Avatar
solta
Člen
Avatar
solta:25. května 1:58

nechápu význam služeb, může mi někdo vysvětlit jaký je rozdíl mezi

$calculatorManager = $this->get('calculator_manager');

a

$calculatorManager = new CalculatorManager();
 
Odpovědět  +1 25. května 1:58
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Odpovídá na solta
Jindřich Máca:25. května 13:47

Ten hlavní rozdíl je asi v tom, že pomocí toho get() vždy získáš pomocí DI jednu a tu samou instanci, když to pomocí new si pokaždé vytváříš novou.

Další rozdíl je, že pokud by měl konstruktor té získávané třídy další parametry, tak s použitím toho get() je neřešíš a rovnou dostáváš instanci se vším všudy, když to při použití new musíš mít k dispozici všechny potřebné parametry pro vytvoření té instance a to může být často docela složité.

Takže závěrem nejde ani tak o použití na tom jednom místě, ale o způsob získávání napříč celou aplikací. ;)

 
Odpovědět  +1 25. května 13:47
Avatar
solta
Člen
Avatar
Odpovídá na Jindřich Máca
solta:25. května 14:10

Díky za osvětlení. Bylo by fajn podobne veci rovnou vysvětlit v članku jistě nebudu sám kdo to potreboval vysvětlit:)

 
Odpovědět 25. května 14:10
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Odpovídá na solta
Jindřich Máca:25. května 15:53

V článku je to zmíněné jako princip DI (Dependenci Injection), což je obecný návrhový vzor, s jehož znalostí se tak trošku počítá. :-) To znamená, že pokud někdo zná DI, je mu jasné i to, co jsem napsal ve svém předchozím komentáři. ;-)

 
Odpovědět 25. května 15:53
Avatar
saavikam
Člen
Avatar
saavikam:7. června 9:57

Ahoj,
můžu se prosím zeptat na kod v src/AppBundle/En­tity/Operation­.php, např:

/**
* @var int První číslo operace.
* @Assert\NotBlan­k(message = "Tohle pole je povinné.")
* @Assert\Type(
* type="int",
* message="Hodnota {{ value }} není validní číslo."
* )
*/
protected $x = 0;

je to komentáři. Znamená to, že Symfony si tyto informace přečte z komentáře?

Děkuji
Andrea

 
Odpovědět 7. června 9:57
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Odpovídá na saavikam
Jindřich Máca:7. června 13:29

Ano, Symfony standardně dělá reflexi nad komentáři a využívá tzv. funkční anotace pro všemožné účely. :-)

 
Odpovědět 7. června 13:29
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 9 zpráv z 9.