ExpressionEngine 2 is a wonderful content management system and arguably the most designer-friendly CMS out there, used by many well-known names, like A List Apart, Andy Clarke and Veerle Pieters. Ironically, however, it’s default configuration is poorly suited to use in a professional web development workflow, which usually involves multiple sites, servers, and developers.
This tutorial will show you how to customize ExpressionEngine 2 so you can hit the ground running with a rock solid yet flexible starting point that can easily deploy to multiple environments in minutes.
Overview
I’m not a programmer. However, the programming mantra don’t repeat yourself, or the DRY principle for those acronym lovers among us, has really begun to resonate within me as I get more involved both with web development and running my own business. In fact, DRY is good advice for living out your life in general. Repeating yourself costs more time up front, and potentially a lot more down the road if you have to go back and make the same change in multiple places.
Plus it’s a hindrance for personal growth because if you’re doing something you’ve already done, you’re not learning something new. What’s better is to identify those places where you do repeat yourself and come up with a system to standardize that task or piece of data.
A Little History
When I first started working with ExpressionEngine a year and a half ago, it was a one-off project and I was a novice designer. Needless to say, the DRY mentality was the furthest thing from my mind. I was happily humming along, mucking with settings as the situation dictated, not documenting anything and having a blast with custom fields and template groups, those things that make EE a designer’s dream come true. It was sort of like my first date with the software. In the end, I liked EE so much that I decided to get exclusive and “marry” it as my CMS of choice for all future projects.
After about the third or fourth site, however, I began to see flaws in our relationship (as is liable to happen when you really get familiar with something) and got frustrated doing menial, repetitive tasks related to deploying and managing EE. This was especially apparent with some ongoing projects which required twice or thrice weekly updates from development to staging to live servers. It got to the point that I was spending nearly as much time managing deployments as I was actually coding.
The Solution
Not content to lose money and slave away at boring drudgery, I sought to tidy up the mess.
What follows is the fruit of my and others’ labor, a guide to applying the DRY principle to developing and deploying sites with EE.
It walks you through how I’ve tweaked and customized ExpressionEngine 2’s flabby, nonsensical default configuration into a lean, efficient workhorse that takes nearly all the repetition out of working with EE. Specifically, these modifications will:
- Provide a starting point with all the commonly used addons installed and settings turned on so you’re not running the installation wizard and starting from scratch every time.
- Integrate EE with a version control system of your choice for rapid deployment to multiple web servers or developers’ workstations and easy management of code. My experience is with SVN but all the principles apply to Git as well.
- Centralize all settings and configs to facilitate easy migration from one server to another, so launching and pushing updates is a cinch rather than a headache.
This has been a rather large endeavour and I couldn't have done it alone. A big thanks go out to the following people, who helped me whether they knew it or not:
- Tony Chester of OnWired and Casey Reid of Clearfire Studios, for getting me going in the right direction.
- Jamie Pittock of Erskine Design and his awesome talk at EECI 2010, for figuring out a lot of this stuff in EE 1 and inspiring me to get it going in EE 2.
- Jeff Freeman of Futurity Web Design for helping me with a little bit of PHP to make it happen (like I said, I’m not a programmer).
Step 1: Download and Installation
For your sanity’s sake get a fresh copy of the latest build of EE 2 before you do any of this. Download and install as normal, preferably on a local server, as you’ll be making lots of changes to the files. Leave out the Agile Records templates when you are prompted.
Grab the example files that are included with this tutorial. You don’t need to do anything with them just yet, but keep them handy.
Step 2: Pouring out the Config Soup
If you’ve ever had to migrate ExpressionEngine from one server to another, you know that this task is no easy feat; in fact, it’s a complete nightmare if you’re unprepared. A lot of this stems from the fact that ExpressionEngine stores config variables and server paths all over creation, to the point that it’s difficult to track them all down and adjust them when you move servers.
Kenn Wilson of Corvid Works sums it up in better English than mine:
“This is what makes Expression Engine so unportable—moving from one server to another, say from development to production, requires updating this URL and path information in literally about a dozen places. It’s clumsy, time-consuming, and error prone.”
He’s right on. Fortunately, there’s another way. Rather than editing all those variables in a dozen places in the control panel and probably forgetting some, you can consolidate them in one place—the config files. That’s right, all those fields scattered over dozens of pages in your CP map to a couple of PHP files. By default, ExpressionEngine stores config information that you will need to worry about in two files. These are:
system/expressionengine/config/config.php
system/expressionengine/config/database.php
Ditching database.php
As you might imagine, database.php
stores the MySQL database connection information. I suppose EllisLab takes the position that it’s easier to find the DB information if it’s in it’s own aptly named file, but I’m going to argue the opposite. This is DRY, damn it! I’d rather open one file and edit my settings from one place, not two, so I did away with database.php
altogether. Well, not quite, but I did take all the database settings from it and move them to config.php
with a little PHP.
<?php if ( ! defined('EXT')){ exit('Invalid file request'); } /* THIS FILE WILL NEED PERMISSIONS SET TO 400 OR SIMILAR SO EE DOESN'T OVERWRITE IT. */ require 'config.php'; break; } ?>
Rename your existing database.php
file to something like old-database.php
and move it onto your desktop, as you’ll need the connection settings later. Replace it with the database.php
included in this tutorial and set the permissions to 400 as indicated.
Congratulations. You’ll never need to worry about database.php
again.
Consolidating config.php
Now that database.php
is telling ExpressionEngine to look for the database connection info in config.php
we need to actually put it in there, but there’s a problem. When EE moves from one server to another, the database connection settings need to change to reflect the new server environment. If we want to develop and deploy EE with a version control system (and trust me, we do), then every time we deploy a working copy to a new server, we would need to download a copy of the config.php
, edit the database settings so they’re correct for that server, FTP it back up to the server, and make sure to tell our version control to ignore it when we issue a commit or update. At the best case scenario, we’d have a separate, non version-controlled config file for each additional server on which the site resides. For me (and I’m a one-man show) that’s:
- iMac’s local server
- Macbook Pro’s local server
- staging server
- live server
Add another couple developers if you work at an agency and you’re looking at a lot of these buggers running around. So what happens when you need to change another config variable, like the license number? Do you email yourself and other developers a copy of this file and upload it to all servers one by one? DRY, my friends, DRY. The only logical answer is a single, version controlled config.php
file which can accommodate all server environments.
Nonsense, you might say, but thanks to some clever PHP it is indeed possible. As you can see in the example below, the PHP case syntax looks for an IP address, and serves the appropriate database settings based on that IP. Now the only things you need to know and change when you deploy to a new server are the IP address and the database connection information, which should be readily available to you.
/* Environmental Variables */ switch ( $_SERVER['SERVER_ADDR'] ) { // local case '127.0.0.1' : $db['expressionengine']['hostname'] = "localhost"; $db['expressionengine']['username'] = "root"; $db['expressionengine']['password'] = "password"; $db['expressionengine']['database'] = "local-db"; break; // staging case '72.10.54.22' : $db['expressionengine']['hostname'] = "mysql.exampleserver.com"; $db['expressionengine']['username'] = "admin"; $db['expressionengine']['password'] = "password"; $db['expressionengine']['database'] = "staging-db"; break; // live case '82.335.65.67' : $db['expressionengine']['hostname'] = "mysql.exampleserver.com"; $db['expressionengine']['username'] = "admin"; $db['expressionengine']['password'] = "password"; $db['expressionengine']['database'] = "live-db"; break; }
At this point I want to distinguish between what I call environmental variables and universal variables. Environmental variables are different on each server environment. Universal variables are the same no matter which server the site resides on, so they go outside the IP switch/case syntax. These are things like the server paths and URLs to the themes folder, template folder, CAPTCHAs, the license number, basically anything besides the aforementioned database information and IP address (these are all commented in the included file for reference).
Did you hear me say that server paths and URLs stay the same no matter what server you’re on? Yes you did. As long as your site’s folder structure remains the same in every instance (and if you’re on version control it obviously will), the custom config.php
included in this tutorials uses PHP variables to detect the root server path and URL and fill them in for you. Why EE doesn’t do this to begin with baffles me, but I digress. No more forgetting to change the server path to your themes folder when you migrate servers and spending an hour figuring out why you have a blank screen instead of a CP. Anyone excited yet?
/* Universal Variables */ $config['app_version'] = "211"; $config['license_number'] = "0000-0000-0000-0000"; $config['debug'] = "1"; $config['install_lock'] = ""; $config['system_folder'] = "admin"; $config['doc_url'] = "http://expressionengine.com/user_guide/"; $config['is_system_on'] = "y"; $config['cookie_prefix'] = ""; $config['site_name'] = "Flourish Interactive Codebase"; $config['allow_extensions'] = "y"; /* General -------------------------------------------------------------------*/ $config['site_index'] = ""; $config['site_url'] = "http://".$_SERVER['HTTP_HOST']; $config['server_path'] = $_SERVER['DOCUMENT_ROOT']; $config['cp_url'] = $config['site_url']."/".$config['system_folder']; /* Universal database connection settings -------------------------------------------------------------------*/ $active_group = 'expressionengine'; $active_record = TRUE; $db['expressionengine']['dbdriver'] = "mysql"; $db['expressionengine']['dbprefix'] = "exp_"; $db['expressionengine']['pconnect'] = FALSE; $db['expressionengine']['swap_pre'] = "exp_"; $db['expressionengine']['db_debug'] = FALSE; $db['expressionengine']['cache_on'] = FALSE; $db['expressionengine']['autoinit'] = FALSE; $db['expressionengine']['char_set'] = "utf8"; $db['expressionengine']['dbcollat'] = "utf8_general_ci"; $db['expressionengine']['cachedir'] = $config['server_path'].$config['system_folder']."/expressionengine/cache/db_cache/"; /* Member directory paths and urls -------------------------------------------------------------------*/ $config['avatar_url'] = $config['site_url']."/uploads/system/avatars/"; $config['avatar_path'] = $config['server_path']."/uploads/system/avatars/"; $config['photo_url'] = $config['site_url']."/uploads/system/member_photos/"; $config['photo_path'] = $config['server_path']."/uploads/system/member_photos/"; $config['sig_img_url'] = $config['site_url']."/uploads/system/signature_attachments/"; $config['sig_img_path'] = $config['server_path']."/uploads/system/signature_attachments/"; $config['prv_msg_upload_path'] = $config['server_path']."/uploads/system/pm_attachments/"; /* Misc directory paths and urls -------------------------------------------------------------------*/ $config['theme_folder_url'] = $config['site_url']."/themes/"; $config['theme_folder_path'] = $config['server_path']."/themes/"; /* Templates Preferences -------------------------------------------------------------------*/ $config['save_tmpl_files'] = "y"; $config['tmpl_file_basepath'] = $config['server_path']."/templates/"; $config['site_404'] = "404/index"; $config['strict_urls'] = "n";
To install the custom config.php
file:
- Rename your existing
config.php
, located atsystem/expressionengine/config/config.php
, to something likeold-config.php
and move it to your desktop. - Grab the
config.php
included in this tutorial and drop it intosystem/expressionengine/config
. Set permissions to 400. - Open up your new
config.php
in your code editor, along withold-database.php
andold-config.php
- Copy and paste the settings from the old files into the new one. The file has been commented so you know what to put where.
Keep in mind that a universal variable can become an environmental variable if you need it to. Let’s say that you want to change your site name automatically based on the server it’s on, so you can tell at a glance if you’re looking at the local, dev or live version of your site. Just delete the variable from the “universal variables” area and copy it into each IP case, assigning it whatever value you want.
Step 3: Cleaning House
Let’s face it; the default install of ExpressionEngine includes a lot of files you don’t need, especially if you’re a professional developer who’s not poking around for the first time. These include the theme files for the Agile Records example site, smileys, wiki themes, and a lot more. Why fatten your site unnecessarily? Put EE on a diet and delete all this stuff, you can always grab a fresh copy and add it back in the unlikely event you need it for a wiki, forum or other community-based site. Delete only what makes sense for you, but I’ve done about a dozen EE sites and never used any of it.
/themes/wiki_themes
/themes/site_themes/agile_records
/themes/profile_themes/agile_records
/images/smileys
/images/avatars
Step 4: Create a Standard Top-level Folder Structure and .htaccess File
Like many tasks in web development, there’s no one right way to go about this, but what’s important is that you pick a way and stick to it. Some people like to put their static asset files (images, css, js, swf, etc.) in a /themes/site_themes/examplesite
folder. I prefer to put each asset folder on the top level because I’m lazy and don’t like to click through three levels of subfolders to access these files during development, plus I like nice short URLs in my HTML and CSS. Now that I’ve gotten used to a standard structure, I do not create additional top level files or folders unless absolutely necessary (you’ll see why in a minute). This is what my top level structure looks like.
-
.htaccess
– will explain more in a minute -
system
– rename this please css
favicon.ico
-
fw
– this is short for “framework” e.g. my CSS background images -
images
– non CMS-managed content images index.php
js
robots.txt
templates
-
themes
– CP and fieldtype themes -
uploads
– where all CMS-managed docs and images go
Now I get around to talking about .htaccess
. It’s a mystery to many developers and frankly it is to me too, but I know enough to use it to remove that unsightly index.php
from EE’s otherwise pretty URLs. I use a variant of the exclude method from the ExpressionEngine Wiki. This is in no way guaranteed to work on your web host, but it’s worked for me on MAMP Pro, HostGator and MediaTemple, both (gs) and (dv). The usual caveats apply, e.g. mod_rewrite
must be enabled in Apache’s http.conf
etc. If you’re using this method of removing index.php
and wish to add a new top level file or folder to your site (and I mean a “real” file or folder, not an EE entry, template or template group), you’ll need to add an exception in .htaccess
or else that file/folder will be inaccessible.
RewriteEngine On RewriteCond $1 !^(admin|css|fw|images|js|templates|themes|uploads|favicon\.ico|robots\.txt|index\.php) [NC] RewriteRule ^(.*)$ index.php?/$1 [L] AddHandler php5-script .php
To install my custom .htaccess
, drop the included file named temp.htaccess
into your top level folder. Remove the "temp" part of the filename (everything before the period). Your operating system might warn you that renaming the file will destroy the universe. Ignore this and hit OK. The file might disappear, which is fine because .htaccess
is a hidden file. Now if you want to edit it, you will need to have hidden files be visible in your OS settings.
Step 5: Install Your Default Add-ons and Configure Them
After developing several EE sites, there are add-ons I am either unwilling or unable to live without. These are the best the EE development community have to offer and they have the honor of being installed in my codebase so that every new site has them from the get-go. They are (and these are all free):
- Freeform by Solspace
- Dive Bar by Pixel & Tonic
- ED Image Resizer by Erskine Design
- Low Seg2Cat by Lodewijk Schutte (Low)
- Accessible CAPTCHA by Greg Salt (Purple Dogfish)
- Character Limiter by EllisLab
Don’t just install these, configure them. For example, I have set up all my email notification templates for Freeform, created additional custom form fields based on what I usually use for a standard contact form, and I have a template called contact.html
which has the front end form code in it, including JavaScript validation and a success message. Even if I need to add a field or two, or move that form code into a different template, it’s a matter of tweaking, not creating from scratch every time. DRY. Minus CSS styling, that form is ready to go out of the box.
Be on the lookout for another article by me soon as I discuss these and a couple of commercial add-ons for EE2 in more detail.
Step 6: Set Up Your Client’s Member Group
Giving unlimited access to my client is scary for both them and me.
This is one of those things you likely forget to do until you’re nearly finished with the site, but it doesn’t need to be if it’s in your codebase. The default EE administrator account belongs to the Super Admins member group, which necessarily has access to everything. Giving unlimited access to my client is scary for both them and me, so I create a second member group called Admins. I usually wait until they’ve picked an email address before I actually make their account but that only takes a couple seconds once you have the member group permissions defined.
In this member group I’ve turned off all access to the templates, site and member administration, communication module, and add-ons. All that most clients need to do is create and edit content, and maybe view their Freeform submissions. That’s it. Simplify their life and yours and take away what they don’t need. Again, I’ve had to tweak this before but a starting point is better than starting from scratch.
Step 7: Working With Your Codebase
Congratulations, you should now have a far superior starting point for your next ExpressionEngine project. So that you can add to it and reuse it, create a new project in your version control and commit your customized ExpressionEngine codebase as version number one. Below are examples of some common operations you’ll likely need to do once you’ve got new projects in the pipeline (may vary depending on server setup, or if you’re using Git instead of SVN).
Create a New Project - 10 minutes
- Clear all caches of your codebase project.
- Export database and import under new project name using PHPMyAdmin or similar.
- SVN export a copy of your codebase to the working copy folder of a new SVN project. VERY IMPORTANT: Note that I said export, not checkout.
- Set the following folders and their contents to permissions 777:
/templates
-
/uploads
(or whatever your upload folder is named) /system/expressionengine/cache/db_cache
- Add DB connection info for new DB to
config.php
. Change the site name, license numbers and any other preferences you need to change. - Load up your control panel and change the file upload preferences. These are stored in the database and cannot be put in the config for some asinine reason.
- Go nuts.
Deploy a Site to a New Server - 10 minutes
- Clear all caches.
- Export and import database using PHPMyAdmin or similar.
- Find IP address and database info and add a new IP case section to
config.php
. - Commit
config.php
to your repository. - Check out your site's repository to the public_html folder of your new server.
- If it’s a local server use your SVN client.
- If it’s a remote server use the SSH command svn checkout http://samplerepository.com/sampleproject/ . The space and dot after the trailing slash checks out the contents of the folder to the current folder, otherwise you’ll get public_html/sampleproject/index.php if you leave out the dot.
- Set the following folders and their contents to permissions 777:
/templates
-
/uploads
(or whatever your upload folder is named) /system/expressionengine/cache/db_cache
- Load up your control panel and change the file upload preferences.
Update a Site to an Existing Server - 1 to 5 minutes
- Clear all caches (only if you've made changes to the database).
- Export and import database using PHPMyAdmin or similar (only if you've made changes to the database).
- Run an SVN update on your site copy:
- If it’s a local server use your SVN client.
- If it’s a remote server use the SSH command svn update. You shouldn’t need to re-enter the URL or password.
- Load up your control panel and change the file upload preferences (only if you've made changes to the database).
Conclusion - Sojourning in the DRY Desert
As you go about your business designing and developing kick-ass ExpressionEngine websites, keep yourself mentally aware of what you’re doing at all times, from a big-picture, functionality perspective. Some pieces of website functionality are nearly identical across sites, they just need some minor markup tweaks and a CSS “skin” to easily transfer from one to another.
In the future, microformats will standardize the markup even more! These are ideal candidates for inclusion in your codebase. One we already discussed is the ubiquitous contact form. Some other potential “standard” functionality (I’ve had multiple clients ask for these things):
- Blogs and their associated comment forms
- Address or v cards
- News release sections
- XML or “Google” sitemaps
- Search and search results pages
- Custom Share This! type code
- Facebook or Twitter timelines
You could theoretically have channels, categories, custom field groups and templates built out and ready to go (I know I do for a lot of these). Your client is still getting the same amount of value that they would if you hand-built these pieces for their site (arguably more since they’ll be refined and tested more often) and you do less work, meaning you can price yourself more competitively, or if you sell fixed fee, charge the same price and turn more of a profit. Remember to have fun and enjoy developing with ExpressionEngine!
Comments