Symfony2 + PHPCR + Doctrine2 + Jackalope recipe

Lately I’ve followed some developments in the Symfony2 corner of the PHP community with great interest. One of the most enticing developments is the usage of a Content Repository as a backend for your CMS. There is some work being done on the Symfony CMF, combining Symfony2, Doctrine2, PHPCR and Jackalope into a set of tools for building CMS’es based on a Content Repository backend. I didn’t get anything of the CMF to run yet, so I decided to dive in to tying these separate techniques together myself, and get a little proof-of-concept working. Here’s the code, and here’s the recipe:

I do not intend to convince you to use a content repository. I think the matters speak for themselves. If you know all the limitations classical setups using relational databases have, you’ll probably see a future in NoSQL databases, as they are affectionately called. MongoDB and CouchDB never convinced me the way the JCR specification did. Google the specs and read them yourself and know what I mean.

1. Get a CR backend running

I chose Apache Jackrabbit, a Java Content Repository (JCR), to do this. As pointed out on the Jackalope website, you’ll need a patched version of Jackrabbit to get things compatible with PHPCR. Actually, it is just version 2.2 with a backport patch of the 2.3 branch applied to it 1, so don’t fear using it. I’m told it is pretty stable.

  1. Get the patched Jackrabbit here.
  2. Run the the server

    java -jar jackrabbit-standalone-*.jar

This will run jackrabbit at port 8080. Visit your jackrabbit front end at http://localhost:8080/. You’ll be asked a username and password, but you can leave them blank. By default, there is no access control. If you see a page about jackrabbit, it works.

2. Install Symfony

I’m assuming you know how to do this. If you don’t, follow the instructions at The short version is:

  1. Download Symfony 2.0 latest version
  2. Extract the archive in your document root, say /var/www/
  3. Visit /Symfony/web/config.php and follow the instructions
  4. Visit /Symfony/web/app_dev.php to determine if the installation worked

3. Install PHPCR, Jackalope, DoctrinePHPCR ODM and the DoctrinePHPCRBundle

Go to your vendor dir and clone the PHPCR libraries from github:

cd /var/www/Symfony/vendor
git clone git://
git clone git://
git clone git://
cd bundles/Symfony/Bundle
git clone git://

4. Configure the autoloader

Open app/autoload.php and configure the namespaces for the downloaded libraries:

/* ... */
    /* ... */
    'Jackalope'             => __DIR__.'/../vendor/jackalope/src',
    'PHPCR'                 => __DIR__.'/../vendor/phpcr/src',
    'Doctrine\\ODM\\PHPCR'  => __DIR__.'/../vendor/phpcr-odm/lib/',
    /* ... */

5. Add the DoctrinePHPCRBundle to your AppKernel

Open app/AppKernel.php and add the bundle to the registerBundles() call:

