Pouze tento týden sleva až 80 % na e-learning týkající se JavaScriptu
Aktuálně: Postihly zákazy tvou profesi? Poptávka po ajťácích prudce roste, využij slevové akce 30% výuky zdarma!
Discount week - April - 30

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

V minulé lekci, První aplikace v Symfony, jsme načali jednoduchou kalkulačku tak, že jsme si naprogramovali model a popovídali o základních myšlenkách MVC.

Dnes se podíváme na kontroler, šablony a další prvky. 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/Entity/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. Symfony MakerBundle má k dispozici pár příkazků na vytvoření entity nebo kontroleru, všechny příkazy můžeme zobrazit pomocí příkazu z kořenové složky Symfony projektu:

php bin/console list make

Entitu vygenerovávat zatím nebudeme, protože nebudeme nic tahat z databáze (vygeneruje to i repozitář). Vytvoříme si proto klasicky soubor entity Operation.php, který bude mít obsah:

<?php

namespace App\Entity;

use App\Model\Calculator;
use Symfony\Component\Validator\Constraints as Assert;

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

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

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

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

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

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

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

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

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

    /**
     * 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(): bool
    {
        return $this->getOperation() == Calculator::DIVIDE && $this->getY() == 0;
    }
}

Vidíte, že v principu se jedná o klasickou třídu s definicí atributů, 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/Model/Cal­culator.php

Určitě jste si všimli konstant, které naše entitní třída využívá a pocházejí ze třídy Calculator. 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(): array
{
    return [
        '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): ?int
{
    $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;
    }
}
...

Nesmíme zapomenout pro import Entity Operation, přidáme tedy ještě tento řádek pod namespace a nad název třídy:

use App\Entity\Operation;
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

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). To pro nás znamená, že díky standardní konfiguraci v souboru config/services.yml, si budeme moci na požádání nechat injektovat náš model do našeho kontroleru.

Kontroler

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

src/Controller/CalculatorController.php

Nyní by nebylo na škodu využít generátor kontroleru. Do konzole tedy napíšeme:

php bin/console make:controller

Zeptá se nás to na název kontroleru, napíšeme CalculatorController a potvrdíme enterem. Vytvořoí se nám kontroler ve složce src/Controller/ a také rovnou šablona ve složce templates/calculator/, šabloně se budeme věnovat potom. Kontroler bude ve výsledné podobě vypadat takto:

<?php

namespace App\Controller;

use App\Entity\Operation;
use App\Model\Calculator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

/**
 * Kontroler kalkulačky.
 * @package App\Controller
 */
