Successfully reported this slideshow.
Your SlideShare is downloading. ×

Doctrine in the Real World

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Loading in …3
×

Check these out next

1 of 93 Ad

Doctrine in the Real World

Download to read offline

Real world Doctrine examples from http://shopopensky.com and http://sociallynotable.com

Real world Doctrine examples from http://shopopensky.com and http://sociallynotable.com

Advertisement
Advertisement

More Related Content

Advertisement

Doctrine in the Real World

  1. 1. doctrine Doctrine in the Real World Real world examples Tuesday, February 8, 2011
  2. 2. My name is Jonathan H. Wage Tuesday, February 8, 2011
  3. 3. • PHP Developer for 10+ years • Long time Symfony and Doctrine contributor • Published Author • Entrepreneur • Currently living in Nashville, Tennessee Tuesday, February 8, 2011
  4. 4. I work full-time for OpenSky http://shopopensky.com Tuesday, February 8, 2011
  5. 5. Previously employed by SensioLabs Tuesday, February 8, 2011
  6. 6. What is OpenSky? Tuesday, February 8, 2011
  7. 7. 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. Tuesday, February 8, 2011
  8. 8. We Love OpenSource • PHP 5.3 • Apache2 • Symfony2 • Doctrine2 • jQuery • mule, stomp, hornetq • MongoDB • nginx • varnish Tuesday, February 8, 2011
  9. 9. We don’t just use open source projects Tuesday, February 8, 2011
  10. 10. We help build them Tuesday, February 8, 2011
  11. 11. OpenSky has some of the top committers in Symfony2 and other projects Tuesday, February 8, 2011
  12. 12. Symfony2 OpenSky Committers • 65 Kris Wallsmith • 52 Jonathan H. Wage • 36 Jeremy Mikola • 36 Bulat Shakirzyanov •6 Justin Hileman Tuesday, February 8, 2011
  13. 13. Doctrine MongoDB Committers • 39 Jonathan H. Wage • 11 Bulat Shakirzyanov • 2 Kris Wallsmith Tuesday, February 8, 2011
  14. 14. MongoDB ODM Committers • 349 Jonathan H. Wage • 226 Bulat Shakirzyanov • 17 Kris Wallsmith • 13 Steven Surowiec • 2 Jeremy Mikola Tuesday, February 8, 2011
  15. 15. Sorry to bore you Tuesday, February 8, 2011
  16. 16. Moving on to the stuff you came here for Tuesday, February 8, 2011
  17. 17. OpenSky uses Doctrine ORM and ODM Tuesday, February 8, 2011
  18. 18. Why? Tuesday, February 8, 2011
  19. 19. We are an eCommerce site Tuesday, February 8, 2011
  20. 20. Actions involving commerce need transactions Tuesday, February 8, 2011
  21. 21. ORM and MySQL • Order • OrderTransaction • OrderShipment Tuesday, February 8, 2011
  22. 22. ODM and MongoDB • Product • Seller • Supplier • User • ... basically everything else that is not involving $$$ and transactions Tuesday, February 8, 2011
  23. 23. Blending the Two Tuesday, February 8, 2011
  24. 24. Defining our Product Document Tuesday, February 8, 2011
  25. 25. /** @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; } } Tuesday, February 8, 2011
  26. 26. Defining our Order Entity Tuesday, February 8, 2011
  27. 27. /** * @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; // ... } Tuesday, February 8, 2011
  28. 28. Setting the Product public function setProduct(Product $product) { $this->productId = $product->getId(); $this->product = $product; } Tuesday, February 8, 2011
  29. 29. • $productId is mapped and persisted • but $product which stores the Product instance is not a persistent entity property Tuesday, February 8, 2011
  30. 30. Order has a reference to product? • How? • Order is an ORM entity stored in MySQL • and Product is an ODM document stored in MongoDB Tuesday, February 8, 2011
  31. 31. Loading Product ODM reference in Order Entity Tuesday, February 8, 2011
  32. 32. Lifecycle Events to the Rescue Tuesday, February 8, 2011
  33. 33. 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 manager Tuesday, February 8, 2011
  34. 34. Add EventListener $eventListener = new OrderPostLoadListener($dm); $eventManager = $em->getEventManager(); $eventManager->addEventListener( array(DoctrineORMEvents::postLoad), $eventListener ); Tuesday, February 8, 2011
  35. 35. 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> Tuesday, February 8, 2011
  36. 36. 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); } } Tuesday, February 8, 2011
  37. 37. 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); Tuesday, February 8, 2011
  38. 38. Seamless • Documents and Entities play together like best friends • Because Doctrine persistence remains transparent from your domain this is possible Tuesday, February 8, 2011
  39. 39. 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 ) ) Tuesday, February 8, 2011
  40. 40. 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/ Tuesday, February 8, 2011
  41. 41. MongoDB ODM SoftDelete Functionality Tuesday, February 8, 2011
  42. 42. I like my deletes soft, not hard Tuesday, February 8, 2011
  43. 43. Why? Tuesday, February 8, 2011
  44. 44. Deleting data is dangerous business Tuesday, February 8, 2011
  45. 45. Flickr accidentally deleted a pro members account and 4000 pictures Tuesday, February 8, 2011
  46. 46. They were able to restore it later but it took some time Tuesday, February 8, 2011
  47. 47. Instead of deleting, simply set a deletedAt field Tuesday, February 8, 2011
  48. 48. 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-softdelete Tuesday, February 8, 2011
  49. 49. Autoload Extension $loader = new UniversalClassLoader(); $loader->registerNamespaces(array( // ... 'DoctrineODMMongoDBSoftDelete' => __DIR__.'/vendor/doctrine-mongodb-odm- softdelete/lib', )); $loader->register(); Tuesday, February 8, 2011
  50. 50. 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); Tuesday, February 8, 2011
  51. 51. 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-bundle Tuesday, February 8, 2011
  52. 52. Autoload the Bundle $loader = new UniversalClassLoader(); $loader->registerNamespaces(array( // ... 'DoctrineODMMongoDBSymfonySoftDeleteBundle' => __DIR__.'/vendor/doctrine- mongodb-odm-softdelete-bundle', )); $loader->register(); Tuesday, February 8, 2011
  53. 53. Register the Bundle public function registerBundles() { $bundles = array( // ... // register doctrine symfony bundles new DoctrineODMMongoDBSymfonySoftDeleteBundleSoftDeleteBundle() ); // ... return $bundles; } Tuesday, February 8, 2011
  54. 54. Enable the Bundle // app/config/config.yml doctrine_mongodb_softdelete.config: ~ Tuesday, February 8, 2011
  55. 55. SoftDeleteManager $sdm = $container->get('doctrine.odm.mongodb.soft_delete.manager'); Tuesday, February 8, 2011
  56. 56. SoftDeleteable ODM Documents must implement this interface interface SoftDeleteable { function getDeletedAt(); function isDeleted(); } Tuesday, February 8, 2011
  57. 57. User implements SoftDeletable /** @mongodb:Document */ class User implements SoftDeleteable { /** @mongodb:Date @mongodb:Index */ private $deletedAt; public function getDeletedAt() { return $this->deletedAt; } public function isDeleted() { return $this->deletedAt !== null ? true : false; } } Tuesday, February 8, 2011
  58. 58. 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(); Tuesday, February 8, 2011
  59. 59. Query Executed db.users.update( { _id : { $in : [new ObjectId('1234567891011123456')] } }, { $set : { deletedAt: new Date() } } ) Tuesday, February 8, 2011
  60. 60. Restore a User // now again later we can restore that same user $user = $dm->getRepository('User')->findOneByUsername('jwage'); $sdm->restore($user); $sdm->flush(); Tuesday, February 8, 2011
  61. 61. Query Executed db.users.update( { _id : { $in : [new ObjectId('1234567891011123456')] } }, { $unset : { deletedAt: true } } ) Tuesday, February 8, 2011
  62. 62. Limit cursors to only show non deleted users $qb = $dm->createQueryBuilder('User') ->field('deletedAt')->exists(false); $query = $qb->getQuery(); $users = $query->execute(); Tuesday, February 8, 2011
  63. 63. Get only deleted users $qb = $dm->createQueryBuilder('User') ->field('deletedAt')->exists(true); $query = $qb->getQuery(); $users = $query->execute(); Tuesday, February 8, 2011
  64. 64. 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(); Tuesday, February 8, 2011
  65. 65. 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); Tuesday, February 8, 2011
  66. 66. Symfony2 and supervisor http://supervisord.org/ Tuesday, February 8, 2011
  67. 67. What is supervisor? Tuesday, February 8, 2011
  68. 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.org Tuesday, February 8, 2011
  69. 69. Daemonize a Symfony2 Console Command with supervisor Tuesday, February 8, 2011
  70. 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? Tuesday, February 8, 2011
  71. 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-mail Tuesday, February 8, 2011
  72. 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; // ... } Tuesday, February 8, 2011
  73. 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" --collection Tuesday, February 8, 2011
  74. 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(); // ... } // ... } Tuesday, February 8, 2011
  75. 75. The Daemon Console Command • You can find the console command code to use to tail a cursor here: • https://gist.github.com/812942 Tuesday, February 8, 2011
  76. 76. 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 users Tuesday, February 8, 2011
  77. 77. 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(); } } Tuesday, February 8, 2011
  78. 78. 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) { } } Tuesday, February 8, 2011
  79. 79. 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); } Tuesday, February 8, 2011
  80. 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. Tuesday, February 8, 2011
  81. 81. Install supervisor http://supervisord.org/installing.html $ easy_install supervisor Tuesday, February 8, 2011
  82. 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.conf r [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 Tuesday, February 8, 2011
  83. 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 $ supervisord Tuesday, February 8, 2011
  84. 84. Where do I use supervisor? • sociallynotable.com • Indexes tweets with links to Amazon.com products • Maintains statistics on each product and lets you shop the popular products each day Tuesday, February 8, 2011
  85. 85. sn:watch-twitter console command Tuesday, February 8, 2011
  86. 86. I can manually start it Tuesday, February 8, 2011
  87. 87. But, what if it crashes or stops unexpectedly? Tuesday, February 8, 2011
  88. 88. This is exactly what supervisor is for Tuesday, February 8, 2011
  89. 89. Setup a configuration profile for supervisor and it will ensure the console command is always running Tuesday, February 8, 2011
  90. 90. [program:watch-twitter] numprocs=1 startretries=100000000000 directory=/ stdout_logfile=/var/www/vhosts/sociallynotable.com/socially-notable/sociallynotable/logs/watch-twitter-supervisord.log autostart=true autorestart=true user=root command=/usr/local/bin/php /var/www/vhosts/sociallynotable.com/socially-notable/sociallynotable/console sn:watch-twitter Tuesday, February 8, 2011
  91. 91. Now when I start supervisor the twitter watcher will always remain running. Even if I kill the pid myself, it will start back up. Tuesday, February 8, 2011
  92. 92. Thanks! I hope this presentation was useful to you! Tuesday, February 8, 2011
  93. 93. Questions? - http://jwage.com - http://shopopensky.com - http://sociallynotable.com - http://twitter.com/jwage - http://facebook.com/jwage - http://about.me/jwage Tuesday, February 8, 2011

×