+ All Categories
Home > Technology > Doctrine In The Real World sflive2011 Paris

Doctrine In The Real World sflive2011 Paris

Date post: 06-May-2015
Category:
Upload: jonathan-wage
View: 8,423 times
Download: 0 times
Share this document with a friend
86
Doctrine in the Real World Real world examples doctrine Friday, March 4, 2011
Transcript
Page 1: Doctrine In The Real World sflive2011 Paris

Doctrine in the Real WorldReal world examples

doctrine

Friday, March 4, 2011

Page 2: Doctrine In The Real World sflive2011 Paris

My name isJonathan H. Wage

Friday, March 4, 2011

Page 3: Doctrine In The Real World sflive2011 Paris

• PHP Developer for 10+ years

• Long time Symfony and Doctrine contributor

• Published Author

• Entrepreneur

• Currently living in Nashville, Tennessee

Friday, March 4, 2011

Page 4: Doctrine In The Real World sflive2011 Paris

Previously employed by SensioLabs

Friday, March 4, 2011

Page 5: Doctrine In The Real World sflive2011 Paris

Partnered with ServerGrove

MongoDB Hosting

http://mongodbhosting.com

Friday, March 4, 2011

Page 6: Doctrine In The Real World sflive2011 Paris

Today, I work full-time for OpenSky

http://shopopensky.com

Friday, March 4, 2011

Page 7: Doctrine In The Real World sflive2011 Paris

What is OpenSky?

Friday, March 4, 2011

Page 8: Doctrine In The Real World sflive2011 Paris

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

Page 9: Doctrine In The Real World sflive2011 Paris

OpenSky Loves OpenSource

• PHP 5.3

• Apache2

• Symfony2

• Doctrine2

• jQuery

• mule, stomp, hornetq

• MongoDB

• nginx

• varnish

Friday, March 4, 2011

Page 10: Doctrine In The Real World sflive2011 Paris

We don’t just use open source projects

Friday, March 4, 2011

Page 11: Doctrine In The Real World sflive2011 Paris

We help build them

Friday, March 4, 2011

Page 12: Doctrine In The Real World sflive2011 Paris

OpenSky has some of the top committers in Symfony2 and other

projects

Friday, March 4, 2011

Page 13: Doctrine In The Real World sflive2011 Paris

Symfony2 OpenSky Committers

• 65 Kris Wallsmith

• 52 Jonathan H. Wage

• 36 Jeremy Mikola

• 36 Bulat Shakirzyanov

• 6 Justin Hileman

Friday, March 4, 2011

Page 14: Doctrine In The Real World sflive2011 Paris

Doctrine MongoDB Committers

• 39 Jonathan H. Wage

• 11 Bulat Shakirzyanov

• 2 Kris Wallsmith

Friday, March 4, 2011

Page 15: Doctrine In The Real World sflive2011 Paris

MongoDB ODM Committers

• 349 Jonathan H. Wage

• 226 Bulat Shakirzyanov

• 17 Kris Wallsmith

• 13 Steven Surowiec

• 2 Jeremy Mikola

Friday, March 4, 2011

Page 16: Doctrine In The Real World sflive2011 Paris

OpenSky uses both the Doctrine ORM and ODM

Friday, March 4, 2011

Page 17: Doctrine In The Real World sflive2011 Paris

Why?

Friday, March 4, 2011

Page 18: Doctrine In The Real World sflive2011 Paris

We are an eCommerce site

Friday, March 4, 2011

Page 19: Doctrine In The Real World sflive2011 Paris

Actions involving commerce need

transactions

Friday, March 4, 2011

Page 20: Doctrine In The Real World sflive2011 Paris

ORM and MySQL

• Order

• Order\Transaction

• Order\Shipment

Friday, March 4, 2011

Page 21: Doctrine In The Real World sflive2011 Paris

ODM and MongoDB

• Product

• Seller

• Supplier

• User

• ... basically everything else that is not involving $$$ and transactions

Friday, March 4, 2011

Page 22: Doctrine In The Real World sflive2011 Paris

Blending the Two

Friday, March 4, 2011

Page 23: Doctrine In The Real World sflive2011 Paris

Defining our Product Document

Friday, March 4, 2011

Page 24: Doctrine In The Real World sflive2011 Paris

