$30 off During Our Annual Pro Sale. View Details »

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. Doctrine 2
    Doctrine 2 — Laravel Austin 1

    View Slide

  2. What is Doctrine?
    An object-relational mapper (ORM) for PHP, that uses
    the Data Mapper pattern.
    Doctrine 2 — Laravel Austin 2

    View Slide

  3. Entities
    Objects that have a distinct identity, you also hear these
    called reference objects
    Doctrine 2 — Laravel Austin 3

    View Slide

  4. Meta Data
    Doctrine 2 — Laravel Austin 4

    View Slide

  5. 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

    View Slide

  6. # App.Article.dcm.yml
    App\Article:
    type: entity
    table: articles
    id:
    id:
    type: integer
    generator:
    strategy: AUTO
    fields:
    title:
    type: string
    6

    View Slide

  7. // App.Article.dcm.xml

    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">








    7

    View Slide

  8. return [
    'App\Article' => [
    'type' => 'entity',
    'table' => 'articles',
    'id' => [
    'id' => [
    'type' => 'integer',
    'generator' => [
    'strategy' => 'auto'
    ]
    ],
    ],
    'fields' => [
    'title' => [
    'type' => 'string'
    ]
    ]
    ]
    ];
    8

    View Slide

  9. 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

    View Slide

  10. 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

    View Slide

  11. 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

    View Slide

  12. Mapped Superclasses
    Mapped superclasses allow us to share common state
    between our entities, without it being an entity itself.
    Doctrine 2 — Laravel Austin 12

    View Slide

  13. Single Table Inheritance
    An inheritance strategy where all classes are mapped to
    a single database table
    Doctrine 2 — Laravel Austin 13

    View Slide

  14. 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

    View Slide

  15. 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

    View Slide

  16. 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

    View Slide

  17. Associations
    Instead of working with foreign keys, you work with
    references to objects
    Doctrine 2 — Laravel Austin 17

    View Slide

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

    View Slide

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

    View Slide

  20. One-To-One (Bidirectional)
    /** @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

    View Slide

  21. One-To-One (Self-referencing)
    /** @Entity */
    class Student
    {
    // ...
    /**
    * One Student has One Student.
    * @OneToOne(targetEntity="Student")
    * @JoinColumn(name="mentor_id", referencedColumnName="id")
    */
    private $mentor;
    // ...
    }
    21

    View Slide

  22. One-To-Many (Bidirectional)
    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

    View Slide

  23. One-To-Many (Unidirectional with Join Table)
    /** @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

    View Slide

  24. One-To-Many (Self-referencing)
    /** @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

    View Slide

  25. Many-To-Many (Unidirectional)
    /** @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

    View Slide

  26. Many-To-Many (Bidirectional)
    /** @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

    View Slide

  27. Many-To-Many (Self-referencing)
    /** @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

    View Slide

  28. Entity Manager
    The central access point to ORM functionality
    Doctrine 2 — Laravel Austin 28

    View Slide

  29. 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

    View Slide

  30. Persisting
    $article = new Article;
    $article->setTitle('Let\'s learn about persisting');
    EntityManager::persist($article);
    EntityManager::flush();
    Doctrine 2 — Laravel Austin 30

    View Slide

  31. Flushing
    // Flush all changes
    EntityManager::flush();
    // Only flush changes of given entity
    EntityManager::flush($article);
    Doctrine 2 — Laravel Austin 31

    View Slide

  32. Removing
    EntityManager::remove($article);
    EntityManager::flush();
    Doctrine 2 — Laravel Austin 32

    View Slide

  33. Repositories
    Design pattern abstraction for your persistence layer
    Doctrine 2 — Laravel Austin 33

    View Slide

  34. Ge!ing a Repository
    $repository = EntityManager::getRepository(Scientist::class);
    Doctrine 2 — Laravel Austin 34

    View Slide

  35. Reusing through Inheritance
    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

    View Slide

  36. Reusing through Composition
    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

    View Slide

  37. Using your custom repository
    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

    View Slide

  38. DQL
    Doctrine Query Language, an Object Query Language
    http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html 38

    View Slide

  39. DQL SELECT clause
    $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

    View Slide

  40. Joins
    $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

    View Slide

  41. 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

    View Slide

  42. High level API methods
    // $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

    View Slide

  43. 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

    View Slide

  44. ResultSetMapping Example
    // 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

    View Slide

  45. Other Topics
    — Proxy objects
    — Reference proxies
    — Association proxies
    — Read-Only entities
    — Extra-Lazy collections
    Doctrine 2 — Laravel Austin 45

    View Slide

  46. 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

    View Slide

  47. DEMO
    Doctrine 2 — Laravel Austin 47

    View Slide

  48. Questions?
    Doctrine 2 — Laravel Austin 48

    View Slide