Integrace databází a ORM v PHP: Doctrine a Eloquent

Integrace databází a ORM v PHP: Doctrine, Eloquent

Integrace databází a ORM v PHP

Integrace databází patří v ekosystému PHP k nejčastějším úkolům. Volba přístupu – od „čistého“ PDO přes query builder až po plnohodnotné ORM (Object–Relational Mapping) – zásadně ovlivňuje bezpečnost, výkon, udržovatelnost i rychlost vývoje. Tento článek shrnuje architektonické styly, klíčové koncepty ORM, výhody a nevýhody různých přístupů, osvědčené postupy, optimalizace výkonu a specifika nejrozšířenějších frameworků (Laravel, Symfony, Nette, Laminas, Yii).

Architektonické styly práce s databází

  • PDO (nízkoúrovňový přístup): Ruční psaní SQL, připravené dotazy, plná kontrola nad výkonem i transakcemi. Vhodné pro vysoce optimalizované části, reporting a složité dotazy.
  • Query Builder: Fluent API pro skládání SQL (dbal, Eloquent Builder, NetteDatabaseExplorer, LaminasDbSql). Snižuje riziko chyb a zvyšuje čitelnost, přitom však zachovává blízkost SQL.
  • ORM: Mapování tabulek na entity a vztahy, Unit of Work, Identity Map, lazy/eager loading, migrace. Typicky Doctrine ORM (Symfony), Eloquent (Laravel), Cycle ORM, Propel, Atlas.

Vzory ORM: Active Record vs. Data Mapper

  • Active Record: Třída entity obsahuje metody save(), find(), delete() (např. Eloquent, Yii AR). Jednodušší pro CRUD, hůře se škáluje pro komplexní domény.
  • Data Mapper: Oddělení doménového modelu od perzistence (např. Doctrine). Vyšší flexibilita pro DDD, složitější konfigurace.

Klíčové koncepty ORM

  • Entity a hodnotové objekty: Entity mají identitu (primární klíč), hodnotové objekty identitu nemají a jsou neměnné.
  • Unit of Work a Identity Map: Sledování změn v entitách, zabránění vzniku duplicitních instancí jednoho řádku.
  • Vztahy: OneToOne, OneToMany, ManyToMany; kardinality a vlastnictví vztahu (owning/inverse side) ovlivňují generované SQL.
  • Načítání dat: Lazy vs. eager loading; strategie join fetch, select in, subselect.
  • Životní cyklus entity: stavy new, managed, detached, removed; události (prePersist, postLoad…).

Bezpečnost: SQL injection a práce s daty

  • Připravené dotazy: Vždy používejte parametrizaci (?/:name), nikdy neskládejte SQL řetězec z neověřených vstupů.
  • Escapování vs. parametrizace: Parametrizace má přednost; escapování je doplňkové a chybové scénáře se mohou u různých ovladačů lišit.
  • Serializace JSON/XML: Při ukládání dokumentů do sloupců používejte validaci schématu, limity velikosti a indexy (např. GIN pro JSONB).
  • Princip nejmenších oprávnění: Aplikační účet s omezenými právy (SELECT/INSERT/UPDATE/DELETE pro konkrétní schéma), samostatné uživatelské role pro migrace.

Transakce a izolace

  • Granularita: Uzavírejte logické pracovní jednotky (service nebo use-case) do transakcí; v ORM používejte EntityManager::transactional() (Doctrine) nebo DB::transaction() (Laravel).
  • Úrovně izolace: READ COMMITTED, REPEATABLE READ, SERIALIZABLE; volba ovlivňuje riziko „phantoms“ a „non-repeatable reads“.
  • Deadlocky: Implementujte idempotentní opakování s exponenciálním zpožděním a auditováním.