/** @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

Page 25: Doctrine In The Real World sflive2011 Paris

Defining our Order Entity

Friday, March 4, 2011

Page 26: Doctrine In The Real World sflive2011 Paris

/** * @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 Documents\Product */ private $product; // ...}

Friday, March 4, 2011

Page 27: Doctrine In The Real World sflive2011 Paris

Setting the Product

public function setProduct(Product $product){ $this->productId = $product->getId(); $this->product = $product;}

Friday, March 4, 2011

Page 28: Doctrine In The Real World sflive2011 Paris

• $productId is mapped and persisted

• but $product which stores the Product instance is not a persistent entity property

Friday, March 4, 2011

Page 29: Doctrine In The Real World sflive2011 Paris

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

Page 30: Doctrine In The Real World sflive2011 Paris

Loading Product ODM reference in Order

Entity

Friday, March 4, 2011

Page 31: Doctrine In The Real World sflive2011 Paris

Lifecycle Events to the Rescue

Friday, March 4, 2011

Page 32: Doctrine In The Real World sflive2011 Paris

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

Page 33: Doctrine In The Real World sflive2011 Paris

$eventListener = new OrderPostLoadListener($dm);$eventManager = $em->getEventManager();$eventManager->addEventListener( array(\Doctrine\ORM\Events::postLoad), $eventListener);

Add EventListener

Friday, March 4, 2011

Page 34: Doctrine In The Real World sflive2011 Paris

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

Page 35: Doctrine In The Real World sflive2011 Paris

OrderPostLoadListeneruse Doctrine\ODM\MongoDB\DocumentManager;use Doctrine\ORM\Event\LifecycleEventArgs;

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:Document\Product', $productId);

// set the product on the order $em = $eventArgs->getEntityManager(); $productReflProp = $em->getClassMetadata('MyBundle:Entity\Order') ->reflClass->getProperty('product'); $productReflProp->setAccessible(true); $productReflProp->setValue($order, $product); }}

Friday, March 4, 2011

Page 36: Doctrine In The Real World sflive2011 Paris

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 databaseecho "Order Title: " . $product->getTitle();print_r($order);

Friday, March 4, 2011

Page 37: Doctrine In The Real World sflive2011 Paris

Seamless

• Documents and Entities play together like best friends

• Because Doctrine persistence remains transparent from your domain this is possible

Friday, March 4, 2011

Page 38: Doctrine In The Real World sflive2011 Paris

Order Object( [id:Entities\Order:private] => 53 [productId:Entities\Order:private] => 4c74a1868ead0ed7a9000000 [product:Entities\Order:private] => Proxies\DocumentProductProxy Object ( [__isInitialized__] => 1 [id:Documents\Product:private] => 4c74a1868ead0ed7a9000000 [title:Documents\Product:private] => Test Product )

)

print_r($order)

Friday, March 4, 2011

Page 40: Doctrine In The Real World sflive2011 Paris

MongoDB ODM SoftDelete Functionality

Friday, March 4, 2011

Page 41: Doctrine In The Real World sflive2011 Paris

I like my deletes soft, not hard

Friday, March 4, 2011

Page 42: Doctrine In The Real World sflive2011 Paris

Why?

Friday, March 4, 2011

Page 43: Doctrine In The Real World sflive2011 Paris

Deleting data is dangerous business

Friday, March 4, 2011

Page 44: Doctrine In The Real World sflive2011 Paris

Flickr accidentally deleted a pro members

account and 5000 pictures

Friday, March 4, 2011

Page 45: Doctrine In The Real World sflive2011 Paris

They were able to restore it later but it

took some time

Friday, March 4, 2011

Page 46: Doctrine In The Real World sflive2011 Paris

Instead of deleting, simply set a deletedAt field

Friday, March 4, 2011

Page 47: Doctrine In The Real World sflive2011 Paris

Install SoftDelete Extension for Doctrine

MongoDB ODM

$ git clone git://github.com/doctrine/mongodb-odm-softdelete src/vendor/doctrine-mongodb-odm-softdelete

http://github.com/doctrine/mongodb-odm-softdelete

Friday, March 4, 2011

Page 48: Doctrine In The Real World sflive2011 Paris

Autoload Extension

