Internet Info, s.r.o. Lupa Měšec Podnikatel Root Zdroják DigiZone Slunečnice Vitalia TopDrive KupDnes Navrcholu NovýTarif Dobrý web Weblogy Woko Jagg Computer.cz SK: MojeLinky

Hlavní navigace

Doctrine 2: načítání, ukládání a mazání

Seriál o novinkách, které pro vývojáře v PHP přináší databázová knihovna (ORM) Doctrine 2, pokračuje. V ukázkách minulých dílů jsme se letmo dotkli Entity Manageru. Dnes se na něj podíváme podrobněji a ukážeme si základní způsoby, jak své entity načítat, ukládat a mazat.

Tweetni to Twitter Jaggni to! Jagg Del.icio.us Delicious

Dáváme sbohem Active Recordu

Pro načítání, ukládání a mazání entit jste možná z jiných ORM zvyklí na přístup zvaný Active Record. V něm si všechny tyto akce zajišťuje sama entita svými metodami:

$article = new Article;
$article->setTitle('Lorem ipsum');
$article->save();
$product = Product::load(123);
$product->setTitle('Foo bar');
$product->save();
$category = Category::load(123);
$category->delete();

Doctrine 2 se od Active Recordu zcela oprošťuje. Nechává entitu samu o sobě nezávislou na jakémkoliv načítání, ukládání či mazání. Nezávislou na konkrétním použitém úložišti nebo obecněji způsobu persistování. Entita si tak řeší jen a pouze nastavování a vracení dat nebo jejich základní konverze a validace – prostě vlastní doménovou logiku.

Načítání, persistování a mazání se pak zajišťuje vně entity, a to s pomocí takzvaného Entity Manageru. Jenom připomenu, že instanci Entity Manageru jsme si připravili už v úvodním díle seriálu.

$article = new Article;
$article->setTitle('Lorem ipsum');
$em->persist($article);
$em->flush()
$product = $em->find('Product', 123);
$product->setTitle('Foo bar');
$em->flush();
$category = $em->find('Category', 123);
$em->remove($category);
$em->flush();

Takový přístup je čistší z hlediska celkového návrhu, protože načítání, persistování nebo mazání dovnitř entity skutečně nepatří. Entita se sama neukládá, někdo ji vezme a někam uloží. Máslo se také samo nedá do ledničky, musí ho tam někdo vložit. Ale úplně stejně může stejné máslo vzít někdo úplně jiný a vložit do úplně jiné ledničky. Anebo vyhodit do koše.

Měli byste si vrýt pod kůži i další železnou zásadu. Nejen, že entita nenačítá, neukládá či nemaže sama sebe, ale neměla by nikdy explicitně načítat, ukládat či mazat jakékoliv jiné entity.

Jinými slovy pokud uvnitř jakékoliv entity potřebujete sáhnout na Entity Manager, děláte něco špatně. Nejspíš jste si špatně navrhli architekturu celé své aplikace a do entity strkáte něco, co tam vůbec nepatří.

Kdyby se vám ale opravdu po starém dobrém Active Recordu stýskalo, můžete to snadno vyřešit jednoduchou nadstavbou nad Doctrine 2. Pro podrobnosti viz sekci „Doctrine2 and ActiveRecord“ v článku Write your own ORM on top of Doctrine2. Mějte ale na paměti, že je to méně čistý návrh, který v důsledku může svádět k dalším navazujícím chybným postupům.

Entity Manager

Entity Manager je ve své podstatě jen fasáda, která umožňuje snadný a rychlý přístup k jednotlivým částem, ze kterých je Doctrine 2 poskládána pod kapotou.

Pokud vás vnitřní architektura frameworku nezajímá a nechcete se pouštět do žádných pokročilých funkcí, vystačíte si hodně dlouho jen s Entity Managerem. Ostatní pro vás může zůstat černou skříňkou někde na pozadí.

Na druhou stranu pro plné využití Doctrine 2 je dobré vidět i trochu hlouběji. Zkusím tedy čas od času, vždy když na to přijde vhodná chvíle, alespoň stručně poodkrýt, co se za Entity Managerem skrývá.

Načítání entit

Pro načtení uložené entity z databáze je k dispozici metoda $em->find(), které se v parametrech předává název entity, kterou chceme načíst, a identifikátor požadovaného záznamu:

// Najde článek s ID 123
$article = $em->find('Article', 123);

Dejte si pozor na to, že pokud není žádný takový záznam nalezen, nevyhazuje se žádná výjimka ani jiná chybová hláška, ale vrací se  NULL.

