Nextras\ORM: Důmyslné a přesto jednoduché ORM

25.07.2015
6 min read
1120 words

Nextras\ORM je lehko použitelná knihovna, vhodná i pro malé projekty. Hlavní maintener není nikdo jiný než databázový mág Jan hrach Škrášek.

Update 2020
Některé odkazy již nemusí být funkční. Poslední verze a dokumentace je dostupná na nextras.org.

Nextras\ORM

Instalace

Knihovna má svůj balíček, takže instalace je úplně jednoduchá.

composer require nextras/orm

Aktuálně je stable verze 1.0.0, ale brzo vyjde verze 1.1.0, která se nyní nachází ve stavu Release Candidate 1.

ORM

Nextras\ORM je střední knihovna pro jednoduchou práci s databází (MySQL, PostreSQL).

Je rozdělena na jednotlivé logické bloky.

Storage ---(DBAL)---> Mapper -> Repository -> Entity

Mapper vykonává dotazy nad Nextras\Dbal, což je knihovna pro práci s databázemi, napsaná taktéž Honzou Škráškem a Honzou Tvrdíkem.

Repository nám skrz mapper připravuje data a vytváří z nich Entity případně kolekce. Nelze nad ním volat jednotlivé SQL dotazy, na to slouží mapper.

Entita je obálka nad daty. Která nemusí být úplně hloupá, to záleží na vás, jak to pojmete. Entity v Nextras\Orm podporují tzv. inject* metody, takže si tam můžete vstříknout, co potřebujete.

Entity

Základní stavební jednotkou Nextras\ORM je samozřejmě entita.

Entity se definují pomocí anotací, a ty jsou velmi vyčerpávající, pojďme si to rozebrat na příkladu.

/**
 * @property        string      $name
 * @property        DateTime    $born
 * @property        string|NULL $web
 * @property-read   int         $year
 * @property-read   int         $age        {virtual}
 * @property        Address     $address    {1:1d Address primary}
 * @property        DateTime    $createdAt  {default now}
 * @property        int         $gender     {enum self::GENDER_*}
 */
class Member extends Nextras\Orm\Entity\Entity
{
    const GENDER_MALE = 1;
    const GENDER_VELKE = 2;

    protected function getterAge()
    {
        return date('Y') - $this->born->format('Y');
    }
}

Property se definují klíčovým slovem @property nebo @property-read. Pokud jde o @property-read nelze do ní zapisovat a ORM to přísně kontroluje.

Poté se definuje typ fieldu, např. string, int, float, bool apod. Zvládno to však i DateTime. Pokud doplníme typ kouzelným |NULL, říkáme tím, že tento field může být nullový.

Následuje název fieldu ve stylu camelCase. Pokud máte radši underscore, myslím, že se to dá nějak donastavit. Ale řekl bych, že camelCase je taková klasika.

Getters & Setters

V základu vám Nextras\ORM samo mapuje sloupečky na fieldy v entitě. Pokud si ale chcete nějaký sloupeček přece jenom upravit, lze použít metod getter<field> a setter<field>. Například getterAge.

Občas se to velmi hodí.

Opatrně s pojmenováním, pokud by jste použili getAge, tak při použítí magie typu $member->age se vám tato metoda nezavolá. To je rozdíl od magického Nette\Object.

Virtual

Virtual je specialní typ, kterým si sami definujete jednotlivý field. Poté vám funguje magie, viz příklad a field $age.

Default

Skvělá vecička při práci s datumem.

@property DateTime $createdAt {default now}

Pakliže nenasetuje nic do $member->createAt, knihovna vám tam vloží aktualní čas/datum.

Enum

Je skvělé, že je zabudovaná podpora pro výčtové typy - jako je enum.

/**
 * @property int $gender {enum self::GENDER_*}
 */
class Member extends Nextras\Orm\Entity\Entity
{
    const GENDER_MALE = 1;
    const GENDER_VELKE = 2;
}

ORM si na začátku sestaví mapu všech možností pro enum, a poté vám vstup krásně validuje.

Relace

ORM nabízí plnou podporu pro všechny možné relace.

  • 1:1 - kniha má jednoho autora
  • 1:m - jedna kniha může mít více obrázků
  • m:1 - jedna kategorie může mít více knih
  • m:n - jeden štítek může být u více knih
  • 1:1d - kniha má jednoho autora (a např. reference je uložena u knihy)

