doctrine
                  Doctrine in the Real World
                          Real world examples




Friday, March 4, 2011
My name is
                        Jonathan H. Wage


Friday, March 4, 2011
• PHP Developer for 10+ years
                        • Long time Symfony and Doctrine
                          contributor

                        • Published Author
                        • Entrepreneur
                        • Currently living in Nashville, Tennessee

Friday, March 4, 2011
Previously employed by
                        SensioLabs


Friday, March 4, 2011
http://mongodbhosting.com

                         Partnered with
                          ServerGrove
                        MongoDB Hosting


Friday, March 4, 2011
Today, I work full-time
                            for OpenSky
                             http://shopopensky.com



Friday, 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

                        •   varnish

Friday, March 4, 2011
We don’t just use open
                      source projects


Friday, March 4, 2011
We help build them



Friday, March 4, 2011
OpenSky has some of
                        the top committers in
                         Symfony2 and other
                               projects

Friday, March 4, 2011
Symfony2 OpenSky
                             Committers
                        • 65   Kris Wallsmith

                        • 52   Jonathan H. Wage

                        • 36   Jeremy Mikola

                        • 36   Bulat Shakirzyanov

                        •6     Justin Hileman




Friday, March 4, 2011
Doctrine MongoDB
                               Committers
                        •   39 Jonathan H. Wage

                        •   11 Bulat Shakirzyanov

                        •   2   Kris Wallsmith




Friday, March 4, 2011
MongoDB ODM
                                  Committers
                        •   349 Jonathan H. Wage

                        •   226 Bulat Shakirzyanov

                        •   17   Kris Wallsmith

                        •   13   Steven Surowiec

                        •   2    Jeremy Mikola




Friday, March 4, 2011
OpenSky uses both the
           Doctrine ORM and ODM


Friday, March 4, 2011
Why?



Friday, March 4, 2011
We are an eCommerce
                           site


Friday, March 4, 2011
Actions involving
                        commerce need
                          transactions


Friday, March 4, 2011
ORM and MySQL

                        • Order
                        • OrderTransaction
                        • OrderShipment


Friday, March 4, 2011
ODM and MongoDB
                        • Product
                        • Seller
                        • Supplier
                        • User
                        • ... basically everything else that is not
                          involving $$$ and transactions


Friday, March 4, 2011
Blending the Two



Friday, March 4, 2011
Defining our Product
                            Document


Friday, 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
                              Entity


Friday, 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 property




Friday, 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 MongoDB




Friday, March 4, 2011
Loading Product ODM
                      reference in Order
                             Entity


Friday, March 4, 2011
Lifecycle Events to the
                                Rescue


Friday, 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 manager

Friday, 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
                          possible




Friday, 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 Functionality


Friday, March 4, 2011
I like my deletes soft,
                               not hard


Friday, March 4, 2011
Why?



Friday, March 4, 2011
Deleting data is
                        dangerous business


Friday, March 4, 2011
Flickr accidentally
                   deleted a pro members
                      account and 5000
                           pictures

Friday, March 4, 2011
They were able to
                        restore it later but it
                          took some time


Friday, March 4, 2011
Instead of deleting, simply
               set a deletedAt field


Friday, 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-softdelete




Friday, 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-bundle




Friday, 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 Daemons



Friday, 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.org


Friday, March 4, 2011
Daemonize a Symfony2
                    Console Command
                      with supervisor



Friday, 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-mail


Friday, 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" --collection



Friday, 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
                                users


Friday, 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-bundle




Friday, 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 supervisor




Friday, 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.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
    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

                                    $ supervisord




Friday, 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 products


Friday, 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.com


Friday, March 4, 2011

