Praktický návod
Jak správně používat Doctrine ORM
Entity drž doménové, repository zaměř na dotazy a změny ulož jedním flushem na hranici use-case.
Nejdřív stručně
ORM mapuje objekty, ne odpovědnost
Doctrine ORM mapuje entity na tabulky a přes unit of work sleduje jejich změny. Entita ale stále nese doménový stav a pravidla; nemá si sama hledat služby, posílat e-maily ani volat databázi.
Repository soustředí dotazy na entity a pojmenovává je podle potřeb aplikace. Aplikační use-case načte potřebné objekty, provede změnu a na jasné hranici nechá unit of work změny uložit.
Připrav si
Co budeš potřebovat
Začni jedním skutečným use-case a jeho datovým modelem, ne univerzální abstrakcí nad Doctrine.
- Symfony projekt s Doctrine ORM a nakonfigurovaným databázovým připojením.
- Jednu doménovou operaci, například potvrzení objednávky, a pravidla, která musí zachovat.
- Testovací databázi a možnost spouštět integrační testy repository.
- Databázové migrace ve verzovacím systému. Schéma neměň ručně podle entity v produkci.
Kroky 1 až 3
Rozděl odpovědnost mezi entity, repository a use-case
Každá vrstva má jednoduchou úlohu. Entity chrání stav, repository hledá a aplikační služba řídí operaci.
1. Navrhni entity kolem doménových pravidel
- Mapuj stabilní identitu, stav a vztahy, které use-case skutečně potřebuje. Kolekce inicializuj v konstruktoru a u obou stran asociace udržuj konzistentní stav.
- Místo veřejných setterů použij metody popisující záměr, například confirm() nebo changeDeliveryAddress(). Entita tak může odmítnout neplatný přechod.
- Do entity neinjektuj service locator, EntityManager, repository ani Symfony container. Pokud pravidlo potřebuje externí data, získej je v aplikační službě a předej výsledek explicitně.
- Neprocházej bez rozmyslu lazy kolekce při serializaci. Pro čtecí obrazovku často lépe poslouží cílený dotaz a výstupní DTO.
composer require symfony/orm-pack Oficiální Doctrine dokumentace k asociacím 2. Piš repository pro konkrétní dotazy
- Jednoduché načtení entity podle identity nech repository. Složitější dotaz pojmenuj podle potřeby aplikace, například findPayableOrders(), ne podle použitého SQL.
- Výběr sloupců, JOIN, řazení a stránkování řeš uvnitř repository pomocí QueryBuilderu nebo DQL. Volající nemusí znát mapování tabulek.
- Repository nemá řídit celý workflow, posílat notifikace ani volat flush. Jeho úkolem je načíst nebo přidat entity a provést specializované datové dotazy.
- U čtecích dotazů zvaž DTO nebo skalární výsledek. Nenačítej velký graf entit jen proto, abys z něj přečetl tři hodnoty.
php bin/console debug:container --tag=doctrine.repository_service Oficiální Symfony dokumentace k repository 3. Uzavři unit of work na hranici use-case
- EntityManager sleduje spravované entity. U nové entity zavolej persist(); změnu už načtené spravované entity obvykle stačí provést její doménovou metodou.
- Flush volej jednou po dokončení aplikační operace, ne uvnitř entity ani každého repository. Hranice use-case pak určuje, které změny patří k sobě.
- Jeden flush používá databázovou transakci. Explicitní transakci přidej, když use-case obsahuje více flushů, přímé DBAL operace nebo zámky, které musí být atomické.
- Uvnitř transakce nečekej na vzdálené API. Externí události řeš po commitu nebo pomocí outboxu; při výjimce počítej s rollbackem a uzavřeným EntityManagerem.
Krok 4
Ověř mapování i chování
Správné atributy nestačí. Testuj výsledné dotazy, změny stavu a hranici transakce.
-
Zkontroluj mapování proti schématu
Příkaz odhalí neplatné asociace i rozdíl mezi mapováním a testovací databází.
php bin/console doctrine:schema:validate -
Spusť integrační testy repository
Ověř filtr, řazení, prázdný výsledek i hranice stránkování nad skutečnou databází.
php bin/phpunit tests/Integration/Repository -
Zkontroluj stav migrací
Mapování a databázové schéma musí být nasaditelné pomocí verzovaných migrací.
php bin/console doctrine:migrations:status
Když to zlobí
Nejčastější chyby
Změna entity se neuložila
Ověř, že entita pochází ze stejného otevřeného EntityManageru a use-case došel až k flush. U odpojené entity ji znovu načti; nepřenášej spravované entity přes frontu nebo dlouhý proces.
Jeden požadavek provádí příliš mnoho dotazů
Zapni profiler a hledej lazy loading ve smyčce. Potřebné vztahy načti cíleným JOINem nebo použij DTO dotaz, ale nenačítej automaticky celý objektový graf.
php bin/console debug:config doctrine Lazy kolekce selže po skončení aplikační operace
Kód čte entitu mimo životní cyklus EntityManageru. Potřebná data načti a převeď na výstupní DTO uvnitř use-case místo serializace odpojené entity.
Repository volá flush po každé změně
Přesuň flush na hranici aplikačního use-case. Jedna operace pak může změnit více entit atomicky a test přesně ví, kdy má být práce potvrzena.
Hotovo
Doctrine má v aplikaci jasné hranice.
Doctrine ORM teď mapuje doménové entity, repository soustředí dotazy a aplikační use-case rozhoduje o flush a transakci. Při dalším rozšiřování sleduj také počet dotazů a velikost načítaného grafu.