IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
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 3 - CMS v Nette a Doctrine 2 - Modely a Layout

V minulé lekci, CMS v Nette a Doctrine 2 - Kostra aplikace, jsme si založili projekt se všemi potřebnými knihovnami a úpravami.

V dnešním Nette tutoriálu začneme tvořit modelovou vrstvu a přidáme layout s úvodní stránkou.

Úvodní strána v Nette Doctrine CMS - CMS v Nette a Doctrine 2

Model

Klíčem k práci s Doctrine jsou anotace. Pomocí nich si uloží do cache informace o entitách, jako jsou názvy sloupců, jejich datové typy, vazby mezi entitami atd. Pokud tedy například pomocí anotace určíme, že proměnná bude typu boolean, při výběru dat z databáze se hodnota automaticky přetypuje na TRUE nebo FALSE (v databázi hodnota bude uložena jako 1 / 0). Informace lze zapisovat i pomocí formátu XML nebo YAML, ale anotace jsou nejpoužívanější.

Entity

app/model/enti­ties/User.php

Vytvoříme si naši první jednoduchou entitu, která bude reprezentovat uživatele. Nezapomínejte na to, že Nette obsahuje třídu Nette\Security\User, jejíž objekt je ve všech presenterech a šablonách v proměnné $user. Neměli bychom tuto proměnnou přepsat, proto se proměnná naší entity bude v šablonách jmenovat $userEntity.

Pro začátek bude naše entita velmi jednoduchá. Obsahuje definici atributů, které odpovídají sloupcům v databázové tabulce user. Název je odvozen od názvu entity, proto pokud bychom měli jiný název tabulky, přidáme anotaci @ORM\Table(name="nazev_tabulky").

Stejně to platí i u názvů sloupců, tam přidáme do závorky parametr name (@ORM\Column(name="nazev_sloupce")).

<?php

namespace App\Model\Entities;

use Doctrine\ORM\Mapping as ORM;
use Kdyby\Doctrine\Entities\BaseEntity;

/**
 * Doctrine entita pro tabulku user.
 * @package App\Model\Entities
 * @ORM\Entity
 */
class User extends BaseEntity
{
    // Pomocné konstanty pro náš model.

    /** Konstanty pro uživatelské role. */
    const ROLE_USER = 1,
        ROLE_ADMIN = 2;

    /** Konstanty pro uživatelské jméno. */
    const MAX_NAME_LENGTH = 15,
        NAME_FORMAT = "^[a-zA-Z0-9]*$";

    // Proměné reprezentující jednotlivé sloupce tabulky.

    /**
     * Sloupec pro ID uživatele.
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     */
    protected $id;

    /**
     * Sloupec pro jméno.
     * @ORM\Column(type="string")
     */
    protected $name;

    /**
     * Sloupec pro heslo.
     * @ORM\Column(type="string")
     */
    protected $password;

    /**
     * Sloupec pro email.
     * @ORM\Column(type="string")
     */
    protected $email;

    /**
     * Sloupec pro IP adresu.
     * @ORM\Column(type="string")
     */
    protected $ip;

    /**
     * Sloupec pro datum registrace.
     * @ORM\Column(name="`registration_date`", type="datetime")
     */
    protected $registrationDate;

    /**
     * Sloupec role uživatele. Význam hodnot viz. konstanty pro uživatelské role.
     * @ORM\Column(type="integer")
     */
    protected $role;

    /**
     * Ověřuje, zda je uživatel v roli administrátora.
     * @return bool vrací true, pokud je uživatel administrátor; jinak vrací false
     */
    public function isAdmin()
    {
        return $this->role === self::ROLE_ADMIN;
    }
}

Pro ID je potřeba zapsat anotací více, Doctrine ho poté automaticky použije jako primární klíč.

Dále si všimněte metody isAdmin() - entita není jen přepravkou na data, ale může umět i jednoduché operace.

Všechny naše entity budou dědit od základní entity, vytvořené rozšířením Kdyby. Získá díky tomu některé příjemné funkce, např. nebudeme muset psát settery a gettery a místo toho budeme moci používat přímo $user->name.

Poznámka: Pokud atributu určíme datový typ date nebo datetime, Doctrine automaticky při tahání data z databáze vytvoří objekt třídy DateTime.

