Supercharging WordPress with Lumberjack

Lumberjack is a WordPress starter theme built by Rareloop. It aims to make WordPress better to work with and move it more inline with modern PHP practices. This post is a walk through of why and how we built Lumberjack.

Let’s face it, WordPress can be a poisoned chalice:

Something that seems very good when it is first received, but in fact does great harm to the person who receives it.

You can create really powerful sites without knowing much code. Chances are what you are building has been solved a million times before and is merely a “copy & paste into functions.php” away. But if you don’t take time out to organise your code, you end up with a horrible code base. No sane developer wants to work on one of these sites, because they are completely unmaintainable.

Over at Rareloop, we’ve recently taken some time out to rebuild our base WordPress setup. Throughout this post, I’ll talk through the principles we set out and the design decisions we made. I’ll go through how we have restructured WordPress, made it maintainable and allow for reuse across sites. The aim of this is to guide you through the why and how of our base. There was more thought than code that went into this rebuild, primarily thanks to the amazing work by Roots with Bedrock and Jared Novack with Timber.

Lumberjack WordPress starter theme

By Adam Tomat (@adamtomat) and Joe Lambert (@joelambert) at Rareloop (@rareloop)

We’ve created a starter theme called Lumberjack (view on github), which includes everything this post covers. I will be actively updating it, but this write-up will only cover Lumberjack v1.0.x. It should still serve as a grounding in how it works. Please fork it, build on it & submit pull requests.

When researching, we came across this awesome Timber Starter Theme by Upstatement. I want to give them credit and thanks as I used their controller logic (e.g. page.php) in Lumberjack. Cheers!

The Vision

WordPress doesn’t have to be a fecal-hurricane of spaghetti code. We should all take some responsibility and not use the excuse “Meh, it’s WordPress”. Before we dive into any code, lets outline what we’d like our new setup to do for us. What principles have we learnt that could help supercharge our sites?

  • Don’t repeat yourself
  • Make your code modular (think input -> output)
  • OOP & Encapsulation
  • MVC
  • Coding standard (PSR-2 Coding Style)
  • Continuous deployment

I won’t go into these specifically, but there are a few clear end goals in mind here.

  1. To make code re-usable within a site
  2. To make code re-usable across multiple sites
  3. The holy grail: All code should be in the same style, regardless of who’s written it
  4. To make WordPress quick and easy to develop with
  5. To make WordPress easily maintainable

Separating your Frontend and Backend

This section is optional, but is totally worth it. I won’t go into this too much, but I’ll be writing a separate post about it (stay tuned). If you’re interested have a look at Primer or PatternLab.

I’ve worked on many projects where WordPress IS the whole website; frontend and backend. There’s normally a grunt/gulp workflow to compile, concat & minify assets and views littered with business logic.

We end up bloating WordPress with frontend workflows. The main issue is that your frontend code is now intrinsically tied to WordPress. Let WordPress do what it’s good at: managing content. We moved the frontend code into a pattern library (Primer). Once we’ve built the frontend (in isolation), it’s only a case of exporting and migrating into WordPress. This means we can iterate on our frontend workflow as much as needed, without ever damaging or depreciating anything in our WordPress core. Win.

I can make your Bedrock

The first step to supercharging WordPress is to tackle dependancy management. We’ll be using Composer, which allows us declare the libraries and plugins our code requires. We also want to explicitly set package versions where possible (e.g. "4.3.1" instead of "~4.3.1"). This is for when we come back to our project in the future, or somebody else is working on the same project, a composer update will install dependancies to a known point in time that’s been tested.

Another area where WordPress can be improved, mainly from a security perspective, is the wp-config.php file. No sensitive credentials, such as live database details, should be stored in your code (or git). The current trend is to use Dotenv, which requires you to have a .env file, containing your sensitive data & not stored in git, on each of your servers. We’ve been using it for a while now and it’s so much better!

