SlideShare a Scribd company logo
1 of 35
Download to read offline
Doctrine: Co dělat,
když entity nestačí?
@ProchazkaFilip
#makeCodeNotWar
Co si povíme?
● minimum využití databáze každého Doctrinisty
● batch operace
● rozšiřování DQL
● metody hydratace
● native queries
#makeCodeNotWar
Minimum využití databáze
každého Doctrinisty
Minimum využití databáze každého Doctrinisty
● Co za vás dělá Doctrine
○ indexy pro vazby a primární klíče
○ vzdálené klíče
● Co si musíte pohlídat sami
○ unique indexy
○ vlastní typy
Pokud se bavíme o nedělání hloupého úložiště z databáze, tak logicky chceme využívat funkce co databáze nabízí. Takový úplný základ jsou indexy a
vzdálené klíče. O trochu pokročilejší jsou pak unikátní klíče a vlastní datové typy.
Indexy a vzdálené klíče
class User {
/**
* @ORMId()
* @ORMColumn(type="uuid")
*/
private $id;
class Order {
/**
* @ORMManyToOne(targetEntity=User::class)
* @ORMJoinColumn(nullable=false)
*/
private $user;
IDčko musíme entitě dát vždy, jinak ji Doctrine nepřijme - tím získáme primární klíče. Když pak děláme vazby pomocí *ToMany nebo *ToOne vazeb, generuje
nám Doctrine i vzdálené klíče, k nim indexy a klidně i many to many tabulky.
Unique indexy
class User {
/**
* @ORMColumn(type="string", unique=true)
*/
private $email;
Nejjednodušší způsob, jak získat unique klíče, je použít unique vlastnost annotace Column. Doctrine nám pak vygeneruje adektávní schéma.
Unique indexy
/**
* @ORMEntity()
* @ORMTable(name="order_item_rating",
* uniqueConstraints={
* @ORMUniqueConstraint(name="order_item_rating_x_unique", columns={
* "order_item_id", "user_id"
* })
* }
* )
*/
class OrderItemRating
Malinko složitější je pak udělat unikátní klíče nad vícero sloupci.
Unique indexy: vkládání a race conditions
● Opravdu to potřebujete řešit?
○ >90% aplikacím stačí check přes repository před flushem
○ >90% aplikací nikdy nenaroste natolik, aby to byl skutečný problém
● Pokud to opravdu opravdu potřebujete řešit
○ kdyby/doctrine ... NonLockingUniqueInserter
○ ^ brzy i samostatně, jako kdyby/doctrine-nonlocking-unique-inserter
○ ^ Symfony friendly ❤
○ ^ sledujte issue kdyby/doctrine#238
S unikátními klíči souvisí řešení race conditions. EntityManager se při flushnutí entity, která poruší unique index, kompletně zamkne a není možné s ním dál
pracovat. Pro 90% aplikací stačí prostý check repozitářem, že takový záznam v databázi neexistuje. Pokud to však opravu potřebujete řešit, koukněte na issue
https://github.com/Kdyby/Doctrine/issues/238, kde se řeší rozdělení Kdyby/Doctrine na framework-agnostic balíčky. Brzy totiž vznikne například kdyby/doctrine-
nonlocking-unique-inserter, který tento problém řeší.
Vlastní datový typ
class UuidType extends Type {
const NAME = 'uuid';
function getName() { return self::NAME; }
function getSQLDeclaration(
array $fieldDeclaration, AbstractPlatform $platform);
function convertToPHPValue(
$value, AbstractPlatform $platform);
function convertToDatabaseValue(
$value, AbstractPlatform $platform);
Zdroj: ramsey/uuid-doctrine
Pěkný příklad vlastního typu je UuidType z balíčku ramsey/uuid-doctrine. Viz http://docs.doctrine-project.org/en/latest/cookbook/custom-mapping-types.html
Vlastní datový typ: komentáře ve schématu
class SomethingBasedOnStringType extends StringType {
function requiresSQLCommentHint(
AbstractPlatform $platform) : bool
// …
$platform->markDoctrineTypeCommented(Type::getType($type));
U vlastních typů je nutné řešit, aby byly správně oanotovány v databázi, protože jinak Doctrine neumí správně diffnout co se změnilo. Pokud však typ
označíme, jakože se má komentovat, Doctrine si do komentáře sloupečku tabulky uloží jak se ten typ jmenuje a bude schéma diffovat správně.
Vlastní datový typ: konverze na úrovni DB
class GeometryType extends Type {
function canRequireSQLConversion() : bool;
function convertToDatabaseValueSQL(
$sqlExpr, AbstractPlatform $platform);
function convertToPHPValueSQL(
$sqlExpr, $platform);
Pokud budete potřebovat pokročilou konverzi binárních dat už na úrovni databáze, slouží k tomu tyto metody.
Vlastní datový typ: registrace
doctrine:
dbal:
types:
uuid:
class: RamseyUuidDoctrineUuidType
Zdroj: ramsey/uuid-doctrine
Ukázka registrace vlastního typu v Symfony.
Vlastní datový typ: použití
/**
* @ORMId()
* @ORMColumn(type="uuid")
*/
private $id;
CREATE TABLE "user" (
id UUID NOT NULL,
email VARCHAR(255) DEFAULT NULL
-- atd
);
COMMENT ON COLUMN "user".id
IS '(DC2Type:uuid)';
Při použití uuid typu na sloupci id se vygeneruje podobné schéma, jako je v pravo.
Logika v databázi
vs
Logika v modelu
Premature optimization is the root of all evil ~ Donald Knuth
O tom, jestli patří logika do databáze nebo do modelu můžeme klidně dál vést svaté války, ale v momentě kdy si jednu z cest zvolíte, měli byste ji co
nejstriktněji dodržovat. Já volím logiku v modelu a to znamená, že logice v databázi se budu vyhýbat dokud to bude možné.
Batch operace
Batch operace s DQL
“UPDATE/DELETE statements are ported directly into a Database statement and
therefore bypass any locking scheme, events and do not increment the
version column. Entities that are already loaded into the persistence context will
NOT be synced with the updated database state. It is recommended to call
EntityManager#clear() and retrieve new instances of any affected entity.”
~ Dokumentace
Dávejte si pozor, pokud píšete SQL, ale i DQL update a delete dotazy, protože Doctrine nemá jak kontrolovat zamykání a nemá jak poznat, které entity obnovit
v paměti. Pokud tedy něco takového pustíte, je doporučováno následně vyčistit entity z paměti a načíst si je znovu.
Batch operace s DQL
● Zkuste nejprve chytřejší způsoby iterace nad výsledkem
○ ORMQuery::iterate();
○ Stránkování
● Raději DQL update, než SQL update
● Neumí JOINy :(
Předtím než se uchýlíme k DQL updatu, tak je vhodné nejprve zkusit, jestli by nestačilo udělat nějakou chytřejší iteraci nad daty. Buď tedy nějaké custom
stránkování výsledků, nebo použití metody iterate(), která umí hydratovat entity postupně a snižujeme tím šanci, že nám dojde paměť, ikdyž potřebujeme
zpracovat miliony entit. Jedna z nevýhod ale je, že UPDATE ani DELETE neumí JOIN.
Batch operace s DQL
$select = $em->createQueryBuilder()
->addSelect('orderItem.price')
->from(OrderItem::class, 'orderItem')
->andWhere('orderItem.order = theOrder.id');
$update = $em->createQueryBuilder()
->update(Order::class, 'theOrder')
->set('theOrder.totalPrice', sprintf('(%s)', $select))
->getQuery();
Ovšem to že neumí JOINy se dá celkem snadno řešit pomocí subselectů.
Batch operace s DQL
UPDATE "order"
SET total_price = (
SELECT SUM(o0_.price) AS dctrn__1
FROM order_item o0_
WHERE o0_.order_id = order.id
)
Výraz na minulém slidu by měl vygenerovat SQL podobné tomuto.
Batch operace s DBAL
● $connection->prepare('UPDATE ...');
● $connection->createQueryBuilder();
Občas se nejde vyhnout použití SQL na batch operace a pokud to opravdu dává smysl, tak není důvod kvůli tomu skřípat zuby. Práci by vám mohl usnadnit
chudší bratříček DibiFluent, tedy QueryBuilder v DBAL.
Rozšiřování DQL
Rozšiřování DQL
● TreeWalker - může modifikovat AST
● output SqlWalker - generuje samotný SQL dotaz
● vlastní funkce
TreeWalker umí modifikovat načtenou strukturu DQL dotazu do stromu objektů, které ho reprezentují (AST) a modifikovat jej. Output SqlWalker umí převést
AST na SQL a když si tento výchozí podědíte, můžete si změnit způsob jakým se výsledné SQL z AST DQLka skládá.
Rozšiřování DQL: limitace
● Gramatika je jasně definovaná, není možné to nijak ohackovat
● Tree Walker může modifikovat výsledné AST, ale opět nezmění gramatiku
● SqlWalker to nemá jak zachránit
Rozšiřování DQL: co jde
Kam nemůže DQL
SELECT order.finishedTime IS NULL AS something
FROM ...;
Tam musí funkce
SELECT IS_NULL(order.finishedTime) AS something
FROM ...;
Funkce je ovšem možné doplnit téměř kamkoliv a chybějící syntax tedy ve výsledku není problém, protože identickou funkcionalitu snadno doplníme.
Rozšiřování DQL: vlastní funkce
class IsNull extends FunctionNode {
private $expression;
public function getSql(SqlWalker $sqlWalker) {
return $sqlWalker->walkArithmeticPrimary($this->expression) . ' IS NULL';
}
public function parse(Parser $parser) {
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->expression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
Vlastní funkce do DQL musí mít minimálně metodu parse(), která tokeny v DQL převede na node v AST a funkci getSql() která zpracovaný node převede na
SQL.
SELECT employee,
AVG(salary) OVER (PARTITION BY employee.department) AS avgSalary
FROM MyEmployee employee;
SELECT employee,
WINDOW_OVER(AVG(salary), employee.department) AS avgSalary
FROM MyEmployee employee;
SELECT employee,
WINDOW(AVG(salary) OVER (PARTITION BY employee.department)) AS avgSalary
FROM MyEmployee employee;
Rozšiřování DQL: komplexnější syntaxe
První ukázka AFAIK není možná docílit, protože mění syntaxi DQL, druhá a třetí ale je, protože naše speciální syntax je obalená do custom funkce.
Rozšiřování DQL: existující knihovny
● oro/doctrine-extensions (MySQL, PostgreSQL)
● opsway/doctrine-dbal-postgresql (PostgreSQL)
● beberlei/DoctrineExtensions (MySQL, Oracle)
● syslogic/doctrine-json-functions (MySQL)
● další na: https://packagist.org/search/?q=dql
Pár zajímavých balíčků, kde jsou již naimplementovány užitečné funkce do DQL.
Ukázka, jak jedna z knihoven řeší speciální operátory v PostgreSQL.
Hydratace
Druhy hydratace
● entity
○ ORMQuery::getResult();
● scalar + entity (mixed results)
○ ORMQuery::getResult();
● array - vytvoří strukturu jako pro entity a tu vrátí
○ ORMQuery::getArrayResult();
● scalar - přejmenuje sloupce na fieldy a přetypuje
○ ORMQuery::getScalarResult();
Né vždy je potřeba hydratovat entity, když bych z nich zase mapoval hned pole, protože je chci jen číst. Dávejte si ale pozor na vracení výsledků z databáze
rovnou jako struktury z API, změnou entit a nebo dotazu si tak snadno rozbijete kompatibilitu API.
Native queries
$rsm = new ResultSetMappingBuilder($em);
$rsm->addRootEntityFromClassMetadata(OrderItem::class, 'order_item')
$connection->createQueryBuilder()
->addSelect($rsm->generateSelectClause())
->from(...)
$em->createNativeQuery($sql, $rsm)
Pokud potřebujete opravdu složité filtrační dotazy, které ale chcete zpracovávat jako entity, můžete použít Native Queries, které umožňují napsat libovolné SQL
a převést výsledek na entity, stejně jako by to dělala Doctrine.
Shrnutí: co si z toho odnést?
● Logika v modelu dokud to jenom trochu jde
● Nebát se využívat hojně vlastní typy
● Rozšiřování DQL je snadné
● Doctrine není určená na batch operace
Kam dál?
● Youtube kanál “Nette Framework” > search “Doctrine”
Dotazy?
Díky za pozornost!
@ProchazkaFilip
#makeCodeNotWar
Díky za pozornost!
@ProchazkaFilip
#makeCodeNotWar

More Related Content

What's hot

MicroKernel - aneb špatný název pro Helper (5. sraz přátel Symfony v Praze)
MicroKernel - aneb špatný název pro Helper (5. sraz přátel Symfony v Praze)MicroKernel - aneb špatný název pro Helper (5. sraz přátel Symfony v Praze)
MicroKernel - aneb špatný název pro Helper (5. sraz přátel Symfony v Praze)Martin Zeman
 
CRCE - přehled datového modelu a vybraná API
CRCE - přehled datového modelu a vybraná APICRCE - přehled datového modelu a vybraná API
CRCE - přehled datového modelu a vybraná APIPremek Brada
 
Jak přemigrovat Slevomat na Doctrine za jedno dopoledne
Jak přemigrovat Slevomat na Doctrine za jedno dopoledneJak přemigrovat Slevomat na Doctrine za jedno dopoledne
Jak přemigrovat Slevomat na Doctrine za jedno dopoledneJosef Kříž
 
Aplikační nastavení v .NET
Aplikační nastavení v .NETAplikační nastavení v .NET
Aplikační nastavení v .NETJan Hřídel
 
INPTP Rekapitulace
INPTP Rekapitulace INPTP Rekapitulace
INPTP Rekapitulace Jan Hřídel
 
Vývoj aplikací pro iOS
Vývoj aplikací pro iOSVývoj aplikací pro iOS
Vývoj aplikací pro iOSPetr Dvorak
 
WebSockets - how to do real-time applications in PHP
WebSockets - how to do real-time applications in PHPWebSockets - how to do real-time applications in PHP
WebSockets - how to do real-time applications in PHPBrnoPHP
 
Na co si dát v Javascriptu pozor? - Barcamp Hradec Králové 2015
Na co si dát v Javascriptu pozor? - Barcamp Hradec Králové 2015Na co si dát v Javascriptu pozor? - Barcamp Hradec Králové 2015
Na co si dát v Javascriptu pozor? - Barcamp Hradec Králové 2015angular-cz
 
Rozšiřujeme jQuery aneb proč si nenapsat plugin?
Rozšiřujeme jQuery aneb proč si nenapsat plugin?Rozšiřujeme jQuery aneb proč si nenapsat plugin?
Rozšiřujeme jQuery aneb proč si nenapsat plugin?Bohdan Ganický
 
Miroslav Bajtoš - Nativní async/await v Node.js - už tam jsme?
Miroslav Bajtoš - Nativní async/await v Node.js - už tam jsme?Miroslav Bajtoš - Nativní async/await v Node.js - už tam jsme?
Miroslav Bajtoš - Nativní async/await v Node.js - už tam jsme?Develcz
 
C# - Vícevláknové aplikace
C# - Vícevláknové aplikaceC# - Vícevláknové aplikace
C# - Vícevláknové aplikaceJan Hřídel
 
Pokročilé techniky programování .NET a C#
Pokročilé techniky programování .NET a C#Pokročilé techniky programování .NET a C#
Pokročilé techniky programování .NET a C#Jan Hřídel
 
jQuery: full frontal
jQuery: full frontaljQuery: full frontal
jQuery: full frontalDavid Grudl
 

What's hot (18)

Kdyby/Events
Kdyby/EventsKdyby/Events
Kdyby/Events
 
MicroKernel - aneb špatný název pro Helper (5. sraz přátel Symfony v Praze)
MicroKernel - aneb špatný název pro Helper (5. sraz přátel Symfony v Praze)MicroKernel - aneb špatný název pro Helper (5. sraz přátel Symfony v Praze)
MicroKernel - aneb špatný název pro Helper (5. sraz přátel Symfony v Praze)
 
CRCE - přehled datového modelu a vybraná API
CRCE - přehled datového modelu a vybraná APICRCE - přehled datového modelu a vybraná API
CRCE - přehled datového modelu a vybraná API
 
Clean code
Clean codeClean code
Clean code
 
Jak přemigrovat Slevomat na Doctrine za jedno dopoledne
Jak přemigrovat Slevomat na Doctrine za jedno dopoledneJak přemigrovat Slevomat na Doctrine za jedno dopoledne
Jak přemigrovat Slevomat na Doctrine za jedno dopoledne
 
Drupal Front-end
Drupal Front-endDrupal Front-end
Drupal Front-end
 
Aplikační nastavení v .NET
Aplikační nastavení v .NETAplikační nastavení v .NET
Aplikační nastavení v .NET
 
INPTP Rekapitulace
INPTP Rekapitulace INPTP Rekapitulace
INPTP Rekapitulace
 
Vývoj aplikací pro iOS
Vývoj aplikací pro iOSVývoj aplikací pro iOS
Vývoj aplikací pro iOS
 
Nette Tester / Posobota
Nette Tester / PosobotaNette Tester / Posobota
Nette Tester / Posobota
 
WebSockets - how to do real-time applications in PHP
WebSockets - how to do real-time applications in PHPWebSockets - how to do real-time applications in PHP
WebSockets - how to do real-time applications in PHP
 
Na co si dát v Javascriptu pozor? - Barcamp Hradec Králové 2015
Na co si dát v Javascriptu pozor? - Barcamp Hradec Králové 2015Na co si dát v Javascriptu pozor? - Barcamp Hradec Králové 2015
Na co si dát v Javascriptu pozor? - Barcamp Hradec Králové 2015
 
ADO.NET
ADO.NETADO.NET
ADO.NET
 
Rozšiřujeme jQuery aneb proč si nenapsat plugin?
Rozšiřujeme jQuery aneb proč si nenapsat plugin?Rozšiřujeme jQuery aneb proč si nenapsat plugin?
Rozšiřujeme jQuery aneb proč si nenapsat plugin?
 
Miroslav Bajtoš - Nativní async/await v Node.js - už tam jsme?
Miroslav Bajtoš - Nativní async/await v Node.js - už tam jsme?Miroslav Bajtoš - Nativní async/await v Node.js - už tam jsme?
Miroslav Bajtoš - Nativní async/await v Node.js - už tam jsme?
 
C# - Vícevláknové aplikace
C# - Vícevláknové aplikaceC# - Vícevláknové aplikace
C# - Vícevláknové aplikace
 
Pokročilé techniky programování .NET a C#
Pokročilé techniky programování .NET a C#Pokročilé techniky programování .NET a C#
Pokročilé techniky programování .NET a C#
 
jQuery: full frontal
jQuery: full frontaljQuery: full frontal
jQuery: full frontal
 

Viewers also liked

Triangulodesuelostextura
TriangulodesuelostexturaTriangulodesuelostextura
TriangulodesuelostexturaPaulina Azpiroz
 
Cover page (joriz)
Cover page (joriz)Cover page (joriz)
Cover page (joriz)graessel
 
2015_CTI_BSc-IT_Module-Description_Final1
2015_CTI_BSc-IT_Module-Description_Final12015_CTI_BSc-IT_Module-Description_Final1
2015_CTI_BSc-IT_Module-Description_Final1Moses75
 
Fairy tail 001_24
Fairy tail 001_24Fairy tail 001_24
Fairy tail 001_24Acnologia
 
the language of literaure
the language of literaurethe language of literaure
the language of literauremariam-aftab
 
Climate and weather
Climate and weatherClimate and weather
Climate and weathermariam-aftab
 
εφηβεία
εφηβείαεφηβεία
εφηβείαgel zosim
 
Value Added Architecture2
Value Added Architecture2Value Added Architecture2
Value Added Architecture2Boluijt
 

Viewers also liked (11)

Triangulodesuelostextura
TriangulodesuelostexturaTriangulodesuelostextura
Triangulodesuelostextura
 
Cover page (joriz)
Cover page (joriz)Cover page (joriz)
Cover page (joriz)
 
2015_CTI_BSc-IT_Module-Description_Final1
2015_CTI_BSc-IT_Module-Description_Final12015_CTI_BSc-IT_Module-Description_Final1
2015_CTI_BSc-IT_Module-Description_Final1
 
Fairy tail 001_24
Fairy tail 001_24Fairy tail 001_24
Fairy tail 001_24
 
the language of literaure
the language of literaurethe language of literaure
the language of literaure
 
Poblacio d'Espanya (II)
Poblacio d'Espanya (II)Poblacio d'Espanya (II)
Poblacio d'Espanya (II)
 
Climate and weather
Climate and weatherClimate and weather
Climate and weather
 
εφηβεία
εφηβείαεφηβεία
εφηβεία
 
Testování prakticky
Testování praktickyTestování prakticky
Testování prakticky
 
Value Added Architecture2
Value Added Architecture2Value Added Architecture2
Value Added Architecture2
 
HEALTHCARE ARCHITECTURE
HEALTHCARE ARCHITECTUREHEALTHCARE ARCHITECTURE
HEALTHCARE ARCHITECTURE
 

Similar to Doctrine: co dělat, když entity nestačí

MicroKernel aneb spatny nazev pro Helper (5. sraz pratel Symfony)
MicroKernel aneb spatny nazev pro Helper (5. sraz pratel Symfony)MicroKernel aneb spatny nazev pro Helper (5. sraz pratel Symfony)
MicroKernel aneb spatny nazev pro Helper (5. sraz pratel Symfony)Péhápkaři
 
Zend Framework 2.0 (ZFMeetup Praha 3.11.2011)
Zend Framework 2.0 (ZFMeetup Praha 3.11.2011)Zend Framework 2.0 (ZFMeetup Praha 3.11.2011)
Zend Framework 2.0 (ZFMeetup Praha 3.11.2011)Martin Hujer
 
Tv 10 11
Tv 10 11Tv 10 11
Tv 10 11352
 
KST/ICSHP - 1. přednáška
KST/ICSHP - 1. přednáškaKST/ICSHP - 1. přednáška
KST/ICSHP - 1. přednáškaJan Hřídel
 
React premature performance optimization
React premature performance optimizationReact premature performance optimization
React premature performance optimizationMartinKritof1
 
Dependency injection v .Net Frameworku
Dependency injection v .Net FrameworkuDependency injection v .Net Frameworku
Dependency injection v .Net FrameworkuRené Stein
 
.NET v SQL Serveru
.NET v SQL Serveru.NET v SQL Serveru
.NET v SQL ServeruJan Drozen
 
MoroSystems na ostravském CZJUGu o Apache Wicket
MoroSystems na ostravském CZJUGu o Apache WicketMoroSystems na ostravském CZJUGu o Apache Wicket
MoroSystems na ostravském CZJUGu o Apache WicketTomáš Páral
 
Relační databáze efektivně z pohledu vývojáře
Relační databáze efektivně z pohledu vývojářeRelační databáze efektivně z pohledu vývojáře
Relační databáze efektivně z pohledu vývojářeJan Smitka
 
Data Restart 2023: Václav Ráš - 10 tipů, jak pracovat s BigQuery
Data Restart 2023: Václav Ráš - 10 tipů, jak pracovat s BigQueryData Restart 2023: Václav Ráš - 10 tipů, jak pracovat s BigQuery
Data Restart 2023: Václav Ráš - 10 tipů, jak pracovat s BigQueryTaste
 
Dependency injection
Dependency injectionDependency injection
Dependency injectionJiří Matula
 
Petr Heinz - Čisté testy, dobré testy
Petr Heinz - Čisté testy, dobré testyPetr Heinz - Čisté testy, dobré testy
Petr Heinz - Čisté testy, dobré testyAnna Kovárová
 
Talend Open Studio DQ
Talend Open Studio DQTalend Open Studio DQ
Talend Open Studio DQdpejcoch
 
KST/ICSHP - 5. a 6. přednáška
KST/ICSHP - 5. a 6. přednáškaKST/ICSHP - 5. a 6. přednáška
KST/ICSHP - 5. a 6. přednáškaJan Hřídel
 
Slovak Sun Training Day 2010 - DTrace
Slovak Sun Training Day 2010 - DTraceSlovak Sun Training Day 2010 - DTrace
Slovak Sun Training Day 2010 - DTraceMartin Cerveny
 

Similar to Doctrine: co dělat, když entity nestačí (20)

MicroKernel aneb spatny nazev pro Helper (5. sraz pratel Symfony)
MicroKernel aneb spatny nazev pro Helper (5. sraz pratel Symfony)MicroKernel aneb spatny nazev pro Helper (5. sraz pratel Symfony)
MicroKernel aneb spatny nazev pro Helper (5. sraz pratel Symfony)
 
Django
DjangoDjango
Django
 
Zend Framework 2.0 (ZFMeetup Praha 3.11.2011)
Zend Framework 2.0 (ZFMeetup Praha 3.11.2011)Zend Framework 2.0 (ZFMeetup Praha 3.11.2011)
Zend Framework 2.0 (ZFMeetup Praha 3.11.2011)
 
Tv 10 11
Tv 10 11Tv 10 11
Tv 10 11
 
KST/ICSHP - 1. přednáška
KST/ICSHP - 1. přednáškaKST/ICSHP - 1. přednáška
KST/ICSHP - 1. přednáška
 
Mesour DataGrid
Mesour DataGridMesour DataGrid
Mesour DataGrid
 
201502.ReinIT.Dev
201502.ReinIT.Dev201502.ReinIT.Dev
201502.ReinIT.Dev
 
React premature performance optimization
React premature performance optimizationReact premature performance optimization
React premature performance optimization
 
Dependency injection v .Net Frameworku
Dependency injection v .Net FrameworkuDependency injection v .Net Frameworku
Dependency injection v .Net Frameworku
 
Úvod do rails
Úvod do railsÚvod do rails
Úvod do rails
 
Spring dao
Spring daoSpring dao
Spring dao
 
.NET v SQL Serveru
.NET v SQL Serveru.NET v SQL Serveru
.NET v SQL Serveru
 
MoroSystems na ostravském CZJUGu o Apache Wicket
MoroSystems na ostravském CZJUGu o Apache WicketMoroSystems na ostravském CZJUGu o Apache Wicket
MoroSystems na ostravském CZJUGu o Apache Wicket
 
Relační databáze efektivně z pohledu vývojáře
Relační databáze efektivně z pohledu vývojářeRelační databáze efektivně z pohledu vývojáře
Relační databáze efektivně z pohledu vývojáře
 
Data Restart 2023: Václav Ráš - 10 tipů, jak pracovat s BigQuery
Data Restart 2023: Václav Ráš - 10 tipů, jak pracovat s BigQueryData Restart 2023: Václav Ráš - 10 tipů, jak pracovat s BigQuery
Data Restart 2023: Václav Ráš - 10 tipů, jak pracovat s BigQuery
 
Dependency injection
Dependency injectionDependency injection
Dependency injection
 
Petr Heinz - Čisté testy, dobré testy
Petr Heinz - Čisté testy, dobré testyPetr Heinz - Čisté testy, dobré testy
Petr Heinz - Čisté testy, dobré testy
 
Talend Open Studio DQ
Talend Open Studio DQTalend Open Studio DQ
Talend Open Studio DQ
 
KST/ICSHP - 5. a 6. přednáška
KST/ICSHP - 5. a 6. přednáškaKST/ICSHP - 5. a 6. přednáška
KST/ICSHP - 5. a 6. přednáška
 
Slovak Sun Training Day 2010 - DTrace
Slovak Sun Training Day 2010 - DTraceSlovak Sun Training Day 2010 - DTrace
Slovak Sun Training Day 2010 - DTrace
 

Doctrine: co dělat, když entity nestačí

  • 1. Doctrine: Co dělat, když entity nestačí? @ProchazkaFilip #makeCodeNotWar
  • 2. Co si povíme? ● minimum využití databáze každého Doctrinisty ● batch operace ● rozšiřování DQL ● metody hydratace ● native queries #makeCodeNotWar
  • 4. Minimum využití databáze každého Doctrinisty ● Co za vás dělá Doctrine ○ indexy pro vazby a primární klíče ○ vzdálené klíče ● Co si musíte pohlídat sami ○ unique indexy ○ vlastní typy Pokud se bavíme o nedělání hloupého úložiště z databáze, tak logicky chceme využívat funkce co databáze nabízí. Takový úplný základ jsou indexy a vzdálené klíče. O trochu pokročilejší jsou pak unikátní klíče a vlastní datové typy.
  • 5. Indexy a vzdálené klíče class User { /** * @ORMId() * @ORMColumn(type="uuid") */ private $id; class Order { /** * @ORMManyToOne(targetEntity=User::class) * @ORMJoinColumn(nullable=false) */ private $user; IDčko musíme entitě dát vždy, jinak ji Doctrine nepřijme - tím získáme primární klíče. Když pak děláme vazby pomocí *ToMany nebo *ToOne vazeb, generuje nám Doctrine i vzdálené klíče, k nim indexy a klidně i many to many tabulky.
  • 6. Unique indexy class User { /** * @ORMColumn(type="string", unique=true) */ private $email; Nejjednodušší způsob, jak získat unique klíče, je použít unique vlastnost annotace Column. Doctrine nám pak vygeneruje adektávní schéma.
  • 7. Unique indexy /** * @ORMEntity() * @ORMTable(name="order_item_rating", * uniqueConstraints={ * @ORMUniqueConstraint(name="order_item_rating_x_unique", columns={ * "order_item_id", "user_id" * }) * } * ) */ class OrderItemRating Malinko složitější je pak udělat unikátní klíče nad vícero sloupci.
  • 8. Unique indexy: vkládání a race conditions ● Opravdu to potřebujete řešit? ○ >90% aplikacím stačí check přes repository před flushem ○ >90% aplikací nikdy nenaroste natolik, aby to byl skutečný problém ● Pokud to opravdu opravdu potřebujete řešit ○ kdyby/doctrine ... NonLockingUniqueInserter ○ ^ brzy i samostatně, jako kdyby/doctrine-nonlocking-unique-inserter ○ ^ Symfony friendly ❤ ○ ^ sledujte issue kdyby/doctrine#238 S unikátními klíči souvisí řešení race conditions. EntityManager se při flushnutí entity, která poruší unique index, kompletně zamkne a není možné s ním dál pracovat. Pro 90% aplikací stačí prostý check repozitářem, že takový záznam v databázi neexistuje. Pokud to však opravu potřebujete řešit, koukněte na issue https://github.com/Kdyby/Doctrine/issues/238, kde se řeší rozdělení Kdyby/Doctrine na framework-agnostic balíčky. Brzy totiž vznikne například kdyby/doctrine- nonlocking-unique-inserter, který tento problém řeší.
  • 9. Vlastní datový typ class UuidType extends Type { const NAME = 'uuid'; function getName() { return self::NAME; } function getSQLDeclaration( array $fieldDeclaration, AbstractPlatform $platform); function convertToPHPValue( $value, AbstractPlatform $platform); function convertToDatabaseValue( $value, AbstractPlatform $platform); Zdroj: ramsey/uuid-doctrine Pěkný příklad vlastního typu je UuidType z balíčku ramsey/uuid-doctrine. Viz http://docs.doctrine-project.org/en/latest/cookbook/custom-mapping-types.html
  • 10. Vlastní datový typ: komentáře ve schématu class SomethingBasedOnStringType extends StringType { function requiresSQLCommentHint( AbstractPlatform $platform) : bool // … $platform->markDoctrineTypeCommented(Type::getType($type)); U vlastních typů je nutné řešit, aby byly správně oanotovány v databázi, protože jinak Doctrine neumí správně diffnout co se změnilo. Pokud však typ označíme, jakože se má komentovat, Doctrine si do komentáře sloupečku tabulky uloží jak se ten typ jmenuje a bude schéma diffovat správně.
  • 11. Vlastní datový typ: konverze na úrovni DB class GeometryType extends Type { function canRequireSQLConversion() : bool; function convertToDatabaseValueSQL( $sqlExpr, AbstractPlatform $platform); function convertToPHPValueSQL( $sqlExpr, $platform); Pokud budete potřebovat pokročilou konverzi binárních dat už na úrovni databáze, slouží k tomu tyto metody.
  • 12. Vlastní datový typ: registrace doctrine: dbal: types: uuid: class: RamseyUuidDoctrineUuidType Zdroj: ramsey/uuid-doctrine Ukázka registrace vlastního typu v Symfony.
  • 13. Vlastní datový typ: použití /** * @ORMId() * @ORMColumn(type="uuid") */ private $id; CREATE TABLE "user" ( id UUID NOT NULL, email VARCHAR(255) DEFAULT NULL -- atd ); COMMENT ON COLUMN "user".id IS '(DC2Type:uuid)'; Při použití uuid typu na sloupci id se vygeneruje podobné schéma, jako je v pravo.
  • 14. Logika v databázi vs Logika v modelu Premature optimization is the root of all evil ~ Donald Knuth O tom, jestli patří logika do databáze nebo do modelu můžeme klidně dál vést svaté války, ale v momentě kdy si jednu z cest zvolíte, měli byste ji co nejstriktněji dodržovat. Já volím logiku v modelu a to znamená, že logice v databázi se budu vyhýbat dokud to bude možné.
  • 16. Batch operace s DQL “UPDATE/DELETE statements are ported directly into a Database statement and therefore bypass any locking scheme, events and do not increment the version column. Entities that are already loaded into the persistence context will NOT be synced with the updated database state. It is recommended to call EntityManager#clear() and retrieve new instances of any affected entity.” ~ Dokumentace Dávejte si pozor, pokud píšete SQL, ale i DQL update a delete dotazy, protože Doctrine nemá jak kontrolovat zamykání a nemá jak poznat, které entity obnovit v paměti. Pokud tedy něco takového pustíte, je doporučováno následně vyčistit entity z paměti a načíst si je znovu.
  • 17. Batch operace s DQL ● Zkuste nejprve chytřejší způsoby iterace nad výsledkem ○ ORMQuery::iterate(); ○ Stránkování ● Raději DQL update, než SQL update ● Neumí JOINy :( Předtím než se uchýlíme k DQL updatu, tak je vhodné nejprve zkusit, jestli by nestačilo udělat nějakou chytřejší iteraci nad daty. Buď tedy nějaké custom stránkování výsledků, nebo použití metody iterate(), která umí hydratovat entity postupně a snižujeme tím šanci, že nám dojde paměť, ikdyž potřebujeme zpracovat miliony entit. Jedna z nevýhod ale je, že UPDATE ani DELETE neumí JOIN.
  • 18. Batch operace s DQL $select = $em->createQueryBuilder() ->addSelect('orderItem.price') ->from(OrderItem::class, 'orderItem') ->andWhere('orderItem.order = theOrder.id'); $update = $em->createQueryBuilder() ->update(Order::class, 'theOrder') ->set('theOrder.totalPrice', sprintf('(%s)', $select)) ->getQuery(); Ovšem to že neumí JOINy se dá celkem snadno řešit pomocí subselectů.
  • 19. Batch operace s DQL UPDATE "order" SET total_price = ( SELECT SUM(o0_.price) AS dctrn__1 FROM order_item o0_ WHERE o0_.order_id = order.id ) Výraz na minulém slidu by měl vygenerovat SQL podobné tomuto.
  • 20. Batch operace s DBAL ● $connection->prepare('UPDATE ...'); ● $connection->createQueryBuilder(); Občas se nejde vyhnout použití SQL na batch operace a pokud to opravdu dává smysl, tak není důvod kvůli tomu skřípat zuby. Práci by vám mohl usnadnit chudší bratříček DibiFluent, tedy QueryBuilder v DBAL.
  • 22. Rozšiřování DQL ● TreeWalker - může modifikovat AST ● output SqlWalker - generuje samotný SQL dotaz ● vlastní funkce TreeWalker umí modifikovat načtenou strukturu DQL dotazu do stromu objektů, které ho reprezentují (AST) a modifikovat jej. Output SqlWalker umí převést AST na SQL a když si tento výchozí podědíte, můžete si změnit způsob jakým se výsledné SQL z AST DQLka skládá.
  • 23. Rozšiřování DQL: limitace ● Gramatika je jasně definovaná, není možné to nijak ohackovat ● Tree Walker může modifikovat výsledné AST, ale opět nezmění gramatiku ● SqlWalker to nemá jak zachránit
  • 24. Rozšiřování DQL: co jde Kam nemůže DQL SELECT order.finishedTime IS NULL AS something FROM ...; Tam musí funkce SELECT IS_NULL(order.finishedTime) AS something FROM ...; Funkce je ovšem možné doplnit téměř kamkoliv a chybějící syntax tedy ve výsledku není problém, protože identickou funkcionalitu snadno doplníme.
  • 25. Rozšiřování DQL: vlastní funkce class IsNull extends FunctionNode { private $expression; public function getSql(SqlWalker $sqlWalker) { return $sqlWalker->walkArithmeticPrimary($this->expression) . ' IS NULL'; } public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->expression = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } Vlastní funkce do DQL musí mít minimálně metodu parse(), která tokeny v DQL převede na node v AST a funkci getSql() která zpracovaný node převede na SQL.
  • 26. SELECT employee, AVG(salary) OVER (PARTITION BY employee.department) AS avgSalary FROM MyEmployee employee; SELECT employee, WINDOW_OVER(AVG(salary), employee.department) AS avgSalary FROM MyEmployee employee; SELECT employee, WINDOW(AVG(salary) OVER (PARTITION BY employee.department)) AS avgSalary FROM MyEmployee employee; Rozšiřování DQL: komplexnější syntaxe První ukázka AFAIK není možná docílit, protože mění syntaxi DQL, druhá a třetí ale je, protože naše speciální syntax je obalená do custom funkce.
  • 27. Rozšiřování DQL: existující knihovny ● oro/doctrine-extensions (MySQL, PostgreSQL) ● opsway/doctrine-dbal-postgresql (PostgreSQL) ● beberlei/DoctrineExtensions (MySQL, Oracle) ● syslogic/doctrine-json-functions (MySQL) ● další na: https://packagist.org/search/?q=dql Pár zajímavých balíčků, kde jsou již naimplementovány užitečné funkce do DQL.
  • 28. Ukázka, jak jedna z knihoven řeší speciální operátory v PostgreSQL.
  • 30. Druhy hydratace ● entity ○ ORMQuery::getResult(); ● scalar + entity (mixed results) ○ ORMQuery::getResult(); ● array - vytvoří strukturu jako pro entity a tu vrátí ○ ORMQuery::getArrayResult(); ● scalar - přejmenuje sloupce na fieldy a přetypuje ○ ORMQuery::getScalarResult(); Né vždy je potřeba hydratovat entity, když bych z nich zase mapoval hned pole, protože je chci jen číst. Dávejte si ale pozor na vracení výsledků z databáze rovnou jako struktury z API, změnou entit a nebo dotazu si tak snadno rozbijete kompatibilitu API.
  • 31. Native queries $rsm = new ResultSetMappingBuilder($em); $rsm->addRootEntityFromClassMetadata(OrderItem::class, 'order_item') $connection->createQueryBuilder() ->addSelect($rsm->generateSelectClause()) ->from(...) $em->createNativeQuery($sql, $rsm) Pokud potřebujete opravdu složité filtrační dotazy, které ale chcete zpracovávat jako entity, můžete použít Native Queries, které umožňují napsat libovolné SQL a převést výsledek na entity, stejně jako by to dělala Doctrine.
  • 32. Shrnutí: co si z toho odnést? ● Logika v modelu dokud to jenom trochu jde ● Nebát se využívat hojně vlastní typy ● Rozšiřování DQL je snadné ● Doctrine není určená na batch operace Kam dál? ● Youtube kanál “Nette Framework” > search “Doctrine”