Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
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 Developer for 10+ years
• Long time Symfony and Doctrine
contributor
• Published Author
• Entrepreneur
• Currently living in Nashville, Tennessee
Friday, March 4, 2011
8. 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
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
27. Setting the Product
public function setProduct(Product $product)
{
$this->productId = $product->getId();
$this->product = $product;
}
Friday, March 4, 2011
28. • $productId is mapped and persisted
• but $product which stores the Product
instance is not a persistent entity property
Friday, March 4, 2011
29. 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
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
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
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 my deletes soft,
not hard
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
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
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
61. 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
62. Get only deleted users
$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
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
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
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
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 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
85. Thanks!
I hope this presentation was useful to you!
Friday, March 4, 2011