An official sub-project of Symfony2, the CMF isn’t “yet another CMS” but a powerful modular collection of libraries and bundles.

Introducing the Symfony2 Content Management Framework

When starting a new web project, you have to decide whether it is a CMS project or an application. This is a difficult decision. For the application approach, you can use a solid and clean framework like Symfony2. But if the project also encompasses some content management, the efforts for that part quickly eat up way too much of the budget.

On the other hand, you can chose a powerful CMS like Drupal. Common CMS functionality is already there at no cost, but custom functionality means either slapping together dozens of modules of sometimes doubtful quality, or a lot of coding without any proper framework. Also, experience shows that when we need to do a task differently than how Drupal thinks, it quickly becomes very hard as you cannot replace or extend core parts of Drupal. The only option is to add callbacks to fix things after the fact, at the costs of unexpected interactions with all the other modules.

The CMF aims to provide a way out of this dilemma. It consists of a couple of Symfony2 bundles and PHP libraries that provide the means to do content management. You can add them to a Symfony2 application to satisfy CMS needs. Or you can use them as a base to build a completely custom CMS without wasting time on the basics. Use the components that make sense for the project, extend what you need and ignore what you don’t.

The CMF follows the idea of Decoupled Content Management. The Symfony2 bundles are about functionality only. Persistence is cleanly separated, and while a default handling for Doctrine PHPCR-ODM is provided others can be used just as well. The CmfCreateBundle enables frontend editing using the general purpose create.js editing framework.

The CMF is currently in release candidate phase, with a stable 1.0 release expected at the time this article is published. A very active community is maintaining and improving the various components. A couple of websites are already using the CMF or parts of it. Also, both Drupal 8 and ezPublish are using the routing component of the CMF.

Basic Usage

The best starting point to explore the CMF is the Sandbox. You can click around in, but best is to install it locally to be able to change code.

$ git clone git://
$ cd cmf-sandbox
$ cp app/config/parameters.yml.dist app/config/parameters.yml
$ cp app/config/phpcr_doctrine_dbal.yml.dist app/config/phpcr.yml
$ curl -s | php --
$ ./composer.phar install

You should now have a Symfony2 project with the CMF set up. The storage engine we chose in Listing 1 is Doctrine PHPCR-ODM with the sqlite database driver. Next we need to initialize the database and the repository. The last line of Listing 2 loads some fixtures so that we actually have something to look at.

$ app/console doctrine:database:create
$ app/console doctrine:phpcr:init:dbal
$ app/console doctrine:phpcr:repository:init
$ app/console doctrine:phpcr:fixtures:load

Now you need to point a web server to the web/ directory of the project. When you point your browser to http://cmf.lo/app_dev.php you will be greeted by the default home page. Figure 1 explains what CMF component handles which part of the page.

The CMF start page

The CmfContentBundle handles simple static content. It provides a model class for the content and a Symfony2 controller that uses either the specified or a default twig template to render the content. The CmfMenuBundle extends the KnpMenuBundle, adding the ability to load menus from Doctrine and having menu items point to content documents so that menu entry URLs do not need to be hardcoded. The CmfBlockBundle builds upon SonataBlockBundle to provide reusable pieces of content. This can be used for layout purposes (put content into a sidebar or have pages that consist of many blocks instead of a mixed WYSIWYG content), to display the same content on several pages and also to allow the page editor to control where application output should appear.

Image and also file handling is encapsulated into the CmfMediaBundle. All other bundles that provide file related services use this bundle rather than re-implementing things. This makes sure that the various components are able to share media files. The CmfMediaBundle provides basic models and optionally integrates with LiipImagineBundle for rendering images, as well as with the WYSIWYG editor image handling plugin elFinder. Integration with the SonataMediaBundle is planned.


When a request is received, the first thing Symfony2 has to do is to decide what code will handle this request. The main information used to make this decision is the requested URL, but other information such as the host name used or accept headers can play a role as well. This process is called Routing in Symfony2 and the result of the routing is information about which Controller to call. In core Symfony2, routing is configured using configuration files that define URL patterns and corresponding controllers.

The CmfRoutingBundle extends Symfony2 core routing to load routes from a database so that they can be edited by the user. A CMF route can simply specify a controller name. More interesting is the option to reference a content object. An additional step called Enhancer inside the routing processes the matched route to infer additional information like the controller and the template to use for the referenced content. The aim of this design is to not duplicate configuration information in the database so that refactorings of the application logic do not need data changes. The logic can also be used to transform URL parts into domain objects to keep the controller lean. Drupal has been using this idea for quite some time, calling it “Upcast”.

The image below illustrates the routing process. The additional steps provided by CMF are colored in green.

The CMF routing process

An interesting side effect of linking routes with content is that the CMF routing can also generate a URL from the content object. This is for example used in the menu system, to get the URLs for menu items that link to content.

To seamlessly integrate the CMF into standard Symfony2 applications, the CmfRoutingBundle also provides a ChainRouter which replaces the core Symfony2 router. The ChainRouter calls the core router, the CMF router and any other routers that where registered to it, in a configurable order. That way, normal Symfony2 routes are still possible and do not need to be transferred into the database.


When the user is allowed to edit content, the CmfCreateBundle shows the Create.js toolbar at the top of the page. By clicking on the Symfony2 logo, the editing bar is minimized and expanded. Clicking the “Edit” field enables Frontend Editing. The content is edited right in the location where it is shown. Create.js provides real WYSIWYG editing instead of changing the HTML structure by adding an iframe around the editable content. That way, the CSS does not need to be adapted and editing does not diverge from display. Upon clicking “Save”, all edited content is stored over AJAX and the page is updated without reloading. The screenshot below shows the editing in action.

Live editing

The CmfCreateBundle integrates create.js into Symfony2 with the help of the CreatePHP library. Create.js uses RDFa attributes on content to know what content is editable and how to communicate with backend to save changes. CreatePHP implements the backend handling for storing changes and also provides helpers so that template writers do not need to manually handle the RDFa attributes. Sounds confusing? Lets add a new type of content to the sandbox to see how this is all done.

Adding a new type of content

To see a bit more, let’s add a new type of content in the sandbox. For the sake of the example, let’s simply add a separate “Intro” field that will be used in a list view of the content. For the model, we simply extend the StaticContent from the CmfContentBundle:

The generic controller will do just fine for this content. But we need a different template to render the intro field. We configure the routing to know that it should specify our new template for the new content class in Listing 4.

# app/config/config.yml
            # ...
            SandboxDocumentIntroContent: SandboxMainBundle:IntroContent:index.html.twig
            # ...

In the template, simply add {{ cmfMainContent.intro }} where you want to output the introduction text field.

Make the content editable with create.js

If you want the field to be editable with create.js, a bit more work is required. You need to provide the mapping between the model and RDFa as shown in Listing 5.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

- Gib Deinen Standort ein -
- or -