Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Doctrine ORM & model

Doctrine ORM & model

Praktické ukázky jak psát model v Nette Frameworku a Doctrine 2 ORM. Včetně nejčastějších problémů a tak o tom, jak se mít rychlý web i s ORM.

Filip Procházka

December 21, 2015
Tweet

More Decks by Filip Procházka

Other Decks in Technology

Transcript

  1. Doctrine ORM & model @ProchazkaFilip

  2. kdyby/doctrine v3.0

  3. 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
  4. Jak psát entity

  5. Databáze neexistuje $articles = [ new Article('abc'), new Article('def'), new

    Article('ghi') ];
  6. Asociace mezi entitami class Comment { private $article; public function

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

    __construct() { $this->comments = new ArrayCollection(); } function addComment(Comment $comment) { $this->comments[] = $comment; }
  8. Nahaté kolekce? Never! class Article { // ... function getComments()

    { return $this->comments; }
  9. Magic to the rescue use Kdyby\Doctrine\Entities\MagicAccessors; class Article { use

    MagicAccessors;
  10. Magic to the rescue - pouze na protected properties -

    magické (get|set)tery - magické collection accessory
  11. Magic to the rescue class Article { private $comments; function

    getComments() { return $this->comments; }
  12. Magic to the rescue class Article { protected $comments;

  13. Magic to the rescue $article->comments ->add(new Comment($article)); // vyhodi vyjimku

    $article->comments ->filter(function () { .. }); // ok
  14. Magic to the rescue class Article { // ... function

    getComments(); function addComment(Comment $comment); function hasComment(Comment $comment); function removeComment(Comment $comment);
  15. Less is more class Comment { private $article; // ...

    function getArticle() { return $this->article; }
  16. Less is more private $published; function isPublished() { return $this->published;

    } function publish() { $this->published = TRUE; }
  17. Tak od teď, databáze už zase existuje

  18. Ž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
  19. Aby entita byla entita - musí mít metadata - její

    NS s driverem musí být registrovaný
  20. Mapping /** @ORM\Entity() */ class Article { use MagicAccessors; use

    Identifier; /** @ORM\Column(type="string") */ protected $title;
  21. Mapping /** * @ORM\OneToMany(targetEntity="Comment", cascade={"persist"}) * @var Comment[]|ArrayCollection */ protected

    $comments;
  22. Zaregistrujeme extensions: console: Kdyby\Console\DI\ConsoleExtension events: Kdyby\Events\DI\EventsExtension annotations: Kdyby\Annotations\DI\AnnotationsExtension doctrine: Kdyby\Doctrine\DI\OrmExtension

  23. Nakonfigurujeme doctrine: metadata: App: %appDir%

  24. 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
  25. Migrace - doctrine/migrations:@dev - zenify/doctrine-migrations extensions: migrations: Zenify\DoctrineMigrations\DI\MigrationsExtension

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

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

  28. Praktické použití entit

  29. V presenterech private $article; public function actionDefault() { $this->article =

    $this->em->find(Article::class, 42); } public function renderDefault() { $this->template->article = $this->article }
  30. 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; }
  31. 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; }
  32. Ve formulářích: alternativně - univerzální formuláře pro create/edit - form

    mappery (Kdyby/DoctrineForms ?)
  33. 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; }
  34. Netriviální dotazování

  35. 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")
  36. DQL & Query builder $qb = $em->createQueryBuilder(); $qb->select('u') ->from(User::class, 'u')

    ->where('u.id = :id')->setParameter('id', 123) ->orderBy('u.name', 'ASC');
  37. DQL & Query builder $query = $qb->getQuery(); /** @var User[]

    $result */ $result = $query->getResult();
  38. 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();
  39. ResultSet: why $paginator = $this['vp']->getPaginator(); $paginator->setItemsCount($articles->countAll()); $this->template->articles = $articles->findAll( $paginator->getOffset(),

    $paginator->getLength() );
  40. 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();
  41. 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());
  42. Ještě složitější (ale mocnější) dotazování

  43. Query Object class QuestionsQuery extends Kdyby\Doctrine\QueryObject { /** * @param

    \Kdyby\Persistence\Queryable $repository * @return \Doctrine\ORM\QueryBuilder */ protected function doCreateQuery(Queryable $repository);
  44. Query Object function inCategory(Category $cat) { $this->filter[] = function (QueryBuilder

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

    $qb) { $qb->addSelect(lp') ->leftJoin('q.lastPost', 'lp'); }; return $this; }
  46. Query Object $query = (new QuestionsQuery()) ->withLastPost() ->inCategory($category); $result =

    $repository->fetch($query);
  47. 1+N problem - optimální množství dat - vysoká komplexita -

    špatná odezva na síti - performance killer
  48. 1+N problem $qb->select('article, comment') ->from(Article::class, 'article') ->leftJoin('article.comments', 'comment')

  49. 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
  50. None
  51. 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
  52. 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; }
  53. Query Object: postFetch $query = (new ArticlesQuery()) ->withComments(); $result =

    $repository->fetch($query);
  54. Repozitáře, fasády, služby aneb pětivrstvý model

  55. Repozitáře - vhodné použití ve facade - vhodné použití v

    services - ale klidně i v presenteru/komponentě - pouze čtení!
  56. Repozitáře /** * @ORM\Entity(repositoryClass="ArticleRepository") */ class Article { } class

    ArticleRepository extends Kdyby\Doctrine\EntityRepository { }
  57. services: - App\Blog\ArticleRepository() - class: App\Blog\ArticleRepository() tags: [doctrine.repositoryEntity: App\Blog\Article] Repozitáře

    jako služby
  58. 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
  59. Facade class BlogFacade { function findPublished(); function fetch(ArticlesQuery $query); function

    saveComment(Comment $comment); class RedactorFacade { function createDraft(); function save(Article $article);
  60. 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
  61. Eventy

  62. Lifecycle eventy - na entitě - “full blown” listenery -

    listenery pro typ
  63. Lifecycle eventy na entitě /** @ORM\Entity @ORM\HasLifecycleCallbacks */ class User

    { /** @ORM\PrePersist */ public function doStuffOnPrePersist() { $this->createdAt = date('Y-m-d H:i:s'); }
  64. 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 } }
  65. Listenery pro typ class MyEventListener { function preUpdate(User $user, LifecycleEventArgs

    $args) { $entityManager = $args->getObjectManager(); // do something with the User }
  66. Cache

  67. Cachovací strategie - result caching - 2nd level caching

  68. Result caching $query = $em->createQuery('SELECT u FROM App\User u'); $query->useResultCache(

    true, $lifetime, $resultCacheId );
  69. 2nd level cache (since 2.5) - READ_ONLY (výchozí) - NOSTRICT_READ_WRITE

    - READ_WRITE (zámky)
  70. 2nd level cache doctrine: secondLevelCache: enabled: true driver: redis(@redis.default_client::getDriver())

  71. 2nd level cache /** * @ORM\Cache(region="product") */ class Product {

    /** * @ORM\ManyToMany(...) * @ORM\Cache(usage="NONSTRICT_READ_WRITE", region="product") */ private $tags;
  72. Shrnutí - jak psát entity - práce s db schématem

    - použití entit - pokládání dotazů - modelové třídy - cache
  73. Možná někdy příště - Zápis do db, čtení z Elasticu

    (to dělá rohlík) - Command Query Responsibility Segregation
  74. Dotazy?

  75. Díky za pozornost! @ProchazkaFilip