Zapomeňte na databázové tabulky
Entity jsou základní kameny v Doctrine 2 a vůbec celé vaší aplikace. Každá entita reprezentuje nějaký objekt reálného světa, takzvaný doménový objekt. Jednu entitu tak budu mít definovanou pro článek, jinou pro kategorii, další pro uživatele.
Pokud jste dosud nepoužívali vůbec žádné ORM, případně jste zvyklí na některé ORM založené na návrhovém vzoru Active Record, byla pro vás takovou základní jednotkou práce databázová tabulka. Měla nějaké atributy, nějaká pravidla, co lze a co nelze, přes cizí klíče mohla odkazovat na další tabulky.
Jestliže je to i váš případ, je teď potřeba udělat malý myšlenkový posun. Při používání Doctrine 2 zkuste na databázové tabulky zapomenout a brát je alespoň zpočátku jako něco, co je prostě někde na pozadí a o co se zatím nemusíte starat. Oprostěte se od všech pomocných mechanizmů, které jsou účelově specifické pro relační databáze, ale v reálném světě vůbec neexistují.
Typickým příkladem jsou cizí klíče. Ve skutečném světě nic takového jako cizí klíč není. Článek je prostě zařazen do nějaké kategorie. To, že se to v relačních databázích řeší nějakým cizím klíčem category_id
v tabulce article
, je jenom vedlejší efekt toho, jak relační databáze fungují.
Obdobně se někdy jeden doménový objekt, který je z logiky věci jedním celkem, v relačních databázích roztrhá do více různých tabulek. Například zboží v e-shopu může mít různé vlastnosti, které bývají z ryze pragmatických důvodů dekomponovány do samostatné key/value tabulky. Nebo můžete mít konkrétní typ produktu poskládaný z jedné tabulky s obecnými atributy společnými pro všechny produkty a z druhé se specifickými atributy jen pro ten konkrétní typ.
V Doctrine 2 pracujete o vrstvu výš, pro modelování doménových objektů se používají entity. Ty nás dokonale odstiňují od konkrétní databáze i se všemi jejími podivnostmi, jako jsou třeba cizí klíče nebo nutnost tříštit doménový objekt do více míst. Práce s ORM se tak stává systémově a logicky čistou.
Rychlá ukázka používání entit
Entity jsou běžné PHP objekty, jak je normálně znáte a používáte. Jenom v nich do komentářů musíme přidat zavináčové anotace, podle kterých Doctrine 2 říkáme, jak s nimi má pracovat.
Pro příklad si vezměme jednoduchý článek s titulkem a textem, který v databázi vytvoříme takto:
CREATE TABLE article ( id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, title VARCHAR(100) NOT NULL, text TEXT NULL );
Příslušnou entitu v Doctrine 2 pak definujeme takto:
/** @entity */ class Article { /** * @id @column(type="integer") * @generatedValue */ private $id; /** @column(length=100) */ private $title; /** @column(type="text", nullable=true) */ private $text; public function getId() { return $this->id; } public function getTitle() { return $this->title; } public function setTitle($title) { $this->title = $title; } public function getText() { return $this->text; } public function setText($text) { $this->text = $text; } }
Pomocí anotací jsme Doctrine 2 řekli, že má do databáze ukládat hodnoty z vlastností $id
, $title
a $text
. Navíc že $id
je primární klíč s automaticky generovanou hodnotou.
S entitou se pak pracuje stejně jako s jakýmkoliv jiným objektem:
$article = new Article; $article->setTitle('Lorem ipsum'); $article->setText('Lorem ipsum dolor sit amet.') echo $article->getTitle(); echo $article->getText();
Pro uložení do databáze či načtení z databáze musíte využít Entity Manager, který jsme si připravili v předchozím dílu. Entity Manageru se budeme věnovat v samostatném článku, takže teď jenom pro základní představu:
// vytvoříme nový článek $article = new Article; $article->setTitle('Lorem ipsum'); $article->setText('Lorem ipsum dolor sit amet.') // článek uložíme do databáze $entityManager->persist($article); $entityManager->flush(); // načteme z databáze článek s ID = 123 $anotherArticle = $entityManager->find('Article', 123); echo $anotherArticle->getTitle(); echo $anotherArticle->getText(); // něco v něm změníme a zase uložíme $anotherArticle->setTitle('Foo bar'); $entityManager->flush();
Seznam všech anotací, které lze při definici entity použít, naleznete v dokumentaci projektu. My si je budeme představovat postupně, když na ně přijde čas.
Vedle komentářových anotací nabízí Doctrine 2 možnost definice entity pomocí XML nebo YML formátu či prostřednictvím speciálních PHP volání. Stejně tak není potřeba vytvářet strukturu databáze ručně a můžeme nechat Doctrine 2, aby nám ji sama vygenerovala z entity. K tomu se ale dostaneme podrobněji v některém z pozdějších dílů seriálu.
Mapování entity na tabulku
Doctrine 2 se snaží z názvu třídy sama odvodit název příslušné databázové tabulky, do které data ukládá. Entitu z našeho příkladu tedy mapuje na tabulku article
.
Pokud chcete entitu explicitně namapovat na jinou tabulku, lze to udělat speciální anotací @table
v záhlaví třídy:
/** * @entity * @table(name="posts") */ class Article // ...
Pokud jste v názvu tabulky použili rezervované slovo z SQL, musíte je obalit zpětnými uvozovkami:
/** * @entity * @table(name="`order`") */ class Order // ...
Tohle je aktuálně jedno ze slabých míst Doctrine 2. Ideální by totiž bylo, kdyby veškeré escapování zajišťovala sama, jako to dělá například dibi. Dříve či později navíc narazíte na případy, kdy vám ani obalování zpětnými uvozovkami nebude v Doctrine 2 fungovat. Vřele tedy doporučuji se používání rezervovaných slov v názvech tabulek a atributů oklikou vyhnout.
U databázových systémů podporujících schémata je samozřejmě možné určit i schéma:
/** * @entity * @table(schema="public", name="posts") */ class Article // ...
Doctrine 2 bohužel nemá nativní podporu pro automatické prefixování všech tabulek aplikace. Lze to zajistit s využitím mechanizmu událostí, což je ale vyšší dívčí a k podrobnějšímu osvětlení se dostaneme v pokročilejší fázi seriálu.
Anotace jednotlivých sloupců
Každý sloupec, který má Doctrine 2 ukládat do databáze, anotujeme pomocí @column
. V závorce za anotací lze upřesnit podrobnější vlastnosti sloupce:
type
- Datový typ daného sloupce. Datové typy podrobněji rozebírá následující podkapitola. Výchozí hodnota je
string
. name
- Název atributu v definici databáze. Standardně se použije stejný název, jako má samotná proměnná, zde ho lze ale změnit a proměnnou tak mapovat na jiný databázový atribut.
unique
- Udává, zda má být nad sloupcem kontrolována unikátnost. Může nabývat hodnot
true
nebofalse
, což je i výchozí hodnota. nullable
- Udává, zda může být do sloupce uložena i
NULL
hodnota. Může nabývat hodnottrue
nebofalse
. Výchozí hodnota jefalse
, takže u každéhoNULL
sloupce to musíte explicitně povolit, jinak se vám v databázi daný sloupec definuje jakoNOT NULL
. length
- Dává smysl jen u řetězcových sloupců a udává jejich maximální délku. Výchozí hodnota je 255.
precision
ascale
- Dávají smysl jen u desetinných čísel a udávají jejich přesnost. Výchozí hodnota je 0.
Příklad použití:
/** @column(name="username", type="string", length=100, unique=true) */ private $name; /** @column(type="text", nullable=true) */ private $description; /** @column(type="decimal", precision=2, scale=1) */ protected $height;
Na tomto místě je nutné upozornit, že takto definované vlastnosti a omezení využívá Doctrine 2 pouze pro prvotní vytvoření databáze. Při následné průběžné práci s entitami k nim ale již sama nijak nepřihlíží, neprovádí podle nich téměř žádné konverze ani kontroly. Vše nechává až na databázové vrstvě. Případně si to musíte dopsat sami ručně do getterů, setterů či speciálních validačních metod.
Datové typy sloupců
Uvnitř anotace @column(type="xxx")
můžete použít kterýkoliv z předdefinovaných datových typů.
Musíte si ale uvědomit, že se nejedná o databázové ani PHP typy. Jsou to speciální mapovací typy Doctrine 2, kterými určujete, co a jak se má z PHP mapovat do databáze. Například definicí @column(type="text")
vlastně říkáte, že Doctrine 2 má mapovat danou proměnnou, ve které je uložena hodnota v PHP typu string
, na databázový sloupec typu CBLOB
.
Jaké všechny typy tedy máme k dispozici a jaké nám zajišťují mapování mezi databází a PHP?
Doctrine 2 typ | PHP typ | Databázový typ |
---|---|---|
string |
string |
VARCHAR |
text |
string |
CLOB |
integer |
integer |
INT |
smallint |
integer |
SMALLINT |
bigint |
string |
BIGINT |
decimal |
double |
DECIMAL |
boolean |
boolean |
BOOLEAN |
date |
DateTime |
DATETIME |
time |
DateTime |
TIME |
datetime |
DateTime |
DATETIME či TIMESTAMP bez časové zóny |
datetimetz |
DateTime |
DATETIME či TIMESTAMP s časovou zónou |
object |
object |
CLOB |
array |
array |
CLOB |
V příštím pokračování seriálu budeme v entitách ještě pokračovat a podíváme se na některé pokročilejší možnosti jejich definice a práce s nimi.
Přehled komentářů