1. doctrine
Doctrine in the Real World
Real world examples
Tuesday, February 8, 2011
2. My name is
Jonathan H. Wage
Tuesday, February 8, 2011
3. • PHP Developer for 10+ years
• Long time Symfony and Doctrine
contributor
• Published Author
• Entrepreneur
• Currently living in Nashville, Tennessee
Tuesday, February 8, 2011
4. I work full-time for
OpenSky
http://shopopensky.com
Tuesday, February 8, 2011
7. A new way to shop
• OpenSky connects you with innovators,
trendsetters and tastemakers.You choose
the ones you like and each week they invite
you to their private online sales.
Tuesday, February 8, 2011
8. We Love OpenSource
• PHP 5.3
• Apache2
• Symfony2
• Doctrine2
• jQuery
• mule, stomp, hornetq
• MongoDB
• nginx
• varnish
Tuesday, February 8, 2011
9. We don’t just use open
source projects
Tuesday, February 8, 2011
19. We are an eCommerce
site
Tuesday, February 8, 2011
20. Actions involving
commerce need
transactions
Tuesday, February 8, 2011
21. ORM and MySQL
• Order
• OrderTransaction
• OrderShipment
Tuesday, February 8, 2011
22. ODM and MongoDB
• Product
• Seller
• Supplier
• User
• ... basically everything else that is not
involving $$$ and transactions
Tuesday, February 8, 2011
28. Setting the Product
public function setProduct(Product $product)
{
$this->productId = $product->getId();
$this->product = $product;
}
Tuesday, February 8, 2011
29. • $productId is mapped and persisted
• but $product which stores the Product
instance is not a persistent entity property
Tuesday, February 8, 2011
30. Order has a reference
to product?
• How?
• Order is an ORM entity stored in MySQL
• and Product is an ODM document stored
in MongoDB
Tuesday, February 8, 2011
33. EventManager
• Event system is controlled by the EventManager
• Central point of event listener system
• Listeners are registered on the manager
• Events are dispatched through the manager
Tuesday, February 8, 2011
34. Add EventListener
$eventListener = new OrderPostLoadListener($dm);
$eventManager = $em->getEventManager();
$eventManager->addEventListener(
array(DoctrineORMEvents::postLoad), $eventListener
);
Tuesday, February 8, 2011
36. OrderPostLoadListener
use DoctrineODMMongoDBDocumentManager;
use DoctrineORMEventLifecycleEventArgs;
class OrderPostLoadListener
{
public function __construct(DocumentManager $dm)
{
$this->dm = $dm;
}
public function postLoad(LifecycleEventArgs $eventArgs)
{
// get the order entity
$order = $eventArgs->getEntity();
// get odm reference to order.product_id
$productId = $order->getProductId();
$product = $this->dm->getReference('MyBundle:DocumentProduct', $productId);
// set the product on the order
$em = $eventArgs->getEntityManager();
$productReflProp = $em->getClassMetadata('MyBundle:EntityOrder')
->reflClass->getProperty('product');
$productReflProp->setAccessible(true);
$productReflProp->setValue($order, $product);
}
}
Tuesday, February 8, 2011
37. All Together Now
// Create a new product and order
$product = new Product();
$product->setTitle('Test Product');
$dm->persist($product);
$dm->flush();
$order = new Order();
$order->setProduct($product);
$em->persist($order);
$em->flush();
// Find the order later
$order = $em->find('Order', $order->getId());
// Instance of an uninitialized product proxy
$product = $order->getProduct();
// Initializes proxy and queries the monogodb database
echo "Order Title: " . $product->getTitle();
print_r($order);
Tuesday, February 8, 2011
38. Seamless
• Documents and Entities play together like
best friends
• Because Doctrine persistence remains
transparent from your domain this is
possible
Tuesday, February 8, 2011
40. Example from Blog
• This example was first written on my
personal blog http://jwage.com
• You can read the blog post here http://
jwage.com/2010/08/25/blending-the-
doctrine-orm-and-mongodb-odm/
Tuesday, February 8, 2011
41. MongoDB ODM
SoftDelete Functionality
Tuesday, February 8, 2011
42. I like my deletes soft,
not hard
Tuesday, February 8, 2011
48. Install SoftDelete
Extension for Doctrine
MongoDB ODM
http://github.com/doctrine/mongodb-odm-softdelete
$ git clone git://github.com/doctrine/mongodb-odm-softdelete src/
vendor/doctrine-mongodb-odm-softdelete
Tuesday, February 8, 2011
49. Autoload Extension
$loader = new UniversalClassLoader();
$loader->registerNamespaces(array(
// ...
'DoctrineODMMongoDBSoftDelete' => __DIR__.'/vendor/doctrine-mongodb-odm-
softdelete/lib',
));
$loader->register();
Tuesday, February 8, 2011
50. Raw PHP Configuration
use DoctrineODMMongoDBSoftDeleteUnitOfWork;
use DoctrineODMMongoDBSoftDeleteSoftDeleteManager;
use DoctrineCommonEventManager;
// $dm is a DocumentManager instance we should already have
use DoctrineODMMongoDBSoftDeleteConfiguration;
$config = new Configuration();
$uow = new UnitOfWork($dm, $config);
$evm = new EventManager();
$sdm = new SoftDeleteManager($dm, $config, $uow, $evm);
Tuesday, February 8, 2011
52. Autoload the Bundle
$loader = new UniversalClassLoader();
$loader->registerNamespaces(array(
// ...
'DoctrineODMMongoDBSymfonySoftDeleteBundle' => __DIR__.'/vendor/doctrine-
mongodb-odm-softdelete-bundle',
));
$loader->register();
Tuesday, February 8, 2011
53. Register the Bundle
public function registerBundles()
{
$bundles = array(
// ...
// register doctrine symfony bundles
new DoctrineODMMongoDBSymfonySoftDeleteBundleSoftDeleteBundle()
);
// ...
return $bundles;
}
Tuesday, February 8, 2011
54. Enable the Bundle
// app/config/config.yml
doctrine_mongodb_softdelete.config: ~
Tuesday, February 8, 2011
55. SoftDeleteManager
$sdm = $container->get('doctrine.odm.mongodb.soft_delete.manager');
Tuesday, February 8, 2011
56. SoftDeleteable
ODM Documents must implement this interface
interface SoftDeleteable
{
function getDeletedAt();
function isDeleted();
}
Tuesday, February 8, 2011
57. User implements
SoftDeletable
/** @mongodb:Document */
class User implements SoftDeleteable
{
/** @mongodb:Date @mongodb:Index */
private $deletedAt;
public function getDeletedAt()
{
return $this->deletedAt;
}
public function isDeleted()
{
return $this->deletedAt !== null ? true : false;
}
}
Tuesday, February 8, 2011
58. 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
60. Restore a User
// now again later we can restore that same user
$user = $dm->getRepository('User')->findOneByUsername('jwage');
$sdm->restore($user);
$sdm->flush();
Tuesday, February 8, 2011
62. Limit cursors to only
show non deleted users
$qb = $dm->createQueryBuilder('User')
->field('deletedAt')->exists(false);
$query = $qb->getQuery();
$users = $query->execute();
Tuesday, February 8, 2011
63. Get only deleted users
$qb = $dm->createQueryBuilder('User')
->field('deletedAt')->exists(true);
$query = $qb->getQuery();
$users = $query->execute();
Tuesday, February 8, 2011
64. Restore several deleted
users
$qb = $dm->createQueryBuilder('User')
->field('deletedAt')->exists(true)
->field('createdAt')->gt(new DateTime('-24 hours'));
$query = $qb->getQuery();
$users = $query->execute();
foreach ($users as $user) {
$sdm->restore($user);
}
$sdm->flush();
Tuesday, February 8, 2011
65. Soft Delete Events
class TestEventSubscriber implements DoctrineCommonEventSubscriber
{
public function preSoftDelete(LifecycleEventArgs $args)
{
$document = $args->getDocument();
- preDelete }
$sdm = $args->getSoftDeleteManager();
- postDelete public function getSubscribedEvents()
{
- preRestore }
return array(Events::preSoftDelete);
- postRestore }
$eventSubscriber = new TestEventSubscriber();
$evm->addEventSubscriber($eventSubscriber);
Tuesday, February 8, 2011
66. Symfony2 and
supervisor
http://supervisord.org/
Tuesday, February 8, 2011
68. Supervisor is a client/server system
that allows its users to monitor and
control a number of processes on
UNIX-like operating systems.
http://supervisord.org
Tuesday, February 8, 2011
70. Scenario
• You want to send an e-mail when new
users register in your system.
• But, sending an e-mail directly from your
action introduces a failure point to your
stack.
• ....What do you do?
Tuesday, February 8, 2011
71. Tailable Cursor
• Use a tailable mongodb cursor
• Tail a NewUser document collection
• Insert NewUser documents from your
actions
• The daemon will instantly process the
NewUser after it is inserted and dispatch
the e-mail
Tuesday, February 8, 2011
73. Create Collection
• The NewUser collection must be capped in
order to tail it so we need to create it.
• Luckily, Doctrine has a console command
for it.
• It will read the mapping information we
configured and create the collection
$ php app/console doctrine:mongodb:schema:create --class="MyBundle:NewUser" --collection
Tuesday, February 8, 2011
74. Insert NewUser upon
Registration
public function register()
{
// ...
$user = new User();
$form = new RegisterForm('register', $user, $validator);
$form->bind($request, $user);
if ($form->isValid()) {
$newUser = new NewUser($user);
$dm->persist($newUser);
$dm->persist($user);
$dm->flush();
// ...
}
// ...
}
Tuesday, February 8, 2011
75. The Daemon Console
Command
• You can find the console command code to
use to tail a cursor here:
• https://gist.github.com/812942
Tuesday, February 8, 2011
76. Executing Console
Command
$ php app/console doctrine:mongodb:tail-cursor MyBundle:NewUser findUnProcessed
new_user.processor
• The command requires 3 arguments:
• document - the name of the document to tail
• finder - the repository finder method used to get the cursor
• processor - the id of the service used to process the new
users
Tuesday, February 8, 2011
77. findUnProcessed()
• We need the findUnProcessed() method to
class NewUserRepository extends DocumentRepository
{
return the unprocessed cursor to tail
public function findUnProcessed()
{
return $this->createQueryBuilder()
->field('isProcessed')->equals(false)
->getQuery()
->execute();
}
}
Tuesday, February 8, 2011
78. NewUserProcessor
We need a service id new_user.processor with a
process(OutputInterface $output, $document) method
use Swift_Message;
use SymfonyComponentConsoleOutputOutputInterface;
class NewUserProcessor
{
private $mailer;
public function __construct($mailer)
{
$this->mailer = $mailer;
}
public function process(OutputInterface $output, $document)
{
}
}
Tuesday, February 8, 2011
79. Send the e-mail
public function process(OutputInterface $output, $document)
{
$user = $document->getUser();
$message = Swift_Message::newInstance()
->setSubject('New Registration')
->setFrom('noreply@domain.com')
->setTo($user->getEmail())
->setBody('New user registration')
;
$this->mailer->send($message);
$document->setIsProcessed(true);
}
Tuesday, February 8, 2011
80. 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
82. Configure a Profile
• We need to configure a profile for supervisor to
know how to run the console command
/
$ vi /etc/supervisor/conf.d/tail-new-user.conf
r
[program:tail-new-user]
numprocs=1
startretries=100
directory=/
stdout_logfile=/path/to/symfonyproject/app/logs/tail-new-user-supervisord.log
autostart=true
autorestart=true
user=root
command=/usr/local/bin/php /path/to/symfonyproject/app/console
doctrine:mongodb:tail-cursor MyBundle:NewUser findUnprocessed
new_user.processor
Tuesday, February 8, 2011
83. Start supervisord
• Start an instance of supervisord
• It will run as a daemon in the background
• The tail-new-user.conf will always be
running
$ supervisord
Tuesday, February 8, 2011
84. Where do I use
supervisor?
• sociallynotable.com
• Indexes tweets with links to Amazon.com
products
• Maintains statistics on each product and
lets you shop the popular products each
day
Tuesday, February 8, 2011
91. Now when I start supervisor the
twitter watcher will always
remain running. Even if I kill the
pid myself, it will start back up.
Tuesday, February 8, 2011
92. Thanks!
I hope this presentation was useful to you!
Tuesday, February 8, 2011