Doctrine in the Real World
Upcoming SlideShare
Loading in...5
×
 

Doctrine in the Real World

on

  • 11,101 views

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

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

Statistics

Views

Total Views
11,101
Views on SlideShare
9,710
Embed Views
1,391

Actions

Likes
27
Downloads
204
Comments
1

40 Embeds 1,391

http://symfony2tips.blogspot.com 401
http://www.symfony-project.org 320
http://www.symfony.es 164
http://swik.net 159
http://symfony.com 90
http://www.symfonylab.com 72
http://test.ical.ly 52
http://symfony2tips.blogspot.fr 12
http://static.slidesharecdn.com 12
http://symfony2tips.blogspot.de 10
http://coderwall.com 10
http://symfony2tips.blogspot.co.uk 9
http://webcache.googleusercontent.com 7
http://www.testically.org 7
http://feeds.feedburner.com 6
http://rimzy.net 5
http://symfony2tips.blogspot.pt 5
http://symfony2tips.blogspot.it 4
http://symfony2tips.blogspot.com.es 4
http://symfony2tips.blogspot.nl 4
http://translate.googleusercontent.com 4
http://symfony2tips.blogspot.com.br 3
http://xss.yandex.net 3
http://symfony2developer.com 3
http://symfony2tips.blogspot.dk 2
http://symfony2tips.blogspot.jp 2
http://symfony2tips.blogspot.ru 2
http://symfony2tips.blogspot.in 2
http://symfony2tips.blogspot.co.at 2
http://symfony2tips.blogspot.be 2
http://symfony2tips.blogspot.co.il 2
http://www.sfexception.com 2
http://symfony2tips.blogspot.com.ar 2
http://symfony2tips.blogspot.ch 1
https://twitter.com 1
http://www.symfony2tips.blogspot.com 1
http://symfony2tips.blogspot.se 1
http://www.netvibes.com 1
http://symfony2tips.blogspot.ca 1
http://symfony2tips.blogspot.hu 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • presentacion de como integrear una mysql con mongodb y como daemoniar tareas. muy buena
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Doctrine in the Real World Doctrine in the Real World Presentation Transcript

  • doctrine Doctrine in the Real World Real world examplesTuesday, February 8, 2011
  • My name is Jonathan H. WageTuesday, February 8, 2011
  • • PHP Developer for 10+ years • Long time Symfony and Doctrine contributor • Published Author • Entrepreneur • Currently living in Nashville, TennesseeTuesday, February 8, 2011
  • I work full-time for OpenSky http://shopopensky.comTuesday, February 8, 2011
  • Previously employed by SensioLabsTuesday, February 8, 2011
  • What is OpenSky?Tuesday, February 8, 2011
  • 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
  • We Love OpenSource • PHP 5.3 • Apache2 • Symfony2 • Doctrine2 • jQuery • mule, stomp, hornetq • MongoDB • nginx • varnishTuesday, February 8, 2011
  • We don’t just use open source projectsTuesday, February 8, 2011
  • We help build themTuesday, February 8, 2011
  • OpenSky has some of the top committers in Symfony2 and other projectsTuesday, February 8, 2011
  • Symfony2 OpenSky Committers • 65 Kris Wallsmith • 52 Jonathan H. Wage • 36 Jeremy Mikola • 36 Bulat Shakirzyanov •6 Justin HilemanTuesday, February 8, 2011
  • Doctrine MongoDB Committers • 39 Jonathan H. Wage • 11 Bulat Shakirzyanov • 2 Kris WallsmithTuesday, February 8, 2011
  • MongoDB ODM Committers • 349 Jonathan H. Wage • 226 Bulat Shakirzyanov • 17 Kris Wallsmith • 13 Steven Surowiec • 2 Jeremy MikolaTuesday, February 8, 2011
  • Sorry to bore youTuesday, February 8, 2011
  • Moving on to the stuff you came here forTuesday, February 8, 2011
  • OpenSky uses Doctrine ORM and ODMTuesday, February 8, 2011
  • Why?Tuesday, February 8, 2011
  • We are an eCommerce siteTuesday, February 8, 2011
  • Actions involving commerce need transactionsTuesday, February 8, 2011
  • ORM and MySQL • Order • OrderTransaction • OrderShipmentTuesday, February 8, 2011
  • ODM and MongoDB • Product • Seller • Supplier • User • ... basically everything else that is not involving $$$ and transactionsTuesday, February 8, 2011
  • Blending the TwoTuesday, February 8, 2011
  • Defining our Product DocumentTuesday, February 8, 2011
  • /** @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
  • Defining our Order EntityTuesday, February 8, 2011
  • /** * @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
  • Setting the Product public function setProduct(Product $product) { $this->productId = $product->getId(); $this->product = $product; }Tuesday, February 8, 2011
  • • $productId is mapped and persisted • but $product which stores the Product instance is not a persistent entity propertyTuesday, February 8, 2011
  • Order has a reference to product? • How? • Order is an ORM entity stored in MySQL • and Product is an ODM document stored in MongoDBTuesday, February 8, 2011
  • Loading Product ODM reference in Order EntityTuesday, February 8, 2011
  • Lifecycle Events to the RescueTuesday, February 8, 2011
  • 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 managerTuesday, February 8, 2011
  • Add EventListener $eventListener = new OrderPostLoadListener($dm); $eventManager = $em->getEventManager(); $eventManager->addEventListener( array(DoctrineORMEvents::postLoad), $eventListener );Tuesday, February 8, 2011
  • 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
  • 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
  • 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
  • Seamless • Documents and Entities play together like best friends • Because Doctrine persistence remains transparent from your domain this is possibleTuesday, February 8, 2011
  • 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
  • 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
  • MongoDB ODM SoftDelete FunctionalityTuesday, February 8, 2011
  • I like my deletes soft, not hardTuesday, February 8, 2011
  • Why?Tuesday, February 8, 2011
  • Deleting data is dangerous businessTuesday, February 8, 2011
  • Flickr accidentally deleted a pro members account and 4000 picturesTuesday, February 8, 2011
  • They were able to restore it later but it took some timeTuesday, February 8, 2011
  • Instead of deleting, simply set a deletedAt fieldTuesday, February 8, 2011
  • 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-softdeleteTuesday, February 8, 2011
  • Autoload Extension $loader = new UniversalClassLoader(); $loader->registerNamespaces(array( // ... DoctrineODMMongoDBSoftDelete => __DIR__./vendor/doctrine-mongodb-odm- softdelete/lib, )); $loader->register();Tuesday, February 8, 2011
  • 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
  • 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-bundleTuesday, February 8, 2011
  • Autoload the Bundle $loader = new UniversalClassLoader(); $loader->registerNamespaces(array( // ... DoctrineODMMongoDBSymfonySoftDeleteBundle => __DIR__./vendor/doctrine- mongodb-odm-softdelete-bundle, )); $loader->register();Tuesday, February 8, 2011
  • Register the Bundle public function registerBundles() { $bundles = array( // ... // register doctrine symfony bundles new DoctrineODMMongoDBSymfonySoftDeleteBundleSoftDeleteBundle() ); // ... return $bundles; }Tuesday, February 8, 2011
  • Enable the Bundle // app/config/config.yml doctrine_mongodb_softdelete.config: ~Tuesday, February 8, 2011
  • SoftDeleteManager $sdm = $container->get(doctrine.odm.mongodb.soft_delete.manager);Tuesday, February 8, 2011
  • SoftDeleteable ODM Documents must implement this interface interface SoftDeleteable { function getDeletedAt(); function isDeleted(); }Tuesday, February 8, 2011
  • 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
  • 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
  • Query Executed db.users.update( { _id : { $in : [new ObjectId(1234567891011123456)] } }, { $set : { deletedAt: new Date() } } )Tuesday, February 8, 2011
  • 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
  • Query Executed db.users.update( { _id : { $in : [new ObjectId(1234567891011123456)] } }, { $unset : { deletedAt: true } } )Tuesday, February 8, 2011
  • 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
  • Get only deleted users $qb = $dm->createQueryBuilder(User) ->field(deletedAt)->exists(true); $query = $qb->getQuery(); $users = $query->execute();Tuesday, February 8, 2011
  • 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
  • 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
  • Symfony2 and supervisor http://supervisord.org/Tuesday, February 8, 2011
  • What is supervisor?Tuesday, February 8, 2011
  • 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.orgTuesday, February 8, 2011
  • Daemonize a Symfony2 Console Command with supervisorTuesday, February 8, 2011
  • 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
  • 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-mailTuesday, February 8, 2011
  • 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
  • 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" --collectionTuesday, February 8, 2011
  • 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
  • The Daemon Console Command • You can find the console command code to use to tail a cursor here: • https://gist.github.com/812942Tuesday, February 8, 2011
  • 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 usersTuesday, February 8, 2011
  • 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
  • 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
  • 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
  • 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
  • Install supervisor http://supervisord.org/installing.html $ easy_install supervisorTuesday, February 8, 2011
  • 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 Tuesday, February 8, 2011
  • 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 $ supervisordTuesday, February 8, 2011
  • 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 dayTuesday, February 8, 2011
  • sn:watch-twitter console commandTuesday, February 8, 2011
  • I can manually start itTuesday, February 8, 2011
  • But, what if it crashes or stops unexpectedly?Tuesday, February 8, 2011
  • This is exactly what supervisor is forTuesday, February 8, 2011
  • Setup a configuration profile for supervisor and it will ensure the console command is always runningTuesday, February 8, 2011
  • [program:watch-twitter]numprocs=1startretries=100000000000directory=/stdout_logfile=/var/www/vhosts/sociallynotable.com/socially-notable/sociallynotable/logs/watch-twitter-supervisord.logautostart=trueautorestart=trueuser=rootcommand=/usr/local/bin/php /var/www/vhosts/sociallynotable.com/socially-notable/sociallynotable/console sn:watch-twitterTuesday, February 8, 2011
  • 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
  • Thanks! I hope this presentation was useful to you!Tuesday, February 8, 2011
  • Questions? - http://jwage.com - http://shopopensky.com - http://sociallynotable.com - http://twitter.com/jwage - http://facebook.com/jwage - http://about.me/jwageTuesday, February 8, 2011