I know I gave my blessing before to diem, and I’ll still admit that it is a very good CMS. However, it has always bothered me a little bit just how invasive this CMS is. While it bills itself as a symfony plugin, it is in truth anything but. You must start out using diem, and then add a little symfony in when you need it.
I was reading, recently, a post on the sympal blog. In this post, Ryan Weaver echoed my latent feelings that a CMS should empower your site with CMS capabilities, but should not take it over completely. In other words, the perfect medium would be in the form of a plugin for symfony 1.x, or bundle perhaps for Symfony 2.x.
There was a comment put in there by somebody from the apostrophe team, which made me realize that, apostrophe was perhaps already meeting this goal. Seeing as I needed to pick a new CMS for an upcoming project, I thought I’d take it for a spin.
I’ll start with, what is in my opinion, the basic backbone of what any CMS should be able to do.
- The first thing, I believe, is managing your websites structure and hierarchy of pages. I don’t consider this to be the same thing as content, but it is certainly fundamental to a website. Even if your site contains some applications (a blog, a wiki, etc.), there should be a way of shoving them under this rubric, as well.
- The next thing, of course, is managing content. Its not called a content managment system for naught, after all. Unfortunately, content is a very overloaded term. Most CMS’s seem to connect that to your site’s page structure. Create a page, throw up a WYSIWYG editor, and viola! Some CMS’s take a more fine grained approach, and consider content in a more abstract light. Content would be comparable to a data structure in a programming language. I believe ezPublish even goes as far as calling content “classes”.
- Lastly, a CMS should be capable of creating pages which don’t fall under the rubric of your page structure directly, for example, an article section. You probably don’t want an entry in your navigation for every single article, but you definitely want a clean url like http://mydomain/articles/this-is-an-article
With that in mind, let’s talk about the “theory” of apostrophe, and how it approaches these scenarios. As far as content, apostrophe seems to have found a ground between the above mentioned approaches. On the one hand, content does not directly equal a page in your site, but can be closely associated with it. On the other hand, content is not relegated to the abstract world of boring data entry forms, but actually real live snippets of html, which can be controlled through boring data entry forms, or an rss feed, or a youtube video, or anything you want if you have some creative ideas.
With apostrophe, the we start out with the concept of a page. A page represents (almost all the time, but we’ll get to that), a public facing url on your site. Each page is backed by a slug. A page is rendered using standard php + symfony helpers. There is one layout, and each page has a template associated with it. There is no easy way, at the moment, to switch this layout, but there really shouldn’t be much of a reason why you would have to.
On the layout that comes with apostrophe, are a number of symfony slots, which are filled in at various points by apostrophe. These are generally things like menus or breadcrumbs.
Each page, has more than one “area” in it. An area is like a content holder. It can hold various types of content, stacked vertically. Each area has a name and is associated to a page. A template helper is provided to render these areas in your template.
When an area is rendered, and you are logged in with sufficient privileges, you have the ability to add content to this area. These are called slots (not to be confused with symfony slots). There are enough built in slots to put together any run-of-the-mill website fairly easily, and creating custom slots is not terribly difficult. Some of these include, plain text, rich text, video, slideshow, pdf. Each slot comes with a displayable interface (normal mode), and an editing interface (edit mode). The edit mode can be a simple form, or it can be a complex multistep, workflow-engine powered web application (see the image slot). The edit mode is powered by a component called editView. The content for the slots is, wisely, stored as a serialized php array, instead of physical columns in a database table, although the latter is certainly possible if not discouraged. This is somewhat similar to the current trend of NoSQL databases, and somewhat similar to the approach that friendfeed used when scaling their database. It would have been better, in my opinion, to store the information in JSON instead of serialized PHP, or to use an actual NoSQL database, but there is nothing stopping you from implementing your own content slots this way. When the content is not in editing mode, the display is governed by a component called normalView, where you can, of course, massage all your data as you see fit before displaying it in the components template. Each content slot is essentially a module, with two components (editView and normalView), a model class and usually a form as well. This makes it fairly trivial to create custom content slots, especially if you have prior symfony experience.
You also have the option of what is called, under the hood, a singleton area. An area which contains one and only one content slot. There is a special view helper for this as well called a_slot (as opposed to a_area).
By default, each content area is associated with the slug, and therefore the page that its on. In that respect, content and pages are somewhat analogous. For a basic site, this may well give you enough mileage to go. However, the developers realized that there is room for content which is not owned by any particular page, for example, global content. This could be an editable footer or logo which makes its way into the layout. They also recognized that not every page in a website is physically there. They call this concept “virtual pages”, and it allows for some very interesting things. Every content area, both infinite and singleton, can be assigned to a “virtual page”, and thereby pulled out in any physical page, no matter what slug it has. Virtual pages are distinguished by virtue of not have a leading “/” in their slug name, thereby removing them from the realm of the navigation. One use case I came up with, was having a sidebar in the layout, but having the content of the sidebar different for each page. In order to do this, I had to specify a slug that was guaranteed to be unique for each page, so I called it something like “sidebar-$page->id”. I will discuss another use case later.
As I mentioned earlier also, a CMS should also be able to handle pages which shouldn’t exist in your navigation, such as articles. For this, they came up with the concept of engines. Basically, instead of having your page rendered via apostrophe, it gets sent to a regular symfony module, written to certain conventions. You could have an module which renders an article page, based on an id in the url, but this page will still be under the rubric of your site. As an additional bonus, since its a regular symfony module, rendered by a regular symfony template, you can use the apostrophe helpers, and put content areas in a virtual page with a slug derived from the article’s id. This was the other use case I was referring to earlier.
This cms follows the principle of less is more, and because of that, it is a clean, elegant system, with almost no learning curve.
That is not say it does not have some problems. The admin view requires that the content areas be surrounded by a swath of DIVs. Unfortunately, these DIVs make it into the end user mode. My designer was not so happy to hear that the recommendation was to “embrace the DIVs”.
Also, the page gets loaded with a lot of incoherent, redundant, inline javascript. Both inline in the page, and sometimes inlined on the element itself. There is no Javascript framework such as what comes with diem. For example, when you click “add slot” in an area, you are greeting with a very eye-appealing dropdown box. Unfortunately, to mimic that behavior seems to require a lot of copy and pasting.
Certain areas of the code also look suspect and “hackish” to me. Any class named “aTools” with only static methods is certainly a red flag. Of course, symfony 1.x also has code like this as well, but that doesn’t mean that bad habits should be copied. Virtual pages are implemented, by temporarily switching a global variable (disguised a static variable), which holds the current page’s slug, for the duration of the rendering, until when the global variable is swapped back to its original state. This feels like hacky code to me.
Over all, I have to certainly commend the developers of apostrophe. They managed to come up with good solutions for some classic problems in web development. They managed to create a tool which is good for end users *and* developers at the same time. Certainly not an easy task.
#1 by Reen at May 6th, 2010
Thanks for this wonderful article. I was searching for a good cms solution that uses symfony. Ive read your article about diem and so i installed and tested it. its quite powerful. thats for sure, but in my opinion it hides the power of symfony too much.
So im going to try apostrophe. Hope its expandable in an easy way so that i can added it too my own plugins (user online, events, etc)
sry for my bad english.
Reen
#2 by Avi at May 6th, 2010
Sure. Apostrophe makes no attempt to hide Symfony from you, and engines are a powerful way to integrate the rest of symfony goodness into the CMS.
#3 by Tom Boutell at May 24th, 2010
Avi, thanks for this thoughtful and in-depth review. I’m glad you see the value in virtual pages, engines, and using Symfony concepts to extend Symfony rather than inventing an entirely new idiom where you don’t have to.
We’ll be moving away from static helper classes gradually and getting on the “sfContext:: is a bad thing to type” bandwagon.
You’re right about our ad-hoc use of JavaScript. It works because we test the daylights out of it. But it would work better faster and sooner if we followed better practices in it. We need to reduce code duplication and make it easier for others to use what we’ve done. However, there’s some quite clean and reusable stuff in aControls.js, notably aMultipleSelect.