Luckily, the clever people at Roots have already solved these problems and open sourced it. Shout out to Chris Sherry for introducing me to Bedrock:

WordPress boilerplate with modern development tools, easier configuration, and an improved folder structure.

We’ll use this as our starting point. Install Bedrock and you’ll have a whole bunch of goodies with very little effort (composer, mu-plugin autoloader, Dotenv config, deploy scripts – to name a few).

This post isn’t a deep dive into installing Bedrock, as their docs are already fantastic. Instead, here’s a few of the pages that are worth a read.

The only thing we’ve added on top of Bedrock was Capistrano deploy scripts. Again, this was a simple drop in.

Don’t forget to set up a local database and add the credentials to your .env file.

We now have a platform that makes managing dependancies (including WordPress) and deploying simple. And it has great documentation you can point to for anyone else working on the code.

But there’s something missing. What about the theme?

Roots also have a WordPress starter theme called Sage. We initially looked into this to see if it was a good fit for our supercharged setup. However, because we have separated our frontend and backend we didn’t need that layer in our theme. So we used some of their ideas and built our own theme.

Introducing Timber

To make a base theme that rocks, we need to break away from the world of WordPress and into the MVC light. The first step is deciding what templating language to use. PHP has a handful of good ones (e.g. Blade, Twig, Mustache). At Rareloop, we have used those three extensively; Twig’s our favourite though.

One major benefit of Twig is that it is the weapon of choice for Timber. If you haven’t heard of Timber before, Upstatement have written a great intro:

Timber is for both WordPress pros and rookies. People new to WordPress will like how it reduces the WordPress-specific knowledge required to theme a website. While pros can take advantage of object-oriented patterns that adhere to DRY and MVC principles.

Remember the vision I outlined above? This knocks it out of the park! My favourite thing about Timber is that is abstracts away WordPress, so you rarely have to deal with it directly ?. It lets us write views in Twig, pass data into views by setting ‘the context’ and never have to write a goddamn nav walker ever again. It’s the first big step towards separating our logic into re-usable components.

Now we’re completely sold on Timber and can’t believe we every used anything else, lets install it.

Installing Timber through composer

Finally, some code! I will be assuming you have installed Bedrock from here on in.

Open you your composer.json file and add the following:

"require": {
    "jarednova/timber": "^0.22.0"
}

We want to lock versions down if possible. You don’t want any unexpected breaking changes from Twig being installed when you run a composer update. By using the ^ symbol, we are saying that we are happy with any backwards-compatible changes.

Next, we want define Timber as a must-use plugin. This means that it will be automatically activated and cannot be deactivated.

"installer-paths": {
    "web/app/mu-plugins/{$name}/": [
        "type:wordpress-muplugin",
        "jarednova/timber",
    ],
    "web/app/plugins/{$name}/": ["type:wordpress-plugin"],
    "web/app/themes/{$name}/": ["type:wordpress-theme"]
},

When you run a composer update, you’ll see Timber installed to web/app/mu-plugins/timber.

Digging into Lumberjack

Firstly, install the Lumberjack theme in web/app/themes/. You can do this either through composer, cloning or just a ZIP download.

If you open up the theme, you’ll see it still has those familiar files that WordPress needs, such as:

  • 404.php
  • archive.php
  • author.php
  • footer.php
  • functions.php
  • header.php
  • index.php
  • page.php
  • screenshot.png
  • search.php
  • sidebar.php
  • single.php
  • style.css

We also needed somewhere for our static assets to live. Naming here isn’t important here, but here’s what we went with:

Assets Directory

Timber works by treating the php files above as controllers (e.g. page.php). WordPress handles the routing, and will decide what file to load depending on the template hierarchy. But instead of writing our HTML and PHP here, we want Timber to take control.

We’ll start with the most basic example, the 404 page. Have a look at the other controllers for more examples.

404.php should do 2 things:

  • Build up data needed to render the view. Timber calls this the context.
  • Render a view

