Doctrine 2: pokročilá definice entit

V minulém díle seriálu jsme nakousli téma entit v Doctrine 2. Dnes budeme s entitami pokračovat a podíváme se na některé pokročilejší možnosti jejich definice a práce s nimi.

Seriál: Doctrine 2 (12 dílů)

  1. Doctrine 2: úvod do systému 21.7.2010
  2. Doctrine 2: základní definice entit 4.8.2010
  3. Doctrine 2: pokročilá definice entit 11.8.2010
  4. Doctrine 2: načítání, ukládání a mazání 26.8.2010
  5. Doctrine 2:stavy entit a transakce 9.9.2010
  6. Doctrine 2: asociace 23.9.2010
  7. Doctrine 2: práce s asociacemi 14.10.2010
  8. Doctrine 2: DQL 3.11.2010
  9. Doctrine 2: Query Builder a nativní SQL 18.11.2010
  10. Doctrine 2: události 9.12.2010
  11. Doctrine 2: událostní handlery 13.1.2011
  12. Architektura aplikace nad Doctrine 2 23.2.2012

Identifikátor entity

Každá entita musí mít určený svůj identifikátor. Což je obdoba primárního klíče u databázové tabulky. Bez identifikátoru nelze daný záznam jednoznačně adresovat. K určení identifikátoru slouží anotace  @id.

/** @entity */
class Article
{
    /** @id @column(type="integer") */
    private $id;
    // ...
}

V tomto případě je nutné každé nové entitě přiřadit hodnotu jejího id ručně. Obvykle se ale chceme spolehnout na automaticky generované identifikátory, v databázi nejčastěji řešené jako auto_increment či sekvence. K tomu slouží anotace  @generatedvalue.

/** @entity */
class Article
{
    /**
     * @id @column(type="integer")
     * @generatedvalue
     */
    private $id;
    // ...
}

Takto definovaná entita se pokusí automaticky zvolit nejlepší způsob generování podle právě používané databáze. Pro MySQL se tedy použije AUTO_INCREMENT sloupec, zatímco u PostgreSQL se nové hodnoty získávají ze sekvence. Pokud chcete mít věc více pod kontrolou, můžete upřesnit požadovanou strategii:

/** @entity */
class Article
{
    /**
     * @id @column(type="integer")
     * @generatedvalue(strategy="IDENTITY")
     */
    private $id;
    // ...
}

Přičemž můžete použít jednu z následujících hodnot:

AUTO
Výchozí hodnota, kdy se nejlepší strategie volí automaticky. Tedy totéž, jako když se parametr strategy vůbec neuvede.
SEQUENCE
Vynutí si použití sekvencí. Funguje tedy pouze v systémech, které sekvence podporují, tedy v PostgreSQL a Oracle.
IDENTITY
Vynutí si použití speciálního sloupce, který si umí sám přiřazovat nové hodnoty. Což je klasický AUTO_INCREMENT u MySQL a SQLite, IDENTITY u MSSQL a SERIAL u PostgreSQL. Z důvodu některých budů v současné vývojové verzi Doctrine 2 je lepší například i u PostgreSQL explicitně používat právě tento typ.
TABLE
Tato možnost zatím není implementována, ale bude umožňovat pro generování identifikátoru využívat jinou speciální tabulku. Cílem je, aby to fungovalo podobně jako sekvence, ale zároveň bylo přenositelné napříč všemi databázovými systémy.
NONE
Identifikátor není automaticky generován, ale musí se do entity ručně přiřazovat. Je to tedy stejné, jako když anotaci @generatedvalue vůbec neuvedete.

Pamatujte na to, že volbou konkrétní strategie si můžete zásadně omezit přenositelnost aplikace mezi různými databázovými systémy. Na druhou stranu pak můžete využívat speciální možnosti zvolené strategie, například podrobněji specifikovat vlastnosti dané sekvence:

/** @entity */
class Article
{
    /**
     * @id @column(type="integer")
     * @generatedvalue(strategy="SEQUENCE")
     * @sequencegenerator(
     *     name="article_id_seq",
     *     initialvalue=1,
     *     allocationsize=1
     * )
     */
    private $id;
    // ...
}