Metoda je v podstatě jen zkratkou do Repository skryté za Entity Managerem. Úplný zápis, který v důsledku udělá totéž, tedy vypadá takto:

// Najde článek s ID 123
$article = $em->getRepository('Article')->find(123);

Složitější dotazy

Bez explicitního získání Repository už si nevystačíme, pokud se budeme chtít na entity dotazovat nějakým složitějším způsobem, než jen přes identifikátor:

// Najde uživatele s uživatelským jménem "novak"
$user = $em->getRepository('User')->findOneBy(array('username' => 'novak'));

Přitom username je název členské proměnné v entitě User, podle které hledání omezujeme. Místo nepřehledného pole v parametru můžete použít magické názvy metod:

// Najde uživatele s uživatelským jménem "novak"
$user = $em->getRepository('User')->findOneByUsername('novak');

Obdobným způsobem se můžeme dotazovat i na celou skupinu entit, které splňují zadané podmínky:

// Najde všechny dvacetileté uživatele
$users = $em->getRepository('User')->findBy(array('age' => 20));
// Najde všechny dvacetileté Nováky
$users = $em->getRepository('User')->findBy(array('age' => 20, 'surname' => 'Novák'));

Pokud chcete najít úplně všechny uživatele v systému, nabízí se k tomu metoda  findAll():

// Najde úplně všechny uživatele
$users = $em->getRepository('User')->findAll();

Pro ještě komplikovanější dotazy už musíte sáhnout po speciálním dotazovacím jazyce DQL. V opravdu krajním případě pak i po starém dobrém SQL, kde ale už riskujete nepřenositelnost aplikace mezi různými databázemi. O jazyce DQL se budeme podrobně bavit někdy jindy, teď jenom ukázka:

$query = $em->createQuery('SELECT u FROM User u WHERE u.age >= 20 AND u.age <= 30');
$users = $q->getResult();

Identity Map

Ve všech případech Doctrine 2 zajišťuje, že od jedné entity s jedním ID máte v celé aplikaci pouze jednu jedinou instanci, která se všude předává jen pomocí referencí. Nemůže se vám tedy stát, že byste měli dvě různé instance entity se stejným identifikátorem:

$first = $em->find('Article', 123);
$second = $em->find('Article', 123);
// TRUE - je to opravdu stejná instance
echo ($first === $second);

Doctrine takové chování na pozadí řídí pomocí Identity Map. Tu si můžete představit jako asociativní pole, kde je každá již načtená entita uchovávaná pod kombinací názvu své třídy a ID.

Repository se pak pokaždé, kdy je požádána o jakoukoliv entitu, nejprve podívá, jestli už ji náhodou v Identity Map nemá. Pokud ano, vrátí ji. V opačném případě se teprve dotáže do databáze.

Ale i v případě složitějších dotazů do databáze kontroluje před vrácením jejich výsledku existenci jednotlivých nalezených záznamů v Identity Map a pokud tam již jsou, tak je upřednostňuje před těmi načtenými z databáze.

Existence Identity Map není nic, co byste museli jakkoliv řešit, Doctrine 2 ji používá naprosto transparentně. Je ale dobré o ní vědět.

Vlastní repository

V případě potřeby lze standardně používanou Repository překrýt vlastní implementací s nějakou rozšířenou či pozměněnou funkčností. Krátký příklad:

class UserRepository extends Doctrine\ORM\EntityRepository
{
    public function getAllAdminUsers()
    {
        return $this->_em->createQuery('SELECT u FROM User u WHERE u.role = "admin"')->getResult();
    }
}

Všimněte si, že novou repository je nutné pomocí speciální anotace nastavit i v definici dané entity:

/**
 * @Entity(repositoryClass="UserRepository")
 */
class User
{
    // ...
}

Následné použití je pak zřejmé:

$admins = $em->getRepository('User')->getAllAdminUsers();

Persistování entit

Místo ukládání se v Doctrine 2 a jí podobných ORM frameworcích používá výraz persistování. Rozdíl mezi těmito dvěma pojmy je filozofický i praktický.

Ukládání musíte provádět při každé provedené změně v entitě. V Active Record návrhu tak po každé změně voláte  $article->save().

Persistování naopak vychází z toho, že entity persistujete pouze jednou po vytvoření její nové instance:

$article = new Article;
$em->persist($article);
$em->flush();

Od tohoto okamžiku už je tato instance pod kontrolou Entity Manageru. Je vedená, jakože se má persistovat napříč různými požadavky uživatelů. A to tak dlouho, dokud zase explicitně neřeknete jinak. Nemusíte tedy volat $em->persist($article) znovu po každé změně. Stačí to jen při vytváření nové instance.

