Ukážeme si, že Doctrine není jenom ORMko a kdy jít o vrstvu níž. Jak DQL naučit věci, které v základu neumí, ale vaše databáze ano. A pár dalších tipů, jak nemít z databáze úplně hloupé úložiště.
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
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;
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
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
10. Vlastní datový typ: komentáře ve schématu
class SomethingBasedOnStringType extends StringType {
function requiresSQLCommentHint(
AbstractPlatform $platform) : bool
// …
$platform->markDoctrineTypeCommented(Type::getType($type));
11. Vlastní datový typ: konverze na úrovni DB
class GeometryType extends Type {
function canRequireSQLConversion() : bool;
function convertToDatabaseValueSQL(
$sqlExpr, AbstractPlatform $platform);
function convertToPHPValueSQL(
$sqlExpr, $platform);
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
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 :(
22. Rozšiřování DQL
● TreeWalker - může modifikovat AST
● output SqlWalker - generuje samotný SQL dotaz
● vlastní funkce
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 ...;
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);
}
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
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”