Fasády

Fasády budou služby (tzn. že v celé aplikaci bude jen jedna instance dané třídy), které budou zprostředkovávat veškeré operace.

app/model/faca­des/UserFacade­.php

Pro začátek bude opět naše třída velmi jednoduchá. Konstruktorem automaticky dostane objekt třídy Kdyby\Doctrine\EntityManager (opět malé vylepšení EntityManager od Doctrine) a přidáme metodu na vyhledání uživatele podle ID.

<?php

namespace App\Model\Facades;

use App\Model\Entities\User;
use Kdyby\Doctrine\EntityManager;
use Nette\Object;

/**
 * Fasáda pro manipulaci s uživateli.
 * @package App\Model\Facades
 */
class UserFacade extends Object
{
    /** @var EntityManager Manager pro práci s entitami. */
    private $em;

    /**
     * Konstruktor s injektovanou třídou pro práci s entitami.
     * @param EntityManager $em automaticky injektovaná třída pro práci s entitami
     */
    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    /**
     * Najde a vrátí uživatele podle jeho ID.
     * @param int|NULL $id ID uživatele
     * @return User|NULL vrátí entitu uživatele nebo NULL pokud uživatel nebyl nalezen
     */
    public function getUser($id)
    {
        return isset($id) ? $this->em->find(User::class, $id) : NULL;
    }
}

Metoda EntityManager::find() vyhledá entitu User podle daného ID. Normálně bychom jako první parametr měli napsat celý název třídy App\Model\Entities\User, ale magická konstanta class (zabudovaná přímo v PHP) tento název včetně jmenného prostoru obsahuje, je proto náš zápis o něco kratší.

V našem případě se buď vrátí načtená entita User (jako načtená chápejte objekt naplněný daty z databáze) nebo NULL (což vrací metoda EntityManager::find(), pokud takovou entitu nenajde).

app/config/con­fig.neon

Jelikož je UserFacade automaticky injectovaná služba, musíme ji zaregistrovat v našem konfiguračním souboru:

...
services:
    - App\Model\Facades\UserFacade
    router: App\RouterFactory::createRouter
...

Presentery

app/presenter­s/BasePresenter­.php

Entitu User i UserFacade budeme využívat v podstatě všech presenterech, proto je uložíme do našeho BasePresenteru:

<?php

namespace App\Presenters;

use App\Model\Entities\User as UserEntity;
use App\Model\Facades\UserFacade;
use Kdyby\Translation\Translator;
use Nette\Application\UI\Presenter;
use Nette\Bridges\ApplicationLatte\Template;

/**
 * Základní presenter pro všechny ostatní presentery aplikace.
 * @package App\Presenters
 */
abstract class BasePresenter extends Presenter
{
    /** @persistent null|string Určuje jazykovou verzi webu. */
    public $locale;

    /**
     * @var Translator Obstarává jazykový překlad na úrovni presenteru.
     * @inject
     */
    public $translator;

    /**
     * @var UserFacade Fasáda pro manipulaci s uživateli.
     * @inject
     */
    public $userFacade;

    /** @var UserEntity Entita pro aktuálního uživatele. */
    protected $userEntity;
…

Dále si zde přetížíme metodu startup(), kde si vytvoříme entitu User. Pokud je uživatel přihlášen, najdeme informace z databáze a uložíme je do entity. Pokud není, pak vytvoříme entitu s jedinou informací, a to že uživatel není v roli administrátora (abychom nemuseli všude připisovat podmínku $user->isLoggedIn(), ale stačilo jen $userEntity->isAdmin()).

/**
 * Volá se na začátku každé akce, každého presenteru a zajišťuje inicializaci entity uživatele.
 */
public function startup()
{
    parent::startup();
    if ($this->getUser()->isLoggedIn()) {
        $this->userEntity = $this->userFacade->getUser($this->getUser()->getId());
    } else {
        // Abychom mohli použít "$userEntity->isAdmin()", když uživatel není přihlášen.
        $entity = new UserEntity();
        $entity->role = UserEntity::ROLE_USER;
        $this->userEntity = $entity;
    }
}

Nezapomeňte do use uvést

use App\Model\Entities\User as UserEntity

Další metoda beforeRender() předává proměnné šabloně ještě před jejím vykreslením - je jedno, o kterou šablonu se jedná - předá je tedy každé šabloně, která se bude vykreslovat. Jelikož se naše entita hodí ve všech šablonách, předáme ji opět v BasePresenter.

/**
 * Volá se před vykreslením každého presenteru a předává společné proměnné do celkového layoutu webu.
 */
public function beforeRender()
{
    parent::beforeRender();
    $this->template->userEntity = $this->userEntity;
}

Šablony

app/presenter­s/templates/@la­yout.latte

Layout je hlavní šablona, která se vykresluje vždy. Šablony pro jednotlivé stránky se poté vloží makrem {include} do obsahu.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">

