Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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;

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. Šablony se zpravidla ukládají do složky templates/.

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 :)

Základy frameworku Symfony pro PHP

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 kvízu, Kvíz - Struktura projektu, výpis, MVC v Symfony, si vyzkoušíme nabyté zkušenosti z předchozích lekcí.


 

Měl 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 411x (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
Přeskočit článek
(nedoporučujeme)
Kvíz - Struktura projektu, výpis, MVC v Symfony
Článek pro vás napsal Jindřich Máca
Avatar
Uživatelské hodnocení:
34 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