Craft has recently gone into beta and it’s definitely worth a look. Here, we interview one of its creators, Brandon Kelly on the philosophy behind Craft and its technical aspects.
Why does the world need another CMS?
Let’s face it: all of the popular CMS’s out there today are a mess. They’ve been mismanaged, taken in directions the code was never meant to go, warped to scratch every last edge case’s itch. They’ve become bland, tangled, bloated, and at worst, practically unusable. Their developers are stretched way too thin and buried under mountains of technical debt. All of this has really put content management in the dark ages for the past few years.
So I felt it’s time to start fresh — wipe the slate clean and build something from the ground up with a clear sense of where we’re taking it.
What do you think differentiates Craft from its competitors like ExpressionEngine or FuelCMS?
Focus. We are very opinionated about what types of features a CMS should be responsible for, versus what should be left to plugins. You won’t ever see a cronjob manager built into Craft, but if someone wants to write that plugin, we’ll do everything we can to make it easier.
There are features which we think the CMS should be responsible for, but which not every site is going to need. Localization is a great example of that. There’s really no clean way to do it with a plugin. It’s such a core part of storing content, affecting all areas of the system, and it’s something you want to make sure other plugins are going to support as well. But few sites are going to need it, and we don’t want to make everyone pay for features (and UI cruft) they don’t need. That’s why we gave Craft its “Packages” concept. Packages enable features that the core is already pre-wired for. So whether you need localization support or not, all content entered into Craft is getting saved on a per-locale basis. Buy the Localize package and suddenly you get control over which locales are available, and which locale your content is getting saved to.
What was the reasoning behind choosing the Yii the framework?
We’re a very small team, and Craft has been a HUGE project. So it’s been vital that we make smart technology decisions every step of the way. Choosing a PHP framework was no different. When we were just getting started a few years ago, we looked at several frameworks that were available at the time to determine which would push us in the right direction as far as possible. Yii fit the bill perfectly. It’s optimized for the code architecture we wanted, it has great internationalization support, it is modular, and the code is clean, consistent, and well documented. As we’ve built out the app, we’ve grown to like it more and more, and we really couldn’t be happier with the decision.
Could you speak to specific featuresets, APIs, etc. in Yii that lead to things like better localization and how it’s optimized for the architecture you wanted?
Sure. Yii comes with a ton of data for practically every locale on the planet — number formats, currency symbols, translations for common things like month names, and so much more. You can easily access all of that data for a particular locale by initializing a new CLocale instance. It also has built-in translation support, which takes all of that data into account.
One of the great things about Yii is that pretty much everything is customizable. Yii realizes that every app is going to be built a little differently, so whenever there’s a possibility for differences, it separates the common functionality from the implementation-specific functionality. Take translation support for example. Some apps might want translations to be defined in the database, others a PHP array, others an XML file, and who knows, others might even be fetching translations from a third party web service. So Yii puts the core translation support in one spot, and lets the app provide a function that returns the translations. This approach has been used everywhere throughout the framework, which has allowed us to build the exact app that we wanted, without resorting to a bunch of framework hacks.
Can you talk a little about about how you decided on the plugin architecture (how it would work, what it would support, how it would interact with the control panel)?
The plugin architecture was actually one of the few things I didn’t have to think much about. After years of writing ExpressionEngine add-ons, I knew exactly what I wanted — plugins should provide additional system components, which work exactly the same as the built-in components.
A key part of the architecture is our service layer. All of the business logic in Craft happens in service classes, which provide APIs that anything else in the system (including other services) can talk to. So there’s only one place in the entire codebase where an entry will actually get written to the DB, even though there are multiple places that deal with saving entries. This is really great for plugins, because they don’t have to reinvent the wheel every time they want to replicate core functionality. Any plugin can call
craft()->entries->saveEntry() just like the core does. And of course, plugins can provide their own services too, which makes it easy for plugins to interact with other plugins.
Could you give an example of a service layer in action?
craft()->entries->saveEntry() function I mentioned.
craft()->entries is the app’s current EntriesService instance, a class that provides several API functions for managing entries.
saveEntry() accepts one argument: an EntryModel instance. That
EntryModel should be filled with all of the data needed to represent the entry that’s about to be saved, including its ID if it’s an existing entry.
saveEntry() validates that data, and saves it out to the database. (In this case, that’s 4 separate tables:
entries_i18n. But EntriesService is the only place in the entire app that needs to know that; everything else can just deal with EntryModel’s.) The function will return true or false depending on whether the entry was saved successfully. If it wasn’t, the EntryModel will also be populated with any validation errors that occurred, so whatever called saveEntry() will have an idea of what went wrong. In most cases, those errors are passed back to the user.
saveEntry() will generally get called from the EntriesController’s
actionSaveEntry() method. It populates a new EntryModel with post data, and then passes that EntryModel on to
craft()->entries->saveEntry(). What happens next depends on the
saveEntry() response, and what type of request it is (normal or Ajax). But the beauty is that anything can call
saveEntry() — it’s not tied to the controller in any way. If a plugin wants to create a new entry, it can. If we decide that the installer should auto-create a Welcome entry for you, we can add a
saveEntry() call to InstallService.
What made you decide to use Twig?
We actually started off writing our own template parser. I wanted something that resembled Django templates, that compiled down to PHP, and was capable of at least some level of template error handling. My first pass at the parser was very simple, but it got us about 80% of the way there. That last 20% proved to be much more difficult, however. It included stuff like string concatenation, object declarations, and precise error handling. It was clear that the only clean way to do those would be to write a proper lexer and parser. We actually started going down that path, and then Paul Burdick pointed us to Twig, which somehow we had completely missed.
Twig was exactly what I wanted. Its syntax is inspired by Django, it compiles templates down to raw PHP, it has a real lexer and parser, it has both syntax and runtime error handling, and it’s super easy to extend. And thanks to the syntax being identical to our home grown templating system, I was able to get Twig up and running with the entire CP converted to it within just a couple hours. Brilliant!
How is Craft going to handle the common problem of modern deployment? Meaning, how can Craft rectify local databases vs production dbs, local configs, etc?
We knew early on that keeping content in sync between multiple servers (dev vs. staging vs. production) would be a problem for Craft just like every other database-storing CMS out there. It doesn’t take any research to know that it’s a very hard problem to solve, so we decided it’s not something we’re going to worry about until some point after 1.0 launches. However we did spend a little time researching sync techniques, and put some code in place that should make our lives much easier down the road.
As far as configs go, Craft config files are just PHP, so you can already have them return one thing or another depending on the current server. We do have more work to do when it comes to what you can configure though. For example we want to make all path/URL settings throughout the CP overridable from config/general.php.
How up-to-date with Twig and Yii can we expect the framework to stay?
We try to keep all of Craft’s dependencies as up-to-date as possible, so long as there aren’t any breaking changes. We haven’t been religious about it to date, but that should change after 1.0 is out once we’ve had a chance to hook Craft up to Composer.
When there are breaking changes (such as the forthcoming Twig 2.0 and Yii 2.0), we’ll shelve it for Craft 2.0.
Can you talk about your build system for different versions of Craft?
That’s something that has changed quite a bit over time, but the gist was always the same — we had files that were package-specific, and the shared files had blocks of code that were package-specific, and the build script was responsible for including/excluding the appropriate files and blocks.
Once we kicked off the private beta, we started seeing something happen that we didn’t expect (but should have): People who had more than one Craft site running would download an update for one of their sites, and upload the same set of files to all of their sites. Inevitably the list of selected packages would vary between the sites and the uploaded files wouldn’t match the packages, and that led to some fun troubleshooting work. So we decided to take a different approach: now everyone gets the exact same set of files, and we’re toggling which features you get right in the code. It’s a much simpler approach, and has actually given us the opportunity to drastically clean up the build script and our web service, as well as paved the way for Craft’s extremely simple in-app package purchasing.
How did you implement the Autoupdate system?
One-click updating was actually the very first thing we started working on. It’s a pretty major bit of functionality, and if we couldn’t do it, I didn’t think it would be worth doing a CMS to begin with.
Here’s a very high-level overview of how it works:
- We have a TeamCity server which creates a new Craft build every time we push our work to the Master branch on GitHub.
- We have a Craft install on the same server with a custom “Craft Releases” plugin, which we use to cut new releases. It’s able to figure out exactly which files have been changed/added/deleted since the last release by parsing Git commit logs, and it records those along with any other metadata for the release, like the release notes.
- Craft’s web service (another Craft plugin called “Elliott”) talks to the Craft Releases plugin to tell the Craft client about any available updates.
- When an update request comes in from a client, Elliott will check which build the request says they are currently running, and create a zip of all the files that have changed since then, and respond with that.
- Craft then takes that update zip, verifies it, backs up the DB, renames any of the changed files to *.bak, moves the new files into place, runs any pending migrations, deletes the temp files, and clears your caches. And all of that happens in just a matter of seconds!
One of Craft’s neatest features is the free trials for packages, how can you implement this securely using PHP (so that people don’t pirate it)?
We decided our stance on piracy early on: piracy is a fact of life, and nothing we do is going to stop it. So instead of wasting a whole bunch of time trying to prevent it, we’ve simply made it impossible to pirate Craft “accidentally”. Yes, anyone that knows anything about PHP could go into the code, comment a few lines out, and boom, all packages are installed without any annoying CP alerts. But at least none of them will be under the impression that they didn’t do something wrong.
Given the number of people who re-use EE and add-on licenses without realizing that it’s not OK (and strangely lacking the motivation to make sure), I’m expecting that this will be pretty big piracy deterrent in and of itself.
One glance of code shows that Craft uses a singular global namespace (Craft); Some people on Twitter have complained that it’s a poor use of PHP’s namespacing feature because instead of Craft/Plugins/PluginName, you have to use PHP 4 style classnames (namely with underscores). Can you go in-depth about the decision to not support this feature of PHP?
You should be asking the PHP guys what they were smoking when they added namespace support to begin with. It’s a mess!
We might add the option for plugins to live in their own namespace, and forgo the class name prefixes, so developers that actually understand how PHP namespacing works can take advantage of it, without forcing us to explain it to everyone else.
There are a couple new tools and standards in PHP that are gaining significant popularity, does Craft intend to support Composer packages? PSR-0 and PSR-1?
Composer-yes. We might look into strictly following PSR-0 and PSR-1 down the road, but at this point it’s safe to say it would be a 2.0 thing.
Can you speak a bit about how Craft intends to help plugin developer with the distribution and sales of their plugins?
Yup! It’s no big secret that we’re planning a plugin store for Craft. We’re still ironing out many of the details, but one thing that’s for sure is it’s going to be built right into the app, just like packages. And many of our policies for packages will carry over to plugins as well.
The Craft Forecast page is a task list of things to come, many of which are small changes (except for a few). What big feature comes next after 1.0?
The goal for the private beta was to get Craft to a point where it’s pretty much feature-complete for 1.0, and the goal for the public beta is to tie up all the loose ends. The Forecast page is currently just a list of those loose ends, which we’re tackling one-by-one.
Once we get to 1.0, we’ve got a second list just as long as the 1.0 list is now, full of the same sort of things, which just didn’t seem quite important enough to be worth pushing back the 1.0 release. That includes things like section duplication, the ability to create new fields right from within the Field Layout pages, and so forth.
Then there’s the 2.0 list, but it’s too early to start talking about that!
When will Craft hit 1.0?
My best guess is mid-to-late Summer. We shall see!
Our thanks to Brandon Kelly and Brad Bell of Pixel & Tonic for taking the time to answer our questions.