2. WHO IS RYAN MAUGER?
• Zend Framework Contributor
• Zend Framework CR Team Member
• Co-Author of Zend Framework 2 in Action (With Rob Allen)
• Technical Editor for Zend Framework: A Beginners Guide
• Community Supporter
• Zend Certified PHP5 Engineer
• Lead Developer at Lupimedia and trainer at Lupijuice
5. What is the ODM?
Object Document Mapper
Similar to an ORM
6. What is the ODM?
Object Document Mapper
Similar to an ORM
But works with a Document Database
7. What is the ODM?
Object Document Mapper
Similar to an ORM
But works with a Document Database
8. Whats the advantage?
• Schema-less design (no complicated ALTER
TABLE statements, just save your data!
• Works with JSON objects, familiar with what
your already doing
• Simple!
9. Whats the advantage?
• Schema-less design (no complicated ALTER
TABLE statements, just save your data!
• Works with JSON objects, familiar with what
your already doing
• Simple! (at least to get started)
10. Whats the advantage?
Doctrine 2
• Uses the Data Mapper pattern
• Simplifies your domain, removes the
persistence logic
• Entities are free to do their job
• Defines a framework by which your domain
model will work, improving consistency
11. Getting started with the
ODM
Persistence operations revolve around the Document Manager
Mapping of properties is carried out through annotations in
your entities, or through xml/yaml mappings
12. <?php
namespace ApplicationBlog;
/**
* @Document(db="blog", collection="posts")
*/
class Post
{
/**
* @Id
*/
private $id;
An example entity
/**
* @Field(type="string")
*/
private $title;
/**
* @String
*/
Properties
private $content;
/** /**
* @EmbedMany(targetDocument="ApplicationBlogComment")
*/ * @Id
private $comments = array();
*/
public function getId() private $id;
{
return $this->id;
} /**
public function setId($id) * @Field(type="string")
{
$this->id = (string) $id; */
return $this; private $title;
}
public function getTitle()
{
/**
return $this->title; * @String
}
*/
public function setTitle($title) private $content;
{
$this->title = (string) $title;
}
return $this; /**
* @EmbedMany(targetDocument="ApplicationBlogComment")
public function getContent()
{ */
return $this->content; private $comments = array();
}
public function setContent($content)
{
$this->content = (string) $content;
return $this;
}
public function getComments()
{
return $this->comments;
}
public function addComment(ApplicationBlogComment $comment)
{
$this->comments[] = $comment;
return $this;
}
}
13. <?php
namespace ApplicationBlog;
/**
* @Document(db="blog", collection="posts")
*/
class Post
{
/**
* @Id
*/
private $id;
An example entity
/**
* @Field(type="string")
*/
private $title;
/**
* @String
*/
Methods
private $content;
/**
* @EmbedMany(targetDocument="ApplicationBlogComment")
public function getContent()
*/ {
private $comments = array();
return $this->content;
public function getId() }
{
return $this->id;
} public function setContent($content)
public function setId($id) {
{
$this->id = (string) $id; $this->content = (string) $content;
return $this; return $this;
}
}
public function getTitle()
{
return $this->title; public function getComments()
}
{
public function setTitle($title) return $this->comments;
{
$this->title = (string) $title; }
return $this;
}
public function addComment(ApplicationBlogComment $comment)
public function getContent()
{ {
return $this->content; $this->comments[] = $comment;
}
return $this;
public function setContent($content)
{
}
$this->content = (string) $content;
return $this;
}
public function getComments()
{
return $this->comments;
}
public function addComment(ApplicationBlogComment $comment)
{
$this->comments[] = $comment;
return $this;
}
}
14. Using your entity
Create
<?php
class IndexController extends Zend_Controller_Action
{
public function init()
{
/* Initialize action controller here */
}
public function indexAction()
{
$documentManager = $this->getInvokeArg('bootstrap')->getResource('odm');
$post = new ApplicationBlogPost();
$post->setTitle('My Interesting Post')
->setContent('I have somethnng very interesting to say');
$documentManager->persist($post);
$documentManager->flush();
}
}
> use blog
switched to db blog
> db.posts.find();
{ "_id" : ObjectId("4dde4067fbd2237df1000000"), "title" : "My Interesting Post", "content" : "I have somethnng very
interesting to say" }
15. Using your entity
Update
<?php
class IndexController extends Zend_Controller_Action
{
public function init()
{
/* Initialize action controller here */
}
public function indexAction()
{
$documentManager = $this->getInvokeArg('bootstrap')->getResource('odm');
$post = $documentManager->getRepository('ApplicationBlogPost')
->find('4dde4067fbd2237df1000000');
$post->setTitle('My Interesting Post')
->setContent('I have something very interesting to say');
$documentManager->persist($post);
$documentManager->flush();
}
}
> db.posts.find();
{ "_id" : ObjectId("4dde4067fbd2237df1000000"), "title" : "My Interesting Post", "content" : "I have something very
interesting to say" }
16. Using your entity
Add an comment
<?php
class IndexController extends Zend_Controller_Action
{
public function init()
{
/* Initialize action controller here */
}
public function indexAction()
{
$documentManager = $this->getInvokeArg('bootstrap')->getResource('odm');
$post = $documentManager->getRepository('ApplicationBlogPost')
->find('4dde4067fbd2237df1000000');
$comment = new ApplicationBlogComment();
$comment->setEmail('foo@example.com')
->setComment('Nice post!');
$post->addComment($comment);
$documentManager->persist($post);
$documentManager->flush();
}
}
> db.posts.find();
{ "_id" : ObjectId("4dde4067fbd2237df1000000"), "comments" : [
{
"_id" : ObjectId("4dde439afbd22380f1000000"),
"email" : "foo@example.com",
"comment" : "Nice post!"
}
], "content" : "I have something very interesting to say", "title" : "My Interesting Post" }
17. Using your entity
Delete
<?php
class IndexController extends Zend_Controller_Action
{
public function init()
{
/* Initialize action controller here */
}
public function indexAction()
{
$documentManager = $this->getInvokeArg('bootstrap')->getResource('odm');
$post = $documentManager->getRepository('ApplicationBlogPost')
->find('4dde4067fbd2237df1000000');
$documentManager->remove($post);
$documentManager->flush();
}
}
> db.posts.find();
>
19. Available Annotations
http://www.doctrine-project.org/docs/mongodb_odm/1.0/en/
reference/annotations-reference.html
@Field(type="string" name="origin")
Field Mappings. Many aliases are available: @String, @Date, @Int, @Float etc.
@EmbedOne / @EmbedMany
Embedded Documents, stores the document inside the current document
@ReferenceOne / @ReferenceMany
Referenced Documents, stores a reference to one or more documents outside the current document
@Document / @EmbeddedDocument
Marks entities which are allowed to be managed by the document manager. additionally
EmbeddedDocuments may not be saved independently, and must be a child of an @Document
@HasLifecycleCallbacks & @PrePersist, @PostPersist etc.
Marks an entity as having callbacks such as PrePersist which will be called automatically by the
DocumentManager during the entities lifetime. (see reference for list)
30. Base Controller
• Provide an easy method to access the
DocumentManager, and perhaps a service
layer / repository
• Not using an action helper because we will be
likely to be using this tightly everywhere, so it
avoids writing _helper a few hundred times!
• You could use an action helper and use IOC or
similar to replace this, this is for simplicity
31. <?php
namespace LupiController;
Base Controller
abstract class Action extends Zend_Controller_Action
{
/**
* @var Zend_Application
*/ Override the constructor at this point!
protected $bootstrap;
/**
* @var DoctrineODMMongoDBDocumentManager
*/
protected $dm;
It’s useful to leave
public function __construct(Zend_Controller_Request_Abstract $request,
Zend_Controller_Response_Abstract $response,
array $invokeArgs = array())
init() for the final
{
$this->setRequest($request)
->setResponse($response)
implementation so
->_setInvokeArgs($invokeArgs);
$this->_helper = new Zend_Controller_Action_HelperBroker($this);
$this->dm = $this->getBootstrap()->getResource('odm');
you don’t need to
$this->init();
} remember to always
public function getBootstrap()
{
if (null === $this->bootstrap) {
call parent::init()
$this->bootstrap = $this->getInvokeArg('bootstrap');
}
return $this->bootstrap;
}
}
32. Simplified controller code
<?php
use LupiController as controller;
class IndexController extends controllerAction
{
Common repository for
/**
* @var DoctrineODMMongoDBDocumentRepository
*/
the controller, set it in
protected $repository;
public function init()
the init
{
$this->repository = $this->dm->getRepository('ApplicationBlogPost');
}
public function indexAction()
{
$post = $this->repository->find('4dde4067fbd2237df1000000');
$comment = new ApplicationBlogComment();
$comment->setEmail('foo@test.com')
->setComment('nice post!');
$post->addComment($comment);
$this->dm->persist($post);
$this->dm->flush();
}
}
33. Simplified controller code
<?php
use LupiController as controller;
class IndexController extends controllerAction
{
Common repository for
/**
* @var DoctrineODMMongoDBDocumentRepository
*/
the controller, set it in
protected $repository;
public function init()
the init
{
$this->repository = $this->dm->getRepository('ApplicationBlogPost');
}
public function indexAction()
{
$post = $this->repository->find('4dde4067fbd2237df1000000');
$comment = new ApplicationBlogComment();
$comment->setEmail('foo@test.com')
->setComment('nice post!');
$post->addComment($comment);
$this->dm->persist($post);
$this->dm->flush();
}
}
Nice verbose code!
37. Managing change
• The db is schema-less
• You can add properties, without needing to
update the old objects until your ready
38. Managing change
• The db is schema-less
• You can add properties, without needing to
update the old objects until your ready
• Simply add the property, and update old
objects through your usual admin interface
39. Managing change
• The db is schema-less
• You can add properties, without needing to
update the old objects until your ready
• Simply add the property, and update old
objects through your usual admin interface
• Objects without the new property will simply
have a null value until you change them
44. Transforming a property
From <?php
To
namespace ApplicationBlog;
/**
* @Document(db="blog", collection="posts")
<?php */
namespace ApplicationBlog; class User
{
/** /**
* @Id
* @Document(db="blog", collection="posts") */
*/ private $id;
class User
{ /**
* @Field(type="string")
/** */
* @Id private $firstname;
*/
private $id; /**
* @Field(type="string")
*/
/** private $lastname;
* @Field(type="string")
*/ /**
* @ReferenceMany(targetDocument=”ApplicationBlogPost”)
private $fulllname; */
private $posts;
/**
* @ReferenceMany(targetDocument=”ApplicationBlogPost”) /**
* @AlsoLoad({“fullname”})
*/ */
private $posts; public function populateNameProperties($fullname)
} {
$name = explode(‘ ‘, $fullname);
$this->firstname = $name[0];
$this->lastname = $name[1];
Use a method to migrate using }
}
@AlsoLoad
45. NOTE!
• When searching, your queries will not know
about the old name!
• Include old and new fields in your searches if
possible until all data has been migrated
46. NOTE!
• The ODM is still in BETA3, there are still
some glitches, but its well worth having a good
look now.
• http://www.doctrine-project.org/ and follow
progress
47. Useful information
• Official docs: http://www.doctrine-project.org/
docs/mongodb_odm/1.0/en/
• Mongodb: http://www.mongodb.org/ excellent
intro to mongodb
• The example sandbox: http://github.com/
bittarman/zf-d2-odm
48. Thanks for listening
Catch me on twitter: @Bittarman
Slides will be available on slideshare
http://slideshare.com/rmauger
ZF & Doctrine 2 ODM
Doctrine