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

Laravel Doctrine

Laravel Doctrine

This presentation was given at the Laravel Austin meetup April 5th, 2018. We took a dive into what Doctrine is, how it works and how it is different from Eloquent.

https://www.meetup.com/Laravel-Austin/events/jrzzbpyxgbhb/

Hunter Skrasek

April 05, 2018
Tweet

More Decks by Hunter Skrasek

Other Decks in Programming

Transcript

  1. What is Doctrine? An object-relational mapper (ORM) for PHP, that

    uses the Data Mapper pattern. Doctrine 2 — Laravel Austin 2
  2. Entities Objects that have a distinct identity, you also hear

    these called reference objects Doctrine 2 — Laravel Austin 3
  3. <?php use Doctrine\ORM\Mapping AS ORM; /** * @ORM\Entity * @ORM\Table(name="articles")

    */ class Article { /** * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") */ protected $id; /** * @ORM\Column(type="string") */ protected $title; public function getId() { return $this->id; } public function getTitle() { return $this->title; } public function setTitle($title) { $this->title = $title; } } 5
  4. # App.Article.dcm.yml App\Article: type: entity table: articles id: id: type:

    integer generator: strategy: AUTO fields: title: type: string 6
  5. // App.Article.dcm.xml <?xml version="1.0" encoding="UTF-8"?> <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">

    <entity name="App\Article" table="articles"> <id name="id" type="integer" column="id"> <generator strategy="AUTO"/> <sequence-generator sequence-name="tablename_seq" allocation-size="100" initial-value="1" /> </id> <field name="title" column="title" type="string" /> </entity> </doctrine-mapping> 7
  6. <?php return [ 'App\Article' => [ 'type' => 'entity', 'table'

    => 'articles', 'id' => [ 'id' => [ 'type' => 'integer', 'generator' => [ 'strategy' => 'auto' ] ], ], 'fields' => [ 'title' => [ 'type' => 'string' ] ] ] ]; 8
  7. <?php class Article { protected $id; protected $title; public static

    function loadMetadata(Doctrine\ORM\Mapping\ClassMetadata $metadata) { $metadata->mapField(array( 'id' => true, 'fieldName' => 'id', 'type' => 'integer' )); $metadata->mapField(array( 'fieldName' => 'title', 'type' => 'string' )); } } 9
  8. Embeddables An embeddable is an object that can be mapped

    to a set of columns in a database table. This object will not be mapped to a table by itself, but will be used inside an entity, so its columns will be mapped to the entity's table instead. Doctrine 2 — Laravel Austin 10
  9. <?php use Doctrine\ORM\Annotation as ORM; /** @ORM\Entity */ class User

    { /** @ORM\Embedded(class = "Address") */ private $address; } /** @ORM\Embeddable */ class Address { /** @ORM\Column(type = "string") */ private $street; /** @ORM\Column(type = "string") */ private $postalCode; /** @ORM\Column(type = "string") */ private $city; /** @ORM\Column(type = "string") */ private $country; } 11
  10. Mapped Superclasses Mapped superclasses allow us to share common state

    between our entities, without it being an entity itself. Doctrine 2 — Laravel Austin 12
  11. Single Table Inheritance An inheritance strategy where all classes are

    mapped to a single database table Doctrine 2 — Laravel Austin 13
  12. <?php namespace MyProject\Model; /** * @Entity * @InheritanceType("SINGLE_TABLE") * @DiscriminatorColumn(name="discr",

    type="string") * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) */ class Person { // ... } /** * @Entity */ class Employee extends Person { // ... } 14
  13. Class Table Inheritance An inheritance strategy where each class is

    mapped to several tables: its own, and the parent tables Doctrine 2 — Laravel Austin 15
  14. <?php namespace MyProject\Model; /** * @Entity * @InheritanceType("JOINED") * @DiscriminatorColumn(name="discr",

    type="string") * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) */ class Person { // ... } /** @Entity */ class Employee extends Person { // ... } Doctrine 2 — Laravel Austin 16
  15. Associations Instead of working with foreign keys, you work with

    references to objects Doctrine 2 — Laravel Austin 17
  16. Many-To-One (Unidirectional) <?php /** @Entity */ class User { //

    ... /** * @ManyToOne(targetEntity="Address") * @JoinColumn(name="address_id", referencedColumnName="id") */ private $address; } /** @Entity */ class Address { // ... } 18
  17. One-To-One (Unidirectional) <?php /** @Entity */ class Product { //

    ... /** * One Product has One Shipment. * @OneToOne(targetEntity="Shipment") * @JoinColumn(name="shipment_id", referencedColumnName="id") */ private $shipment; // ... } /** @Entity */ class Shipment { // ... } 19
  18. One-To-One (Bidirectional) <?php /** @Entity */ class Customer { //

    ... /** * One Customer has One Cart. * @OneToOne(targetEntity="Cart", mappedBy="customer") */ private $cart; // ... } /** @Entity */ class Cart { // ... /** * One Cart has One Customer. * @OneToOne(targetEntity="Customer", inversedBy="cart") * @JoinColumn(name="customer_id", referencedColumnName="id") */ private $customer; // ... } 20
  19. One-To-One (Self-referencing) <?php /** @Entity */ class Student { //

    ... /** * One Student has One Student. * @OneToOne(targetEntity="Student") * @JoinColumn(name="mentor_id", referencedColumnName="id") */ private $mentor; // ... } 21
  20. One-To-Many (Bidirectional) <?php use Doctrine\Common\Collections\ArrayCollection; /** @Entity */ class Product

    { /** * One Product has Many Features. * @OneToMany(targetEntity="Feature", mappedBy="product") */ private $features; } /** @Entity */ class Feature { /** * Many Features have One Product. * @ManyToOne(targetEntity="Product", inversedBy="features") * @JoinColumn(name="product_id", referencedColumnName="id") */ private $product; } 22
  21. One-To-Many (Unidirectional with Join Table) <?php /** @Entity */ class

    User { /** * Many User have Many Phonenumbers. * @ManyToMany(targetEntity="Phonenumber") * @JoinTable(name="users_phonenumbers", * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, * inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)} * ) */ private $phonenumbers; } /** @Entity */ class Phonenumber { // ... } 23
  22. One-To-Many (Self-referencing) <?php /** @Entity */ class Category { /**

    * One Category has Many Categories. * @OneToMany(targetEntity="Category", mappedBy="parent") */ private $children; /** * Many Categories have One Category. * @ManyToOne(targetEntity="Category", inversedBy="children") * @JoinColumn(name="parent_id", referencedColumnName="id") */ private $parent; } 24
  23. Many-To-Many (Unidirectional) <?php /** @Entity */ class User { /**

    * Many Users have Many Groups. * @ManyToMany(targetEntity="Group") * @JoinTable(name="users_groups", * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} * ) */ private $groups; } /** @Entity */ class Group { // ... } 25
  24. Many-To-Many (Bidirectional) <?php /** @Entity */ class User { /**

    * Many Users have Many Groups. * @ManyToMany(targetEntity="Group", inversedBy="users") * @JoinTable(name="users_groups") */ private $groups; } /** @Entity */ class Group { /** * Many Groups have Many Users. * @ManyToMany(targetEntity="User", mappedBy="groups") */ private $users; } 26
  25. Many-To-Many (Self-referencing) <?php /** @Entity */ class User { /**

    * Many Users have Many Users. * @ManyToMany(targetEntity="User", mappedBy="myFriends") */ private $friendsWithMe; /** * Many Users have many Users. * @ManyToMany(targetEntity="User", inversedBy="friendsWithMe") * @JoinTable(name="friends", * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, * inverseJoinColumns={@JoinColumn(name="friend_user_id", referencedColumnName="id")} * ) */ private $myFriends; } 27
  26. Finding Entities $article = EntityManager::find('App\Article', 1); $article->setTitle('Different title'); $article2 =

    EntityManager::find('App\Article', 1); if ($article === $article2) { echo "Yes we are the same!"; } Doctrine 2 — Laravel Austin 29
  27. Flushing // Flush all changes EntityManager::flush(); // Only flush changes

    of given entity EntityManager::flush($article); Doctrine 2 — Laravel Austin 31
  28. Reusing through Inheritance <?php use Doctrine\ORM\EntityRepository; class DoctrineScientistRepository extends EntityRepository

    implements ScientistRepository { // public function find($id) already implemented in parent class! public function findByName($name) { return $this->findBy(['name' => $name]); } } // Then, in one of your ServiceProviders use App\Research\Scientist; class AppServiceProvider { public function register() { $this->app->bind(ScientistRepository::class, function($app) { // This is what Doctrine's EntityRepository needs in its constructor. return new DoctrineScientistRepository( $app['em'], $app['em']->getClassMetaData(Scientist::class) ); }); } } 35
  29. Reusing through Composition <?php use Doctrine\Common\Persistence\ObjectRepository; class DoctrineScientistRepository implements ScientistRepository

    { private $genericRepository; public function __construct(ObjectRepository $genericRepository) { $this->genericRepository = $genericRepository; } public function find($id) { return $this->genericRepository->find($id); } public function findByName($name) { return $this->genericRepository->findBy(['name' => $name]); } } // Then, in one of your ServiceProviders use App\Research\Scientist; class AppServiceProvider { public function register() { $this->app->bind(ScientistRepository::class, function(){ return new DoctrineScientistRepository( EntityManager::getRepository(Scientist::class) ); }); } } 36
  30. Using your custom repository <?php namespace MyDomain\Model; use Doctrine\ORM\EntityRepository; use

    Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity(repositoryClass="MyDomain\Model\UserRepository") */ class User { } class UserRepository extends EntityRepository { public function getAllAdminUsers() { return $this->em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"') ->getResult(); } } $admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers(); Doctrine 2 — Laravel Austin 37
  31. DQL SELECT clause <?php $query = $em->createQuery('SELECT u FROM MyProject\Model\User

    u WHERE u.age > :age'); $query->setParameter('age', 20); $users = $query->getResult(); http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html 39
  32. Joins <?php $query = $em->createQuery("SELECT u FROM User u JOIN

    u.address a WHERE a.city = 'Berlin'"); $query = $em->createQuery("SELECT u, a FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); $users = $query->getResult(); http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html 40
  33. Query Builder An API for conditionally constructing a DQL query

    http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html 41
  34. High level API methods <?php // $qb instanceof QueryBuilder $qb->select('u,

    a') // string 'u' is converted to array internally ->from('User', 'u') ->join('u.address', 'a') ->where($qb->expr()->eq('a.city', '?1')) ->orderBy('u.surname', 'ASC') ->setParameter(1, 'Berlin'); Doctrine 2 — Laravel Austin 42
  35. Native SQL Execute native SELECT SQL, mapping the results to

    Doctrine entities http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/native-sql.html 43
  36. ResultSetMapping Example <?php // Equivalent DQL query: "select u from

    User u join u.address a WHERE u.name = ?1" // User owns association to an Address and the Address is loaded in the query. $rsm = new ResultSetMapping; $rsm->addEntityResult('User', 'u'); $rsm->addFieldResult('u', 'id', 'id'); $rsm->addFieldResult('u', 'name', 'name'); $rsm->addJoinedEntityResult('Address' , 'a', 'u', 'address'); $rsm->addFieldResult('a', 'address_id', 'id'); $rsm->addFieldResult('a', 'street', 'street'); $rsm->addFieldResult('a', 'city', 'city'); $sql = 'SELECT u.id, u.name, a.id AS address_id, a.street, a.city FROM users u ' . 'INNER JOIN address a ON u.address_id = a.id WHERE u.name = ?'; $query = $this->em->createNativeQuery($sql, $rsm); $query->setParameter(1, 'romanb'); $users = $query->getResult(); http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/native-sql.html 44
  37. Other Topics — Proxy objects — Reference proxies — Association

    proxies — Read-Only entities — Extra-Lazy collections Doctrine 2 — Laravel Austin 45
  38. Best Practices — Constrain relationships — Use cascades judiciously —

    Initialize collections in the constructor — Don't map foreign keys to fields — Explicit transaction demarcation Doctrine 2 — Laravel Austin 46