And here’s what it looks like:

<?php
/**
 * The template for displaying 404 pages (Not Found)
 *
 * @package  WordPress
 * @subpackage  Timber
 * @since    Timber 0.1
 */

// Build up the data
$context = Timber::get_context();

// Render the template, passing in the data
Timber::render('404.twig', $context);

This is awesome, but raises a few important questions:

  1. What is the context and can I change it?
  2. Where is the 404.twig template stored and what does it look like?
  3. How do we define load paths for our twig views?

If you’re new to Timber, I recommend having a gander at their Getting Started docs. I may glance over a few details or assume a certain level of knowledge of Timber going forward.

What is the context and can I change it?

The short answer is the TimberSite class. It comes with a default Timber context object and represents information about your site. To add data to the context, on a global scale, all you need to do is extend this class. We’ll talk about where this lives in our codebase later.

Where is the 404.twig template stored and what does it look like?

These .twig files can live anywhere, but we’ve stuck them under a views directory in our theme, which looks a little something like this:

Views Directory

The base.twig file is just our boilerplate HTML which includes the doctype, head, body etc.

And our 404.twig template looks like:

{% extends "base.twig" %}

{% block content %}
    <div>
        <h1 class="heading-1">404 - Page not found</h1>
        <p>Sorry, we couldn't find what you're looking for.</p>
    </div>
{% endblock %}

How do we define load paths for our twig views?

Check out their docs on the matter: Configure template locations.

For this theme, you should be able to get away with just adding this to your functions.php file.

Timber::$dirname = [
    'views',
    'views/templates',
];

Okay, lets take a breather and look at what we’ve covered:

We have Bedrock installed, with Timber being loaded via composer as a must-use plugin. We have looked at logic in our controllers (based off the timber-starter-theme), with relevant twig files too. And we have configured our template load paths.

Just a quick note regarding custom page templates. We decided to name these custom templates like so: page--<template-name>.php. For example, the home template would need a page--home.php file. We are using a double hyphen -- to avoid the template lookup which will try and match the home slug to page-home.php.

For an individual custom post type, we let WordPress do the template lookup. So we just use single-<custom-post-type-name>.php, e.g. single-lumberjack_project.php.

Tackling ‘that dreaded file’

Let’s talk about functions.php for a minute. Over the years:

I've seen things... terrible things

We’re talking thousands of lines of code, with no thought or care about organisation; a frankenstein of Stack Overflow code that nobody ever wants to touch for fear of accidentally summoning the devil. I’ve recently seen somebody doing a curl request at the top of the file like so: eval(gzinflate(base64_decode(YnVsbHNoaXQ=))).

And why is this bad? Other than being painful to maintain, it’s often very difficult to reuse code across multiple sites. The first step we need to take is to group functions together, in separate files. This is nothing new and you may already be doing it; if so, great! Sage have taken steps to solve this too and are requiring files. We used this idea to supercharge our functions.php file.

There are 2 parts to this story: looking at the separate files and autoloading. First, we need a new directory to keep our nicely organised files in. Within our theme, we have a lumberjack directory. This is where we’ll put any app specific code. For example: autoloading script, defining custom post types, custom functions, theme support, our TimberSite child class, etc. Here’s a quick look at how we’ve set it up:

Lumberjack folder structure

It’s worth noting that everything here (excluding autoload.php) is namespaced under Lumberjack. We only allow classes under the src/ directory too.

Functions is where our separate functions live. When grouping functions, create a simple class; even if there is only one function. For example, when enqueueing scripts with WordPress – add a new Functions/Assets.php class, like so:

<?php

namespace Lumberjack\Functions;

class Assets
{
    /**
     * En-queue required assets
     */
    public static function load()
    {
        // code...
    }
}

In Lumberjack, this class currently serves as more of an example than a real world use case.

We are basically using classes here as a way to group things. We don’t care about state or instantiating the class, so that’s why our methods are static as it makes it easier for us to call.

