Zend Framework 1 + Doctrine 2. Jonathan H. Wage. â¢PHP Developer for over 10 years. â¢Symfony Contributor. â¢Doctrine
Zend Framework 1 + Doctrine 2
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
2
Zend Framework 1 + Doctrine 2
I work at OpenSky
•What is OpenSky? “a social commerce platform”
•Based in New York and is a major open •source software advocate •http://www.shopopensky.com
3
Zend Framework 1 + Doctrine 2
OpenSky Technologies •PHP 5.3.2 •Apache2 •Symfony2 •Doctrine2 •jQuery •mule, stomp, hornetq •MongoDB •nginx •varnish
4
Zend Framework 1 + Doctrine 2
Ralph Schindler •Software Engineer on the Zend Framework team At Zend for almost 3 years Before that TippingPoint/3Com
•Programming PHP for 12+ years •Live in New Orleans, LA. Lived in Austin, Tx for 5 years
•Where To Find Me: http://ralphschindler.com http://twitter.com/ralphschindler http://github.com/ralphschindler ralphschindler on freenode
5
Zend Framework 1 + Doctrine 2
Guilherme Blanco •Programming Experience 12+ years web development experience 9 years with PHP
•Software Engineer at Yahoo! •Open Source Evangelist Contributes regularly to the Doctrine Project,
Symfony, and Zend Framework
•Where to find me: http://twitter.com/guilhermeblanco http://github.com/guilhermeblanco
6
Zend Framework 1 + Doctrine 2
Doctrine 2
7
The Doctrine Project
What is Doctrine? • Open Source PHP Project started in 2006 • Specializes in ) */ class User { /** @Id @Column(type="integer") @GeneratedValue */ private $id; /** @Column(length=50) */ private $name; }
42
Zend Framework 1 + Doctrine 2
Map entities to RDBMS tables • Entities are just regular PHP objects: Mapped By:
•Annotations •YAML
43
Zend Framework 1 + Doctrine 2
Entities\User: type: entity table: users id: id: type: integer generator: strategy: AUTO fields: name: type: string length: 255
Map entities to RDBMS tables • Entities are just regular PHP objects: Mapped By:
•Annotations •YAML •XML
44
Zend Framework 1 + Doctrine 2
Mapping Performance • Only parsed once • Cached using configured cache driver • Subsequent requests pull mapping information from configured cache driver
45
Zend Framework 1 + Doctrine 2
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();
46
Zend Framework 1 + Doctrine 2
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
47
Zend Framework 1 + Doctrine 2
Working with Objects • Removing an object:
$user = $em->getRepository('User') ->find(array('name' => 'jwage')); // schedule for deletion $em->remove($user); $em->flush(); // issues delete
48
Zend Framework 1 + Doctrine 2
Transactions • Implicit:
$user = new User; $user->setName('George'); $em->persist($user); $em->flush(); •EntityManager#flush() will begin and commit/rollback a transaction
49
Zend Framework 1 + Doctrine 2
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; }
50
Zend Framework 1 + Doctrine 2
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); });
51
Zend Framework 1 + Doctrine 2
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;
52
Zend Framework 1 + Doctrine 2
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;
53
Zend Framework 1 + Doctrine 2
Which is faster? • The one using no ORM, and no abstraction at all? • Or the one using the Doctrine ORM?
54
Zend Framework 1 + Doctrine 2
Which is faster? • The one using no ORM, and no abstraction at all? • Or the one using the Doctrine ORM?
Doctrine2
0.0094 seconds
mysql_query 0.0165 seconds • Doctrine2 wins! How?
55
Zend Framework 1 + Doctrine 2
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;
56
Zend Framework 1 + Doctrine 2
Much Faster • Transactions matter and can affect performance greater than any code optimization!
Doctrine2
0.0094 seconds 0.0028
mysql_query 0.0165 seconds
57
Zend Framework 1 + Doctrine 2
Locking Support • Optimistic locking with integer:
class User { // ... /** @Version @Column(type="integer") */ private $version; // ... }
58
Zend Framework 1 + Doctrine 2
Locking Support • Optimistic locking with timestamp:
class User { // ... /** @Version @Column(type="datetime") */ private $version; // ... }
59
Zend Framework 1 + Doctrine 2
Locking Support • Verify version when finding:
use Doctrine\DBAL\LockMode; use Doctrine\ORM\OptimisticLockException; $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!"; }
60
Zend Framework 1 + Doctrine 2
Locking Support • Example implementation:
$post = $em->find('BlogPost', 123456); echo ''; echo '';
$postId = (int) $_GET['id']; $postVersion = (int) $_GET['version']; $post = $em->find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion);
61
Zend Framework 1 + Doctrine 2
DQL Doctrine Query Language
62
Zend Framework 1 + Doctrine 2
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 ) */ 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 }
72
Zend Framework 1 + Doctrine 2
Single Table Inheritance /** * @Entity * @InheritanceType("SINGLE_TABLE") * @DiscriminatorColumn(name="discr", type="string") * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) */ class Person { // ... } /** * @Entity */ class Employee extends Person { // ... }
73
Zend Framework 1 + Doctrine 2
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.
74
Zend Framework 1 + Doctrine 2
Class Table Inheritance /** * @Entity * @InheritanceType("JOINED") * @DiscriminatorColumn(name="discr", type="string") * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) */ class Person { // ... } /** @Entity */ class Employee extends Person { // ... }
75
Zend Framework 1 + Doctrine 2
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.
76
Zend Framework 1 + Doctrine 2
Bulk Inserts with Domain • Insert 10000 objects batches of 20:
$batchSize = 20; for ($i = 1; $i 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! } }
77
Zend Framework 1 + Doctrine 2
Bulk Update with DQL
$q = $em->createQuery('update Manager m set m.salary = m.salary * 0.9'); $numUpdated = $q->execute();
78
Zend Framework 1 + Doctrine 2
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; }
79
Zend Framework 1 + Doctrine 2
Bulk Delete with DQL
$q = $em->createQuery('delete from Manager m where m.salary > 100000'); $numDeleted = $q->execute();
80
Zend Framework 1 + Doctrine 2
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; }
81
Zend Framework 1 + Doctrine 2
Events •Doctrine triggers events throughout the •lifecycle of objects it manages: preRemove postRemove prePersist postPersist preUpdate postUpdate preLoad postLoad
82
Zend Framework 1 + Doctrine 2
Example /** * @Entity * @HasLifecycleCallbacks */ class BlogPost { // ... /** @PreUpdate */ public function prePersist() { $this->createdAt = new DateTime(); } /** @PreUpdate */ public function preUpdate() { $this->updatedAt = new DateTime(); } }
83
Zend Framework 1 + Doctrine 2
Using Raw SQL • Write a raw SQL string • Map the result set of the SQL query using a ResultSetMapping instance
84
Zend Framework 1 + Doctrine 2
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();
85
Zend Framework 1 + Doctrine 2
Why use an object mapper?
86
Zend Framework 1 + Doctrine 2
Encapsulation Encapsulate your domain in an object oriented interface
87
Zend Framework 1 + Doctrine 2
Maintainability The organization of your domain logic in an OO way improved maintainability
88
Zend Framework 1 + Doctrine 2
Testability Keeping a clean OO domain model makes your business logic easily testable for improved stability
89
Zend Framework 1 + Doctrine 2
Portability Write portable and thin application controller code and fat models.
90
Zend Framework 1 + Doctrine 2
Demo Time
91
The Doctrine Project
What we are going to accomplish •Start with a vanilla Zend Framework Project •Ensure all dependencies are met •Configure out application to utilize Doctrine •Create an Entity (in our library) with Annotations •Generate the Database •Generate Proxies + Repositories •Create a Controller for basic crud •Talk about what would happen next
92
Zend Framework 1 + Doctrine 2
Where To Get The Code •http://github.com/ralphschindler/NOLASnowball Self contained Project Branches:
•master - Clean ZF Project, with ZF embedded in library/ folder •non-model-artifacts - Authentication service, Login form •doctrine2-managed – Has following libraries: Doctrine2, Symfony (Copied into library), Bisna (ZF1 + Doctrine2 Integration library) – Has the following entity created: NOLASnowball\Entity\Stand – Has the proper application.ini settings – Has scripts/doctrine.php setup for easy use
•doctrine2-managed-crud – Created Stand Controller, actions are complete, view scripts complete – Proxies & Repositories are generated – Assumes you’ve generated the SQL (locally would need to change db credentials) 93
Zend Framework 1 + Doctrine 2
Lets look at code! •Demo time
94
Zend Framework 1 + Doctrine 2
Exercises and Things To Implement •flush() could be a postDispatch() function call •All interaction with Entities could be moved into a Service Layer Once such implementation: https://github.com/guilhermeblanco/
ZF1-Doctrine2-ServiceLayer
•Add relationships, and alter forms accordingly
95
Zend Framework 1 + Doctrine 2
Recommended Reading •Doctrine Website & Manual (Also has download) http://www.doctrine-project.org/
•Zend Framework Web & Manual (With download) http://framework.zend.com/
•Ralph Schindler’s Sample Application https://github.com/ralphschindler/NOLASnowball
•Guilherme Blanco’s ZF1 + D2 Integration code https://github.com/guilhermeblanco/ZendFramework1-Doctrine2
96
Zend Framework 1 + Doctrine 2
Questions? •Q & A Time
97
Zend Framework 1 + Doctrine 2