Who am I?
Jonathan H. Wage
PHP Developer for over 10 years
Symfony Contributor
Doctrine Contributor
Published Author
Business Owner
Nashville, TN Resident
http://www.twitter.com/jwage
http://www.facebook.com/jwage
I work at
What is OpenSky?
“a social commerce platform”
Based in New York and is a major open
source software advocate
http://www.shopopensky.com
Who is on the team?
• Roman S. Borschel
• Guilherme Blanco
• Benjamin Eberlei
• Bulat Shakirzyanov
• Jonathan H.Wage
Project History
- First commit April 13th 2006
- First stable version finished and Released September
1st 2008
- One of the first ORM implementations for PHP
- 1.0 is First LTS(long term support) release.
Maintained until March 1st 2010
- Integrated with many popular frameworks: Symfony,
Zend Framework, Code Igniter
What is MongoDB?
http://en.wikipedia.org/wiki/MongoDB
“MongoDB (from "humongous") is an open source,
scalable, high-performance, schema free, document-
oriented database written in the C++ programming
language.
The goal of MongoDB is to bridge the gap between
key-value stores - which are fast and highly scalable
- and traditional RDBMS systems - which provide rich
queries and deep functionality. It is designed for
problems that aren't easily solved by traditional
RDBMSs, for example if databases span many
servers.”
Using MongoDB in PHP
Download and run MongoDB Server
http://www.mongodb.org/display/DOCS/Downloads
Install Mongo PECL extension
http://www.php.net/manual/en/mongo.installation.php
$ /path/to/mongodb/bin/mongod
$ pecl install mongo
Selecting Databases
MongoDB intances can be selected
using the selectDB() method:
$mongo = new Mongo('mongodb://localhost');
$db = $mongo->selectDB('dbname');
Selecting Collections
MongoCollection instances can be
selected using the selectCollection()
method:
$mongo = new Mongo('mongodb://localhost');
$db = $mongo->selectDB('dbname');
$coll = $db->selectCollection('users');
Finding a Document
We can easily find the document we just
inserted using the findOne() method:
$user = $coll->findOne(array('username' => 'jwage'));
Updating a Document
You can find a document, change it and
save the entire document:
$user = $coll->findOne(array('username' => 'jwage'));
$user['username'] = 'jonwage';
$user['password'] = md5('newpassword');
$coll->save($user);
Atomic Updates
The following is faster and safer:
$coll->update(array(
'username' => 'jwage'
), array(
'$set' => array(
'username' => 'jonwage',
'password' => md5('newpassword')
)
));
Atomic Updates
$inc - increments field by the number value
$set - sets field to value
$unset - deletes a given field
$push - appends value to an array
$pushAll - appends multiple values to an array
$addToSet - appends multiple new values to an array
$pop - removes the last element in an array
$pull - removes all occurrences of a value from an array
$pullAll - removes all occurrences of multiple values from an array
Atomic Examples
Increment num_comments with $inc:
// $inc a blog posts number of comments by 1
$db->posts->update($id, array('$inc' => array('num_comments' => 1)))
Atomic Examples
Push new comment on to end of array:
// $push a new comment on to a blog posts comments
$db->posts->update($id, array('$push' => array(
'comments' => array('Hi how are you?')
)));
Atomic Examples
Set an individual field value:
// $set a lock on a blog post
$db->posts->update($id, array('$set' => array('locked' => 1)));
Atomic Examples
Unset a field from a document:
// $unset a lock on a blog post
$db->posts->update($id, array('$unset' => array('locked' => 1)));
Removing Documents
You can remove documents using the
remove() method:
$coll->remove(array('username' => 'jwage'));
What is it?
- Layer on top of Mongo PECL extension
- Work with objects instead of arrays
- Create rich OO PHP domain and persist
transparently using Doctrine
What does it do?
- Maps PHP classes to MongoDB
- Manages the state of objects
- Tracks changes and persists them
- Constructs regular PHP objects from
MongoDB data
How does it do it?
- Implements UnitOfWork
http://martinfowler.com/eaaCatalog/unitOfWork.html
- Tracks changes to managed objects
by maintaining a copy of its original
data
- Computes changesets and persists
them transparently
Easier to build than ORM
- Less work to convert objects to arrays
for persistence to MongoDB
- Less work to convert the data in the
database to objects since it is already
stored in a object-like structure
instead of flat like in a RDBMS
Architecture
Documents
- Lightweight persistent domain object
- Regular PHP class
- Does not extend any base Doctrine class
- Cannot be final or contain final methods
- Any two documents in a hierarchy of classes must not
have a mapped property with the same name
- Supports inheritance, polymorphic associations and
polymorphic queries.
- Both abstract and concrete classes can be documents
- Documents may extend non-document classes as well
as document classes, and non-document classes may
extend document classes
Architecture
- No more base class required
- Values stored in object properties
- Doctrine is transparent
namespace Documents;
class User
{
private $id;
private $name;
}
Architecture
The DocumentManager
- Central access point to the ODM functionality provided by
Doctrine. API is used to manage the persistence of your
objects and to query for persistent objects.
- Employes transactional write behind strategy that delays the
execution of queries in order to execute them in the most
efficient way
- Internally a DocumentManager uses a UnitOfWork to keep
track of your objects
Managing Documents
To manage documents with Doctrine
you need a DocumentManager:
$config = new DoctrineODMMongoDBConfiguration();
$config->setMetadataCacheImpl(new DoctrineCommonCacheArrayCache);
$driverImpl = $config->newDefaultAnnotationDriver(array(__DIR__."/Documents"));
$config->setMetadataDriverImpl($driverImpl);
$config->setProxyDir(__DIR__ . '/Proxies');
$config->setProxyNamespace('Proxies');
$dm = DoctrineODMMongoDBDocumentManager::create($mongo, $config);
DocumentManager
The DocumentManager is the central
place for managing the state of PHP
objects. It has methods like:
persist($document)
find()
findOne()
refresh($document)
remove($document)
detach($document)
merge($document)
flush()
Document Classes
A Doctrine MongoDB Document is just a regular old PHP object:
class User
{
private $id;
private $username;
private $password;
public function getId()
{
return $this->id;
}
public function getUsername()
{
return $this->username;
}
public function setUsername($username)
{
$this->username = $username;
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password)
{
$this->password = md5($password);
}
}
Mapping Information
- Annotations
- YAML
- XML
- PHP
/** @Document */
class User
{
/** @Id */
private $id;
/** @String */
private $username;
/** @String */
private $password;
// ...
}
Query Builder
Executing queries:
// Execute and get the first result
$user = $q->getSingleResult();
// Execute and return an array of results
$users = $q->execute();
// Execute update or remove query
$q->execute();
// Iterate over cursor
foreach ($q->iterate() as $user) {
}
Embedded Documents
Push a new address on to the end of
the addresses array:
$db->users->update($user['_id'], array('$push' => array(
'addresses' => array(
'address1' => 'New address'
)
Embedded Mapping
Map an embedded document using
@EmbedOne and @EmbedMany:
class User
{
/** @Id */
public $id;
/** @String */
public $name;
/** @EmbedMany(targetDocument="Address") */
public $addresses = array();
}
/** @EmbeddedDocument */
class Address
{
/** @String */
public $address;
/** @String */
public $city;
/** @String */
public $state;
/** @String */
public $zipcode;
Embedded Mapping
References
Reference documents from within
another document by storing an
embedded document with the following
properties:
$ref - referenced collection
$id - value of _id for the object referenced
$db - referenced database
References
- No JOIN syntax like in a relational DB
- References are resolved in app code
- Store one or many references
Reference Mapping
/** @Document */
class Organization
{
/** @Id */
private $id;
/** @String */
private $name;
/** @ReferenceMany(targetDocument="User") */
private $users = array();
public function setName($name)
{
$this->name = $name;
}
public function addUser(User $user)
{
$this->users[] = $user;
}
}
Reference Mapping
/** @Document */
class User
{
/** @Id */
private $id;
/** @String */
private $name;
/** @ReferenceOne(targetDocument="Organization") */
private $organization;
public function setName($name)
{
$this->name = $name;
}
public function setOrganization(Organization $organization)
{
$this->organization = $organization;
$organization->addUser($this);
}
}
Persisting
$organization = new Organization();
$organization->setName('Sensio Labs');
$user = new User();
$user->setName('Jonathan H. Wage');
$user->setOrganization($organization);
$dm->persist($organization);
$dm->persist($user);
$dm->flush();
Inserted Organization
Resulting organization has a reference
to the user:
Array
(
[_id] => 4c86acd78ead0e8759000000
[name] => Sensio Labs
[users] => Array
(
[0] => Array
(
[$ref] => User
[$id] => 4c86acd78ead0e8759010000
[$db] => doctrine_odm_sandbox
)
)
)
Inserted User
Resulting user has a reference to the
organization:
Array
(
[_id] => 4c86acd78ead0e8759010000
[name] => Jonathan H. Wage
[organization] => Array
(
[$ref] => Organization
[$id] => 4c86acd78ead0e8759000000
[$db] => doctrine_odm_sandbox
)
)
Working with References
A single reference is represented by a
proxy object:
$user = $dm->find('User', array('name' => 'Jonathan H. Wage'));
// instance of uninitialized OrganizationProxy
$organization = $user->getOrganization();
// calling getter or setter for uninitialized proxy
// queries the database and initialized the proxy document
// query invoked, organization data loaded and doc initialized
echo $organization->getName();
Working with References
What does a proxy look like?
class OrganizationProxy extends Organization
{
private function initialize()
{
// ...
}
public function getName()
{
$this->initialize();
return parent::getName();
}
}
Working with References
A many reference is represented by an
uninitialized PersistentCollection:
$organization = $dm->find('Organization', array('name' => 'Sensio Labs'));
$users = $organization->getUsers(); // uninitialized collection
// Queries database for users and initializes collection
foreach ($users as $user)
{
// ...
}
Creating Queries
From the DocumentManager you can
execute DQL queries using the query()
method:
$users = $dm->query('find all FROM User');
$users = $db->user->find();
More Examples
// Find all posts greater than or equal to a date
$posts = $dm->query('find all FROM BlogPost WHERE createdAt >= ?',
array($date));
// Find all posts sorted by created at desc
$posts = $dm->query('find all FROM BlogPost sort createdAt desc');
// Update user
$dm->query('update DocumentsUser set password = ? where username = ?',
array('newpassword', 'jwage'));
Events
Doctrine triggers events throughout the
lifecycle of objects it manages:
- preRemove
- postRemove
- prePersist
- postPersist
- preUpdate
- postUpdate
- preLoad
- postLoad
Maintaining Timestamps
/**
* @Document
* @HasLifecycleCallbacks
*/
class BlogPost
{
/** @Id */
public $id;
/** @Date */
public $createdAt;
/** @Date */
public $updatedAt;
/** @PreUpdate */
public function prePersist()
{
$this->createdAt = new DateTime();
}
/** @PreUpdate */
public function preUpdate()
{
$this->updatedAt = new DateTime();
}
}
Sample Scenario
Our original document looks like this:
/**
* @Document
*/
class User
{
/** @Id */
public $id;
/** @String */
public $name;
}
Renaming Fields
Later we want to store first and last
names in separate fields:
/**
* @Document
*/
class User
{
/** @Id */
public $id;
/** @String */
public $firstName;
/** @String */
public $lastName;
}
Renaming Fields
Handle documents with only a name:
/**
* @Document
* @HasLifecycleCallbacks
*/
class User
{
/** @Id */
public $id;
/** @String */
public $firstName;
/** @String */
public $lastName;
/** @PreLoad */
public function preLoad(array &$data)
{
if (isset($data['name']))
{
$e = explode(' ', $data['name']);
unset($data['name']);
$data['firstName'] = $e[0];
$data['lastName'] = $e[1];
}
}
}