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

Doctrine 2

Doctrine 2

Goran Jurić

May 09, 2013
Tweet

More Decks by Goran Jurić

Other Decks in Programming

Transcript

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

    View full-size slide

  2. 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, ...)

    View full-size slide

  3. 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)

    View full-size slide

  4. 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();

    View full-size slide

  5. Prednosti Data Mappera
    • SRP - Single Responsibility Principle
    • POPO - Plain Old PHP Objects
    • Lakše testiranje
    • Baza postaje detalj
    ◦ batch inserti
    ◦ NoSQL

    View full-size slide

  6. Doctrine projekt
    • Database Abstraction Layer
    • Object Relational Mapper
    • MongoDB Object Document Mapper
    • PHP Content Repository ODM (PHPCR)
    • Doctrine Common
    ◦ Autoloading
    ◦ Annotations
    ◦ Caching

    View full-size slide

  7. 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);

    View full-size slide

  8. 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 }

    View full-size slide

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

    View full-size slide

  10. Definiranje modela
    XML
    1
    2 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
    8
    9
    10
    11
    12
    13
    14

    View full-size slide

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

    View full-size slide

  12. 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);

    View full-size slide

  13. One-To-Many, Bidirectional
    1 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;

    View full-size slide

  14. 26 // ...
    27 }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  19. Rad s relacijama
    1 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);

    View full-size slide

  20. 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();

    View full-size slide

  21. 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)
    ◦ ...

    View full-size slide

  22. Doctrine Query Language
    1 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

    View full-size slide

  23. 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();

    View full-size slide

  24. 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 }

    View full-size slide

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

    View full-size slide

  26. 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 }

    View full-size slide

  27. Event listeneri
    • pre/postRemove
    • pre/postPersist
    • pre/postUpdate
    • postLoad
    • loadClassMetadata
    • pre/postFlush
    • onFlush
    • onClear

    View full-size slide

  28. Cachiranje
    • Metadata Cache
    • Query Cache
    • Result Cache

    View full-size slide

  29. Nasljeđivanje

    View full-size slide

  30. 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!

    View full-size slide

  31. 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 }

    View full-size slide

  32. 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 }

    View full-size slide

  33. Ostalo
    • Nativni rad s PHP DateTime objektima
    • Kaskadiranje perzistiranja i brisanja entiteta
    • Nisu podržane DB specific SQL naredbe

    View full-size slide

  34. Performanse
    • Relativno brz (transakcije)
    • Obavezno cachiranje metadata podataka i querija
    • Fallback na direktan pristup bazi

    View full-size slide

  35. Ekstenzije
    • Doctrine Migrations
    • Doctrine Fixtures
    • Modified Preorder Tree Traversal
    • MySQL specific SQL
    • Versionable
    • Translatable
    • Blameable
    • Softdeleteable
    • ...

    View full-size slide

  36. The end
    Hvala
    * mail: [email protected]
    * twitter: @goran_juric
    * blog: http://gogs.info/
    Built with Landslide:
    https://github.com/adamzap/landslide

    View full-size slide