Now, for the guts of the method we want to ignore what WordPress recommends (and every answer on SO too). No more named callback functions. If you check out the docs on add_filter, you’ll see that it also accepts an anonymous function. Which means inside of our nicely named class and method, we can do this:

// Register the filter
add_filter('wp_enqueue_scripts', function ($paths) {
    wp_deregister_script('jquery');

    // Load CDN jQuery in the footer
    wp_register_script('jquery', 'https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js', false, '1.11.3', true);

    wp_enqueue_script('jquery');
});

Here’s our complete class:

<?php

namespace Lumberjack\Functions;

class Assets
{
    /**
     * En-queue required assets
     *
     * @param  string  $filter   The name of the filter to hook into
     * @param  integer $priority The priority to attach the filter with
     */
    public static function load($filter = 'wp_enqueue_scripts', $priority = 10)
    {
        // Register the filter
        add_filter($filter, function ($paths) {
            wp_deregister_script('jquery');

            // Load CDN jQuery in the footer
            wp_register_script('jquery', 'https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js', false, '1.11.3', true);

            wp_enqueue_script('jquery');
        }, $priority);
    }
}

So. Much. Better.

Core

Moving onto the Core folder, it’s a very similar story. These classes are the bread and butter of Lumberjack and contain the base classes used throughout the theme. Typically, they’ll extend Timber too. The main file here is Core/Site.php, which extends TimberSite. This lets us add our own data to the global context and bring in any Twig extensions if needed.

<?php

namespace Lumberjack\Core;

use TimberSite;
use TimberHelper;
use Lumberjack\Core\Menu;

class Site extends TimberSite
{
    public function __construct()
    {
        add_filter('get_twig', [$this, 'addToTwig']);
        add_filter('timber_context', [$this, 'addToContext']);

        parent::__construct();
    }

    public function addToContext($data)
    {
        $data['is_home'] = is_home();
        $data['is_front_page'] = is_front_page();
        $data['is_logged_in'] = is_user_logged_in();

        // Get the page title, and prefix it with ' | ' if it exists (for use in html title)
        $data['wp_title'] = TimberHelper::function_wrapper('wp_title', ['|', false, 'right']);

        // In Timber, you can use TimberMenu() to make a standard WordPress menu available to the
        // Twig template as an object you can loop through. And once the menu becomes available to
        // the context, you can get items from it in a way that is a little smoother and more
        // versatile than WordPress's wp_nav_menu. (You need never again rely on a
        // crazy "Walker Function!")
        $data['menu'] = new Menu('main-nav');

        return $data;
    }

    public function addToTwig($twig)
    {
        // this is where you can add your own functions to twig
        // $twig->addExtension(new Twig_Extension_StringLoader());
        // $twig->addFilter('myfoo', new Twig_Filter_Function('myfoo'));

        return $twig;
    }
}

In this directory, we also have Menu.php and MenuItem.php. These both extend Timber and allow us to inject a little more magic where needed. The most important part of these classes is the $PostClass variable, which tells Timber which class to return for the pages in a menu. We’ll get onto why this is important shortly. MenuItem also adds a nice BEM‘ed up class out the box too, for the current nav item.

Configuration

Config/ is where you would register any configurations with WordPress, such as any custom post types, menus, taxonomies and theme support. These are just normal PHP classes, no fancy extending here. Again, we do not care about state with these so every method is static.

Taking CustomPostTypes as an example, it has a register() method which simply hooks into the WordPress action init, and will run the types method. This will then register any custom post types. This file has one already defined as an example.

Post Types

Lastly, we have a folder called PostTypes which is for our custom post type classes. Before digging into what these look like, I want to talk about why we need a class for each of our post types. Timber does a great job of abstracting away WordPress and allows you to retrieve posts like so:

