Slide 1

Slide 1 text

Doctrine ORM & model @ProchazkaFilip

Slide 2

Slide 2 text

kdyby/doctrine v3.0

Slide 3

Slide 3 text

Co si povíme - jak psát entity - práce s db schématem - použití entit - pokládání dotazů - modelové třídy (a jak nepsat repozitáře) - jak cachovat

Slide 4

Slide 4 text

Jak psát entity

Slide 5

Slide 5 text

Databáze neexistuje $articles = [ new Article('abc'), new Article('def'), new Article('ghi') ];

Slide 6

Slide 6 text

Asociace mezi entitami class Comment { private $article; public function __construct(Article $article) { $this->article = $article; }

Slide 7

Slide 7 text

Asociace mezi entitami class Article { private $comments; public function __construct() { $this->comments = new ArrayCollection(); } function addComment(Comment $comment) { $this->comments[] = $comment; }

Slide 8

Slide 8 text

Nahaté kolekce? Never! class Article { // ... function getComments() { return $this->comments; }

Slide 9

Slide 9 text

Magic to the rescue use Kdyby\Doctrine\Entities\MagicAccessors; class Article { use MagicAccessors;

Slide 10

Slide 10 text

Magic to the rescue - pouze na protected properties - magické (get|set)tery - magické collection accessory

Slide 11

Slide 11 text

Magic to the rescue class Article { private $comments; function getComments() { return $this->comments; }

Slide 12

Slide 12 text

Magic to the rescue class Article { protected $comments;

Slide 13

Slide 13 text

Magic to the rescue $article->comments ->add(new Comment($article)); // vyhodi vyjimku $article->comments ->filter(function () { .. }); // ok

Slide 14

Slide 14 text

Magic to the rescue class Article { // ... function getComments(); function addComment(Comment $comment); function hasComment(Comment $comment); function removeComment(Comment $comment);

Slide 15

Slide 15 text

Less is more class Comment { private $article; // ... function getArticle() { return $this->article; }

Slide 16

Slide 16 text

Less is more private $published; function isPublished() { return $this->published; } function publish() { $this->published = TRUE; }

Slide 17

Slide 17 text

Tak od teď, databáze už zase existuje

Slide 18

Slide 18 text

Životní cyklus entity - create -> persist -> flush -> konec rq - load -> update -> flush -> konec rq - load -> delete -> flush -> konec rq Existence entity - začíná s create - končí s delete+flush

Slide 19

Slide 19 text

Aby entita byla entita - musí mít metadata - její NS s driverem musí být registrovaný

Slide 20

Slide 20 text

Mapping /** @ORM\Entity() */ class Article { use MagicAccessors; use Identifier; /** @ORM\Column(type="string") */ protected $title;

Slide 21

Slide 21 text

Mapping /** * @ORM\OneToMany(targetEntity="Comment", cascade={"persist"}) * @var Comment[]|ArrayCollection */ protected $comments;

Slide 22

Slide 22 text

Zaregistrujeme extensions: console: Kdyby\Console\DI\ConsoleExtension events: Kdyby\Events\DI\EventsExtension annotations: Kdyby\Annotations\DI\AnnotationsExtension doctrine: Kdyby\Doctrine\DI\OrmExtension

Slide 23

Slide 23 text

Nakonfigurujeme doctrine: metadata: App: %appDir%

Slide 24

Slide 24 text

Schéma (pomocí sf console) $ php www/index.php orm:schema:create orm:schema:update --dump-sql $ php www/index.php o:s:u --dump-sql

Slide 25

Slide 25 text

Migrace - doctrine/migrations:@dev - zenify/doctrine-migrations extensions: migrations: Zenify\DoctrineMigrations\DI\MigrationsExtension

Slide 26

Slide 26 text

Používáme $article = new Article('Abc'); $article->addComment(new Comment($article)); $em->persist($article); $em->flush();

Slide 27

Slide 27 text

Používáme $article = $em->find(Article::class, 1); $article->title = "lol"; $em->flush();

Slide 28

Slide 28 text

Praktické použití entit

Slide 29

Slide 29 text

V presenterech private $article; public function actionDefault() { $this->article = $this->em->find(Article::class, 42); } public function renderDefault() { $this->template->article = $this->article }

Slide 30

Slide 30 text

Ve formulářích: vytvoření protected function createComponentForm() { $form = new UI\Form; $form->onSuccess[] = function ($form, $values) { $article = new Article($values->title); $this->em->persist($article); $this->em->flush(); } return $form; }

Slide 31

Slide 31 text

Ve formulářích: editace protected function createComponentForm() { $form = new UI\Form; $form->onSuccess[] = function ($form, $values) { $this->article->setTitle($values->title); $this->em->flush(); } return $form; }

Slide 32

Slide 32 text

Ve formulářích: alternativně - univerzální formuláře pro create/edit - form mappery (Kdyby/DoctrineForms ?)

Slide 33

Slide 33 text

V komponentách class ArticleControl extends UI\Control { private $article; function __construct(Article $article) { $this->article = $article; } public function render() { $this->template->article = $this->article; }

Slide 34

Slide 34 text

Netriviální dotazování

Slide 35

Slide 35 text

EntityRepository function findBy(array $criteria, array $orderBy, $limit, $offset) $repo->findBy(['article.title' => $title]); $repo->findBy([], ['article.title' => 'DESC']) function countBy(array $criteria) $repo->countBy(['article.author' => $author]); function findPairs($criteria, $value, $orderBy, $key) $repo->findPairs(['currency' => 'USD'], "name")

Slide 36

Slide 36 text

DQL & Query builder $qb = $em->createQueryBuilder(); $qb->select('u') ->from(User::class, 'u') ->where('u.id = :id')->setParameter('id', 123) ->orderBy('u.name', 'ASC');

Slide 37

Slide 37 text

DQL & Query builder $query = $qb->getQuery(); /** @var User[] $result */ $result = $query->getResult();

Slide 38

Slide 38 text

Native query $sql = 'SELECT * FROM users WHERE name = ?'; $rsm = new ResultSetMapping(); // build rsm here $query = $entityManager->createNativeQuery($sql, $rsm); $query->setParameter(1, 'romanb'); $users = $query->getResult();

Slide 39

Slide 39 text

ResultSet: why $paginator = $this['vp']->getPaginator(); $paginator->setItemsCount($articles->countAll()); $this->template->articles = $articles->findAll( $paginator->getOffset(), $paginator->getLength() );

Slide 40

Slide 40 text

ResultSet: why function findAll($limit, $offset) { return $this->repository ->createQuery("SELECT a FROM App\Article a") ->setMaxResults($limit) ->setFirstResult($offset); function countAll() { return $this->repository ->createQuery("SELECT COUNT(a.id) FROM App\Article a") ->getSingleScalarResult();

Slide 41

Slide 41 text

ResultSet: how public function findAll() { $query = $this->repository ->createQuery("SELECT a FROM App\Article a") return new ResultSet($query); } // usage $this->template->articles = $articles->findAll() ->applyPaginator($this['vp']->getPaginator());

Slide 42

Slide 42 text

Ještě složitější (ale mocnější) dotazování

Slide 43

Slide 43 text

Query Object class QuestionsQuery extends Kdyby\Doctrine\QueryObject { /** * @param \Kdyby\Persistence\Queryable $repository * @return \Doctrine\ORM\QueryBuilder */ protected function doCreateQuery(Queryable $repository);

Slide 44

Slide 44 text

Query Object function inCategory(Category $cat) { $this->filter[] = function (QueryBuilder $qb) use ($cat) { $qb->andWhere('q.category = :categoryId') ->setParameter('categoryId', $cat->getId()); }; return $this; }

Slide 45

Slide 45 text

Query Object public function withLastPost() { $this->select[] = function (QueryBuilder $qb) { $qb->addSelect(lp') ->leftJoin('q.lastPost', 'lp'); }; return $this; }

Slide 46

Slide 46 text

Query Object $query = (new QuestionsQuery()) ->withLastPost() ->inCategory($category); $result = $repository->fetch($query);

Slide 47

Slide 47 text

1+N problem - optimální množství dat - vysoká komplexita - špatná odezva na síti - performance killer

Slide 48

Slide 48 text

1+N problem $qb->select('article, comment') ->from(Article::class, 'article') ->leftJoin('article.comments', 'comment')

Slide 49

Slide 49 text

M*N problem: násobení řádků - moc dat na projití - vysoká komplexita - moc práce pro databázi - moc práce pro doctrine - performance killer

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

Query Object: efektivně - každá query musí načítat pouze toOne relace - toMany relace načítat s WHERE IN dalšími queries (postFetch) - konstatní počet queries

Slide 52

Slide 52 text

Query Object: postFetch public function withComments() { $this->onPostFetch[] = function ($_, Queryable $repository, \Iterator $iterator) { $ids = array_keys(iterator_to_array($iterator, TRUE)); $repository->createQueryBuilder() ->select('partial article.{id}', 'comments') ->from(Article::class, 'article') ->leftJoin('article.comments', 'comments') ->andWhere('article.id IN (:ids)')->setParameter('ids', $ids) ->getQuery()->getResult(); } return $this; }

Slide 53

Slide 53 text

Query Object: postFetch $query = (new ArticlesQuery()) ->withComments(); $result = $repository->fetch($query);

Slide 54

Slide 54 text

Repozitáře, fasády, služby aneb pětivrstvý model

Slide 55

Slide 55 text

Repozitáře - vhodné použití ve facade - vhodné použití v services - ale klidně i v presenteru/komponentě - pouze čtení!

Slide 56

Slide 56 text

Repozitáře /** * @ORM\Entity(repositoryClass="ArticleRepository") */ class Article { } class ArticleRepository extends Kdyby\Doctrine\EntityRepository { }

Slide 57

Slide 57 text

services: - App\Blog\ArticleRepository() - class: App\Blog\ArticleRepository() tags: [doctrine.repositoryEntity: App\Blog\Article] Repozitáře jako služby

Slide 58

Slide 58 text

Facade - brána mezi presenterem/komponentou a modelem - snadnější sdílení logiky mezi api a frontem - vhodné místo pro flush - není nutné mít 1:1 k entitám

Slide 59

Slide 59 text

Facade class BlogFacade { function findPublished(); function fetch(ArticlesQuery $query); function saveComment(Comment $comment); class RedactorFacade { function createDraft(); function save(Article $article);

Slide 60

Slide 60 text

Služby - dělení aplikace na menší logické celky - nějaké konkrétní operace nad db/entitami - klidně i externí api - používají se ve facades

Slide 61

Slide 61 text

Eventy

Slide 62

Slide 62 text

Lifecycle eventy - na entitě - “full blown” listenery - listenery pro typ

Slide 63

Slide 63 text

Lifecycle eventy na entitě /** @ORM\Entity @ORM\HasLifecycleCallbacks */ class User { /** @ORM\PrePersist */ public function doStuffOnPrePersist() { $this->createdAt = date('Y-m-d H:i:s'); }

Slide 64

Slide 64 text

Listenery class MyEventListener implements Kdyby\Events\Subscriber { function preUpdate(LifecycleEventArgs $args) { $entityManager = $args->getObjectManager(); $entity = $args->getObject(); if ($entity instanceof User) { // do something with the User } }

Slide 65

Slide 65 text

Listenery pro typ class MyEventListener { function preUpdate(User $user, LifecycleEventArgs $args) { $entityManager = $args->getObjectManager(); // do something with the User }

Slide 66

Slide 66 text

Cache

Slide 67

Slide 67 text

Cachovací strategie - result caching - 2nd level caching

Slide 68

Slide 68 text

Result caching $query = $em->createQuery('SELECT u FROM App\User u'); $query->useResultCache( true, $lifetime, $resultCacheId );

Slide 69

Slide 69 text

2nd level cache (since 2.5) - READ_ONLY (výchozí) - NOSTRICT_READ_WRITE - READ_WRITE (zámky)

Slide 70

Slide 70 text

2nd level cache doctrine: secondLevelCache: enabled: true driver: redis(@redis.default_client::getDriver())

Slide 71

Slide 71 text

2nd level cache /** * @ORM\Cache(region="product") */ class Product { /** * @ORM\ManyToMany(...) * @ORM\Cache(usage="NONSTRICT_READ_WRITE", region="product") */ private $tags;

Slide 72

Slide 72 text

Shrnutí - jak psát entity - práce s db schématem - použití entit - pokládání dotazů - modelové třídy - cache

Slide 73

Slide 73 text

Možná někdy příště - Zápis do db, čtení z Elasticu (to dělá rohlík) - Command Query Responsibility Segregation

Slide 74

Slide 74 text

Dotazy?

Slide 75

Slide 75 text

Díky za pozornost! @ProchazkaFilip