Doctrine 2

Doctrine 2

34ac8ea75c650746c8eaf03246b8d6a7?s=128

Goran Jurić

May 09, 2013
Tweet

Transcript

  1. Doctrine 2 Goran Jurić ZgPHP meetup #21, 9. 5. 2013.

  2. ORM

  3. Objektno Relacijsko Mapiranje • pretvara objektne iz (obično) relacijske baze

    podataka u objekte • Postoji više ORM patterna: ◦ Active Record (Doctrine 1, Propel, RoR, ...) ◦ Table Gateway (Zend Framework) ◦ Data Mapper (Doctrine 2, Hibernate, ...)
  4. ActiveRecord vs Data Mapper • Active Record: An object that

    wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data. • Data Mapper: A layer of Mappers that moves data between objects and a database while keeping them independent of each other and the mapper itself. Martin Fowler: Patterns of Enterprise Application Architecture (2003)
  5. ActiveRecord vs Data Mapper 1 // Bootstrap Propel 2 3

    $author = new Author(); 4 $author->setFirstName('Jane'); 5 $author->setLastName('Austen'); 6 $author->save(); _ 1 // Bootstrap Doctrine 2 // Fetch Entity Manager instance 3 4 $author = new Author(); 5 $author->setFirstName('Jane'); 6 $author->setLastName('Austen'); 7 8 $entityManager->persist($author); 9 $entityManager->flush();
  6. Prednosti Data Mappera • SRP - Single Responsibility Principle •

    POPO - Plain Old PHP Objects • Lakše testiranje • Baza postaje detalj ◦ batch inserti ◦ NoSQL
  7. Doctrine projekt • Database Abstraction Layer • Object Relational Mapper

    • MongoDB Object Document Mapper • PHP Content Repository ODM (PHPCR) • Doctrine Common ◦ Autoloading ◦ Annotations ◦ Caching
  8. Instalacija 1 require_once "vendor/autoload.php"; 2 3 use Doctrine\ORM\Tools\Setup; 4 use

    Doctrine\ORM\EntityManager; 5 6 $paths = array("/path/to/entities-or-mapping-files"); 7 $isDevMode = false; 8 $dbParams = array( 9 'driver' => 'pdo_mysql', 10 'user' => 'root', 11 'password' => 'password', 12 'dbname' => 'foo', 13 ); 14 15 $config = Setup::createAnnotationMetadataConfiguration( 16 $paths, 17 $isDevMode 18 ); 19 $entityManager = EntityManager::create($dbParams, $config);
  9. Definiranje modela Anotacije 1 /** 2 * @Entity @Table(name="products") 3

    **/ 4 class Product 5 { 6 /** 7 * @Id 8 * @Column(type="integer") 9 * @GeneratedValue 10 **/ 11 protected $id; 12 13 /** 14 * @Column(type="string") 15 **/ 16 protected $name; 17 18 // .. (other code) 19 }
  10. Definiranje modela YAML 1 # config/yaml/Product.dcm.yml 2 Product: 3 type:

    entity 4 table: products 5 id: 6 id: 7 type: integer 8 generator: 9 strategy: AUTO 10 fields: 11 name: 12 type: string
  11. Definiranje modela XML 1 <!-- config/xml/Product.dcm.xml --> 2 <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"

    3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping 5 http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd"> 6 7 <entity name="Product" table="products"> 8 <id name="id" type="integer"> 9 <generator strategy="AUTO" /> 10 </id> 11 12 <field name="name" type="string" /> 13 </entity> 14 </doctrine-mapping>
  12. Relacije One-To-One - Unidirectional 1 /** @Entity **/ 2 class

    Product 3 { 4 // ... 5 6 /** 7 * @OneToOne(targetEntity="Shipping") 8 * @JoinColumn(name="shipping_id", referencedColumnName="id") 9 **/ 10 private $shipping; 11 } 12 13 /** @Entity **/ 14 class Shipping 15 { 16 // ... 17 }
  13. One-To-One - Unidirectional Generirani SQL 1 CREATE TABLE Product (

    2 id INT AUTO_INCREMENT NOT NULL, 3 shipping_id INT DEFAULT NULL, 4 UNIQUE INDEX UNIQ_6FBC94267FE4B2B (shipping_id), 5 PRIMARY KEY(id) 6 ) ENGINE = InnoDB; 7 8 CREATE TABLE Shipping ( 9 id INT AUTO_INCREMENT NOT NULL, 10 PRIMARY KEY(id) 11 ) ENGINE = InnoDB; 12 13 ALTER TABLE Product ADD FOREIGN KEY (shipping_id) 14 REFERENCES Shipping(id);
  14. One-To-Many, Bidirectional 1 <?php 2 /** @Entity **/ 3 class

    Product 4 { 5 // Inverse side 6 /** 7 * @OneToMany(targetEntity="Feature", mappedBy="product") 8 **/ 9 private $features; 10 // ... 11 12 public function __construct() { 13 $this->features = new \Doctrine\Common\Collections\ArrayCollection(); 14 } 15 } 16 17 /** @Entity **/ 18 class Feature 19 { 20 // ... 21 /** 22 * @ManyToOne(targetEntity="Product", inversedBy="features") 23 * @JoinColumn(name="product_id", referencedColumnName="id") 24 **/ 25 private $product;
  15. 26 // ... 27 }

  16. One-To-Many, Bidirectional Generirani SQL 1 CREATE TABLE Product ( 2

    id INT AUTO_INCREMENT NOT NULL, 3 PRIMARY KEY(id) 4 ) ENGINE = InnoDB; 5 6 CREATE TABLE Feature ( 7 id INT AUTO_INCREMENT NOT NULL, 8 product_id INT DEFAULT NULL, 9 PRIMARY KEY(id) 10 ) ENGINE = InnoDB; 11 12 ALTER TABLE Feature ADD FOREIGN KEY (product_id) 13 REFERENCES Product(id);
  17. Ostali tipovi relacija • One-To-One, Unidirectional • One-To-One, Bidirectional •

    One-To-One, Self-referencing • One-To-Many, Unidirectional with Join Table • Many-To-One, Unidirectional • One-To-Many, Bidirectional • One-To-Many, Self-referencing • Many-To-Many, Unidirectional • Many-To-Many, Bidirectional • Many-To-Many, Self-referencing
  18. Važno! • InnoDB baza • Članske varijable ne bi smjele

    biti public • Inicijalizacija kolekcija • Doctrine ne poziva konstruktor (proxy) • Odabir owning i reverse strane relacije! ◦ Owning "drži" relaciju ◦ Owning strana se provjerava prilikom spremanja ◦ Lazy loading
  19. Generiranje sheme Podešavanje CLI 1 // cli-config.php 2 require_once "bootstrap.php";

    3 4 $helperSet = new \Symfony\Component\Console\Helper\HelperSet(array( 5 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($entityManager) 6 )); Kreiranje sheme 1 $ php vendor/bin/doctrine orm:schema-tool:create
  20. Rad s relacijama 1 <?php 2 /** @Entity **/ 3

    class Product 4 { 5 /** 6 * @OneToMany(targetEntity="Feature", mappedBy="product") 7 **/ 8 private $features; 9 // ... 10 11 public function __construct() { 12 $this->features = new \Doctrine\Common\Collections\ArrayCollection(); 13 } 14 15 public function setFeatures(array $features) 16 { 17 $this->features = $features; 18 } 19 public function addFeature(Feature $feature) 20 { 21 $this->features[] = $feature; 22 } 23 public function removeFeature(Feature $feature) 24 { 25 $this->getFeatures()->removeElement($feature);
  21. 26 } 27 }

  22. Rad s entitetima 1 // Editiranje 2 $article = $entityManager->find('CMS\Article',

    1234); 3 $article->setHeadline('Novi naslov!'); 4 $entityManager->flush(); 5 6 // Lazy load 7 8 // accessing a method of the user instance triggers the lazy-load 9 echo "Author: " . $article->getAuthor()->getName() . "\n"; 10 11 // Lazy Loading Proxies pass instanceof tests: 12 if ($article->getAuthor() instanceof User) { 13 // a User Proxy is a generated "UserProxy" class 14 } 15 16 // accessing the comments as an iterator triggers the lazy-load 17 // retrieving ALL the comments of this article from the database 18 // using a single SELECT statement 19 foreach ($article->getComments() AS $comment) { 20 echo $comment->getText() . "\n\n"; 21 } 22 23 // Brisanje 24 $entityManager->remove($article); 25 $entityManager->flush();
  23. Repozitorij 1 // $em instanceof EntityManager 2 $user = $em->getRepository('MyProject\Domain\User')

    3 ->find($id); 4 5 // All users that are 20 years old 6 $users = $em->getRepository('MyProject\Domain\User') 7 ->findBy(array('age' => 20)); 8 9 // All users that are 20 years old and have a surname of 'Miller' 10 $users = $em->getRepository('MyProject\Domain\User') 11 ->findBy(array('age' => 20, 'surname' => 'Miller')); 12 13 // A single user by its nickname 14 $user = $em->getRepository('MyProject\Domain\User') 15 ->findOneBy(array('nickname' => 'romanb')); • Moguće extendanje Repozitorija: ◦ User::getAllAdmins() ◦ Article::getArticlesByAuthor($author) ◦ ...
  24. Doctrine Query Language 1 <?php 2 3 // Automatski escape

    parametera 4 $query = $em->createQuery('SELECT u from ForumUser u WHERE ' . 5 '(u.username = :name OR u.username = :name2) AND u.id = :id'); 6 $query->setParameters(array( 7 'name' => 'Bob', 8 'name2' => 'Alice', 9 'id' => 321, 10 )); 11 $users = $query->getResult(); // array of ForumUser objects 12 13 // Nije potrebno definirati relacije 14 $query = $em->createQuery('SELECT DISTINCT u.id FROM CmsArticle a JOIN a.user u'); 15 $ids = $query->getResult(); // array of CmsUser ids
  25. QueryBuilder 1 $qb = $em->createQueryBuilder(); 2 3 $qb->select('u') 4 ->from('User',

    'u') 5 ->where('u.id = ?1') 6 ->orderBy('u.name', 'ASC'); 7 8 $query = $qb->getQuery(); 9 10 $result = $query->getResult(); 11 $single = $query->getSingleResult(); 12 $array = $query->getArrayResult(); 13 $scalar = $query->getScalarResult(); 14 $singleScalar = $query->getSingleScalarResult();
  26. QueryBuilder Metode 1 class QueryBuilder 2 { 3 public function

    select($select = null); 4 public function delete($delete = null, $alias = null); 5 public function update($update = null, $alias = null); 6 public function set($key, $value); 7 public function from($from, $alias = null); 8 public function innerJoin($join, $alias = null, $conditionType = null, $condition = null); 9 public function leftJoin($join, $alias = null, $conditionType = null, $condition = null); 10 public function where($where); 11 public function andWhere($where); 12 public function orWhere($where); 13 public function groupBy($groupBy); 14 public function addGroupBy($groupBy); 15 public function having($having); 16 public function andHaving($having); 17 public function orHaving($having); 18 public function orderBy($sort, $order = null); 19 public function addOrderBy($sort, $order = null); // Default $order = 'ASC' 20 }
  27. Events

  28. Lifecycle Callbacks 1 /** @Entity @HasLifecycleCallbacks */ 2 class User

    3 { 4 // ... 5 6 /** 7 * @Column(type="string", length=255) 8 */ 9 public $value; 10 11 /** @Column(name="created_at", type="datetime") */ 12 private $createdAt; 13 14 /** @PrePersist */ 15 public function doStuffOnPrePersist() 16 { 17 $this->createdAt = new DateTime("now");; 18 } 19 20 /** @PrePersist */ 21 public function doOtherStuffOnPrePersist() 22 { 23 $this->value = 'changed from prePersist callback!'; 24 } 25 26 ...
  29. 1 ... 2 3 /** @PostPersist */ 4 public function

    doStuffOnPostPersist() 5 { 6 $this->value = 'changed from postPersist callback!'; 7 } 8 9 /** @PostLoad */ 10 public function doStuffOnPostLoad() 11 { 12 $this->value = 'changed from postLoad callback!'; 13 } 14 15 /** @PreUpdate */ 16 public function doStuffOnPreUpdate() 17 { 18 $this->value = 'changed from preUpdate callback!'; 19 } 20 }
  30. Event listeneri • pre/postRemove • pre/postPersist • pre/postUpdate • postLoad

    • loadClassMetadata • pre/postFlush • onFlush • onClear
  31. Cachiranje • Metadata Cache • Query Cache • Result Cache

  32. Nasljeđivanje

  33. Mapped Superclass 1 /** @MappedSuperclass */ 2 class MappedSuperclassBase 3

    { 4 /** @Column(type="integer") */ 5 protected $mapped1; 6 /** @Column(type="string") */ 7 protected $mapped2; 8 /** 9 * @OneToOne(targetEntity="MappedSuperclassRelated1") 10 * @JoinColumn(name="related1_id", referencedColumnName="id") 11 */ 12 protected $mappedRelated1; 13 } 14 15 /** @Entity */ 16 class EntitySubClass extends MappedSuperclassBase 17 { 18 /** @Id @Column(type="integer") */ 19 private $id; 20 /** @Column(type="string") */ 21 private $name; 22 } • Ne može biti entitet!
  34. Single Table Inheritance 1 namespace MyProject\Model; 2 3 /** 4

    * @Entity 5 * @InheritanceType("SINGLE_TABLE") 6 * @DiscriminatorColumn(name="discr", type="string") 7 * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) 8 */ 9 class Person 10 { 11 // ... 12 } 13 14 /** 15 * @Entity 16 */ 17 class Employee extends Person 18 { 19 // ... 20 }
  35. Class Table Inheritance 1 namespace MyProject\Model; 2 3 /** 4

    * @Entity 5 * @InheritanceType("JOINED") 6 * @DiscriminatorColumn(name="discr", type="string") 7 * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) 8 */ 9 class Person 10 { 11 // ... 12 } 13 14 /** @Entity */ 15 class Employee extends Person 16 { 17 // ... 18 }
  36. Ostalo • Nativni rad s PHP DateTime objektima • Kaskadiranje

    perzistiranja i brisanja entiteta • Nisu podržane DB specific SQL naredbe
  37. Performanse • Relativno brz (transakcije) • Obavezno cachiranje metadata podataka

    i querija • Fallback na direktan pristup bazi
  38. Ekstenzije • Doctrine Migrations • Doctrine Fixtures • Modified Preorder

    Tree Traversal • MySQL specific SQL • Versionable • Translatable • Blameable • Softdeleteable • ...
  39. The end Hvala * mail: goran@ingop.hr * twitter: @goran_juric *

    blog: http://gogs.info/ Built with Landslide: https://github.com/adamzap/landslide