Doctrine In The Real World sflive2011 Paris

  • 1.
    doctrine Doctrine in the Real World Real world examples Friday, March 4, 2011
  • 2.
    My name is Jonathan H. Wage Friday, March 4, 2011
  • 3.
    • PHP Developerfor 10+ years • Long time Symfony and Doctrine contributor • Published Author • Entrepreneur • Currently living in Nashville, Tennessee Friday, March 4, 2011
  • 4.
    Previously employed by SensioLabs Friday, March 4, 2011
  • 5.
    http://mongodbhosting.com Partnered with ServerGrove MongoDB Hosting Friday, March 4, 2011
  • 6.
    Today, I workfull-time for OpenSky http://shopopensky.com Friday, March 4, 2011
  • 7.
  • 8.
    A new wayto 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 • varnish Friday, March 4, 2011
  • 10.
    We don’t justuse open source projects Friday, March 4, 2011
  • 11.
    We help buildthem Friday, March 4, 2011
  • 12.
    OpenSky has someof the top committers in Symfony2 and other projects Friday, March 4, 2011
  • 13.
    Symfony2 OpenSky Committers • 65 Kris Wallsmith • 52 Jonathan H. Wage • 36 Jeremy Mikola • 36 Bulat Shakirzyanov •6 Justin Hileman Friday, March 4, 2011
  • 14.
    Doctrine MongoDB Committers • 39 Jonathan H. Wage • 11 Bulat Shakirzyanov • 2 Kris Wallsmith Friday, March 4, 2011
  • 15.
    MongoDB ODM Committers • 349 Jonathan H. Wage • 226 Bulat Shakirzyanov • 17 Kris Wallsmith • 13 Steven Surowiec • 2 Jeremy Mikola Friday, March 4, 2011
  • 16.
    OpenSky uses boththe Doctrine ORM and ODM Friday, March 4, 2011
  • 17.
  • 18.
    We are aneCommerce site Friday, March 4, 2011
  • 19.
    Actions involving commerce need transactions Friday, March 4, 2011
  • 20.
    ORM and MySQL • Order • OrderTransaction • OrderShipment Friday, March 4, 2011
  • 21.
    ODM and MongoDB • Product • Seller • Supplier • User • ... basically everything else that is not involving $$$ and transactions Friday, March 4, 2011
  • 22.
  • 23.
    Defining our Product Document Friday, 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 Entity Friday, 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 ismapped and persisted • but $product which stores the Product instance is not a persistent entity property Friday, March 4, 2011
  • 29.
    Order has areference to product? • How? • Order is an ORM entity stored in MySQL • and Product is an ODM document stored in MongoDB Friday, March 4, 2011
  • 30.
    Loading Product ODM reference in Order Entity Friday, March 4, 2011
  • 31.
    Lifecycle Events tothe Rescue Friday, 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 manager Friday, 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 possible Friday, 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 Functionality Friday, March 4, 2011
  • 41.
    I like mydeletes soft, not hard Friday, March 4, 2011
  • 42.
  • 43.
    Deleting data is dangerous business Friday, March 4, 2011
  • 44.
    Flickr accidentally deleted a pro members account and 5000 pictures Friday, March 4, 2011
  • 45.
    They were ableto restore it later but it took some time Friday, March 4, 2011
  • 46.
    Instead of deleting,simply set a deletedAt field Friday, 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-softdelete Friday, 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-bundle Friday, 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 toonly 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 deletedusers $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.
  • 66.
    Symfony2 and supervisor http://supervisord.org/ Friday, March 4, 2011
  • 67.
  • 68.
    Supervisor is aclient/server system that allows its users to monitor and control a number of processes on UNIX-like operating systems. http://supervisord.org Friday, March 4, 2011
  • 69.
    Daemonize a Symfony2 Console Command with supervisor Friday, 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-mail Friday, 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" --collection Friday, 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 users Friday, 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-bundle Friday, 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 supervisor Friday, 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.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 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 $ supervisord Friday, March 4, 2011
  • 84.
    Where do Iuse supervisor? • http://sociallynotable.com • Keeps daemon running that watches twitter • Indexes tweets with links to amazon products • Maintains tweet statistics and ranks the popular products Friday, 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.com Friday, March 4, 2011