    <title>Nette Doctrine 2 blog</title>

    <link rel="stylesheet" href="{$basePath}/css/style.css">
    <link rel="shortcut icon" href="{$basePath}/favicon.ico">
    <meta name="viewport" content="width=device-width">
    {block head}{/block}
</head>
<body>
    <div id="container">

        {if count($flashes) > 0}
            <div id="flashes">
                <div class="text">
                    <div n:foreach="$flashes as $flash" n:class="flash, $flash->type">
                        {$flash->message}
                    </div>
                </div>
            </div>
        {/if}

        <div id="header">
            <div id="logo">
                <h1>Blog system</h1>
            </div>
            <div id="userInfo">
                {if $user->isLoggedIn()}
                    {_common.loggedAs}: {$userEntity->name}
                {/if}
            </div>
            <div id="menu">
                <a n:href="Homepage:default">{_menu.homepage}</a>
            </div>
        </div>
        <div id="content">
            {include content}
        </div>
        <div id="footer">
            <div class="text">
                &copy; Konesoft Corporation
            </div>
        </div>
    </div>

    {block scripts}
    <script src="//code.jquery.com/jquery-1.11.2.min.js"></script>
    <script src="//nette.github.io/resources/js/netteForms.min.js"></script>
    <script src="{$basePath}/js/main.js"></script>
    {/block}
</body>
</html>

Nette kromě proměnné $user předává šablonám automaticky i proměnné $flashes, která obsahuje pole flash zpráv (ty se vytvoří metodou Nette\Application\UI\Control::flashMessage($message, $type)), a $basePath - ta obsahuje název kořenové složky projektu.

Všimněte si, že texty v naší šabloně jsou připravené pro překlad. K tomuto článku přiložím všechny soubory pro překlad, ať to nemusíme v každém článku dopisovat. Je zde podpora pro český a anglický jazyk.

Také bude přiložen CSS soubor. Přijde mi bezdůvodné ho tu ukazovat a vysvětlovat (je ke stažení v projektu), design není cílem této série. Stejně si ho upravíte dle vlastního vkusu.

Poznámka: U překladu znamená první slovo název souboru, ostatní jsou názvy klíčů. Proto makro {_menu.homepage} použije klíč homepage ze souboru menu (přesný soubor se ještě určí podle jazyka). Soubory jsou ve formátu NEON.

app/presenter­s/templates/Ho­mepage/default­.latte

Na úvodní stránce bude zobrazeno několik nejnovějších článků. To si vytvoříme až později, nyní přidáme jen stránku s nadpisem, aby Nette nevyhazovalo chybu 404.

{block content}
<h2>{_homepage.header}</h2>

To je z dnešní lekce vše.

Příště, v lekci CMS v Nette a Doctrine 2 - Registrace uživatele, přidáme možnost registrace uživatele.


 

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 272x (3.43 MB)
Aplikace je včetně zdrojových kódů v jazyce PHP

 

Předchozí článek
CMS v Nette a Doctrine 2 - Kostra aplikace
Všechny články v sekci
CMS v Nette a Doctrine 2
Přeskočit článek
(nedoporučujeme)
CMS v Nette a Doctrine 2 - Registrace uživatele
Článek pro vás napsal Martin Konečný (pavelco1998)
Avatar
Uživatelské hodnocení:
13 hlasů
Autor se o IT moc nezajímá, raději by se věnoval speciálním jednotkám jako jsou SEALs nebo SAS. Když už to ale musí být něco z IT, tak tvorba web. aplikací v PHP. Také vyvýjí novou českou prohlížečovou RPG hru a provozuje osobní web http://www.mkonecny.cz
Aktivity