1:1d

Tato relace se řídí postfixem primary, který určuje jaká entita je hlavní a která nese referenci (in storage).

/**
 * @property Author {1:1d Author primary}
 */
class Book extends Nextras\Orm\Entity\Entity {
}

V této entitě máme uloženého autora.

Repozitáře a kolekce

Skrze repozitáře/kolekce můžeme data filtrovat, řadit, omezova apod.

Obecně se dá říci, že metody get* nám vrací právě jednu entitu a find* nám vrací kolekci, nad kterou pak můžeme vykonávat další dotazy.

Metody nad kolekcemi:

  • findBy(array $conds) - filtruje podle hodnot
  • getBy(array $conds) - stejná jako findBy, ale vratí 1. řádek (entitu)
  • orderBy($column, $direction) - seřadí pole entity podle soupečku/ů
  • limitBy($limit, $offset) - vratí část pole
  • fetch() - vrací 1. řádek (entitu)
  • fetchAll() - vrací všechny řádky (entity)
  • fetchPairs($key, $value) - vrací pole, kde je klíč $key například ID a hodnota $value například NAME

Výhody

Výhody podle mého nazoru:

  • jednoduchá instalace
  • aktuální dokumentace
  • intuitivní používání
  • základní i pokročilá práce s entitami
    • vyhledávání
    • třídění
    • sortování
    • rekurzivní ukládání (tohle je pro mě TOP1)
    • rekurzivní mazání
  • základní CRUD v Repository
  • podpora MySQL a PostgreSQL

Někdo by mohl namítat, že už tu existuje Doctrina. Ano, ale na můj vkus je velká a použítí není uplně jednoduché, pokud člověk není tak zkušený nebo nemá připravený dev-stack.

Za to Nextras\ORM se hodí i na malé projekty (od 2-3 provázaných entit), přes střední až po velké aplikace.

Nedostatky

Podle mě je jeden větší nedostatek (možná, že už není, protože vývoj neustále probíhá a nové verze se jen hrnou) a to, že programátor musí definovat relace na obou stranách.

Jedná se konkrétně o nextras/orm#29, kde se můžete zapojit směle do diskuze.

Lépe na příkladu..

/**
 * @property OneHasMany|Image[] $images {1:m Image}
 */
class Book {
}

/**
 * @property Book $book {m:1 Book}
 */
class Image {
}

Vždy se musí uvést i závislost u druhé entity (Image), což může trochu vadit. Ne vždy se totiž potřebuji dotazovat na knihu jako $image->book->... Ale na tohle se dá zvyknout.

Nástroje

Dle statusu na twitteru, Honza pracuje na nějakém rozšíření do PhpStormu, kterou všichni určitě rádi uvítáme (tweet).

Nedávno jsem jeden projekt převáděl na Nextras\ORM a u toho mě napadla myšlenka vytvořit generátor, který by mi s tím pomohl. A tak vznikl contributte/nextras-orm-generator.

Stačí mu dát přístupy do databáze a on vám vytvoří entity, repozitáře, mappery a případně fasády dle vašich tabulek.

Umí správně nakonfigurovat typy sloupečků, DateTime, enum a základní vazby. Bohužel nepozná, o jakou vazbu se jedná, zda-li o 1:1, 1:m, m:1, m:n nebo 1:1d. To už si musíte dodefinovat sami.

Každopádné vám ušetří spoustu času.

Do budoucna plánuji i konvertor z Doctrine ---> Nextras\ORM a také z LeanMapperu ---> Nextras\ORM.

Na Githubu je k dispozici základní example, jak generátor funguje. Mam v plánu dodělat základní popisek ke configu a také one-phar-file, aby se to dalo jednoduše spouštět.

Budu rád, když mi necháte komentář, jesti se vám to líbí nebo případně, zda-li vám to fungovalo/nefungovalo dle očekávání.

Závěrem..

Závěrem bych rád poděkoval Honzovi Škráškovi za práci na ORM a DBAL, celkově na Nextras. Má to vše hlavu a patu. Dokumentace je velmi zdařilá a mě osobně se v ní dobře orientuje.

Díky ti.. :-)

Currently working

I am currently digging deeper to serverless stuf. Also trying to do my best around Nette [contributte.org & componette.org]. Taking care of 242 libraries across various Github organizations.