Složený identifikátor

Doctrine 2 teoreticky podporuje i kompozitní identifikátor složený z více sloupců. Ten se pak definuje tak, že anotaci @id uvedete u každého z dotčených sloupců:

/** @entity */
class Permission
{
    /** @id @column */
    private $role;
    /** @id @column */
    private $resource;
    /** @column(type="boolean") */
    private $permission;
}

V praxi je ale aktuální vývojová verze Doctrine 2 se složenými identifikátory na štíru, obzvlášť pokud se ještě i kombinují s asociacemi na jiné entity. Polovina věcí tak nefunguje a zbytek se chová podivně.

Než se tedy Doctrine 2 dostane do první stabilní verze, raději se prozatím složeným identifikátorům vyhněte a dočasně je obejděte umělým klíčem:

/**
 * @entity
 * @table(
 *     name="permission",
 *     uniqueconstraints={
 *         @uniqueconstraint(
 *             name="permission_unique",
 *             columns={"role", "resource"}
 *         )
 *     }
 * )
 */
class Permission
{
    /**
     * @id @column
     * @generatedvalue
     */
    private $id;
    /** @column */
    private $role;
    /** @column */
    private $resource;
    /** @column(type="boolean") */
    private $permission;
}

Unikátní sloupce a indexy

Kontrolu na unikátnost nad jedním sloupcem je nejlepší definovat přímo u konkrétního sloupce:

/** @entity */
class Article
{
    /** @column(length=50, unique=true) */
    private $slug;
    // ...
}

Unikátní klíč přes více sloupců je nutné definovat v záhlaví celé entity pomocí anotace @table a v ní zanořené anotace  @uniqueconstraint:

/**
 * @entity
 * @table(
 *     name="article",
 *     uniqueconstraints={
 *         @uniqueconstraint(name="url_unique", columns={"category", "slug"})
 *     }
 * )
 */
class Article
// ...

Obdobným způsobem se nad tabulkou definují i databázové indexy, jenom se pak používá vnořená anotace  @index:

/**
 * @entity
 * @table(
 *     name="article",
 *     indexes={
 *         @index(name="title_idx", columns={"title"})
 *     }
 * )
 */
class Article
// ...

Je potřeba upozornit na to, že takto definovaná omezení využívá Doctrine 2 pouze pro to, aby uměla správně vygenerovat základní strukturu databáze. Při další průběžné práci s entitami k nim již sama nijak nepřihlíží, nijak je nekontroluje a nechává vše až na databázové vrstvě, případně na vlastních ručně psaných kontrolách.

Vlastní datové typy

V předchozím dílu seriálu jsme si představili základní datové typy, které nám Doctrine 2 standardně nabízí. Pokud vám nestačí, můžete si sami vytvořit vlastní typ. To spočívá v implementaci jednoduchého rozhraní, kde definujete metody pro převod hodnoty z PHP do databáze a naopak.

Bohužel lze zatím mapovat jednu členskou proměnnou právě na jeden databázový atribut. Není tedy možné pomocí vlastních typů rozkládat například nějaký složitější objekt do více databázových sloupců. Tato možnost se ale plánuje zahrnout do některých budoucích verzí Doctrine 2.

Dědění a konstruktor

Doctrine 2 nevyžaduje, abyste entitní třídy dědili od nějakého společného předka, jak je tomu u řady jiných ORM. Můžete tedy entity psát bez jakéhokoliv dědění či je odvodit od libovolného předka dle vlastní vůle a potřeby. Klidně i od Nette Object:

/** @entity */
class Article extends NetteObject
// ...

Stejná volnost platí i pro konstruktor, který si můžete definovat zcela podle své potřeby. Ovšem s jedním důležitým upozorněním – konstruktor se provádí jen a pouze při vytváření nové prázdné instance operátorem new. Pokud systém načítá již uloženou entitu z databáze, vytváří a plní její instanci, tak se již konstruktor nespouští.

