Doctrine In The Real World sflive2011 Paris

  • 8,252 views
Uploaded on

 

More in: Technology , Business
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
8,252
On Slideshare
0
From Embeds
0
Number of Embeds
11

Actions

Shares
Downloads
132
Comments
0
Likes
19

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. doctrine Doctrine in the Real World Real world examplesFriday, March 4, 2011
  • 2. My name is Jonathan H. WageFriday, March 4, 2011
  • 3. • PHP Developer for 10+ years • Long time Symfony and Doctrine contributor • Published Author • Entrepreneur • Currently living in Nashville, TennesseeFriday, March 4, 2011
  • 4. Previously employed by SensioLabsFriday, March 4, 2011
  • 5. http://mongodbhosting.com Partnered with ServerGrove MongoDB HostingFriday, March 4, 2011
  • 6. Today, I work full-time for OpenSky http://shopopensky.comFriday, March 4, 2011
  • 7. What is OpenSky?Friday, March 4, 2011
  • 8. A new way to shop • OpenSky connects you with innovators, trendsetters and tastemakers.You choose the ones you like and each week they invite you to their private online sales.Friday, March 4, 2011
  • 9. OpenSky Loves OpenSource • PHP 5.3 • Apache2 • Symfony2 • Doctrine2 • jQuery • mule, stomp, hornetq • MongoDB • nginx • varnishFriday, March 4, 2011
  • 10. We don’t just use open source projectsFriday, March 4, 2011
  • 11. We help build themFriday, March 4, 2011
  • 12. OpenSky has some of the top committers in Symfony2 and other projectsFriday, March 4, 2011
  • 13. Symfony2 OpenSky Committers • 65 Kris Wallsmith • 52 Jonathan H. Wage • 36 Jeremy Mikola • 36 Bulat Shakirzyanov •6 Justin HilemanFriday, March 4, 2011
  • 14. Doctrine MongoDB Committers • 39 Jonathan H. Wage • 11 Bulat Shakirzyanov • 2 Kris WallsmithFriday, March 4, 2011
  • 15. MongoDB ODM Committers • 349 Jonathan H. Wage • 226 Bulat Shakirzyanov • 17 Kris Wallsmith • 13 Steven Surowiec • 2 Jeremy MikolaFriday, March 4, 2011
  • 16. OpenSky uses both the Doctrine ORM and ODMFriday, March 4, 2011
  • 17. Why?Friday, March 4, 2011
  • 18. We are an eCommerce siteFriday, March 4, 2011
  • 19. Actions involving commerce need transactionsFriday, March 4, 2011
  • 20. ORM and MySQL • Order • OrderTransaction • OrderShipmentFriday, March 4, 2011
  • 21. ODM and MongoDB • Product • Seller • Supplier • User • ... basically everything else that is not involving $$$ and transactionsFriday, March 4, 2011
  • 22. Blending the TwoFriday, March 4, 2011
  • 23. Defining our Product DocumentFriday, March 4, 2011
  • 24. /** @mongodb:Document(collection="products") */ class Product { /** @mongodb:Id */ private $id; /** @mongodb:String */ private $title; public function getId() { return $this->id; } public function getTitle() { return $this->title; } public function setTitle($title) { $this->title = $title; } }Friday, March 4, 2011
  • 25. Defining our Order EntityFriday, March 4, 2011
  • 26. /** * @orm:Entity * @orm:Table(name="orders") * @orm:HasLifecycleCallbacks */ class Order { /** * @orm:Id @orm:Column(type="integer") * @orm:GeneratedValue(strategy="AUTO") */ private $id; /** * @orm:Column(type="string") */ private $productId; /** * @var DocumentsProduct */ private $product; // ... }Friday, March 4, 2011
  • 27. Setting the Product public function setProduct(Product $product) { $this->productId = $product->getId(); $this->product = $product; }Friday, March 4, 2011
  • 28. • $productId is mapped and persisted • but $product which stores the Product instance is not a persistent entity propertyFriday, March 4, 2011
  • 29. Order has a reference to product? • How? • Order is an ORM entity stored in MySQL • and Product is an ODM document stored in MongoDBFriday, March 4, 2011
  • 30. Loading Product ODM reference in Order EntityFriday, March 4, 2011
  • 31. Lifecycle Events to the RescueFriday, March 4, 2011
  • 32. EventManager • Event system is controlled by the EventManager • Central point of event listener system • Listeners are registered on the manager • Events are dispatched through the managerFriday, March 4, 2011
  • 33. Add EventListener $eventListener = new OrderPostLoadListener($dm); $eventManager = $em->getEventManager(); $eventManager->addEventListener( array(DoctrineORMEvents::postLoad), $eventListener );Friday, March 4, 2011
  • 34. In Symfony2 DI <?xml version="1.0" encoding="utf-8" ?> <container xmlns="http://www.symfony-project.org/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/ schema/dic/services/services-1.0.xsd"> <parameters> <parameter key="order.post_load.listener.class">OrderPostLoadListener</parameter> </parameters> <services> <service id="order.post_load.listener" class="%order.post_load.listener.class%" scope="container"> <argument type="service" id="doctrine.odm.mongodb.default_document_manager" /> <tag name="doctrine.orm.default_event_listener" event="postLoad" /> </service> </services> </container>Friday, March 4, 2011
  • 35. OrderPostLoadListener use DoctrineODMMongoDBDocumentManager; use DoctrineORMEventLifecycleEventArgs; class OrderPostLoadListener { public function __construct(DocumentManager $dm) { $this->dm = $dm; } public function postLoad(LifecycleEventArgs $eventArgs) { // get the order entity $order = $eventArgs->getEntity(); // get odm reference to order.product_id $productId = $order->getProductId(); $product = $this->dm->getReference(MyBundle:DocumentProduct, $productId); // set the product on the order $em = $eventArgs->getEntityManager(); $productReflProp = $em->getClassMetadata(MyBundle:EntityOrder) ->reflClass->getProperty(product); $productReflProp->setAccessible(true); $productReflProp->setValue($order, $product); } }Friday, March 4, 2011
  • 36. All Together Now // Create a new product and order $product = new Product(); $product->setTitle(Test Product); $dm->persist($product); $dm->flush(); $order = new Order(); $order->setProduct($product); $em->persist($order); $em->flush(); // Find the order later $order = $em->find(Order, $order->getId()); // Instance of an uninitialized product proxy $product = $order->getProduct(); // Initializes proxy and queries the monogodb database echo "Order Title: " . $product->getTitle(); print_r($order);Friday, March 4, 2011
  • 37. Seamless • Documents and Entities play together like best friends • Because Doctrine persistence remains transparent from your domain this is possibleFriday, March 4, 2011
  • 38. print_r($order) Order Object ( [id:EntitiesOrder:private] => 53 [productId:EntitiesOrder:private] => 4c74a1868ead0ed7a9000000 [product:EntitiesOrder:private] => ProxiesDocumentProductProxy Object ( [__isInitialized__] => 1 [id:DocumentsProduct:private] => 4c74a1868ead0ed7a9000000 [title:DocumentsProduct:private] => Test Product ) )Friday, March 4, 2011
  • 39. Example from Blog • This example was first written on my personal blog http://jwage.com • You can read the blog post here http:// jwage.com/2010/08/25/blending-the- doctrine-orm-and-mongodb-odm/Friday, March 4, 2011
  • 40. MongoDB ODM SoftDelete FunctionalityFriday, March 4, 2011
  • 41. I like my deletes soft, not hardFriday, March 4, 2011
  • 42. Why?Friday, March 4, 2011
  • 43. Deleting data is dangerous businessFriday, March 4, 2011
  • 44. Flickr accidentally deleted a pro members account and 5000 picturesFriday, March 4, 2011
  • 45. They were able to restore it later but it took some timeFriday, March 4, 2011
  • 46. Instead of deleting, simply set a deletedAt fieldFriday, March 4, 2011
  • 47. Install SoftDelete Extension for Doctrine MongoDB ODM http://github.com/doctrine/mongodb-odm-softdelete $ git clone git://github.com/doctrine/mongodb-odm-softdelete src/ vendor/doctrine-mongodb-odm-softdeleteFriday, March 4, 2011
  • 48. Autoload Extension $loader = new UniversalClassLoader(); $loader->registerNamespaces(array( // ... DoctrineODMMongoDBSoftDelete => __DIR__./vendor/doctrine-mongodb-odm- softdelete/lib, )); $loader->register();Friday, March 4, 2011
  • 49. Raw PHP Configuration use DoctrineODMMongoDBSoftDeleteUnitOfWork; use DoctrineODMMongoDBSoftDeleteSoftDeleteManager; use DoctrineCommonEventManager; // $dm is a DocumentManager instance we should already have use DoctrineODMMongoDBSoftDeleteConfiguration; $config = new Configuration(); $uow = new UnitOfWork($dm, $config); $evm = new EventManager(); $sdm = new SoftDeleteManager($dm, $config, $uow, $evm);Friday, March 4, 2011
  • 50. Symfony2 Integration http://github.com/doctrine/mongodb-odm-softdelete-bundle $ git clone git://github.com/doctrine/mongodb-odm-softdelete-bundle.git src/vendor/doctrine-mongodb-odm-softdelete-bundleFriday, March 4, 2011
  • 51. Autoload the Bundle $loader = new UniversalClassLoader(); $loader->registerNamespaces(array( // ... DoctrineODMMongoDBSymfonySoftDeleteBundle => __DIR__./vendor/doctrine- mongodb-odm-softdelete-bundle, )); $loader->register();Friday, March 4, 2011
  • 52. Register the Bundle public function registerBundles() { $bundles = array( // ... // register doctrine symfony bundles new DoctrineODMMongoDBSymfonySoftDeleteBundleSoftDeleteBundle() ); // ... return $bundles; }Friday, March 4, 2011
  • 53. Enable the Bundle // app/config/config.yml doctrine_mongodb_softdelete.config: ~Friday, March 4, 2011
  • 54. SoftDeleteManager $sdm = $container->get(doctrine.odm.mongodb.soft_delete.manager);Friday, March 4, 2011
  • 55. SoftDeleteable ODM Documents must implement this interface interface SoftDeleteable { function getDeletedAt(); }Friday, March 4, 2011
  • 56. User implements SoftDeletable /** @mongodb:Document */ class User implements SoftDeleteable { /** @mongodb:Date @mongodb:Index */ private $deletedAt; public function getDeletedAt() { return $this->deletedAt; } }Friday, March 4, 2011
  • 57. SoftDelete a User $user = new User(jwage); // ... $dm->persist($user); $dm->flush(); // later we can soft delete the user jwage $user = $dm->getRepository(User)->findOneByUsername(jwage); $sdm->delete($user); $sdm->flush();Friday, March 4, 2011
  • 58. Query Executed db.users.update( { _id : { $in : [new ObjectId(1234567891011123456)] } }, { $set : { deletedAt: new Date() } } )Friday, March 4, 2011
  • 59. Restore a User // now again later we can restore that same user $user = $dm->getRepository(User)->findOneByUsername(jwage); $sdm->restore($user); $sdm->flush();Friday, March 4, 2011
  • 60. Query Executed db.users.update( { _id : { $in : [new ObjectId(1234567891011123456)] } }, { $unset : { deletedAt: true } } )Friday, March 4, 2011
  • 61. Limit cursors to only show non deleted users $qb = $dm->createQueryBuilder(User) ->field(deletedAt)->exists(false); $query = $qb->getQuery(); $users = $query->execute();Friday, March 4, 2011
  • 62. Get only deleted users $qb = $dm->createQueryBuilder(User) ->field(deletedAt)->exists(true); $query = $qb->getQuery(); $users = $query->execute();Friday, March 4, 2011
  • 63. Restore several deleted users $qb = $dm->createQueryBuilder(User) ->field(deletedAt)->exists(true) ->field(createdAt)->gt(new DateTime(-24 hours)); $query = $qb->getQuery(); $users = $query->execute(); foreach ($users as $user) { $sdm->restore($user); } $sdm->flush();Friday, March 4, 2011
  • 64. Soft Delete Events class TestEventSubscriber implements DoctrineCommonEventSubscriber { public function preSoftDelete(LifecycleEventArgs $args) { $document = $args->getDocument(); - preDelete } $sdm = $args->getSoftDeleteManager(); - postDelete public function getSubscribedEvents() { - preRestore } return array(Events::preSoftDelete); - postRestore } $eventSubscriber = new TestEventSubscriber(); $evm->addEventSubscriber($eventSubscriber);Friday, March 4, 2011
  • 65. PHP DaemonsFriday, March 4, 2011
  • 66. Symfony2 and supervisor http://supervisord.org/Friday, March 4, 2011
  • 67. What is supervisor?Friday, March 4, 2011
  • 68. Supervisor is a client/server system that allows its users to monitor and control a number of processes on UNIX-like operating systems. http://supervisord.orgFriday, March 4, 2011
  • 69. Daemonize a Symfony2 Console Command with supervisorFriday, March 4, 2011
  • 70. Scenario • You want to send an e-mail when new users register in your system. • But, sending an e-mail directly from your action introduces a failure point to your stack. • ....What do you do?Friday, March 4, 2011
  • 71. Tailable Cursor • Use a tailable mongodb cursor • Tail a NewUser document collection • Insert NewUser documents from your actions • The daemon will instantly process the NewUser after it is inserted and dispatch the e-mailFriday, March 4, 2011
  • 72. Define NewUser namespace MyCompanyBundleMyBundleDocument; /** * @mongodb:Document(collection={ * "name"="new_users", * "capped"="true", * "size"="100000", * "max"="1000" * }, repositoryClass="MyCompanyBundleMyBundleDocumentNewUserRepository") */ class NewUser { /** @mongodb:Id */ private $id; /** @mongodb:ReferenceOne(targetDocument="User") */ private $user; /** @mongodb:Boolean @mongodb:Index */ private $isProcessed = false; // ... }Friday, March 4, 2011
  • 73. Create Collection • The NewUser collection must be capped in order to tail it so we need to create it. • Luckily, Doctrine has a console command for it. • It will read the mapping information we configured and create the collection $ php app/console doctrine:mongodb:schema:create --class="MyBundle:NewUser" --collectionFriday, March 4, 2011
  • 74. Insert NewUser upon Registration public function register() { // ... $user = new User(); $form = new RegisterForm(register, $user, $validator); $form->bind($request, $user); if ($form->isValid()) { $newUser = new NewUser($user); $dm->persist($newUser); $dm->persist($user); $dm->flush(); // ... } // ... }Friday, March 4, 2011
  • 75. Executing Console Command $ php app/console doctrine:mongodb:tail-cursor MyBundle:NewUser findUnProcessed new_user.processor • The command requires 3 arguments: • document - the name of the document to tail • finder - the repository finder method used to get the cursor • processor - the id of the service used to process the new usersFriday, March 4, 2011
  • 76. findUnProcessed() • We need the findUnProcessed() method to class NewUserRepository extends DocumentRepository { return the unprocessed cursor to tail public function findUnProcessed() { return $this->createQueryBuilder() ->field(isProcessed)->equals(false) ->getQuery() ->execute(); } }Friday, March 4, 2011
  • 77. NewUserProcessor We need a service id new_user.processor with a process(OutputInterface $output, $document) method use Swift_Message; use SymfonyComponentConsoleOutputOutputInterface; class NewUserProcessor { private $mailer; public function __construct($mailer) { $this->mailer = $mailer; } public function process(OutputInterface $output, $document) { } }Friday, March 4, 2011
  • 78. Send the e-mail public function process(OutputInterface $output, $document) { $user = $document->getUser(); $message = Swift_Message::newInstance() ->setSubject(New Registration) ->setFrom(noreply@domain.com) ->setTo($user->getEmail()) ->setBody(New user registration) ; $this->mailer->send($message); $document->setIsProcessed(true); }Friday, March 4, 2011
  • 79. Tailable Cursor Bundle https://github.com/doctrine/doctrine-mongodb-odm- tailable-cursor-bundleFriday, March 4, 2011
  • 80. Daemonization • Now, how do we really daemonize the console command and keep it running 24 hours a day, 7 days a week? • The answer is supervisor, it will allow us to configure a console command for it to manage the process id of and always keep an instance of it running.Friday, March 4, 2011
  • 81. Install supervisor http://supervisord.org/installing.html $ easy_install supervisorFriday, March 4, 2011
  • 82. Configure a Profile • We need to configure a profile for supervisor to know how to run the console command/ $ vi /etc/supervisor/conf.d/tail-new-user.confr [program:tail-new-user] numprocs=1 startretries=100 directory=/ stdout_logfile=/path/to/symfonyproject/app/logs/tail-new-user-supervisord.log autostart=true autorestart=true user=root command=/usr/local/bin/php /path/to/symfonyproject/app/console doctrine:mongodb:tail-cursor MyBundle:NewUser findUnprocessed new_user.processor Friday, March 4, 2011
  • 83. Start supervisord • Start an instance of supervisord • It will run as a daemon in the background • The tail-new-user.conf will always be running $ supervisordFriday, March 4, 2011
  • 84. Where do I use supervisor? • http://sociallynotable.com • Keeps daemon running that watches twitter • Indexes tweets with links to amazon products • Maintains tweet statistics and ranks the popular productsFriday, March 4, 2011
  • 85. Thanks! I hope this presentation was useful to you!Friday, March 4, 2011
  • 86. Questions? - http://jwage.com - http://twitter.com/jwage - http://facebook.com/jwage - http://about.me/jwage - http://shopopensky.com - http://mongodbhosting.com - http://servergrove.com - http://sociallynotable.comFriday, March 4, 2011