Migrace schématu a verzování

  • Nástroje: Doctrine Migrations, Laravel Migrations, Phinx; verzujte spolu s kódem a CI/CD.
  • Změny bez výpadku: Přidávejte nové sloupce jako NULL/s výchozí hodnotou, provádějte zpětné doplnění dat v dávkách a poté zpřísněte omezení NOT NULL.
  • Seed/fixtures: Deterministická testovací data; oddělujte seed pro dev od produkční inicializace.

Výkon a optimalizace (ORM i SQL)

  • Počet dotazů N+1: Používejte eager loading (with() v Eloquent, JOIN FETCH v Doctrine) a dávkové načítání (batching).
  • Indexy: Měřte a přidávejte složené indexy podle skutečných dotazů; sledujte plány EXPLAIN.
  • Stránkování a streamování: Keyset pagination (pomocí >/<) je u velkých tabulek efektivnější než OFFSET; využívejte kurzory.
  • Cache: Aplikační cache (Redis, Memcached), cache druhé úrovně (Doctrine), cache dotazů tam, kde je to bezpečné.
  • Hromadné operace: Dávkové vkládání/aktualizace s vypnutím sledování změn v ORM (clear, detach) a využitím native queries pro rozsáhlé operace.
  • Asynchronní běh: V kombinaci se Swoole/RoadRunner využívejte trvalá připojení a pooly; dejte pozor na stavovost objektů ORM mezi požadavky.

Doménové modelování a repozitáře

  • Repository pattern: Zapouzdřuje dotazy nad agregáty a udržuje doménový kontrakt; v Doctrine lze implementovat vlastní třídy Repository.
  • Specifikace a CQRS: Náročné čtecí dotazy přesuňte do read-modelů (DTO/projekce) a oddělte je od zápisového modelu.
  • Event sourcing: V doménách vyžadujících audit zvažte ukládání událostí; projekce denormalizujte do tabulek určených ke čtení.

Podpora typů a moderní PHP

  • Typované vlastnosti a readonly: Zvyšují spolehlivost entit; vyžadují opatrnost při použití proxy pro lazy loading.
  • Enum a backed enum: Mapujte na malé tabulky nebo sloupce ENUM; v Doctrine/Eloquent je používejte jako vlastní typy/CASTy.
  • Atributy: Konfigurace ORM pomocí PHP atributů namísto anotací nebo YAML Doctrine; přehlednější a typově bezpečnější.
  • Statická analýza: Psalm/PHPStan s generiky (phpdoc) pro kolekce; odhalí nekompatibility dotazů ještě před nasazením.

Práce s více databázemi, replikace a škálování

  • Rozdělení čtení a zápisu: Zápisy na master, čtení z replik; pozor na replication lag a konzistenci po zápisu (sticky sessions, read your write).
  • Sharding: Logická distribuce dat (uživatelé, tenanti) podle klíče; repozitáře rozhodují o shardu.
  • Více nájemců: Izolace pro každého nájemce (schéma/tabulky) vs. sdílené tabulky se sloupcem tenant_id a row-level security.

