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. 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
  2. Asociace mezi entitami class Comment { private $article; public function

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

    __construct() { $this->comments = new ArrayCollection(); } function addComment(Comment $comment) { $this->comments[] = $comment; }
  4. Magic to the rescue - pouze na protected properties -

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

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

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

    function getArticle() { return $this->article; }
  8. Ž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
  9. Aby entita byla entita - musí mít metadata - její

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

    Identifier; /** @ORM\Column(type="string") */ protected $title;
  11. V presenterech private $article; public function actionDefault() { $this->article =

    $this->em->find(Article::class, 42); } public function renderDefault() { $this->template->article = $this->article }
  12. 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; }
  13. 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; }
  14. 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; }
  15. 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")
  16. DQL & Query builder $qb = $em->createQueryBuilder(); $qb->select('u') ->from(User::class, 'u')

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

    $result */ $result = $query->getResult();
  18. 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();
  19. 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();
  20. 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());
  21. Query Object class QuestionsQuery extends Kdyby\Doctrine\QueryObject { /** * @param

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

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

    $qb) { $qb->addSelect(lp') ->leftJoin('q.lastPost', 'lp'); }; return $this; }
  24. 1+N problem - optimální množství dat - vysoká komplexita -

    špatná odezva na síti - performance killer
  25. 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
  26. 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
  27. 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; }
  28. Repozitáře - vhodné použití ve facade - vhodné použití v

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

    ArticleRepository extends Kdyby\Doctrine\EntityRepository { }
  30. 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
  31. Facade class BlogFacade { function findPublished(); function fetch(ArticlesQuery $query); function

    saveComment(Comment $comment); class RedactorFacade { function createDraft(); function save(Article $article);
  32. 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
  33. Lifecycle eventy na entitě /** @ORM\Entity @ORM\HasLifecycleCallbacks */ class User

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

    $args) { $entityManager = $args->getObjectManager(); // do something with the User }
  36. 2nd level cache /** * @ORM\Cache(region="product") */ class Product {

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

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

    (to dělá rohlík) - Command Query Responsibility Segregation