While this is very useful, it isn’t very DRY. You can end up writing the same query 2 or 3 times to get a list of posts for a custom post type. I also wasn’t a fan of the API as there’s 2 different classes for getting posts.

So instead, we created a PostType\Post.php class which extends TimberPost and becomes our single point of reference any time we deal with a post. Essentially, like a model. If you check the docs again for the Timber class, there’s an underrated section on using the proper class. By default, when you use get_posts or get_post your returned posts will be the TimberPost class. But we can override this with the 2nd parameter, and tell it to return our Lumberjack\PostType\Post class instead. This is so important, because it means that we can add methods to our Post class and know they will always be available to us in our app.

For example, you could add a next() method which handles the logic for finding the next post, and know you will always have $post->next() available to you.

Here is the current state of our PostTypes/Post.php class:

<?php

namespace Lumberjack\PostTypes;

use Timber;
use TimberPost;

class Post extends TimberPost
{
    protected static $postType = 'post';

    /**
     * Get all posts of this type
     *
     * @param  integer $perPage The number of items to return (defaults to all)
     * @return array           Array of Post objects
     */
    public static function all($perPage = -1)
    {
        $args = [
            'post_type'     => static::$postType,
            'post_status'   => 'publish',
            'posts_per_page' => $perPage,
            'orderby'       => 'menu_order',
            'order'         => 'ASC'
        ];

        return static::posts($args);
    }

    /**
     * Convenience function that takes a standard set of WP_Query arguments but mixes it with
     * arguments that mean we're selecting the right post type
     *
     * @param  array $args standard WP_Query array
     * @return array           An array of Post objects
     */
    public static function query($args = null)
    {
        $args = is_array($args) ? $args : [];

        // Set the correct post type
        $args = array_merge($args, ['post_type' => static::$postType]);

        if (!isset($args['post_status'])) {
            $args['post_status'] = 'publish';
        }

        return static::posts($args);
    }

    /**
     * Raw query function that uses the arguments provided to make a call to Timber::get_posts
     * and casts the returning data in instances of ourself.
     *
     * @param  array $args standard WP_Query array
     * @return array           An array of Post objects
     */
    public static function posts($args = null)
    {
        return Timber::get_posts($args, get_called_class());
    }

    public static function postType()
    {
        return static::$postType;
    }
}

In our app, we can query posts in any of the following ways and they’ll automatically get scoped to the correct post type and return our model.

use Lumberjack\PostTypes\Post;

// Get the post (from the loop)
new Post();

// Find post with ID of 1
new Post(1);

// All posts
Post::all();

// Query posts
Post::query($query);

Creating a new custom post type, such as Project is as simple as creating a new class that extends Lumberjack\PostTypes\Post:

<?php

namespace Lumberjack\PostTypes;

class Project extends Post
{
    protected static $postType = 'lumberjack_project';
}

And registering it in Lumberjack\Config\CustomPostTypes.

Autoloading

While it’s great that we have separated out this logic, we still need a way of including these files into our scripts.

There are so many different techniques, so feel free to switch out our method and plug your own in. We used the closure implementation from PHP-FIG, and put it inside lumberjack/autoload.php.

Currently it only supports loading files from a single namespace, which is configured in the file using the $prefix variable.

// project-specific namespace prefix
$prefix = 'Lumberjack\\';

We then require autoload.php in our bootstrap.php file. Now anytime we reference any classes under the Lumberjack namespace, there will be an automatic attempt to load the file.

Naming here is important. If you have the class Lumberjack\PostTypes\Post, the autoloader will attempt to load the file from /path/to/theme/lumberjack/src/PostTypes/Post.php whenever you try and use it.

Here’s a few examples:

<?php

use Lumberjack\PostTypes\Post;

// This will trigger the autoloader and attempt a require
$post = new Post();

// So will this
$posts = Post::all();

Bootstrap

Let’s rewind back to functions.php for a second. We talked about stripping out functions into classes but we didn’t touch on what lives in there instead. Here is its current contents:

