Doctrine In The Real World sflive2011 Paris
Upcoming SlideShare
Loading in...5
×
 

Doctrine In The Real World sflive2011 Paris

on

  • 8,666 views

 

Statistics

Views

Total Views
8,666
Views on SlideShare
5,225
Embed Views
3,441

Actions

Likes
17
Downloads
130
Comments
0

19 Embeds 3,441

http://www.lafermeduweb.net 2431
http://www.developpez.net 419
http://www.symfony.es 252
http://inmedics.dev 135
http://blogdwich.fr 133
http://staging.inmedics.com 19
http://www.emmanuelpereira.com 15
http://coderwall.com 10
http://inmedics.com 8
http://webcache.googleusercontent.com 5
http://translate.googleusercontent.com 3
http://www.sfexception.com 3
http://symfony2developer.com 2
http://static.slidesharecdn.com 1
http://www.mefeedia.com 1
http://paper.li 1
http://twitter.com 1
http://dev.symfony2developer.com 1
https://www.linkedin.com 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…
Post Comment
Edit your comment

Doctrine In The Real World sflive2011 Paris Doctrine In The Real World sflive2011 Paris Presentation Transcript

  • doctrine Doctrine in the Real World Real world examplesFriday, March 4, 2011
  • My name is Jonathan H. WageFriday, March 4, 2011
  • • PHP Developer for 10+ years • Long time Symfony and Doctrine contributor • Published Author • Entrepreneur • Currently living in Nashville, TennesseeFriday, March 4, 2011
  • Previously employed by SensioLabsFriday, March 4, 2011
  • http://mongodbhosting.com Partnered with ServerGrove MongoDB HostingFriday, March 4, 2011
  • Today, I work full-time for OpenSky http://shopopensky.comFriday, March 4, 2011
  • What is OpenSky?Friday, March 4, 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.Friday, March 4, 2011
  • OpenSky Loves OpenSource • PHP 5.3 • Apache2 • Symfony2 • Doctrine2 • jQuery • mule, stomp, hornetq • MongoDB • nginx • varnishFriday, March 4, 2011
  • We don’t just use open source projectsFriday, March 4, 2011
  • We help build themFriday, March 4, 2011
  • OpenSky has some of the top committers in Symfony2 and other projectsFriday, March 4, 2011
  • Symfony2 OpenSky Committers • 65 Kris Wallsmith • 52 Jonathan H. Wage • 36 Jeremy Mikola • 36 Bulat Shakirzyanov •6 Justin HilemanFriday, March 4, 2011
  • Doctrine MongoDB Committers • 39 Jonathan H. Wage • 11 Bulat Shakirzyanov • 2 Kris WallsmithFriday, March 4, 2011
  • MongoDB ODM Committers • 349 Jonathan H. Wage • 226 Bulat Shakirzyanov • 17 Kris Wallsmith • 13 Steven Surowiec • 2 Jeremy MikolaFriday, March 4, 2011
  • OpenSky uses both the Doctrine ORM and ODMFriday, March 4, 2011
  • Why?Friday, March 4, 2011
  • We are an eCommerce siteFriday, March 4, 2011
  • Actions involving commerce need transactionsFriday, March 4, 2011
  • ORM and MySQL • Order • OrderTransaction • OrderShipmentFriday, March 4, 2011
  • ODM and MongoDB • Product • Seller • Supplier • User • ... basically everything else that is not involving $$$ and transactionsFriday, March 4, 2011
  • Blending the TwoFriday, March 4, 2011
  • Defining our Product DocumentFriday, March 4, 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; } }Friday, March 4, 2011
  • Defining our Order EntityFriday, March 4, 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; // ... }Friday, March 4, 2011
  • Setting the Product public function setProduct(Product $product) { $this->productId = $product->getId(); $this->product = $product; }Friday, March 4, 2011
  • • $productId is mapped and persisted • but $product which stores the Product instance is not a persistent entity propertyFriday, March 4, 2011
  • 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
  • Loading Product ODM reference in Order EntityFriday, March 4, 2011
  • Lifecycle Events to the RescueFriday, March 4, 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 managerFriday, March 4, 2011
  • Add EventListener $eventListener = new OrderPostLoadListener($dm); $eventManager = $em->getEventManager(); $eventManager->addEventListener( array(DoctrineORMEvents::postLoad), $eventListener );Friday, March 4, 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>Friday, March 4, 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); } }Friday, March 4, 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);Friday, March 4, 2011
  • Seamless • Documents and Entities play together like best friends • Because Doctrine persistence remains transparent from your domain this is possibleFriday, March 4, 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 ) )Friday, March 4, 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/Friday, March 4, 2011
  • MongoDB ODM SoftDelete FunctionalityFriday, March 4, 2011
  • I like my deletes soft, not hardFriday, March 4, 2011
  • Why?Friday, March 4, 2011
  • Deleting data is dangerous businessFriday, March 4, 2011
  • Flickr accidentally deleted a pro members account and 5000 picturesFriday, March 4, 2011
  • They were able to restore it later but it took some timeFriday, March 4, 2011
  • Instead of deleting, simply set a deletedAt fieldFriday, March 4, 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-softdeleteFriday, March 4, 2011
  • Autoload Extension $loader = new UniversalClassLoader(); $loader->registerNamespaces(array( // ... DoctrineODMMongoDBSoftDelete => __DIR__./vendor/doctrine-mongodb-odm- softdelete/lib, )); $loader->register();Friday, March 4, 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);Friday, March 4, 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-bundleFriday, March 4, 2011
  • Autoload the Bundle $loader = new UniversalClassLoader(); $loader->registerNamespaces(array( // ... DoctrineODMMongoDBSymfonySoftDeleteBundle => __DIR__./vendor/doctrine- mongodb-odm-softdelete-bundle, )); $loader->register();Friday, March 4, 2011
  • Register the Bundle public function registerBundles() { $bundles = array( // ... // register doctrine symfony bundles new DoctrineODMMongoDBSymfonySoftDeleteBundleSoftDeleteBundle() ); // ... return $bundles; }Friday, March 4, 2011
  • Enable the Bundle // app/config/config.yml doctrine_mongodb_softdelete.config: ~Friday, March 4, 2011
  • SoftDeleteManager $sdm = $container->get(doctrine.odm.mongodb.soft_delete.manager);Friday, March 4, 2011
  • SoftDeleteable ODM Documents must implement this interface interface SoftDeleteable { function getDeletedAt(); }Friday, March 4, 2011
  • 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
  • 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
  • Query Executed db.users.update( { _id : { $in : [new ObjectId(1234567891011123456)] } }, { $set : { deletedAt: new Date() } } )Friday, March 4, 2011
  • 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
  • Query Executed db.users.update( { _id : { $in : [new ObjectId(1234567891011123456)] } }, { $unset : { deletedAt: true } } )Friday, March 4, 2011
  • 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
  • Get only deleted users $qb = $dm->createQueryBuilder(User) ->field(deletedAt)->exists(true); $query = $qb->getQuery(); $users = $query->execute();Friday, March 4, 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();Friday, March 4, 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);Friday, March 4, 2011
  • PHP DaemonsFriday, March 4, 2011
  • Symfony2 and supervisor http://supervisord.org/Friday, March 4, 2011
  • What is supervisor?Friday, March 4, 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.orgFriday, March 4, 2011
  • Daemonize a Symfony2 Console Command with supervisorFriday, March 4, 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?Friday, March 4, 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-mailFriday, March 4, 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; // ... }Friday, March 4, 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" --collectionFriday, March 4, 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(); // ... } // ... }Friday, March 4, 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 usersFriday, March 4, 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(); } }Friday, March 4, 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) { } }Friday, March 4, 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); }Friday, March 4, 2011
  • Tailable Cursor Bundle https://github.com/doctrine/doctrine-mongodb-odm- tailable-cursor-bundleFriday, March 4, 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.Friday, March 4, 2011
  • Install supervisor http://supervisord.org/installing.html $ easy_install supervisorFriday, March 4, 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 Friday, March 4, 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 $ supervisordFriday, March 4, 2011
  • 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
  • Thanks! I hope this presentation was useful to you!Friday, March 4, 2011
  • 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