/* ... */
public function registerBundles()
    $bundles = array(
        /* ... */
        new Symfony\Bundle\DoctrinePHPCRBundle\DoctrinePHPCRBundle()
    /* ... */

6. Configure the ODM default service

Add the following section to your app/config/config.yml file:

            url: http://localhost:8080/server/
        workspace: default
        username: ''
        password: ''
        auto_mapping: true

7. Set up the Doctrine system types

To have Jackrabbit understand some type internals Doctrine uses for document mapping, we’ll need to make the content repository aware of these types. There is a console command for that, to make things easy:

app/console doctrine:phpcr:register-system-node-types

If you skip this step, persisting documents with a Doctrine DocumentManager will fail with a Jackrabbit error message complaining about phpcr being an unknown namespace.

8. Set up a PagesBundle

You should, of course, change the class and namespace names to whatever you wish. Here’s what I used:

In src/Melp, I created a PagesBundle. This bundle will hold my routes for editing, creating, deleting and showing pages from the content repository. It will also hold the Document model classes I will be using for mapping documents to objects:

cd /var/www/Symfony/src
mkdir -p Melp/PagesBundle/{Controller,Document,Resources{,/views}}

This creates the bundle’s directory structure. There is also a console command for it, which can scaffold the directory structure and some classes for you:

cd /var/www/Symfony
app/console generate:bundle

The MelpPagesBundle class will be in /var/www/Symfony/src/Melp/PagesBundle/MelpPagesBundle.php, and contains the following code:

namespace Melp\PagesBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class MelpPagesBundle extends Bundle {}

Add the bundle instance to the AppKernel class’s bundle initialization routine:

/* ... */
public function registerBundles()
    $bundles = array(
        /* ... */
        new Melp\Bundle\PagesBundle\MelpPagesBundle()
    /* ... */

9. Set up the controller, routes and view scripts

I’ll use Annotations for the controller set up. I was a bit reluctant at first to use annotations for such configuration, but I find it much easier and clear to use annotations than the other configuration options. The only thing we’ll need is to let the router configuration know the controller exists. We’ll do that in app/config/routing.yml:

    resource:  "@MelpPagesBundle/Controller/PageController.php"
    type: annotation

I will also use Twig view scripts. You can of course use PHP view scripts or whatever you prefer, but Twig has had my heart for some time already. The @Template annotation will tell Symfony to render the view resources using the twig template engine as is configured in the default Symfony2 distribution you downloaded. 2

The controller class will then look like this:

namespace Melp\PagesBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
class PageController extends Controller
     * @Route("/")
    function indexAction()
        return $this->redirect($this->generateUrl('view', array('path' => 'home')));
     * @Route("/{path}", requirements={"path" = "(?!(edit|delete|create)).+"}, name="view")
     * @Template
    function viewAction($path)
     * @Route("/edit/{path}", requirements={"path" = ".*"}, name="edit")
     * @Template
    function editAction($path)
     * @Route("/create/{path}", requirements={"path" = ".*"}, name="create")
    function createAction($path)
     * @Route("/delete/{path}", requirements={"path" = ".*"}, name="delete")
    function deleteAction($path)

Since I want to have the routes reflect the path in the content repository, I use a wildcard pattern for the paths, so they may contain slashes. You can use PCRE in your routes, which I used to exclude the /edit, /create and /delete routes from the default viewAction() to avoid conflicts.

These actions will implement CRUD actions for nodes inside the content repository.

10. Create the document model class

To have Doctrine manage my model class, I will need to attach Doctrine annotations to it, so Doctrine will know how to read and write model data from and to the content repository backend. I will use a simple Page class, with title and content properties, wich we will later implement the CRUD for, as layed out in the Controller above.

In src/Melp/PagesBundle/Document, create the model reflecting the ‘Page’ data structure we’ll be using to edit and show pages 3.

namespace Melp\PagesBundle\Document;
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;
 * @PHPCRODM\Document(alias="page")
class Page
    /** @PHPCRODM\Id */
    public $path;
    /** @PHPCRODM\String */
    public $title;
    /** @PHPCRODM\String */
    public $content;

11. Create a Pages facade and register it as a service

I created a facade for controlling how pages are persisted and added some logic to that for placing pages in a configured root:

        class: Melp\PagesBundle\Service\PagesFacade
            - @doctrine_phpcr.odm.default_document_manager
            - 'Melp\PagesBundle\Document\Page'
            - "/pages/"
            - "home"

The facade exposes the following methods:

public function __construct(\Doctrine\ODM\PHPCR\DocumentManager $dm, $documentClass, $root, $default)

function createDefault($title = "Homepage")

function removePath($path)

function findPath($path)

function remove($node)

function persist($node)

This saves some clutter in the controller code. The Facade dispatches to the document manager and the document repository for finding and persisting pages.

12. Create a PageType form class

Following a best practice not to initialize your forms inside your controller, I created a PageType class in src/Melp/PagesBundle/Form/Type/PageType.php:

namespace Melp\PagesBundle\Form\Type;
use \Symfony\Component\Form\AbstractType;
use \Symfony\Component\Form\FormBuilder;
class PageType extends AbstractType
    function buildForm(FormBuilder $builder, array $options)
                ->add('title', 'text', array('required' => true))
                ->add('content', 'textarea')

This class will be used inside the controller to build the forms for creating new pages and editing existing ones.

13. Implement the CRUD Controller

I chose to implement a very, very basic CRUD for the document class. The code speaks for itself. I used twig templates to render the output of the edit, create and view actions, and rely heavy on flash messages and redirects for feedback to the user.


Much, much easier than an RDBMS based hierarchical system of pages, much more convenient and easier to control, and crazily scalable. I think there is much future in this kind of set up. So get comfortable with it while I’ll do the same, and I hope to get even deeper into the subject some next blog.

I hope this post helped you some further if you got stuck, and is of some inspiration if you want to get started. All tips and comments are appreciated.


  2. See app/config/config.yml, the framework.templating section 

  3. A small introduction is given to this in 

This entry was posted in Development and tagged , , , . Bookmark the permalink. Trackbacks are closed, but you can post a comment.


  1. Posted July 26, 2011 at 22:50 | Permalink

    Thanks for this post, its really detailed. You should promote this post on the symfony-cmf-devs mailinglist. The PageBundle looks like a good first implementation of a Symfony CMF based CMS :-)

  2. Posted July 26, 2011 at 23:01 | Permalink

    awesome write up. are you coming to the CMF camp this week? either way we need to push forward with documentation and example code so your blog post is invaluable. thanks for being part of the PHPCR effort!

  3. Posted July 27, 2011 at 08:10 | Permalink

    few things, I know you use wp so please install the subscribe to comments thing so i can get notified whenever the discussion flares other thing is perhaps doing a hybrid approach would be nicer i will post a blog post about doing it with deps but basically is the same with SE it is too late for now to try the setup but I will first thing tomorrow, so I am sort of confused as i come from the wordpress side, a CMS would work much better with this approach with jackrabbit right? I know of some blog projects like PSSBlog which tries to match the mapping of wp, I am talking about the importance to transfer the info to this approach, and also another blog from dustin10 on whose key feature is that he is using all FOS Bundles which makes it a powerful thing. Any thoughts?

  4. Posted July 27, 2011 at 08:13 | Permalink

    the last thing I missed was a feedburner via email subscription, check my blog I have one, I can even make one myself of your blog and subscribe :) that is what i will do, I can’t wait for you to do it :) keep blogging on your next steps, we are following, and another thing, take that captcha out! just upgrade wp, you dont need that thing it is too hard!

  5. Posted July 27, 2011 at 09:49 | Permalink

    awesome post, i will look into your PagesBundle!

    one remark on point 3: if you init the submodules of phpcr-odm recursively, you will get jackalope and phpcr in versions that should be compatible. phpcr-odm and jackalope master might diverge sometimes.

    did you see the cmf NavigationBundle and ContentBundle? there is a CMF Sandbox that integrates the phpcr doctrine layer with jackalope and everything, as well as the cmf bundles with a symfony2 installation. i do hope that during the cmf workshop in italy we bring that sandbox up to date.

  6. drm
    Posted July 27, 2011 at 22:39 | Permalink

    Thanks for all your comments, guys. I don’t have time examining all your suggestions right now, but I will any time soon.

    @cordoval, I enabled the notify plugin and disabled the captcha. If, all of the sudden, I am now eligible for discount on viagra, I’ll know where to thank you ;) Thanks for the tips. Not sure what you mean by your feedburner suggestion, though …?

  7. Posted July 29, 2011 at 18:38 | Permalink

    about the feedburner thing you can cut a feedburner then go under publicize tab on and then activate delivery over email then you will understand there is a widget there you can add to your blog

    I have it as link at just finished doing a post on phpcr cmf-sandbox install

  8. Posted January 27, 2012 at 00:00 | Permalink

    Hello. I’ve found a typo at the step 3. In the line git clone git:// is phpcr-odm and not phpcd-odm

    At the next line the URL for DoctrinePHPCRBundle has changed fromgit:// to git://

    I also think that the code at the step 5 has to changed somehow but I haven’t figured out how yet. Hint ?

  9. Posted February 4, 2012 at 15:10 | Permalink

    note that we are building official docs now at

    one thing missing in this tutorial is to register the annotations for phpcr-odm. see also

  10. Posted February 21, 2013 at 01:30 | Permalink

    Thanks for sharing your info. I truly appreciate your efforts and I will be waiting for your next write ups thanks once again.

  11. Pallavi
    Posted May 2, 2013 at 11:40 | Permalink


    I am new to the Symfony and CMF too.. Please help me to get out of this issue is urgent.. “None of the chained routers were able to generate route: Route ‘/cms/simple’ not found, /cms/simple”

    Thanks, In Advance

  12. Posted April 22, 2014 at 21:01 | Permalink

    This text is worth everyone’s attention. When can I find out more?

    Here is my blog: [candy crush saga cheats]( “candy crush saga cheats”)

  13. Posted April 24, 2014 at 05:23 | Permalink

    I simply couldn’t depart your site prior to suggesting that I actually enjoyed the standard info a person supply in your visitors? Is gonna be again frequently in order to investigate cross-check new posts

    Feel free to visit my webpage :: [download belle free]( “download belle free”)

  14. Anonymous
    Posted May 21, 2014 at 06:20 | Permalink


  15. Posted September 23, 2014 at 02:07 | Permalink

    After I originally left a comment I seem to have clicked the -Notify me when new comments are added- checkbox and now each time a comment is added I receive 4 emails with the same comment. Perhaps there is an easy method you can remove me from that service? Cheers!

Post a Comment

Your email is never published nor shared.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">