Advertisement
Advertisement

More Related Content

Advertisement
Advertisement

ZendCon2010 The Doctrine Project

  1. Jonathan H. Wage | OpenSky The Doctrine Project
  2. Who am I? Jonathan H. Wage PHP Developer for over 10 years Symfony Contributor Doctrine Contributor Published Author Business Owner Nashville, TN Resident http://www.twitter.com/jwage http://www.facebook.com/jwage
  3. I work at What is OpenSky? “a social commerce platform” Based in New York and is a major open source software advocate http://www.shopopensky.com
  4. OpenSky Technologies PHP 5.3.2 Apache2 Symfony2 Doctrine2 jQuery mule, stomp, hornetq MongoDB nginx varnish
  5. What is Doctrine? - Open Source PHP Project started in 2006 - Specializes in database functionality - Database Abstraction Layer (DBAL) - Database Migrations - Object Relational Mapper (DBAL) - MongoDB Object Document Manager (ODM) - CouchDB Object Document Manager (ODM)
  6. Who is on the team? • Roman S. Borschel • Guilherme Blanco • Benjamin Eberlei • Bulat Shakirzyanov • Jonathan H.Wage
  7. Project History - First commit April 13th 2006 - First stable version finished and Released September 1st 2008 - One of the first ORM implementations for PHP - 1.0 is First LTS(long term support) release. Maintained until March 1st 2010 - Integrated with many popular frameworks: Symfony, Zend Framework, Code Igniter
  8. Doctrine Libraries - Database Abstraction Layer - Database Migrations - Object Relational Mapper - MongoDB Object Document Manager - CouchDB Object Document Manager
  9. DBAL Database Abstraction Layer
  10. Database Abstraction Layer The Doctrine Database Abstraction Layer (DBAL) is a thin layer on top of PDO, it offers: - select, update, delete, transactions - database schema introspection - schema management
  11. Can be used standalone
  12. Evolved fork of PEAR MDB, MDB2, Zend_Db, etc.
  13. Download You can download a standalone package to get started using the DBAL: http://www.doctrine-project.org/projects/dbal/download
  14. Autoloader To use any Doctrine library you must register an autoloader: use DoctrineCommonClassLoader; require '/path/to/doctrine-common/lib/Doctrine/Common/ClassLoader.php'; $classLoader = new ClassLoader('DoctrineDBAL', '/path/to/doctrine-dbal/lib'); $classLoader->register();
  15. Create a Connection $config = new DoctrineDBALConfiguration(); //.. $connectionParams = array( 'dbname' => 'mydb', 'user' => 'user', 'password' => 'secret', 'host' => 'localhost', 'driver' => 'pdo_mysql', ); $conn = DriverManager::getConnection($connectionParams);
  16. Data API prepare($sql) - Prepare a given sql statement and return the DoctrineDBALDriver Statement instance. executeUpdate($sql, array $params) - Executes a prepared statement with the given sql and parameters and returns the affected rows count. execute($sql, array $params) - Creates a prepared statement for the given sql and passes the parameters to the execute method, then returning the statement. fetchAll($sql, array $params) - Execute the query and fetch all results into an array. fetchArray($sql, array $params) - Numeric index retrieval of first result row of the given query. fetchBoth($sql, array $params) - Both numeric and assoc column name retrieval of the first result row. fetchColumn($sql, array $params, $colnum) - Retrieve only the given column of the first result row. fetchRow($sql, array $params) - Retrieve assoc row of the first result row. select($sql, $limit, $offset) - Modify the given query with a limit clause. delete($tableName, array $identifier) - Delete all rows of a table matching the given identifier, where keys are column names. insert($tableName, array $data) - Insert a row into the given table name using the key value pairs of data.
  17. Very Similar to PDO $users = $conn->fetchAll('SELECT * FROM users');
  18. Schema Manager Learn about and modify your database through the SchemaManager: $sm = $conn->getSchemaManager();
  19. Introspection API listDatabases() listFunctions() listSequences() listTableColumns($tableName) listTableConstraints($tableName) listTableDetails($tableName) listTableForeignKeys($tableName) listTableIndexes($tableName) listTables()
  20. Introspection API $tables = $sm->listTables(); foreach ($tables as $table) { $columns = $sm->listTableColumns($table); // ... }
  21. DDL Statements Progromatically issue DDL statements: $columns = array( 'id' => array( 'type' => DoctrineDBALType::getType('integer'), 'autoincrement' => true, 'primary' => true, 'notnull' => true ), 'test' => array( 'type' => DoctrineDBALType::getType('string'), 'length' => 255 ) ); $options = array(); $sm->createTable('new_table', $columns, $options);
  22. DDL Statements Progromatically issue DDL statements: $definition = array( 'name' => 'user_id_fk', 'local' => 'user_id', 'foreign' => 'id', 'foreignTable' => 'user' ); $sm->createForeignKey('profile', $definition);
  23. Try a Method You can try a method and return true if the operation was successful: if ($sm->tryMethod('createTable', 'new_table', $columns, $options)) { // do something }
  24. Drop and Create Database try { $sm->dropDatabase('test_db'); } catch (Exception $e) {} $sm->createDatabase('test_db');
  25. Drop and Create Database A little better! Every drop and create functionality in the API has a method that follows the dropAndCreate pattern: $sm->dropAndCreateDatabase('test_db');
  26. Schema Representation $platform = $em->getConnection()->getDatabasePlatform(); $schema = new DoctrineDBALSchemaSchema(); $myTable = $schema->createTable("my_table"); $myTable->addColumn("id", "integer", array("unsigned" => true)); $myTable->addColumn("username", "string", array("length" => 32)); $myTable->setPrimaryKey(array("id")); // get queries to create this schema. $queries = $schema->toSql($platform); Array ( [0] => CREATE TABLE my_table (id INTEGER NOT NULL, username VARCHAR(32) NOT NULL, PRIMARY KEY("id")) )
  27. Schema Representation Array ( [0] => DROP TABLE my_table ) Returns the reverse SQL of what toSql() returns // ...... // get queries to safely delete this schema. $dropSchema = $schema->toDropSql($platform); Array ( [0] => DROP TABLE my_table )
  28. Comparing Schemas $fromSchema = new DoctrineDBALSchemaSchema(); $myTable = $fromSchema->createTable("my_table"); $myTable->addColumn("id", "integer", array("unsigned" => true)); $myTable->addColumn("username", "string", array("length" => 32)); $myTable->setPrimaryKey(array("id")); $toSchema = new DoctrineDBALSchemaSchema(); $myTable = $toSchema->createTable("my_table"); $myTable->addColumn("id", "integer", array("unsigned" => true)); $myTable->addColumn("username", "string", array("length" => 32)); $myTable->addColumn("email", "string", array("length" => 255)); $myTable->setPrimaryKey(array("id")); $comparator = new DoctrineDBALSchemaComparator(); $schemaDiff = $comparator->compare($fromSchema, $toSchema); // queries to get from one to another schema. $queries = $schemaDiff->toSql($platform); print_r($queries); ALTER TABLE my_table ADD email VARCHAR(255) NOT NULL
  29. ORM Object Relational Mapper
  30. What is ORM? “Technique for converting data between incompatible type systems in object- oriented programming languages.” http://en.wikipedia.org/wiki/Object-relational_mapping
  31. The ORM is built on top of Common and DBAL
  32. ORM Goals - Maintain transparency - Keep domain and persistence layer separated - Performance - Consistent and decoupled API - Well defined semantics
  33. http://www.doctrine-project.org/projects/orm/download Download
  34. Architecture Entities - Lightweight persistent domain object - Regular PHP class - Does not extend any base Doctrine class - Cannot be final or contain final methods - Any two entities in a hierarchy of classes must not have a mapped property with the same name - Supports inheritance, polymorphic associations and polymorphic queries. - Both abstract and concrete classes can be entities - Entities may extend non-entity classes as well as entity classes, and non-entity classes may extend entity classes
  35. Architecture - No more base class required - Values stored in object properties - Persistence is done transparently namespace Entities; class User { private $id; private $name; }
  36. Architecture The EntityManager - Central access point to the ORM functionality provided by Doctrine 2. API is used to manage the persistence of your objects and to query for persistent objects. - Employes transactional write behind strategy that delays the execution of SQL statements in order to execute them in the most efficient way - Execute at end of transaction so that all write locks are quickly releases - Internally an EntityManager uses a UnitOfWork to keep track of your objects
  37. Create EntityManager Create a new EntityManager instance: $config = new DoctrineORMConfiguration(); $config->setMetadataCacheImpl(new DoctrineCommonCacheArrayCache); $driverImpl = $config->newDefaultAnnotationDriver(array(__DIR__."/Entities")); $config->setMetadataDriverImpl($driverImpl); $config->setProxyDir(__DIR__ . '/Proxies'); $config->setProxyNamespace('Proxies'); $em = DoctrineORMEntityManager::create($conn, $config);
  38. Map entities to RDBMS tables Entities are just regular PHP objects namespace Entities; class User { private $id; private $name; }
  39. Map entities to RDBMS tables Entities are just regular PHP objects Mapped By: - Annotations namespace Entities; /** * @Entity @Table(name="users") */ class User { /** @Id @Column(type="integer") @GeneratedValue */ private $id; /** @Column(length=50) */ private $name; }
  40. Map entities to RDBMS tables Entities are just regular PHP objects: Mapped By: - Annotations - YAML EntitiesUser: type: entity table: users id: id: type: integer generator: strategy: AUTO fields: name: type: string length: 255
  41. Map entities to RDBMS tables Entities are just regular PHP objects: Mapped By: - Annotations - YAML - 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://doctrine-project.org/schemas/orm/doctrine-mapping.xsd"> <entity name="EntitiesUser" table="users"> <id name="id" type="integer"> <generator strategy="AUTO"/> </id> <field name="name" type="string" length="50"/> </entity> </doctrine-mapping>
  42. Mapping Performance - Only parsed once - Cached using configured cache driver - Subsequent requests pull mapping information from configured cache driver
  43. Working with Objects Use the $em to manage the persistence of your entities: $user = new User; $user->setName('Jonathan H. Wage'); $em->persist($user); $em->flush();
  44. Working with Objects Updating an object: $user = $em->getRepository('User') ->find(array('name' => 'jwage')); // modify the already managed object $user->setPassword('changed'); $em->flush(); // issues update
  45. Working with Objects Removing an object: $user = $em->getRepository('User') ->find(array('name' => 'jwage')); // schedule for deletion $em->remove($user); $em->flush(); // issues delete
  46. Transactions Implicit: EntityManager#flush() will begin and commit/rollback a transaction $user = new User; $user->setName('George'); $em->persist($user); $em->flush();
  47. Transactions Explicit: // $em instanceof EntityManager $em->getConnection()->beginTransaction(); // suspend auto-commit try { //... do some work $user = new User; $user->setName('George'); $em->persist($user); $em->flush(); $em->getConnection()->commit(); } catch (Exception $e) { $em->getConnection()->rollback(); $em->close(); throw $e; }
  48. Transactions A more convenient explicit transaction: // $em instanceof EntityManager $em->transactional(function($em) { //... do some work $user = new User; $user->setName('George'); $em->persist($user); });
  49. Transactions and Performance for ($i = 0; $i < 20; ++$i) { $user = new User; $user->name = 'Jonathan H. Wage'; $em->persist($user); } $s = microtime(true); $em->flush(); $e = microtime(true); echo $e - $s;
  50. Transactions and Performance How you use transactions can greatly affect performance. Here is the same thing using raw PHP code: $s = microtime(true); for ($i = 0; $i < 20; ++$i) { mysql_query("INSERT INTO users (name) VALUES ('Jonathan H. Wage')", $link); } $e = microtime(true); echo $e - $s;
  51. Which is faster? - The one using no ORM, and no abstraction at all? - Or the one using the Doctrine ORM?
  52. Which is faster? - The one using no ORM, and no abstraction at all? - Or the one using the Doctrine ORM? - Doctrine2 wins! How? Doctrine2 0.0094 seconds mysql_query 0.0165 seconds
  53. Not Faster Doctrine just automatically performed the inserts inside one transaction. Here is the code updated to use transactions: $s = microtime(true); mysql_query('START TRANSACTION', $link); for ($i = 0; $i < 20; ++$i) { mysql_query("INSERT INTO users (name) VALUES ('Jonathan H. Wage')", $link); } mysql_query('COMMIT', $link); $e = microtime(true); echo $e - $s;
  54. Much Faster Transactions matter and can affect performance greater than any code optimization! Doctrine2 0.0094 seconds mysql_query 0.0165 seconds 0.0028
  55. Locking Support Optimistic locking with integer: class User { // ... /** @Version @Column(type="integer") */ private $version; // ... }
  56. Locking Support Optimistic locking with timestamp: class User { // ... /** @Version @Column(type="datetime") */ private $version; // ... }
  57. Locking Support Verify version when finding: use DoctrineDBALLockMode; use DoctrineORMOptimisticLockException; $theEntityId = 1; $expectedVersion = 184; try { $entity = $em->find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion); // do the work $em->flush(); } catch(OptimisticLockException $e) { echo "Sorry, but someone else has already changed this entity. Please apply the changes again!"; }
  58. Locking Support Example implementation: $post = $em->find('BlogPost', 123456); echo '<input type="hidden" name="id" value="' . $post->getId() . '" />'; echo '<input type="hidden" name="version" value="' . $post->getCurrentVersion() . '" />'; $postId = (int) $_GET['id']; $postVersion = (int) $_GET['version']; $post = $em->find('BlogPost', $postId, DoctrineDBALLockMode::OPTIMISTIC, $postVersion);
  59. DQL Doctrine Query Language
  60. DQL - DQL stands for Doctrine Query Language and is an Object Query Language derivate that is very similar to the Hibernate Query Language (HQL) or the Java Persistence Query Language (JPQL). - DQL provides powerful querying capabilities over your object model. Imagine all your objects lying around in some storage (like an object database). When writing DQL queries, think about querying that storage to find a certain subset of your objects.
  61. DQL Parser - Parser completely re-written from scratch - Parsed by top down recursive descent lexer parser that constructs an AST(Abstract Syntax Tree) - Platform specific SQL is generated from AST
  62. Doctrine Query Language $q = $em->createQuery('SELECT u FROM User u'); $users = $q->execute();
  63. Query Builder Same query built using the QueryBuilder $qb = $em->createQueryBuilder() ->select('u') ->from('User', 'u'); $q = $qb->getQuery(); $users = $q->execute();
  64. More Examples $query = $em->createQuery( 'SELECT u, g, FROM User u ' . 'LEFT JOIN u.Groups g ' . 'ORDER BY u.name ASC, g.name ASC' ); $users = $query->execute(); $qb = $em->createQueryBuilder() ->select('u, g') ->from('User', 'u') ->leftJoin('u.Groups', 'g') ->orderBy('u.name', 'ASC') ->addOrderBy('g.name', 'ASC'); $query = $qb->getQuery();
  65. Executing Queries Executing and getting results $users = $query->execute(); foreach ($users as $user) { // ... foreach ($user->getGroups() as $group) { // ... } }
  66. Executing Queries Execute query and iterate over results keeping memory usage low: foreach ($query->iterate() as $user) { // ... foreach ($user->getGroups() as $group) { // ... } }
  67. Result Cache Optionally cache the results of your queries in your driver of choice: $cacheDriver = new DoctrineCommonCacheApcCache(); $config->setResultCacheImpl($cacheDriver); $query = $em->createQuery('select u from EntitiesUser u'); $query->useResultCache(true, 3600, 'my_query_name'); $users = $query->execute(); $users = $query->execute(); // 2nd time pulls from cache
  68. Inheritance Doctrine supports mapping entities that use inheritance with the following strategies: - Mapped Superclass - Single Table Inheritance - Class Table Inheritance
  69. Mapped Superclasses /** @MappedSuperclass */ abstract class MappedSuperclassBase { /** @Column(type="integer") */ private $mapped1; /** @Column(type="string") */ private $mapped2; /** * @OneToOne(targetEntity="MappedSuperclassRelated1") * @JoinColumn(name="related1_id", referencedColumnName="id") */ private $mappedRelated1; // ... more fields and methods } /** @Entity */ class EntitySubClass extends MappedSuperclassBase { /** @Id @Column(type="integer") */ private $id; /** @Column(type="string") */ private $name; // ... more fields and methods }
  70. Single Table Inheritance /** * @Entity * @InheritanceType("SINGLE_TABLE") * @DiscriminatorColumn(name="discr", type="string") * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) */ class Person { // ... } /** * @Entity */ class Employee extends Person { // ... }
  71. Single Table Inheritance - All entities share one table. - To distinguish which row represents which type in the hierarchy a so- called discriminator column is used.
  72. Class Table Inheritance /** * @Entity * @InheritanceType("JOINED") * @DiscriminatorColumn(name="discr", type="string") * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) */ class Person { // ... } /** @Entity */ class Employee extends Person { // ... }
  73. Class Table Inheritance - Each class in a hierarchy is mapped to several tables: its own table and the tables of all parent classes. - The table of a child class is linked to the table of a parent class through a foreign key constraint. - A discriminator column is used in the topmost table of the hierarchy because this is the easiest way to achieve polymorphic queries.
  74. Bulk Inserts with Domain Insert 10000 objects batches of 20: $batchSize = 20; for ($i = 1; $i <= 10000; ++$i) { $user = new User; $user->setStatus('user'); $user->setUsername('user' . $i); $user->setName('Mr.Smith-' . $i); $em->persist($user); if ($i % $batchSize == 0) { $em->flush(); $em->clear(); // Detaches all objects from Doctrine! } }
  75. Bulk Update with DQL $q = $em->createQuery('update Manager m set m.salary = m.salary * 0.9'); $numUpdated = $q->execute();
  76. Bulk Update with Domain Update objects in batches of 20: $batchSize = 20; $i = 0; $q = $em->createQuery('select u from User u'); $iterableResult = $q->iterate(); foreach($iterableResult AS $row) { $user = $row[0]; $user->increaseCredit(); $user->calculateNewBonuses(); if (($i % $batchSize) == 0) { $em->flush(); // Executes all updates. $em->clear(); // Detaches all objects from Doctrine! } ++$i; }
  77. Bulk Delete with DQL $q = $em->createQuery('delete from Manager m where m.salary > 100000'); $numDeleted = $q->execute();
  78. Bulk Delete with Domain $batchSize = 20; $i = 0; $q = $em->createQuery('select u from User u'); $iterableResult = $q->iterate(); while (($row = $iterableResult->next()) !== false) { $em->remove($row[0]); if (($i % $batchSize) == 0) { $em->flush(); // Executes all deletions. $em->clear(); // Detaches all objects from Doctrine! } ++$i; }
  79. Events Doctrine triggers events throughout the lifecycle of objects it manages: - preRemove - postRemove - prePersist - postPersist - preUpdate - postUpdate - preLoad - postLoad
  80. Example /** * @Entity * @HasLifecycleCallbacks */ class BlogPost { // ... /** @PreUpdate */ public function prePersist() { $this->createdAt = new DateTime(); } /** @PreUpdate */ public function preUpdate() { $this->updatedAt = new DateTime(); } }
  81. Using Raw SQL - Write a raw SQL string - Map the result set of the SQL query using a ResultSetMapping instance
  82. Using Raw SQL $sql = 'SELECT id, name FROM users WHERE username = ?'; $rsm = new ResultSetMapping; $rsm->addEntityResult('User', 'u'); $rsm->addFieldResult('u', 'id', 'id'); $rsm->addFieldResult('u', 'name', 'name'); $query = $this->_em->createNativeQuery($sql, $rsm); $query->setParameter(1, 'jwage'); $users = $query->getResult();
  83. Why use an object mapper?
  84. Encapsulate your domain in an object oriented interface Encapsulation
  85. The organization of your domain logic in an OO way improved maintainability Maintainability
  86. Keeping a clean OO domain model makes your business logic easily testable for improved stability Testability
  87. Write portable and thin application controller code and fat models. Portability
  88. Questions? - http://www.twitter.com/jwage - http://www.facebook.com/jwage - http://www.jwage.com OpenSky is hiring! Inquire via e-mail at jwage@theopenskyproject.com or in person after this presentation!
Advertisement