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.
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/entities/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/facades/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/config.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/presenters/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/presenters/templates/@layout.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"> © 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/presenters/templates/Homepage/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 274x (3.43 MB)
Aplikace je včetně zdrojových kódů v jazyce PHP