Práce s NoSQL a speciálními typy

  • JSON/JSONB: Mapujte na DTO a validujte; přidávejte indexy pro často dotazované cesty (->, ->>, #>).
  • Geodata: PostGIS; v Doctrine vlastní typy pro geometry, v Eloquent casty a kontrola SRID.
  • Fulltext: MySQL InnoDB FULLTEXT/Boolean mode nebo specializovaný vyhledávač (OpenSearch, Meilisearch) se synchronizací prostřednictvím událostí.

Zpracování změn: souběžnost a audit

  • Optimistické zámky: Sloupec version/updated_at pro detekci konfliktů; ORM při kolizi vyvolá výjimku.
  • Pesimistické zámky: SELECT ... FOR UPDATE pro kritické sekce; nezapomeňte na časové limity.
  • Auditní stopa: Listeners/subscribers zaznamenávají změny; podle potřeby ukládejte rozdíly nebo celé snímky.

Testování a izolace

  • Transakce pro každý test: Obalte test do transakce a na konci proveďte rollback; jde o rychlý a izolovaný přístup.
  • Fixtures a factories: Vytvářejte data prostřednictvím doménového API; neobcházejte validační logiku.
  • Integrační vs. jednotkové testy: Složitější dotazy testujte integračně proti skutečné databázi (např. v kontejnerech), doménu pomocí jednotkových testů se stuby repozitářů.

Integrace ve frameworcích

  • Laravel (Eloquent): Active Record, migrations, seeder, factories, globální dotazy a dotazy s omezeným rozsahem, casts, události modelů; snadný eager loading a withCount().
  • Symfony (Doctrine ORM): Data Mapper, EntityManager, Repository, QueryBuilder, hydrátory, cache druhé úrovně; silný ekosystém nástrojů.
  • Nette (Database/Explorer, integrace Doctrine): Lehký query builder a mapování na řádkové objekty; pro DDD se často kombinuje s Doctrine.
  • Laminas (Db): SQL builder, adaptéry, TableGateway; vhodné pro projekty, které upřednostňují explicitní SQL.
  • Yii (Active Record): Podobné Eloquentu, s propracovaným with() a validačními scénáři.

Logování a observabilita

  • SQL log: Zapněte logování dotazů včetně parametrů a doby trvání; v produkci postupujte uvážlivě (vzorkování).
  • Tracing: OpenTelemetry/Jaeger; anotujte hraniční body (repozitáře, transakce), přidejte atributy DB span (db.system, statement, rows).
  • Rozpočet výkonu: Definujte SLA pro latenci dotazů a počet volání databáze na jeden požadavek.

Typické anti-patterny a jak se jim vyhnout

  • Hromadné přiřazování bez whitelistu: V Eloquent používejte $fillable/$guarded; v Doctrine validujte DTO před hydratací.
  • Business logika v kontrolerech: Přesuňte ji do služeb a domén; kontroler volá konkrétní případ užití.
  • N+1 a eager loading všude: Vždy měřte; někdy je lepší explicitní SQL s agregací.
  • Neřízený lazy loading v šablonách: Vede k nestabilní latenci; data připravte v aplikační vrstvě.

Plán implementace v projektu

  1. Volba přístupu: Určete, kde použijete ORM, kde query builder a kde nativní SQL.
  2. Model a hranice: Navrhněte entity, agregáty, repozitáře, transakční hranice a pravidla načítání.
  3. Migrace a seed: Nastavte nástroje, konvence verzování a CI pipeline pro migrační kroky.
  4. Bezpečnost: Parametrizace, role DB, audit dotazů a přístupů, testy proti injekcím.
  5. Výkon: Indexy, eager loading, cache, keyset pagination, sledování plánů.
  6. Testy: Strategie pro jednotkové a integrační testy, transakční testy, fixtures.
  7. Observabilita: Logování SQL, tracing, metriky a upozornění.

Tabulkové srovnání přístupů

Přístup Kontrola nad SQL Produktivita Složitost Vhodné pro
PDO Maximální Nižší Nižší Výkonnostně citlivé části, reporting
Query Builder Vysoká Střední Střední CRUD se složitými filtry
ORM – Active Record Střední Vysoká Střední Rychlé CRUD, menší až středně velké domény
ORM – Data Mapper Střední Střední Vyšší DDD, složité vztahy, dlouhodobé projekty

Závěr

Úspěšná integrace databází v PHP spočívá v promyšlené kombinaci nástrojů a přístupů. ORM výrazně zvyšuje produktivitu a udržovatelnost, vyžaduje však znalost svých interních mechanismů (Unit of Work, strategie načítání, transakce). Pokud jde o výkon, je legitimní sáhnout po query builderu či čistém SQL – zásadní je měřit, profilovat a vymezit jasné architektonické hranice. Díky moderním možnostem PHP (typy, enumy, atributy) a disciplíně v oblasti migrací, testování a observability lze vybudovat robustní datovou vrstvu, která obstojí jak při rychlém vývoji, tak při vysokém zatížení produkčního prostředí.