• PHP Developer for 10+ years
• Long time Symfony and Doctrine
contributor
• Published Author
• Entrepreneur
• Currently living in Nashville, Tennessee
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
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
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
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
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
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
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
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
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
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
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
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