Entita ani žádná její metoda nesmí být označena jako final, a to ani v kterémkoliv jejím předkovi. V opačném případě nebude moci Doctrine 2 vytvářet proxy třídy. Co jsou proxy třídy a k čemu jsou dobré, si řekneme někdy příště.

Gettery a settery

Doctrine 2 nevyžaduje pro svou funkčnost mít definované vůbec žádné gettery ani settery. Entita se bude správně ukládat do databáze a načítat z databáze i bez nich. Doctrine 2 totiž umožňuje data entit plnit a číst „zadem“, a to i pokud dané členské proměnné definujete jako  private.

Dokonce právě naopak, zapamatujte si, že v entitách nikdy nesmíte žádné členské proměnné definovat jako public a umožnit tak přistupovat k nim zvenku napřímo. Pokud to porušíte, nebudou vám entity v některých specifických případech fungovat podle očekávání.

Takže gettery a settery samozřejmě potřebujeme sami pro sebe, abychom mohli k datům entity přistupovat zvenku a entitu běžně používat.

V tomto nám Doctrine 2 vůbec nepomáhá. Dodržuje totiž pravidlo nulové magie a vše nechává na vývojáři. Sama neposkytuje žádné magické funkce, které by fungovaly tak nějak automaticky samy od sebe. Všechny gettery a settery, které chceme používat, je tedy nutné definovat explicitně.

Doctrine 2 tím pádem dokonce ani nezajišťuje vůbec žádné validace či konverze přijímaných a vracených dat, ty si musíme také zajistit sami. Jednou z možností je provádět je právě v setterech:

/** @entity */
class Article
{
    // ...
    public function setTitle($title)
    {
        $title = trim($title);
        if (!$title) {
            throw new Exception('Title is required field.');
        }
        if (strlen($title) > 100) {
            throw new Exception('Title should be up to 100 characters long.');
        }
        $this->title = $title;
    }
    // ...
}

Ač je to trochu víc psaní, máme zase veřejné rozhraní své entity naprosto pevně pod kontrolou a můžeme si je definovat přesně tak, jak chceme a jak potřebujeme.

Na druhou stranu, pokud si chcete ulehčit práci, můžete si bez problémů definovat nějakého předka, kam vyčleníte veškerý opakující se kód nebo klidně i nějakou tu magii. A od něj si pak můžete všechny své entity bez problémů podědit.

Klonování a serializace

Klonování a serializace Doctrine 2 entit je problematické. A to opravdu velice problematické. Pokud to tedy jen trošku jde, tak se snažte entity vůbec neklonovat a neserializovat. V opačném případě riskujete rozličné divné a nečekané chování.

Špatná zpráva navíc je, že s tím asi nepůjde ani v budoucnu nic udělat, protože se tu naráží na limity a principielní omezení samotného jazyka PHP.

Pokud byste ale klonování nebo serializaci entit opravdu někdy potřebovali, důkladně si předtím pročtěte návod, co a jak je nutné pohlídat a ošetřit.

Co dál?

Prošli jsme si základní možnosti definice entit. V nejbližších dílech se podíváme, jak pracovat s asociacemi mezi různými entitami nebo jak zajistit ukládání entit do databáze a jejich načítání z databáze.

Krátce před vydáním tohoto článku byla do vývojové verze Doctrine 2 zahrnuta funkční podpora kompozitních klíčů. Podrobnosti jsou popsány v článku Experimental Doctrine 2 Feature: Associated Entities as Id Fields na Doctrine blogu.

Provozuje vývojářskou firmu Medio Interactive. Vystudoval informační a znalostní inženýrství na VŠE, kde stále příležitostně přednáší o tvorbě webů.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 11

Přehled komentářů

tomaash Identifikátory
drevolution Re: Identifikátory
Jan Tichý Re: Identifikátory
jos Re: Identifikátory
Jan Tichý Re: Identifikátory
Radek Tuc identifikatory a podekovani
koubel překlep
Jimilo Persistování entit
drevolution Re: Persistování entit
Matko pokracovani
Martin Malý Re: pokracovani
Zdroj: https://www.zdrojak.cz/?p=3294