https://crtzr.co/8

The WordPress (L)OOP

It's becoming clear that I have a strange relationship with WordPress. For years, it has been my blogging platform of choice and I recommend it to a lot of people. I bought it into the workplace and we used it on a number of projects, we even rebuilt the Binamic website using it. The admin interface is solid, the feature set broad and the community strong. I love WordPress.

It's when I have to actually work with WordPress that the cracks start to form. I find the theme API inconsistent and awkward. Functions are badly named, parameter ordering is all over the place, global variables are chucked around like they are going out of fashion and everything feels a bit sketchy. I hate WordPress.

Don't even get me started on the fucking date function.

The Loop

One of the constant pain points for me is "The Loop". This arcane little nest of statements is the backbone to every WordPress theme, but it's a foundation that leaves a bad taste in my mouth.

Consider the homepage of a blog, which lists the latest ten posts, a loop to that effect would look something like:

<?php if (have_posts()) : ?>
	<ul>
		<?php while (have_posts()) : the_post() ?>
			<li id="post-<?php the_ID() ?>">
				<h2><a href="<?php the_permalink() ?>"><?php the_title(); ?></a></h2>
				<?php the_content('Continue Reading...'); ?>
			</li>
		<?php endwhile ?>
	</ul>
<?php endif ?>

Incase you are not sure exactly what's going on here I'll give you a quick rundown:

  1. If there is at least one post
  2. Print an unordered list element
  3. Loop through all of the available posts using while
  4. Call the_post() to set up the $post global variable
  5. Print a list element with the ID set to the post ID
  6. Print a header element, with a link to the full post
  7. Print out the post content, with the link to "Continue Reading"
  8. Close of the tags and statements

It works, but it's ugly - Step #4 is pretty awful alone, nearly all of the template tags are dependant on that function call which isn't particularly elegant.

It also doesn't lend itself to easy reuse (copy and paste is not DRY) and can lead to massively over complicated templates.

It doesn't need to be this way…

The (L)OOP

I don't enjoy writing ugly code, and I want to have fun with this blog, so I'm writing my own Theme API on top of the existing one.

The style and structure of what I have so far is mainly based on the Model View Controller design pattern (MVC) which was (probably) made popular by Rails. At work we do a lot of work with CakePHP and I've become fairly familiar with how MVC looks within PHP and what you can and can't do (you have to leave all the really cool stuff out, this just ain't Ruby). I'm also using Object Orientated Programming (OOP) to pass around dynamic objects instead of static arrays.

Now my loop looks like:

<?php echo $posts->all() ?>

One line. Well, not really one line, there's a lot of stuff this function does…

First thing to note, I call echo to print out the posts, all functions in my new API return values, noting "auto prints". I think this is more true to PHP and it means if a function doesn't return what you need, you can bend it to do what you want.

The $posts variable is actually a Collection instance, all of the posts are actually stored in $posts->data.

The all() method renders a partial:

<?php if (!empty($posts)) : ?>
	<ul>
		<?php foreach ($posts as $post) : ?>
			<?php echo $post->render() ?>
		<?php endforeach ?>
	</ul>
<?php endif ?>

This is very similar to the core WordPress loop, but I use a foreach to loop round the contents of $posts (which gets passed down into the partial) and prints them out by calling render() on each individual post.

Another partial is rendered, this time it's only concern is the $post of the current iteration:

<li id="post-<?php echo $post->id() ?>">
	<?php echo $post->header() ?>
	<?php echo $post->excerpt() ?>
	<?php echo $post->more() ?>
</li>

I hope you'll agree, that's pretty clean! Down here, $post is actually an instance of a PostsPresenter class, this wraps the original data with a set of handy methods which you can use to show the post.

Here's an example of a cut down PostPresenter, which contains everything to use $post->header():

class PostsPresenter extends Presenter
{
	var $name = 'posts';
	var $singular_name = 'post';

	var $title = null;
	var $header = null;
	var $permalink = null;

	function id()
	{
		return $this->data->ID;
	}

	function title()
	{
		if (!$this->title) {
			$this->title = apply_filters('the_title', $this->data->post_title, $this->id());
		}
		return $this->title;
	}

	function header()
	{
		if (!$this->header) {
			$this->header = '<h2>'.$this->link($this->title()).'</h2>'."\n";
		}
		return $this->header;
	}

	function permalink()
	{
		if (!$this->permalink) {
			$this->permalink = apply_filters('the_permalink', get_permalink($this->data));
		}
		return $this->permalink;
	}

	function link($text)
	{
		return '<a href="'.$this->permalink().'>'.$text.'</a>';
	}
}

This looks like a lot of code, and compared to the original loop example it is but all this is built in by default. If you are happy with the default formatting you only need to call $posts->all() and you're done.

I've built this API so there are several points where you can step in and customise the output:

  1. Override partials/posts.php to change the way the collection is displayed
  2. Override partials/post.php to change the way each single post is displayed
  3. Override the Posts Presenter to change how each individual post attribute works

By keeping everything small, self contained and consistent I am hoping to keep the system as flexible as possible.

I've already built a load of good stuff into the API and I've got more planned, right now it's just fun to love developing on the platform I love.

Roadmap

This isn't ready for public consumption, I haven't even finished building this blog yet! Once I have, expect a release to GitHub so you can get your hands on it if you think it is something you are going to want to use.

After that, I'm going to wait for WordPress 3 which is dropping with a new default theme. I'm going to work to bring this API up to date and make sure it can handle everything in the new theme - after that, the sky is the limit!