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

Patterns Behind Doctrine

Patterns Behind Doctrine

How does Doctrine talk to your database? What are Data Mapper, Unit Of Work, and Identity Map? These are the questions I want to answer in this talk. We will look at how Doctrine ORM implements them and what they are there for. Finally we will look at how they compare to Active Record and what the benefits and drawbacks are to help you choose which one fits your needs best.

Denis Brumann

December 06, 2018
Tweet

More Decks by Denis Brumann

Other Decks in Programming

Transcript

  1. THE PATTERNS BEHIND
    DOCTRINE
    Denis Brumann

    View Slide

  2. Denis Brumann
    [email protected]
    @dbrumann
    Software Developer
    Berlin, Germany

    View Slide

  3. View Slide

  4. View Slide

  5. namespace App\Entity;
    use Doctrine\ORM\Mapping as ORM;
    /**
    * @ORM\Entity(repositoryClass="App\Repository\OrderItemRepository")
    * @ORM\Table(name="app_order_item")
    */
    class OrderItem
    {
    /**
    * @ORM\Id()
    * @ORM\Column(type="integer")
    * @ORM\GeneratedValue(strategy="AUTO")
    */
    private $id;
    /**
    * @ORM\ManyToOne(targetEntity="App\Entity\Order", inversedBy="items")
    * @ORM\JoinColumn(nullable=false)

    View Slide

  6. namespace App\Entity;
    use Doctrine\ORM\Mapping as ORM;
    /**
    * @ORM\Entity(repositoryClass="App\Repository\OrderItemRepository")
    * @ORM\Table(name="app_order_item")
    */
    class OrderItem
    {
    /**
    * @ORM\Id()
    * @ORM\Column(type="integer")
    * @ORM\GeneratedValue(strategy="AUTO")
    */
    private $id;
    /**
    * @ORM\ManyToOne(targetEntity="App\Entity\Order", inversedBy="items")
    * @ORM\JoinColumn(nullable=false)

    View Slide

  7. /**
    * @ORM\ManyToOne(targetEntity="App\Entity\Order", inversedBy="items")
    * @ORM\JoinColumn(nullable=false)
    */
    private $order;
    /**
    * @ORM\Column()
    */
    private $name;
    /**
    * @ORM\Column(length=16)
    */
    private $price;
    public function getId(): int
    {
    return $this->id ?? 0;
    }
    public function getOrder(): Order

    View Slide

  8. namespace App\Entity;
    use DateTimeImmutable;
    use Doctrine\Common\Collections\ArrayCollection;
    use Doctrine\ORM\Mapping as ORM;
    /**
    * @ORM\Entity(repositoryClass="App\Repository\OrderRepository")
    * @ORM\Table(name="app_order")
    */
    class Order
    {
    /**
    * @ORM\Id()
    * @ORM\Column(type="integer")
    * @ORM\GeneratedValue(strategy="AUTO")
    */
    private $id;
    /**
    * @ORM\OneToMany(targetEntity="App\Entity\OrderItem", mappedBy="order", cascade={"persist"})
    */
    private $items;
    /**

    View Slide

  9. namespace App\Entity;
    use DateTimeImmutable;
    use Doctrine\Common\Collections\ArrayCollection;
    use Doctrine\ORM\Mapping as ORM;
    /**
    * @ORM\Entity(repositoryClass="App\Repository\OrderRepository")
    * @ORM\Table(name="app_order")
    */
    class Order
    {
    /**
    * @ORM\Id()
    * @ORM\Column(type="integer")
    * @ORM\GeneratedValue(strategy="AUTO")
    */
    private $id;
    /**
    * @ORM\OneToMany(targetEntity="App\Entity\OrderItem", mappedBy="order", cascade={"persist"})
    */
    private $items;
    /**

    View Slide

  10. namespace App\Entity;
    use DateTimeImmutable;
    use Doctrine\Common\Collections\ArrayCollection;
    use Doctrine\ORM\Mapping as ORM;
    /**
    * @ORM\Entity(repositoryClass="App\Repository\OrderRepository")
    * @ORM\Table(name="app_order")
    */
    class Order
    {
    /**
    * @ORM\Id()
    * @ORM\Column(type="integer")
    * @ORM\GeneratedValue(strategy="AUTO")
    */
    private $id;
    /**
    * @ORM\OneToMany(targetEntity="App\Entity\OrderItem", mappedBy="order", cascade={"persist"})
    */
    private $items;
    /**

    View Slide

  11. /**
    * @ORM\Column(type="datetime_immutable")
    */
    private $createdOn;
    public function __construct()
    {
    $this->items = new ArrayCollection();
    $this->createdOn = new DateTimeImmutable();
    }
    public function getId(): int
    {
    return $this->id ?? 0;
    }
    public function getCreatedOn(): DateTimeImmutable
    {
    return $this->createdOn;
    }
    public function getItems(): array
    {
    return $this->items->toArray();
    }
    public function addItem(OrderItem $item): void
    {

    View Slide

  12. /**
    * @ORM\Column(type="datetime_immutable")
    */
    private $createdOn;
    public function __construct()
    {
    $this->items = new ArrayCollection();
    $this->createdOn = new DateTimeImmutable();
    }
    public function getId(): int
    {
    return $this->id ?? 0;
    }
    public function getCreatedOn(): DateTimeImmutable
    {
    return $this->createdOn;
    }
    public function getItems(): array
    {
    return $this->items->toArray();
    }
    public function addItem(OrderItem $item): void
    {

    View Slide

  13. }
    public function addItem(OrderItem $item): void
    {
    $this->items->add($item);
    $item->setOrder($this);
    }
    public function removeItem(OrderItem $item): void
    {
    $this->items->remove($item);
    }
    public function getItemCount(): int
    {
    return $this->items->count();
    }
    public function getTotal(): string
    {
    $total = '0';
    foreach ($this->items as $item) {
    $total = bcadd($total, $item->getPrice());
    }
    return $total;
    }
    }

    View Slide

  14. }
    public function addItem(OrderItem $item): void
    {
    $this->items->add($item);
    $item->setOrder($this);
    }
    public function removeItem(OrderItem $item): void
    {
    $this->items->remove($item);
    }
    public function getItemCount(): int
    {
    return $this->items->count();
    }
    public function getTotal(): string
    {
    $total = '0';
    foreach ($this->items as $item) {
    $total = bcadd($total, $item->getPrice());
    }
    return $total;
    }
    }

    View Slide

  15. INSERT

    View Slide

  16. namespace App\Order;
    use App\Entity\Order;
    use App\Entity\OrderItem;
    use Doctrine\ORM\EntityManagerInterface;
    class Checkout
    {
    private $manager;
    public function __construct(EntityManagerInterface $manager)
    {
    $this->manager = $manager;
    }
    public function checkout(array $cartItems): void
    {
    $order = new Order();
    foreach ($cartItems as $cartItem) {
    $orderItem = new OrderItem();

    View Slide

  17. public function checkout(array $cartItems): void
    {
    $order = new Order();
    foreach ($cartItems as $cartItem) {
    $orderItem = new OrderItem();
    $orderItem->setName($cartItem->getName());
    $orderItem->setPrice($cartItem->getPrice());
    $order->addItem($orderItem);
    }
    $this->manager->persist($order);
    $this->manager->flush();
    }

    View Slide

  18. HOW DOES DOCTRINE
    KNOW WHAT TO SAVE?
    We only called persist on our order,

    not on the items.

    View Slide

  19. Maintains a list of objects
    affected by a business
    transaction and coordinates
    the writing out of changes
    and the resolution of
    concurrency problems.
    UNIT OF WORK

    View Slide

  20. View Slide

  21. View Slide

  22. View Slide

  23. View Slide

  24. namespace App\Entity;
    use DateTimeImmutable;
    use Doctrine\Common\Collections\ArrayCollection;
    use Doctrine\ORM\Mapping as ORM;
    /**
    * @ORM\Entity(repositoryClass="App\Repository\OrderRepository")
    * @ORM\Table(name="app_order")
    */
    class Order
    {
    /**
    * @ORM\Id()
    * @ORM\Column(type="integer")
    * @ORM\GeneratedValue(strategy="AUTO")
    */
    private $id;
    /**
    * @ORM\OneToMany(targetEntity="App\Entity\OrderItem", mappedBy="order", cascade={"persist"})
    */
    private $items;
    /**

    View Slide

  25. View Slide

  26. View Slide

  27. public function checkout(array $cartItems): void
    {
    $order = new Order();
    foreach ($cartItems as $cartItem) {
    $orderItem = new OrderItem();
    $orderItem->setName($cartItem->getName());
    $orderItem->setPrice($cartItem->getPrice());
    $order->addItem($orderItem);
    $this->manager->persist($orderItem);
    }
    $this->manager->persist($order);
    $this->manager->flush();
    }

    View Slide

  28. View Slide

  29. Doctrine ORM recognizes
    unintentionally non-persisted
    entities.
    You have to add cascade to the
    association or persist each entity
    manually.
    Only entities managed by the
    UnitOfWork will be inserted.

    View Slide

  30. View Slide

  31. $item = new OrderItem();
    $item->setName('Socks');
    $item->setPrice('999');
    $manager->persist($item);
    $order = new Order();
    $order->addItem($item);
    $manager->persist($order);
    $manager->flush();

    View Slide

  32. $item = new OrderItem();
    $item->setName('Socks');
    $item->setPrice('999');
    $manager->persist($item);
    $order = new Order();
    $order->addItem($item);
    $manager->persist($order);
    $manager->flush();

    View Slide

  33. $item = new OrderItem();
    $item->setName('Socks');
    $item->setPrice('999');
    $manager->persist($item);
    $order = new Order();
    $order->addItem($item);
    $manager->persist($order);
    $manager->flush();

    View Slide

  34. UnitOfWork will take care of
    correctly ordering each commit.
    You can persist your entities in
    any order you like.

    View Slide

  35. FIND

    View Slide

  36. View Slide

  37. {% extends "base.html.twig" %}
    {% block body %}
    Order list

    Create sample orders
    Truncate orders


    {% for order in orders %}


    Order number #{{ order.id }}
    {{ order.createdOn|date('d.m.Y') }}

    contains {{ order.items|length }} items for a total of
    {{ order.total / 100 }}€.

    {% endfor %}

    {% endblock %}

    View Slide

  38. {% extends "base.html.twig" %}
    {% block body %}
    Order list

    Create sample orders
    Truncate orders


    {% for order in orders %}


    Order number #{{ order.id }}
    {{ order.createdOn|date('d.m.Y') }}

    contains {{ order.items|length }} items for a total of
    {{ order.total / 100 }}€.

    {% endfor %}

    {% endblock %}

    View Slide

  39. View Slide

  40. View Slide

  41. LAZY LOADING

    View Slide

  42. View Slide

  43. View Slide

  44. View Slide

  45. View Slide

  46. View Slide

  47. namespace Proxies\__CG__\App\Entity;
    /**
    * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR
    */
    class Order extends \App\Entity\Order implements \Doctrine\ORM\Proxy\Proxy
    {
    /**
    * @var \Closure the callback responsible for loading properties in the
    proxy object. This callback is called with
    * three parameters, being respectively the proxy object to be
    initialized, the method that triggered the
    * initialization process and an array of ordered parameters that were
    passed to that method.
    *
    * @see \Doctrine\Common\Persistence\Proxy::__setInitializer
    */
    public $__initializer__;
    /**

    View Slide

  48. 'getCreatedOn', []);
    return parent::getCreatedOn();
    }
    /**
    * {@inheritDoc}
    */
    public function getItems(): array
    {
    $this->__initializer__ && $this->__initializer__->__invoke($this,
    'getItems', []);
    return parent::getItems();
    }
    /**
    * {@inheritDoc}
    */
    public function addItem(\App\Entity\OrderItem $item): void
    {

    View Slide

  49. View Slide

  50. FETCH MODES

    View Slide

  51. /**
    * @ORM\Entity(repositoryClass="App\Repository\OrderRepository")
    * @ORM\Table(name="app_order")
    */
    class Order
    {
    /**
    * @ORM\Id()
    * @ORM\Column(type="integer")
    * @ORM\GeneratedValue(strategy="AUTO")
    */
    private $id;
    /**
    * @ORM\OneToMany(
    * targetEntity="App\Entity\OrderItem",
    * mappedBy="order",
    * cascade={"persist"},
    * fetch="EAGER"
    * )
    */
    private $items;

    View Slide

  52. public function findOrderWithItems(int $id): Order
    {
    return $this->createQueryBuilder('o')
    ->select(['o', 'i'])
    ->where('o.id = :order_id')
    ->setParameter('order_id', $id)
    ->join('o.items', 'i')
    ->getQuery()
    ->getSingleResult();
    }

    View Slide

  53. public function findOrderWithItems(int $id): Order
    {
    return $this->createQueryBuilder('o')
    ->select(['o', 'i'])
    ->where('o.id = :order_id')
    ->setParameter('order_id', $id)
    ->join('o.items', 'i')
    ->getQuery()
    ->getSingleResult();
    }

    View Slide

  54. public function findOrderWithItems(int $id): Order
    {
    return $this->createQueryBuilder('o')
    ->select(['o', 'i'])
    ->where('o.id = :order_id')
    ->setParameter('order_id', $id)
    ->join('o.items', 'i')
    ->getQuery()
    ->getSingleResult();
    }

    View Slide

  55. CUSTOM OBJECT
    HYDRATION

    View Slide

  56. namespace App\DTO;
    use DateTimeImmutable;
    final class OrderSummary
    {
    private $orderId;
    private $orderDate;
    private $itemCount;
    private $total;
    public function __construct(int $orderId, DateTimeImmutable $orderDate, int $itemCount, int $total)
    {
    $this->orderId = $orderId;
    $this->orderDate = $orderDate;
    $this->itemCount = $itemCount;
    $this->total = $total;
    }
    public function getOrderId(): int
    {
    return $this->orderId;
    }
    public function getOrderDate(): DateTimeImmutable
    {
    return $this->orderDate;
    }

    View Slide

  57. {
    $this->orderId = $orderId;
    $this->orderDate = $orderDate;
    $this->itemCount = $itemCount;
    $this->total = $total;
    }
    public function getOrderId(): int
    {
    return $this->orderId;
    }
    public function getOrderDate(): DateTimeImmutable
    {
    return $this->orderDate;
    }
    public function getItemCount(): int
    {
    return $this->itemCount;
    }
    public function getTotal(): int
    {
    return $this->total;
    }
    }

    View Slide

  58. public function findSummarizedOrders(): array
    {
    $dql = 'SELECT NEW App\\DTO\\OrderSummary( '
    . 'o.id, o.createdOn, COUNT(i.order), SUM(i.price)'
    . ') FROM App\\Entity\\Order o '
    . 'JOIN o.items i GROUP BY i.order';
    return $this->getEntityManager()
    ->createQuery($dql)
    ->getResult();
    }

    View Slide

  59. SELECT
    o.id,
    o.created_on,
    COUNT(i.order_id),
    SUM(i.price)
    FROM
    app_order AS o
    JOIN
    app_order_item i on o.id = i.order_id
    GROUP BY
    o.id

    View Slide

  60. View Slide

  61. View Slide

  62. public function findMostExpensiveOrder(): ?Order
    {
    $sql = <<SELECT o.*, SUM(i.price) AS order_sum
    FROM app_order AS o
    JOIN app_order_item i ON o.id = i.order_id
    GROUP BY i.order_id
    ORDER BY order_sum DESC LIMIT 1;
    SQL;
    $resultSetMapping = new ResultSetMappingBuilder($this->getEntityManager());
    $resultSetMapping->addRootEntityFromClassMetadata(Order::class, 'o');
    return $this->getEntityManager()->createNativeQuery($sql, $resultSetMapping)->getOneOrNullResult();
    }

    View Slide

  63. Doctrine loads associated
    entities lazily by default.
    Writing custom queries with
    JOIN, hydrate results into
    custom DTO or as scalar results
    can improve read-performance.

    View Slide

  64. IDENTITY MAP

    View Slide

  65. View Slide

  66. $order = $this->em->find(Order::class, 1);
    dump($this->em->getUnitOfWork()->getIdentityMap());
    $sameOrder = $this->em->find(Order::class, 1);
    dump($order === $sameOrder);

    View Slide

  67. $order = $this->em->find(Order::class, 1);
    dump($this->em->getUnitOfWork()->getIdentityMap());
    $sameOrder = $this->em->find(Order::class, 1);
    dump($order === $sameOrder);

    View Slide

  68. $order = $this->em->find(Order::class, 1);
    dump($this->em->getUnitOfWork()->getIdentityMap());
    $sameOrder = $this->em->find(Order::class, 1);
    dump($order === $sameOrder);

    View Slide

  69. View Slide

  70. View Slide

  71. View Slide

  72. View Slide

  73. Doctrine stores previously loaded
    entities in an IdentityMap.
    Loading an entity multiple times
    does only perform 1 database
    query (in some special cases).
    Each new find() returns the current
    entity (with its unsaved changes).

    View Slide

  74. UPDATE

    View Slide

  75. $item = new OrderItem();
    $item->setName('Coffee');
    $item->setPrice('149');
    $order = new Order();
    $order->setId(1);
    $order->addItem($item);
    $this->manager->flush();

    View Slide

  76. $item = new OrderItem();
    $item->setName('Coffee');
    $item->setPrice('149');
    $order = new Order();
    $order->setId(1);
    $order->addItem($item);
    $this->manager->flush();
    WILL THIS WORK?

    View Slide

  77. View Slide

  78. View Slide

  79. Entity States
    A NEW entity instance has no persistent identity, and is
    not yet associated with an EntityManager and a
    UnitOfWork (i.e. those just created with the "new"
    operator).

    View Slide

  80. Entity States
    A MANAGED entity instance is an instance with a
    persistent identity that is associated with an
    EntityManager and whose persistence is thus managed.

    View Slide

  81. Entity States
    A DETACHED entity instance is an instance with a
    persistent identity that is not (or no longer) associated
    with an EntityManager and a UnitOfWork.

    View Slide

  82. Entity States
    A REMOVED entity instance is an instance with a
    persistent identity, associated with an EntityManager,
    that will be removed from the database upon
    transaction commit.

    View Slide

  83. /**
    * An entity is in MANAGED state when its persistence is managed by an EntityManager.
    */
    const STATE_MANAGED = 1;
    /**
    * An entity is new if it has just been instantiated (i.e. using the "new" operator)
    * and is not (yet) managed by an EntityManager.
    */
    const STATE_NEW = 2;
    /**
    * A detached entity is an instance with persistent state and identity that is not
    * (or no longer) associated with an EntityManager (and a UnitOfWork).
    */
    const STATE_DETACHED = 3;
    /**
    * A removed entity instance is an instance with a persistent identity,
    * associated with an EntityManager, whose persistent state will be deleted
    * on commit.
    */
    const STATE_REMOVED = 4;

    View Slide

  84. $item = new OrderItem();
    $item->setName('Coffee');
    $item->setPrice('149');
    $order = new Order();
    $order->setId(1);
    $order->addItem($item);
    $this->manager->persist($order);
    $this->manager->flush();

    View Slide

  85. $item = new OrderItem();
    $item->setName('Coffee');
    $item->setPrice('149');
    $order = $this->entityManager

    ->find(Order::class, $id);
    $order->addItem($item);
    $this->manager->persist($order);
    $this->manager->flush();

    View Slide

  86. $item = new OrderItem();
    $item->setName('Coffee');
    $item->setPrice('149');
    $order = $this->entityManager

    ->find(Order::class, $id);
    $order->addItem($item);
    $this->manager->persist($order);
    $this->manager->flush();

    View Slide

  87. $item = new OrderItem();
    $item->setName('Coffee');
    $item->setPrice('149');
    $order = $this->entityManager

    ->find(Order::class, $id);
    $order->addItem($item);
    $this->manager->persist($order);
    $this->manager->flush();

    View Slide

  88. $repository = $this->entityManager->getRepository(Order::class);
    $order = $repository->findOrderWithItems($id);
    $items = $order->getItems();
    $randomItem = $items[random_int(0, count($items) - 1)];
    $randomItem->setPrice('1');
    $this->entityManager->flush();

    View Slide

  89. View Slide

  90. View Slide

  91. View Slide

  92. {
    "00000000072a344b000000003da8d2f1": {
    "price": ["119","1"]
    }
    }

    View Slide

  93. $oid = spl_object_hash($entity)

    View Slide

  94. $oid = spl_object_hash($entity)
    This function returns a unique identifier for the
    object. This id can be used as a hash key for storing
    objects, or for identifying an object, as long as the
    object is not destroyed. Once the object is destroyed,
    its hash may be reused for other objects.

    View Slide

  95. $oid = "00000000600cd8330000000053755686"

    View Slide

  96. Doctrine internally references
    objects through their object
    hash, instead of their id.
    Simply adding an id to an entity
    will perform an insert, not an
    update.

    View Slide

  97. ACTIVE RECORD

    View Slide

  98. An object that wraps a row in a database table or view,
    encapsulates the database access, and adds domain logic
    on that data.
    https://www.martinfowler.com/eaaCatalog/activeRecord.html

    View Slide

  99. Models allow you to query for data in your tables,
    as well as insert new records into the table.

    View Slide

  100. /**
    * Doctrine_Record
    * All record classes should inherit this super class
    *
    * @package Doctrine
    * @subpackage Record
    * @author Konsta Vesterinen
    * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
    * @link www.doctrine-project.org
    * @since 1.0
    * @version $Revision: 7673 $
    */
    abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Countable, IteratorAggregate,
    Serializable
    {
    /**
    * STATE CONSTANTS
    */
    /**
    * DIRTY STATE
    * a Doctrine_Record is in dirty state when its properties are changed
    */
    const STATE_DIRTY = 1;
    /**
    * TDIRTY STATE
    * a Doctrine_Record is in transient dirty state when it is created
    * and some of its fields are modified but it is NOT yet persisted into database

    View Slide

  101. * TDIRTY STATE
    * a Doctrine_Record is in transient dirty state when it is created
    * and some of its fields are modified but it is NOT yet persisted into database
    */
    const STATE_TDIRTY = 2;
    /**
    * CLEAN STATE
    * a Doctrine_Record is in clean state when all of its properties are loaded from the database
    * and none of its properties are changed
    */
    const STATE_CLEAN = 3;
    /**
    * PROXY STATE
    * a Doctrine_Record is in proxy state when its properties are not fully loaded
    */
    const STATE_PROXY = 4;
    /**
    * NEW TCLEAN
    * a Doctrine_Record is in transient clean state when it is created and none of its fields are
    modified
    */
    const STATE_TCLEAN = 5;
    /**
    * LOCKED STATE
    * a Doctrine_Record is temporarily locked during deletes and saves
    *

    View Slide

  102. * TDIRTY STATE
    * a Doctrine_Record is in transient dirty state when it is created
    * and some of its fields are modified but it is NOT yet persisted into database
    */
    const STATE_TDIRTY = 2;
    /**
    * CLEAN STATE
    * a Doctrine_Record is in clean state when all of its properties are loaded from the database
    * and none of its properties are changed
    */
    const STATE_CLEAN = 3;
    /**
    * PROXY STATE
    * a Doctrine_Record is in proxy state when its properties are not fully loaded
    */
    const STATE_PROXY = 4;
    /**
    * NEW TCLEAN
    * a Doctrine_Record is in transient clean state when it is created and none of its fields are
    modified
    */
    const STATE_TCLEAN = 5;
    /**
    * LOCKED STATE
    * a Doctrine_Record is temporarily locked during deletes and saves
    *

    View Slide

  103. * TDIRTY STATE
    * a Doctrine_Record is in transient dirty state when it is created
    * and some of its fields are modified but it is NOT yet persisted into database
    */
    const STATE_TDIRTY = 2;
    /**
    * CLEAN STATE
    * a Doctrine_Record is in clean state when all of its properties are loaded from the database
    * and none of its properties are changed
    */
    const STATE_CLEAN = 3;
    /**
    * PROXY STATE
    * a Doctrine_Record is in proxy state when its properties are not fully loaded
    */
    const STATE_PROXY = 4;
    /**
    * NEW TCLEAN
    * a Doctrine_Record is in transient clean state when it is created and none of its fields are
    modified
    */
    const STATE_TCLEAN = 5;
    /**
    * LOCKED STATE
    * a Doctrine_Record is temporarily locked during deletes and saves
    *

    View Slide

  104. $item = new OrderItem();
    $item->setName('Coffee');
    $item->setPrice('149');

    $order = new Order();
    $order->addItem($item);
    $this->manager->persist($order);
    $this->manager->flush();

    View Slide

  105. $item = new OrderItem();
    $item->setName('Coffee');
    $item->setPrice('149');

    $order = new Order();
    $order->addItem($item);
    $this->manager->persist($order);
    $this->manager->flush();

    View Slide

  106. $item = new OrderItem();
    $item->setName('Coffee');
    $item->setPrice('149');

    $order = new Order();
    $order->addItem($item);
    $order->save();

    View Slide

  107. $item = new OrderItem();
    $item->setName('Coffee');
    $item->setPrice('149');
    $order = $this->entityManager

    ->find(Order::class, $id);
    $order->addItem($item);
    $this->manager->flush();

    View Slide

  108. $item = new OrderItem();
    $item->setName('Coffee');
    $item->setPrice('149');
    $order = $this->entityManager

    ->find(Order::class, $id);
    $order->addItem($item);
    $this->manager->flush();

    View Slide

  109. $item = new OrderItem();
    $item->setName('Coffee');
    $item->setPrice('149');
    $order = Order::find($id);
    $order->addItem($item);
    $this->manager->flush();

    View Slide

  110. $item = new OrderItem();
    $item->setName('Coffee');
    $item->setPrice('149');
    $order = Order::find($id);
    $order->addItem($item);
    $this->manager->flush();

    View Slide

  111. $item = new OrderItem();
    $item->setName('Coffee');
    $item->setPrice('149');
    $order = Order::find($id);
    $order->addItem($item);
    $order->save();

    View Slide

  112. Data Mapper Active Record
    logic & data is split up into
    multiple classes:

    Entity Manager, UnitOfWork, Repository
    carries both data and
    persistence logic:

    Base Model
    explicit configuration implicit configuration

    (convention over configuration)
    more code to write less code to write
    Model can be independent
    from ORM
    places restrictions on model

    no private properties, inherit __construct

    View Slide

  113. UnitOfWork controls entity
    states, commit order, what
    to write and how to write
    (insert/update).
    Summary

    View Slide

  114. Proxy allows to lazily load
    associated entities, only
    when they are needed.
    Summary

    View Slide

  115. IdentityMap prevents
    EntityManager from
    performing the same
    SELECT-query multiple
    times.
    Summary

    View Slide