You are browsing a version that is no longer maintained. |
First steps using the ODM
The best way to understand the Doctrine MongoDB ODM is to see it in action. In this section, you'll walk through each step needed to start persisting documents to and from MongoDB.
A Simple Example: A Product
Creating a Document Class
Suppose you're building an application where products need to be displayed.
Without even thinking about Doctrine or MongoDB, you already know that you
need a Product
object to represent those products. Create this class
inside the Document
directory of your AcmeStoreBundle
:
The class - often called a document, meaning a basic class that holds data - is simple and helps fulfill the business requirement of needing products in your application. This class can't be persisted to Doctrine MongoDB yet - it's just a simple PHP class.
Add Mapping Information
Doctrine allows you to work with MongoDB in a much more interesting way than just fetching data back and forth as an array. Instead, Doctrine allows you to persist entire objects to MongoDB and fetch entire objects out of MongoDB. This works by mapping a PHP class and its properties to entries of a MongoDB collection.
For Doctrine to be able to do this, you just have to create metadata, or
configuration that tells Doctrine exactly how the Product
class and its
properties should be mapped to MongoDB. This metadata can be specified
in a number of different formats including XML or directly inside the
Product
class via annotations:
- PHP
1 // src/Acme/StoreBundle/Document/Product.php namespace Acme\StoreBundle\Document; use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB; /** * @MongoDB\Document */ class Product { /** * @MongoDB\Id */ protected $id; /** * @MongoDB\Field(type="string") */ protected $name; /** * @MongoDB\Field(type="float") */ protected $price; } 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 - XML
1 <!-- src/Acme/StoreBundle/Resources/config/doctrine/Product.mongodb.xml --> <doctrine-mongo-mapping xmlns="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping.xsd"> <document name="Acme\StoreBundle\Document\Product"> <id /> <field fieldName="name" type="string" /> <field fieldName="price" type="float" /> </document> </doctrine-mongo-mapping> 2 3 4 5 6 7 8 9 10 11 12
You can also check out Doctrine's Basic Mapping Documentation for
all details about mapping information. If you use annotations, you'll
need to prepend all annotations with |
Persisting Objects to MongoDB
Now that you have a mapped Product
document complete with getter and
setter methods, you're ready to persist data to MongoDB. From inside a controller,
this is pretty easy. Add the following method to the DefaultController
of the bundle:
1 // src/Acme/StoreBundle/Controller/DefaultController.php
use Acme\StoreBundle\Document\Product;
use Doctrine\ODM\MongoDB\DocumentManager;
use Symfony\Component\HttpFoundation\Response;
// ...
public function createAction(DocumentManager $dm)
{
$product = new Product();
$product->setName('A Foo Bar');
$product->setPrice('19.99');
$dm->persist($product);
$dm->flush();
return new Response('Created product id '.$product->getId());
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
If you're following along with this example, you'll need to create a route that points to this action to see it in work. |
Let's walk through this example:
- lines 8-10 In this section, you instantiate and work with the
$product
object like any other, normal PHP object; - line 12 This line fetches Doctrine's document manager object, which is responsible for handling the process of persisting and fetching objects to and from MongoDB;
- line 13 The
persist()
method tells Doctrine to "manage" the$product
object. This does not actually cause a query to be made to MongoDB (yet); - line 14 When the
flush()
method is called, Doctrine looks through all of the objects that it's managing to see if they need to be persisted to MongoDB. In this example, the$product
object has not been persisted yet, so the document manager makes a query to MongoDB, which adds a new entry.
If you are using `autowiring`, you can use type hinting to fetch the doctrine_mongodb.odm.document_manager
service:
1 // App/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Doctrine\ODM\MongoDB\DocumentManager as DocumentManager;
use App\Document\Product;
use Symfony\Component\HttpFoundation\Response;
class DefaultController extends AbstractController
{
public function createProduct(DocumentManager $dm)
{
$product = new Product();
$product->setName('A Foo Bar');
$product->setPrice('19.99');
$dm->persist($product);
$dm->flush();
return new Response('Created product id '.$product->getId());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
In fact, since Doctrine is aware of all your managed objects, when you
call the |
When creating or updating objects, the workflow is always the same. In the next section, you'll see how Doctrine is smart enough to update entries if they already exist in MongoDB.
Doctrine provides a library that allows you to programmatically load testing data into your project (i.e. fixture data). For information, see DoctrineFixturesBundle. |
Fetching Objects from MongoDB
Fetching an object back out of MongoDB is even easier. For example, suppose
you've configured a route to display a specific Product
based on its
id
value:
When you query for a particular type of object, you always use what's known as its repository. You can think of a repository as a PHP class whose only job is to help you fetch objects of a certain class. You can access the repository object for a document class via:
1 $repository = $dm->getRepository(Product::class);
The |
Once you have your repository, you have access to all sorts of helpful methods:
1 // query by the identifier (usually "id")
$product = $repository->find($id);
// dynamic method names to find based on a column value
$product = $repository->findOneById($id);
$product = $repository->findOneByName('foo');
// find *all* products
$products = $repository->findAll();
// find a group of products based on an arbitrary column value
$products = $repository->findByPrice(19.99);
2
3
4
5
6
7
8
9
10
11
12
Of course, you can also issue complex queries, which you'll learn more about in the Querying for Objects section. |
You can also take advantage of the useful findBy()
and findOneBy()
methods
to easily fetch objects based on multiple conditions:
Updating an Object
Once you've fetched an object from Doctrine, updating it is easy. Suppose you have a route that maps a product id to an update action in a controller:
1 public function updateAction(DocumentManager $dm, $id)
{
$product = $dm->getRepository(Product::class)->find($id);
if (!$product) {
throw $this->createNotFoundException('No product found for id '.$id);
}
$product->setName('New product name!');
$dm->flush();
return $this->redirectToRoute('homepage');
}
2
3
4
5
6
7
8
9
10
11
12
13
Updating an object involves just three steps:
- Fetching the object from Doctrine;
- Modifying the object;
- Calling
flush()
on the document manager.
Notice that calling $dm->persist($product)
isn't necessary. Recall that
this method simply tells Doctrine to manage or watch the $product
object.
In this case, since you fetched the $product
object from Doctrine, it's
already managed.
Deleting an Object
Deleting an object is very similar, but requires a call to the remove()
method of the document manager:
As you might expect, the remove()
method notifies Doctrine that you'd
like to remove the given document from the MongoDB. The actual delete operation
however, isn't actually executed until the flush()
method is called.
Querying for Objects
As you saw above, the built-in repository class allows you to query for one or many objects based on an number of different parameters. When this is enough, this is the easiest way to query for documents. Of course, you can also create more complex queries.
Using the Query Builder
Doctrine's ODM ships with a query Builder object, which allows you to construct a query for exactly which documents you want to return. If you use an IDE, you can also take advantage of auto-completion as you type the method names. From inside a controller:
In this case, 10 products with a name of foo, ordered from lowest price to highest price are returned.
The QueryBuilder
object contains every method necessary to build your
query. For more information on Doctrine's Query Builder, consult Doctrine's
Query Builder documentation. For a list of the available conditions you
can place on the query, see the Conditional Operators documentation specifically.
Custom Repository Classes
In the previous section, you began constructing and using more complex queries from inside a controller. In order to isolate, test and reuse these queries, it's a good idea to create a custom repository class for your document and add methods with your query logic there.
To do this, add the name of the repository class to your mapping definition.
- PHP
1 // src/Acme/StoreBundle/Document/Product.php namespace Acme\StoreBundle\Document; use Acme\StoreBundle\Repository\ProductRepository; use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB; /** * @MongoDB\Document(repositoryClass=ProductRepository::class) */ class Product { //... } 2 3 4 5 6 7 8 9 10 11 12 13 - XML
1 <!-- src/Acme/StoreBundle/Resources/config/doctrine/Product.mongodb.xml --> <!-- ... --> <doctrine-mongo-mapping xmlns="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping.xsd"> <document name="Acme\StoreBundle\Document\Product" repository-class="Acme\StoreBundle\Repository\ProductRepository"> <!-- ... --> </document> </doctrine-mongo-mapping> 2 3 4 5 6 7 8 9 10 11 12 13
You have to create the repository in the namespace indicated above. Make sure it
extends the default DocumentRepository
. Next, add a new method -
findAllOrderedByName()
- to the new repository class. This method will query
for all of the Product
documents, ordered alphabetically.
1 // src/Acme/StoreBundle/Repository/ProductRepository.php
namespace Acme\StoreBundle\Repository;
use Doctrine\ODM\MongoDB\Repository\DocumentRepository;
class ProductRepository extends DocumentRepository
{
public function findAllOrderedByName()
{
return $this->createQueryBuilder()
->sort('name', 'ASC')
->getQuery()
->execute();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
You can use this new method just like the default finder methods of the repository:
When using a custom repository class, you still have access to the default
finder methods such as |
Service Repositories
In the previous section, you learnt how to create custom repository classes and how
to get them using DocumentManager
. Another way of obtaining a repository instance
is to use the repository as a service and inject it as a dependency into other services.
1 // src/Acme/StoreBundle/Repository/ProductRepository.php
namespace Acme\StoreBundle\Repository;
use Acme\StoreBundle\Document\Product;
use Doctrine\Bundle\MongoDBBundle\Repository\ServiceDocumentRepository;
use Doctrine\Common\Persistence\ManagerRegistry;
/**
* Remember to map this repository in the corresponding document's repositoryClass.
* For more information on this see the previous chapter.
*/
class ProductRepository extends ServiceDocumentRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Product::class);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
The ServiceDocumentRepository
class your custom repository is extending allows you to
leverage Symfony's autowiring and autoconfiguration. To register all of your
repositories as services you can use the following service configuration:
- YAML
- XML
1 <?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <defaults autowire="true" autoconfigure="true"/> <prototype namespace="Acme\StoreBundle\Repository\" resource="%kernel.root_dir%/../src/Acme/StoreBundle/Repository/*"/> </services> </container> 2 3 4 5 6 7 8 9 10 11 12