class CalculatorController extends AbstractController
{
    /**
     * Výchozí vykreslovací metoda tohoto kontroleru.
     * @param Request    $request    HTTP požadavek
     * @param Calculator $calculator Model pro práci s operacemi kalkulačky.
     * @return Response HTTP odpověď
     * @Route("/", name="homepage")
     */
    public function index(Request $request, Calculator $calculator): Response
    {
        $calculatorForm = $this->createFormBuilder(new Operation())
            ->add('operation', ChoiceType::class, [
                'label' => 'Operace:',
                'choices' => $calculator->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.
        $calculatorForm->handleRequest($request);
        if ($calculatorForm->isSubmitted() && $calculatorForm->isValid())
            $result = $calculator->calculate($calculatorForm->getData());

        // Vykreslení šablony s předáním formuláře a výsledku.
        return $this->render('calculator/index.html.twig', [
            'calculatorForm' => $calculatorForm->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ůžeme vidět získání modelu pomocí DI v parametru metody index(). 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. builderu, 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íme 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á šablonový systém Twig.

templates/calculator/index.html.twig

Přejdeme do složky templates/, poté calculator/ a otevřeme si vygenerovaný soubor index.html.twig. Ten upravíme následovně:

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

{% block stylesheets %}
    {{ parent() }}
    <style>
        .wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; }
    </style>
{% endblock %}

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

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

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

Jak je vidět, začneme definicí bloku pro CSS, kde pomocí volání parent() zachováme případné styly z nadřazené šablony base.html.twig. Dále nejprve upravíme <h1>, který bude sloužit zároveň i jako html element <title> pro celou stránku. Přepíšeme zbylý obsah na naše vypsání výsledku třeba do <h2> a pomocí {{ result }} zde zobrazíme data předaná z kontroleru. Nesmíme zapomenout na podmínku, že výsledek se zobrazí pouze, pokud není null. Celý definovaný formulář pak vykreslíme do šablony pomocí funkce form() a názvu našeho formuláře ('calculatorForm').

A tím je naše práce na šabloně i na celém projektu hotová. Pokud se teď podíváme na kořenovou URL projektu (například tedy 127.0.0.1:8000 v případě vestavěného serveru), uvidíme zde plně funkční kalkulačku a můžeme na ní vyzkoušet všechny chytáky, které ná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 dnešní lekci opravdu vše.

V následujícím cvičení, Řešené úlohy k 1.-4. lekci frameworku Symfony pro PHP, si procvičíme nabyté zkušenosti z předchozích lekcí.


 

Měla jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

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

 

Předchozí článek
První aplikace v Symfony
Všechny články v sekci
Základy frameworku Symfony pro PHP
Článek pro vás napsal Jindřich Máca
Avatar
Jak se ti líbí článek?
4 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 (20)

 

 

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

Avatar
mirextichy
Člen
Avatar
mirextichy:14.3.2019 7:19

Kalkulačka, kterou jsem "postřípkoval" z autorových lekcí mi zaboha nefungovala (mám předposlední Wampserver64), pořád nějaký exception! Tak jsem se naštval, stáhnul hotovou zip verzi od autora, svoji jsem smazal (já osel) a rozbalil staženou.A stále exception:

(2/2) ErrorException
Warning: "continue" targeting switch is equivalent to "break". Did you mean to use "continue 2"?
in UnitOfWork.php line 2718

Chvíli jsem brousil po webu a pak mně to napadlo! ...přepni z PHP 7.3 na PHP 7.1 a hle: vše je OK :)! Kalkulačka kalkulí!

Pro autora: jaké je řešení? Předem díky za odpověď.

 
Odpovědět
14.3.2019 7:19
Avatar
Miroslav Petras:20.8.2020 15:50

Ahoj,
prosimte, jak dojde k tomu, ze se zavola metoda isDividedByZero() ?
Nikde v kodu jeji volani nevidim ...

Diky

 
Odpovědět
20.8.2020 15:50
Avatar
Odpovídá na Miroslav Petras
Miroslav Petras:20.8.2020 17:58

Jo, tak uz vim jak to je :).

 
Odpovědět
20.8.2020 17:58
Avatar
MiraJ
Člen
Avatar
MiraJ:6.10.2020 15:28

V sablone lepe nez "if result" je pouzit "if result is not empty", jinak se pri vysledku rovno 0 nezobrazi vysledek.

 
Odpovědět
6.10.2020 15:28
Avatar
Odpovídá na mirextichy
Renáta Fejglová:29.10.2020 16:09

V modelu Calculator uvnitř funkce calculate není u switch rozhodování použit žádný break, který by ukončoval case větve. Syntaxes PHP 7.3 s tím má pravděpodobně problém a ptá se, zda je to záměr nebo chyba.

 
Odpovědět
29.10.2020 16:09
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Odpovídá na Renáta Fejglová
Filip Sáblík:20. února 14:27

není break, proto ze je return

 
Odpovědět
20. února 14:27
Avatar
Odpovídá na Filip Sáblík
Renáta Fejglová:23. února 16:19

Ano, já chápu, ale PHP interpreter s tím má stejně od verze 7.3 problém. I když to je (byla) naprosto validní alternativa použít return místo break nebo u některých větví switche break vynechat. Takže v tomto případě buď "otrocky" doplnit break, i když zde není nutný, ať je kontrola syntaxe spokojená, nebo odchytit a třeba zahodit tuto specifickou vygenerovanou výjimku.

 
Odpovědět
23. února 16:19
Avatar
Odpovídá na Renáta Fejglová
Filip Sáblík:23. února 18:36

Na to ti asi exaktně neodpovím. Ale Syntaxe switche by implicitně měla obsahovat break; pro ukončeni procházení switche. Proto se to PHP interpreteru nelíbí. Jenže zde získáš stav z funkce návratovou funkcí return, čímž nesplníš potřebu interpreteru po break, ale z funkce dostaneš co potřebuješ.

podle mně tu řešíš spíš nějakou vnitřní potřebu (možná OCD) , naplnit potřebu interpreteru. Ačkoli dopad na funkčnost a čistotu tu není žádný, tak buď si dopiš break, nebo si nastav výjimku :-)

 
Odpovědět
23. února 18:36
Avatar
Renáta Fejglová:23. února 18:48

Myslím, že se nechápeme. Neřeším čistotu kódu. Jen jsem reagovala jsem na původní dotaz tazatele, proč dostává od PHP uvedené verze výjimku bez použití breaku. Tu výjimku generuje sám interpret (odzkoušeno) a pokud se jí tazatel chce zbavit, tak buď musí upravit kód nebo ji odchytit Nic víc, nic míň.

 
Odpovědět
23. února 18:48
Avatar
Filip Sáblík:24. února 9:24

http://sandbox.onlinephpfunctions.com/…b8950ccd6da8

asi ne, nicméně jsem zjednodušil tvůj "problém" a hodil do PHP sandboxu - viz. link výše, kde lze otestovat části kódu, případně získat chybová hlášení. PHP verze lze pohodlně přepínat...

Projel jsem kód v PHP 7.3.,7.4., 8.*
a bez problému.

Jinak kalkulačku jsem si u sebe rozjížděl taky, a vše bez problému. Můžeš sem hodit screen co konkrétně se děje?

 
Odpovědět
24. února 9:24
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 20. Zobrazit vše