$loader = new UniversalClassLoader();$loader->registerNamespaces(array( // ... 'Doctrine\\ODM\\MongoDB\\SoftDelete' => __DIR__.'/vendor/doctrine-mongodb-odm-softdelete/lib',));$loader->register();

Friday, March 4, 2011

Page 49: Doctrine In The Real World sflive2011 Paris

use Doctrine\ODM\MongoDB\SoftDelete\Configuration;

use Doctrine\ODM\MongoDB\SoftDelete\UnitOfWork;use Doctrine\ODM\MongoDB\SoftDelete\SoftDeleteManager;use Doctrine\Common\EventManager;

// $dm is a DocumentManager instance we should already have

$config = new Configuration();$uow = new UnitOfWork($dm, $config);$evm = new EventManager();$sdm = new SoftDeleteManager($dm, $config, $uow, $evm);

Raw PHP Configuration

Friday, March 4, 2011

Page 50: Doctrine In The Real World sflive2011 Paris

Symfony2 Integrationhttp://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

Page 51: Doctrine In The Real World sflive2011 Paris

Autoload the Bundle

$loader = new UniversalClassLoader();$loader->registerNamespaces(array( // ... 'Doctrine\\ODM\\MongoDB\\Symfony\\SoftDeleteBundle' => __DIR__.'/vendor/doctrine-mongodb-odm-softdelete-bundle',));$loader->register();

Friday, March 4, 2011

Page 52: Doctrine In The Real World sflive2011 Paris

Register the Bundle

public function registerBundles(){ $bundles = array( // ...

// register doctrine symfony bundles new Doctrine\ODM\MongoDB\Symfony\SoftDeleteBundle\SoftDeleteBundle() );

// ...

return $bundles;}

Friday, March 4, 2011

Page 53: Doctrine In The Real World sflive2011 Paris

Enable the Bundle

// app/config/config.yml

doctrine_mongodb_softdelete.config: ~

Friday, March 4, 2011

Page 54: Doctrine In The Real World sflive2011 Paris

SoftDeleteManager

$sdm = $container->get('doctrine.odm.mongodb.soft_delete.manager');

Friday, March 4, 2011

Page 55: Doctrine In The Real World sflive2011 Paris

SoftDeleteable

interface SoftDeleteable{ function getDeletedAt();}

ODM Documents must implement this interface

Friday, March 4, 2011

Page 56: Doctrine In The Real World sflive2011 Paris

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

Page 57: Doctrine In The Real World sflive2011 Paris

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

Page 58: Doctrine In The Real World sflive2011 Paris

Query Executed

db.users.update( { _id : { $in : [new ObjectId('1234567891011123456')] } }, { $set : { deletedAt: new Date() } })

Friday, March 4, 2011

Page 59: Doctrine In The Real World sflive2011 Paris

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

Page 60: Doctrine In The Real World sflive2011 Paris

Query Executed

db.users.update( { _id : { $in : [new ObjectId('1234567891011123456')] } }, { $unset : { deletedAt: true } })

Friday, March 4, 2011

Page 61: Doctrine In The Real World sflive2011 Paris

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

Page 62: Doctrine In The Real World sflive2011 Paris

Get only deleted users

$qb = $dm->createQueryBuilder('User') ->field('deletedAt')->exists(true);$query = $qb->getQuery();$users = $query->execute();

Friday, March 4, 2011

Page 63: Doctrine In The Real World sflive2011 Paris

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

Page 64: Doctrine In The Real World sflive2011 Paris

Soft Delete Events

- preDelete- postDelete- preRestore- postRestore

class TestEventSubscriber implements \Doctrine\Common\EventSubscriber{ public function preSoftDelete(LifecycleEventArgs $args) { $document = $args->getDocument(); $sdm = $args->getSoftDeleteManager(); }

public function getSubscribedEvents() { return array(Events::preSoftDelete); }}

$eventSubscriber = new TestEventSubscriber();$evm->addEventSubscriber($eventSubscriber);

Friday, March 4, 2011

Page 65: Doctrine In The Real World sflive2011 Paris

PHP Daemons

Friday, March 4, 2011

Page 66: Doctrine In The Real World sflive2011 Paris

Symfony2 and supervisor

http://supervisord.org/

Friday, March 4, 2011

Page 67: Doctrine In The Real World sflive2011 Paris

What is supervisor?

Friday, March 4, 2011

Page 68: Doctrine In The Real World sflive2011 Paris

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

Page 69: Doctrine In The Real World sflive2011 Paris

Daemonize a Symfony2 Console Command

with supervisor

Friday, March 4, 2011

Page 70: Doctrine In The Real World sflive2011 Paris

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

Page 71: Doctrine In The Real World sflive2011 Paris

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

Page 72: Doctrine In The Real World sflive2011 Paris

Define NewUsernamespace MyCompany\Bundle\MyBundle\Document;

/** * @mongodb:Document(collection={ * "name"="new_users", * "capped"="true", * "size"="100000", * "max"="1000" * }, repositoryClass="MyCompany\Bundle\MyBundle\Document\NewUserRepository") */class NewUser{ /** @mongodb:Id */ private $id;

/** @mongodb:ReferenceOne(targetDocument="User") */ private $user;

/** @mongodb:Boolean @mongodb:Index */ private $isProcessed = false;

// ...}

Friday, March 4, 2011

Page 73: Doctrine In The Real World sflive2011 Paris

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

Page 74: Doctrine In The Real World sflive2011 Paris

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

Page 75: Doctrine In The Real World sflive2011 Paris

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

Page 76: Doctrine In The Real World sflive2011 Paris

findUnProcessed()

• We need the findUnProcessed() method to return the unprocessed cursor to tail

class NewUserRepository extends DocumentRepository{ public function findUnProcessed() { return $this->createQueryBuilder() ->field('isProcessed')->equals(false) ->getQuery() ->execute(); }}

Friday, March 4, 2011

Page 77: Doctrine In The Real World sflive2011 Paris

NewUserProcessor

use Swift_Message;use Symfony\Component\Console\Output\OutputInterface;

class NewUserProcessor{ private $mailer;

public function __construct($mailer) { $this->mailer = $mailer; }

public function process(OutputInterface $output, $document) { }}

We need a service id new_user.processor with a process(OutputInterface $output, $document) method

Friday, March 4, 2011

Page 78: Doctrine In The Real World sflive2011 Paris

Send the e-mailpublic function process(OutputInterface $output, $document){ $user = $document->getUser();

$message = Swift_Message::newInstance() ->setSubject('New Registration') ->setFrom('[email protected]') ->setTo($user->getEmail()) ->setBody('New user registration') ; $this->mailer->send($message);

$document->setIsProcessed(true);}

Friday, March 4, 2011

Page 80: Doctrine In The Real World sflive2011 Paris

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

Page 81: Doctrine In The Real World sflive2011 Paris

Install supervisorhttp://supervisord.org/installing.html

$ easy_install supervisor

Friday, March 4, 2011

Page 82: Doctrine In The Real World sflive2011 Paris

Configure a Profile• We need to configure a profile for supervisor to

know how to run the console command

[program:tail-new-user]numprocs=1

startretries=100directory=/

stdout_logfile=/path/to/symfonyproject/app/tail-new-user-supervisord.logautostart=true

autorestart=trueuser=root

command=/usr/local/bin/php /path/to/symfonyproject/app/console doctrine:mongodb:tail-cursor MyBundle:NewUser

findUnprocessed new_user.processor

[program:tail-new-user]numprocs=1

startretries=100directory=/

stdout_logfile=/path/to/symfonyproject/app/tail-new-user-supervisord.logautostart=true

autorestart=trueuser=root

command=/usr/local/bin/php /path/to/symfonyproject/app/console doctrine:mongodb:tail-cursor MyBundle:NewUser

findUnprocessed new_user.processor[program:tail-new-user]numprocs=1startretries=100directory=/stdout_logfile=/path/to/symfonyproject/app/logs/tail-new-user-supervisord.logautostart=trueautorestart=trueuser=rootcommand=/usr/local/bin/php /path/to/symfonyproject/app/console doctrine:mongodb:tail-cursor MyBundle:NewUser findUnprocessed new_user.processor

$ vi /etc/supervisor/conf.d/tail-new-user.conf

Friday, March 4, 2011

Page 83: Doctrine In The Real World sflive2011 Paris

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

Page 84: Doctrine In The Real World sflive2011 Paris

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

Page 85: Doctrine In The Real World sflive2011 Paris

Thanks!I hope this presentation was useful to you!

Friday, March 4, 2011

Page 86: Doctrine In The Real World sflive2011 Paris

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


Recommended