ORMs and Doctrine 2

July 30, 2010

We've probably all used the active record design pattern at some point in our PHP careers. It allows for fast development and many full stack frameworks have incorporated it with validation and automatic form creation. But when creating larger applications, the datasources and model relationships quickly become hard to maintain.

A push for the data mapper pattern in PHP has come about in the past couple of years and it is becoming much more prominent. The Zend Framework Quick Start Tutorial now uses the data mapper pattern (if you're looking for an even better tutorial with ZF and data mappers, check out Survive the Deepend) and Symfony 2 will come with a data mapper as the 'M' part of their MVC. If you haven't seen it already, the likelihood of you coming in contact with the pattern first hand is becoming greater and greater.

This brings me to Doctrine 2, which I believe is the next big leap in the PHP ORM world. Doctrine 2 is an object persistence layer that sits on top of a database abstraction layer.  It allows you to load and save plain old PHP objects (POPO) in the RDBMS of your choice (and even MongoDB with another library).  This means you can focus on your application's domain model and not on the structure of your database.

It may sound complicated, but let's take a look at how it works.

(I'd like to point out that there are currently four ways to map your database to your objects: YAML, XML, PHP, and Annotations.  It's all personaly preference, but I use annotations, and that's what I'll demonstrate here.)

As I said earlier, the models are plain old PHP objects, but with added annotations to tell Doctrine how to map the properties to the database. Here's an example model for a blog article:

<?php

/**
 * @Table(name="BlogArticle")
 */
class Article
{

    /**
     * @Id @Column(type="integer", name="article_id")
     * @GeneratedValue
     */
    protected $id;

    /**
     * @Column(type="string", name="title", length="255", nullable="false")
     */
    protected $title = '';

    /**
     * @Column(type="text", name="content", nullable="false")
     */
    protected $content = '';

    /**
     * @Column(type="datetime", name="date", nullable="false")
     */
    protected $date;

    // ... constructor, getters, and setters 
}

Here we have an article model that is persisted in the 'BlogArticle' table.  The 'id' is the primary key and uses the 'article_id' column, the 'title' property is pulled from the 'title' column in the database... and etc.  So now when you use Doctrine to load an entry from the database, this object is instantiated with the persisted data and you're ready to do your domain logic. You can even generate the database tables from these models, but I won't go into detail on that here.

Now let's look at how to get a new article persisted into the database. First I'll add a constructor to that model so we can set the data:

//... namespace etc
class Article {
//... properties

    /**
     * @param string $title
     * @param string $content
     * @param DateTime $date
     */
    public function __construct($title, $content = '', \DateTime $date = null)
    {
        if (null === $date) {
            $date = new \DateTime();
        }

        $this->_title = $title;
        $this->_content = $content;
        $this->_date = $date;
    }
}

Now the code to create and persist a new article:

$em = new \Doctrine\ORM\EntityManager::create($configOptions...);

$article = new \Blog\Model\Article('New Article', 'This is the content!');

$em->persist($article);
$em->flush();

And that's it.  Doctrine will generate and execute the SQL so you don't have to worry about it.

Now let's see how to get articles back out of the database. For simple selects from the database:

// All queries go through the repository
$articleRepository = $em->getRepository('Article');

// All articles
$articles = $articleRepository->findAll();

// An article from its primary id
$article = $articleRepository->find($id);

And if you want to do a full query, DQL query builder give you full control while still allowing you to think in terms of your domain objects:

$yesterday = new \DateTime('yesterday');
$qb = $articleRepository->createQueryBuilder('a');
$qb->where('a.date > :dateMin')
   ->setParameter('dateMin', $yesterday->format('Y-m-d'));

$articles = $qb->getQuery()->getResult();

This will get all articles that have been posted since yesterday.

As you can see, this makes it very simple to persist and load PHP objects.  Only during the creation of your model mapping annotations do you have to worry about the structure of your database.  Once that's complete, all of the functions are in terms of the objects and their properties.  Your models have no hardcoded dependencies (objects don't persist themselves, it's handled by a third party) which makes them more reusable, easier to unit test, and all around much more fun to work with.

I could go on and on about the features of Doctrine 2, but hopefully this has piqued your interest enough for you to check it out and learn more at the Doctrine documentation.  If you need any help with it, I and many others are usually in the Doctrine IRC channel (#doctrine on freenode.net) so feel free to ask questions.

If you'd like to see a more in depth example of a blog application using [a modified] Zend Framework and Doctrine 2, check out my Blog github project, which is what is powering this site.  I'll be touching Doctrine 2 a lot in future blog articles as I use it in almost every project I work on (personal or professionally), so check back if you want to see more.


Comments

Fork me on GitHub