Odebírání entit

Persistované entity se smažou z databáze pomocí metody  $em->remove($article):

$article = $em->find('Article', 123);
$em->remove($article);
$em->flush();

Samotná třída $article pak sice až do konce běhu daného skriptu existuje a má v sobě stále svůj obsah, není ale už v evidenci Entity Manageru, takže jakékoliv další změny se už do databáze nijak nepromítnou a při příštím požadavku na tuto entitu už nebude vůbec existovat.

Potvrzení a odeslání změn

Jak je patrné i z příkladů výše, pro potvrzení všech provedených změn musíte pokaždé nakonec zavolat metodu  $em->flush().

Typický případ je ten, že během jednoho běhu skriptu provedete více různých změn, které se řadí jakoby do fronty, a pak je všechny najednou potvrdíte a odešlete do databáze jedním  $em->flush():

// Vytvoříme a persistujeme nový článek
$article = new Article;
$em->persist($article);
// Změníme titulek produktu s ID 123
$product = $em->find('Product', 123);
$product->setTitle('Foo bar');
// Odstraníme kategorii s ID 123
$category = $em->find('Category', 123);
$em->remove($category);
// Všechny změny výše potvrdíme a pošleme do databáze
$em->flush();

Pokud byste $em->flush() nezavolali, tak se žádná z těchto změn do databáze neuloží a všechny provedené změny ve všech persistovaných entitách se po dokončení aktuálního dotazu ztratí.

Zmiňovaná fronta změn v Doctrine 2 opravdu existuje a je reprezentovaná návrhovým vzorem UnitOfWork. O něm, stejně jako o transakčním zpracování nebo různých stavech entit, ale zase až příště.

Jan Tichý

Jan Tichý

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ů.

Kurz SEO - Praha, Brno

DW - Školení SEO
  • Jak fungují vyhledávače a co od nich můžete očekávat.
  • Analýza klíčových slov - kde hledat, jak slova vybrat, jak optimalizovat.
  • Metody linkbuildingu - jak získat zpětné odkazy aniž byste za ně museli platit.
  • Vyhodnocování SEO - nesledujte jen pozice.

Další informace o kurzu SEO »

Akce: Využijte last minute slevu na školení v Brně!

Přehled názorů

Čistý návrh entity
Marek Šudák 26. 8. 2010 12:48
Nový
├ 
Re: Čistý návrh entity
Václav Novotný 26. 8. 2010 13:00
Nový
├ 
Re: Čistý návrh entity
Jan Tichý 26. 8. 2010 13:40
Nový
└ 
Re: Čistý návrh entity
Tharos 3. 9. 2010 01:30
Nový
 
└ 
Re: Čistý návrh entity
Vít Šesták (v6ak) 5. 9. 2010 18:12
Nový
Re: Doctrine 2: načítání, ukládání a mazání
pavel 26. 8. 2010 13:25
Nový
└ 
Re: Doctrine 2: načítání, ukládání a mazání
Jan Tichý 26. 8. 2010 13:44
Nový
 
└ 
Re: Doctrine 2: načítání, ukládání a mazání
Pavel 26. 8. 2010 13:48
Nový
Díky za seriál
Tharos 26. 8. 2010 16:32
Nový
Dodatek k poslednímu odstavci
Vojtěch Vondra 26. 8. 2010 21:44
Nový
└ 
Re: Dodatek k poslednímu odstavci
Václav Novotný 27. 8. 2010 09:11
Nový
Jedna instance na entitu
Vít Šesták (v6ak) 5. 9. 2010 18:22
Nový
└ 
Re: Jedna instance na entitu
Václav Novotný 5. 9. 2010 18:43
Nový
 
└ 
Re: Jedna instance na entitu
Vít Šesták (v6ak) 5. 9. 2010 18:49
Nový
 
 
└ 
Re: Jedna instance na entitu
Václav Novotný 5. 9. 2010 19:26
Nový
Random() v Doctrine 2
tomp 22. 10. 2010 13:48
Nový
Hledání podle nullové hodnoty
Daniel Mikeš 28. 12. 2010 23:04
Nový
       

Tento text je již více než dva měsíce starý. Chcete-li na něj reagovat v diskusi, pravděpodobně vám již nikdo neodpoví. Pro řešení aktuálních problémů doporučujeme využít naše diskusní fórum.

Zasílat nově přidané příspěvky e-mailem