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● 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
Indexy a vzdálené klíče
class User {
/**
* @ORM\Id()
* @ORM\Column(type="uuid")
*/
private $id;
class Order {
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=false)
*/
private $user;
Unique indexy/**
* @ORM\Entity()
* @ORM\Table(name="order_item_rating",
* uniqueConstraints={
* @ORM\UniqueConstraint(name="order_item_rating_x_unique", columns={
* "order_item_id", "user_id"
* })
* }
* )
*/
class OrderItemRating
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
Vlastní datový typclass 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
Vlastní datový typ: komentáře ve schématu
class SomethingBasedOnStringType extends StringType {
function requiresSQLCommentHint(
AbstractPlatform $platform) : bool
// …
$platform->markDoctrineTypeCommented(Type::getType($type));
Vlastní datový typ: konverze na úrovni DB
class GeometryType extends Type {
function canRequireSQLConversion() : bool;
function convertToDatabaseValueSQL(
$sqlExpr, AbstractPlatform $platform);
function convertToPHPValueSQL(
$sqlExpr, $platform);
Vlastní datový typ: registrace
doctrine:
dbal:
types:
uuid:
class: Ramsey\Uuid\Doctrine\UuidType
Zdroj: ramsey/uuid-doctrine
Vlastní datový typ: použití
/**
* @ORM\Id()
* @ORM\Column(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)';
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
Batch operace s DQL● Zkuste nejprve chytřejší způsoby iterace nad výsledkem
○ ORM\Query::iterate();
○ Stránkování
● Raději DQL update, než SQL update
● Neumí JOINy :(
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();
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
)
Rozšiřování DQL● TreeWalker - může modifikovat AST● output SqlWalker - generuje samotný SQL dotaz● vlastní funkce
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 jdeKam nemůže DQL
SELECT order.finishedTime IS NULL AS something
FROM ...;
Tam musí funkce
SELECT IS_NULL(order.finishedTime) AS something
FROM ...;
Rozšiřování DQL: vlastní funkceclass 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);
}
SELECT employee,
AVG(salary) OVER (PARTITION BY employee.department) AS avgSalary
FROM My\Employee employee;
SELECT employee,
WINDOW_OVER(AVG(salary), employee.department) AS avgSalary
FROM My\Employee employee;
SELECT employee,
WINDOW(AVG(salary) OVER (PARTITION BY employee.department)) AS avgSalary
FROM My\Employee employee;
Rozšiřování DQL: komplexnější syntaxe
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
Druhy hydratace● entity
○ ORM\Query::getResult();
● scalar + entity (mixed results)○ ORM\Query::getResult();
● array - vytvoří strukturu jako pro entity a tu vrátí○ ORM\Query::getArrayResult();
● scalar - přejmenuje sloupce na fieldy a přetypuje○ ORM\Query::getScalarResult();
Native queries
$rsm = new ResultSetMappingBuilder($em);
$rsm->addRootEntityFromClassMetadata(OrderItem::class, 'order_item')
$connection->createQueryBuilder()
->addSelect($rsm->generateSelectClause())
->from(...)
$em->createNativeQuery($sql, $rsm)
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”