<?php

Timber::$dirname = [
    'views',
    'views/templates',
];

require_once('lumberjack/bootstrap.php');

All it is doing is telling Timber where to look for .twig views and loading our bootstrap file. We are using the bootstrap.php file to register everything the theme needs. This file should be as close to vanilla PHP as possible (e.g. no WordPress voodoo). If you still need to register or configure one-off WordPress stuff, I’d recommend creating a class for it or putting it in functions.php.

So the bootstrapping file simply sets up our core, config and functions:

<?php

namespace Lumberjack;

use Lumberjack\Core\Site;
use Lumberjack\Config\ThemeSupport;
use Lumberjack\Config\CustomPostTypes;
use Lumberjack\Config\CustomTaxonomies;
use Lumberjack\Config\Menus;
use Lumberjack\Functions\Assets;

require_once('autoload.php');

/**
 * ------------------
 * Core
 * ------------------
 */

// Set up the default Timber context & extend Twig for the site
new Site;

/**
 * ------------------
 * Config
 * ------------------
 */

// Register support of certain theme features
ThemeSupport::register();

// Register any custom post types
CustomPostTypes::register();

// Register any custom taxonomies
CustomTaxonomies::register();

// Register WordPress menus
Menus::register();

/**
 * ------------------
 * Functions
 * ------------------
 */

// Enqueue assets
Assets::load();

Consistency

Every developer codes in a particular style. Typically, it’s a mash of ‘the best bits’ they’ve seen from various coding conventions, plus their preferred way of doing things. If you are not following a coding convention, you should be. And even if you are, there still needs to be a way to enforce these conventions.

If multiple developers are working on a project, you should not be able to distinguish what each developer wrote. It should look like it was written by one developer.

Linting

Linting is the process of checking the source code for programmatic as well as stylistic errors. This is most helpful in identifying some common and uncommon mistakes that are made during coding. A Linter is a program that supports linting (verifying code quality). They are available for most languages like JavaScript, CSS, HTML, PHP, etc..

We can use a linter when developing to enforce coding styles, as well as point out any syntax errors (e.g. missing semicolons). At Rareloop, we use Sublime Text 3, which has a handy linting plugin called Sublime Linter. Once that’s installed you need to bolt on any language specific linting plugins. For PHP, we use SublimeLinter-phpcs which uses PHP_CodeSniffer under the hood.

Now we need to tell it which standard to adhere to. I’d recommend going with a common standard instead of trying to define your own. It will make it much easier for other developers to write code on your project. Right now, the one to use is the PSR-2 coding standard. You can tell SublimeLinter-phpcs to use PSR-2 by selecting Preferences -> Package Settings -> SublimeLinter -> Settings - User. You’ll need something like this:

{
    "user": {
        "linters": {
            "phpcs": {
                "@disable": false,
                "args": [],
                "excludes": [],
                "standard": "PSR2"
            }
        }
    }
}

Editor Config

Even if you’re writing beautiful, linted code, there is still room for subtle inconsistencies to creep in. Things such as mixing tabs v spaces (mainly between developers), trailing whitespace or trailing new lines. These are things that your editor should take care of, but every editor/IDE could handle differently.

Lumberjack uses EditorConfig which aims to maintain a consistent style between editors and IDE’s that support it. For Sublime Text, you’ll need to install the Sublime Editor Config plugin. Our .editorconfig is basic, but looks like this:

# editorconfig.org

root = true

[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

These settings will be applied to all file types in our theme.

Summary

We’ve put a lot of thought and care into Lumberjack, and we have some more exciting additions lined up already for it. Lumberjack is also still quite new to us; we have only used it on 2 live projects so far. But it’s a massive improvement on what we had before, and we hope it helps other developers too. If you have any questions or feedback (good or bad), I’d love to hear it; please get in touch on Twitter.

Lumberjack axe icon from Mete Eraydın from the Noun Project