Inheritance and Mapped Super Classes in Doctrine post

Posted on 2010-07-28 by jwage


Single Collection Inheritance in the Doctrine MongoDB ODM allows you to map multiple classes in an inheritance hierarchy to a single collection in MongoDB. An example might be in a CMS where you have several different content types like the base Node, Page and BlogPost which all extends an abstract ContentType class.

First define the ContentType class that is a @MappedSuperclass:

/**
 * @MappedSuperclass
 * @HasLifecycleCallbacks
 */
abstract class ContentType
{
    /** @Id */
    protected $id;

    /** @Date */
    protected $createdAt;

    /** @Date */
    protected $updatedAt;

    public function getId()
    {
        return $this->id;
    }

    /** @PreUpdate */
    public function preUpdate()
    {
        $this->updatedAt = new DateTime();
    }

    /** @PrePersist */
    public function prePersist()
    {
        $this->createdAt = new DateTime();
        $this->updatedAt = new DateTime();
    }
}

Now we can define our base Node content type:

/**
 * @Document(collection="pages")
 * @InheritanceType("SINGLE_COLLECTION")
 * @DiscriminatorField(fieldName="type")
 * @DiscriminatorMap({
 *   "node"="Node",
 *   "page"="Page",
 *   "blog_post"="BlogPost"
 * })
 */
class Node extends ContentType
{
    /** @String */
    protected $title;

    public function getTitle()
    {
        return $this->title;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }
}

This gives us our base content type node functionality that we can extend to create the Page document which adds a body field for the page:

/** @Document */
class Page extends Node
{
    /** @String */
    protected $body;

    public function getBody()
    {
        return $this->body;
    }

    public function setBody($body)
    {
        $this->body = $body;
    }
}

And the BlogPost document is a custom type of page that adds some additional fields related to blog posts like an excerpt and tags:

/** @Document */
class BlogPost extends Page
{
    /** @Collection */
    private $tags = array();

    /** @String */
    private $excerpt;

    public function getExcerpt()
    {
        return $this->excerpt;
    }

    public function setExcerpt($excerpt)
    {
        $this->excerpt = $excerpt;
    }

    public function addTag($tag)
    {
        $this->tags[] = $tag;
    }

    public function removeTag($tag)
    {
        $key = array_search($tag, $this->tags);
        if ($key !== false) {
            unset($this->tags[$key]);
        }
    }

    public function getTags()
    {
        return $this->tags;
    }
}

You can easily add new content types by mapping a document class that extends the base Node. All your documents will be stored in a single collection and a discriminator field will be used to discriminate which class created each document.

Now we can use our document classes and create new instances and persist them. Here is an example where we create a new blog post:

$post = new BlogPost();
$post->setTitle('Test');
$post->setExcerpt('Testing');
$post->setBody('w00t');
$post->addTag('test');
$dm->persist($post);
$dm->flush();

The above would result in a document like the following in MongoDB:

Array
(
    [_id] => 4c4f38978ead0ef23f000000
    [createdAt] => MongoDate Object
        (
            [sec] => 1280260247
            [usec] => 0
        )

    [updatedAt] => MongoDate Object
        (
            [sec] => 1280260247
            [usec] => 0
        )

    [title] => Test
    [body] => w00t
    [tags] => Array
        (
            [0] => test
        )

    [excerpt] => Testing
    [type] => blog_post
)

Categories: articles