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

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

Unicorn College ONEbit hosting 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 6x (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. :-)
Miniatura
Předchozí článek
První aplikace v Symfony
Miniatura
Všechny články v sekci
Základy frameworku Symfony pro PHP
Aktivity (2)

 

 

Komentáře

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.

Zatím nikdo nevložil komentář - buď první!