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

Laravel Doctrine

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

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/

Avatar for Hunter Skrasek

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