WordPress 3.5 + Me

WordPress 3.5 dropped today. This is a special release for me because my picture made it to the Credits and I had 30-40 of my patches committed. Here’s the full list: https://core.trac.wordpress.org/search?q=wonderboymusic&noquickjump=1&changeset=on

The hightlights:

I have 55 patches on deck for 3.6 already, excited to see what makes it! If anyone out there is thinking about contributing to core and is hesitant, don’t be. 90% of success is showing up. Be There. Subscribe to Trac. Comment on tickets. Test patches. Occasionally check in on IRC. The people who are making WordPress are there. You could be one of them.

I was just a little lad with a dream 2 years ago at my first WordCamp in NYC when I grilled Nacin and Koop about using IDs instead of classes in the CSS selectors for Twenty Ten. Koop talked to me afterward and suggested I contribute to core. My first patch was at the after-party for WordCamp San Francisco 2011 at 2am at the old Automattic space at the Pier on the Embarcadero. I got 1 patch into 3.2. 1 patch into 3.3. Zero into 3.4. And here we are.

WordPress + Regionalization

Regionalized merch, regionalized promos, regionalized blog posts, and the list goes on and on

Regionalized merch, regionalized promos, regionalized blog posts, and the list goes on and on

One of the coolest things about WordPress is that it is built from the ground up with translation tools. Many blogs want to vibe in a language other than English, and many blogs want to get their international game on and present content to people in several languages.

But what if you want to detect a user’s geographic location and display content based on their country code (“US” for USA, “FR” for France, et al) or your own regional site code (“EU” for European Union countries, for example)? This is not built-in but can be built by you if you know what you are doing. Let’s learn!

Btdubs, eMusic makes extensive use of regionalization. We have 4 regional sites: US, UK, EU, CA. We regionalize everything, so almost every database query on our site has to be subject to some filtering. We also have to intersect WP with our regionalized catalog data and in various places link them together magically.

Custom Taxonomy: Region

Creating new taxonomies in WP requires code, but adding terms to that taxonomy can be done by anyone in the admin. Let’s get the code out of the way:

/**
 * Shortcut function for assigning Labels to a custom taxonomy
 * 
 * @param string $term
 * @param string $plural If not specified, "s" is added to the end of $term
 * @return array Labels for use by the custom taxonomy
 * 
 */
function emusic_tax_inflection( $str = '', $plural = '' ) {
    $p = strlen( $plural ) ? $plural : $str . 's';

    return array( 
        'name'              => _x( $p, 'taxonomy general name' ),
        'singular_name'     => _x( $str, 'taxonomy singular name' ),
        'search_items'      => __( 'Search ' . $p ),
        'all_items'         => __( 'All ' . $p ),
        'parent_item'       => __( 'Parent ' . $str ),
        'parent_item_colon' => __( 'Parent ' . $str . ':' ),
        'edit_item'         => __( 'Edit ' . $str ),
        'update_item'       => __( 'Update ' . $str ),
        'add_new_item'      => __( 'Add New ' . $str ),
        'new_item_name'     => __( 'New ' . $str . ' Name' ),
        'menu_name'         => __( $p ),
    );
}

$post_types = get_post_types( array( 'exclude_from_search' => false, '_builtin' => false ) );

$defaults = array(
    'hierarchical'      => true,
    'public'            => true,
    'show_ui'           => true,
    '_builtin'          => true,
    'show_in_nav_menus' => false,
    'query_var'         => true,
    'rewrite'           => false
);

register_taxonomy( 'region', $post_types, wp_parse_args( array(
    'labels' => emusic_tax_inflection( 'Region' )
), $defaults ) );

Because we load a bunch of custom taxonomies, this code helps us stay modular. But yes, this is the amount of code required if you only have one taxonomy!

All you *really* need to know is: we made a hierarchical taxonomy. It’s called region. We are now going to use it EVERYWHERE.

Boom

So now that we have a taxonomy for region, we want to be able to assign region(s) to posts.

Sorting

We also want to view the region in the posts list table. We can add custom columns to the posts table and make the region column sortable. We need some more code for that:

class CustomClassNamedWhatever {

.......

/**
 * Filters for Admin
 * 
 */
function admin() {
    add_filter( 'manage_posts_columns', array( $this, 'manage_columns' ) );
    add_action( 'manage_posts_custom_column', array( $this, 'manage_custom_column' ), 10, 2 );
    add_filter( 'posts_clauses', array( $this, 'clauses' ), 10, 2 );

	// this is an internal method that gives me an array of relevant post types
    foreach ( $this->get_post_types( false ) as $t ) {
        add_filter( "manage_edit-{$t}_sortable_columns", array( $this, 'sortables' ) );               
    }  
}

/**
 * Register sortable columns
 * 
 * @param array $columns
 * @return array
 */
function sortables( $columns ) {
    $post_type_obj = get_post_type();

    if ( is_object_in_taxonomy( $post_type_obj, 'region' ) )
	$columns['region'] = 'region';		

    return $columns;
}

/**
 * Add custom column headers
 * 
 * @param array $defaults
 * @return array
 */
function manage_columns( $columns ) {
    $columns['region'] = __( 'Region' );
    return $columns;
}

/**
 * Output terms for post in tax
 * 
 * @param int $id
 * @param string $tax
 */
function _list_terms( $id, $tax ) {
    $terms = wp_get_object_terms( $id, $tax, array( 'fields' => 'names' ) );
    if ( ! empty( $terms ) )
        echo join( ', ', $terms );
}
/**
 * Output custom HTML for custom column row
 * 
 * @param string $column
 * @param int $id
 */
function manage_custom_column( $column, $post_id ) {
    if ( 'region' === $column )
	$this->_list_terms( $id, $column );   
}

/**
 * Filter SQL with this monstrosity for sorting
 * 
 * @global hyperdb $wpdb
 * @param array $clauses
 * @param WP_Query $wp_query
 * @return array
 * 
 * TODO: fix this
 */
function clauses( $clauses, $wp_query ) {
    global $wpdb;

    if ( isset( $wp_query->query['orderby'] ) && 
        'region' === $wp_query->query['orderby'] ) {
        $tax = $wp_query->query['orderby'];

        $clauses['join'] .= <<term_relationships} ON {$wpdb->posts}.ID={$wpdb->term_relationships}.object_id
LEFT OUTER JOIN {$wpdb->term_taxonomy} USING (term_taxonomy_id)
LEFT OUTER JOIN {$wpdb->terms} USING (term_id)
SQL;

        $clauses['where'] .= " AND (taxonomy = '{$tax}' OR taxonomy IS NULL)";
        $clauses['groupby'] = "object_id";
        $clauses['orderby']  = "GROUP_CONCAT({$wpdb->terms}.name ORDER BY name ASC) ";
        $clauses['orderby'] .= ( 'ASC' == strtoupper( $wp_query->get('order') ) ) ? 'ASC' : 'DESC';
    }

    return $clauses;
}

........

}

After all of that, we can sort our posts by region:

Sorting

If you’re new to WordPress, you may have just barfed. Just know that we added relevant code to make the Region column sortable. Meanwhile, we still haven’t done anything to make our site regionalized. If we let WordPress just do its thing, you would still get content from every region, not to the one you are specifically targeting.

Geolocation

We have very specific values we check to regionalize users. If you’re anonymous, we get an X-Akamai-Edgescape HTTP header that can parsed, and it contains a value for “country_code.” Based on that country code, we can assign you to a region. Anybody with a debug console can probably view this header in their eMusic requests. If you’re logged-in or “cookied” – we pin you to your original country code.

At the end of the day, we want to be able to set some constants:

/**
 * Register default constants
 * 
 */
if ( ! defined( 'THE_COUNTRY' ) )
    define( 'THE_COUNTRY', get_country_code() );

if ( ! defined( 'THE_REGION' ) )
    define( 'THE_REGION', get_region() );

if ( ! defined( 'CURRENCY_SYMBOL' ) ) {
    global $currency_symbol_map;
    if ( isset( $currency_symbol_map[THE_REGION] ) ) {
	define( 'CURRENCY_SYMBOL', $currency_symbol_map[THE_REGION] );
    } else {
	define( 'CURRENCY_SYMBOL', $currency_symbol_map['US'] );
    }
}

get_country_code() and get_region() are very specific to eMusic, so I won’t bore you with them, but they both return a 2-character-uppercase value for country or region, “US” and “US” for example. Now, anywhere in our code where we need to refer to region or country, THE_REGION and THE_COUNTRY will do it.

Linking Region to Taxonomy

Ok cool, we have a taxonomy, and we have some vague representation of region / country defined as constants. We still don’t have regionalized content. One way we could link region to taxonomy terms would be to make a map and refer to it when necessary:

$regions_map = array(
    'US' => 3,
    'UK' => 6,
    'CA' => 9,
    'EU' => 11,
);

This method sucks. Why? Are those IDs term_ids or term_taxonomy_ids? What if they change? Also, grabbing a PHP global every time you need to translate region code to term_id is weird. How else can we get the terms belonging to the region taxonomy? Let’s try this database call which is cached in memory after the first time it is retrieved:

get_terms( 'region' );

That’s great, but what do I do with it? Because your Region codes are also the names of your region terms, you could dynamically create your map like this:

$map = array();
foreach ( $terms as $term )
    $map[$term->name] = $term->term_taxonomy_id;

Next question: where do I set this, and how do I account for crazy stuff like switch_to_blog()? Try this:

/**
 * Automatically resets regions_tax_map on switch_to_blog()
 * Because we switch_to_blog() in sunrise.php, this function is
 * called when plugins_loaded to create initial values
 * 
 * @param int $blog_id
 * 
 */
function set_regions_tax_map( $blog_id = 0 ) {
     if ( ! isset( get_emusic()->regions_tax_maps ) )
	get_emusic()->regions_tax_maps = array();	

     if ( ! taxonomy_exists( 'region' ) )
	return;

     if ( empty( $blog_id ) )
	$blog_id = get_current_blog_id();

     if ( isset( get_emusic()->regions_tax_maps[$blog_id] ) ) {
	get_emusic()->regions_tax_map = get_emusic()->regions_tax_maps[$blog_id];	
	return;
    }

    $terms = get_terms( 'region' );

    $map = array();
    foreach ( $terms as $term )
	$map[$term->name] = $term->term_taxonomy_id;

    get_emusic()->regions_tax_maps[$blog_id] = $map;
    get_emusic()->regions_tax_map = get_emusic()->regions_tax_maps[$blog_id];
}

/**
 * The initial setting of $emusic->regions_tax_map on load
 * 
 */
add_action( 'init', 'set_regions_tax_map', 20 );
/**
 * Resets context on switch to blog
 * 
 */
add_action( 'switch_blog', 'set_regions_tax_map' );

STILL, we don’t have regionalized content, we just have a standard way of mapping region code to term_taxonomy_ids. So why do I care about the IDs? Because we have to use them to alter our default WP queries so that posts only intersect the region of the current user.

Tax Query

Tax Query is an advanced way of altering WP_Query. If you load a page of WP posts, you ran WP_Query. A developer can make their own instances of WP_Query or alter the global one. We need to alter the global one and we don’t want to ever use query_posts to do that. The first thing we need to do is retrieve our map of Term Name => Term Taxonomy ID:

/**
 * Gets a map of region name => term_taxonomy_id
 * 
 * @return array
 */
function get_current_regions_tax_map() {
    return get_emusic()->regions_tax_map;
}

Now that we have that, we want to use it in the tax query that we are going to inject into WP_Query. One step at a time, here’s the tax_query portion, encapsulated in a function that always returns the proper context:

/**
 * Encapsulates common code to create a regionalized tax_query
 * 
 * e.g. $tax_query = array( get_region_tax_query() );
 * 
 * @param int $region
 * @param int $all
 * @return array
 */
function get_region_tax_query( $region = '', $all = true ) {
	$regions_tax_map = get_current_regions_tax_map();
	if ( empty( $regions_tax_map ) )
		return;

	$terms = array();
	$terms[] = $regions_tax_map[ empty( $region ) ? THE_REGION : strtoupper( $region ) ];
	if ( true === $all )
		$terms[] = $regions_tax_map['ALL'];

	$tax_query = array(
		'operator'		=> 'IN',
		'taxonomy'		=> 'region', 
		'field'			=> 'term_taxonomy_id',
		'terms'			=> $terms,
		'include_children'	=> false
	);

	return $tax_query;
}

Being able to query by term_taxonomy_id is important, because it is the primary key for the term / taxonomy relationship. If I had passed term_ids, they would need to be translated into term_taxonomy_ids by WP_Tax_Query before WP_Query could complete its logic. I helped nurse this new functionality along in WordPress core (3.5): https://core.trac.wordpress.org/ticket/21228

Ok cool, we have the tax_query portion, but we haven’t applied it to the global WP_Query yet, let’s do that next.

“pre_get_posts”

Here is what you may use in your theme or plugin to apply regionalization to WP_Query. The best place to hook in is “pre_get_posts.” In this hook, we can directly alter the query:

class MyAwesomeExampleThemeClass {

.....

protected function regionalize() {
    add_filter( 'pre_get_posts', array( $this, 'pre_posts' ) );
}

/**
 * 
 * @param WP_Query $query
 * @return WP_Query
 */
function pre_posts( $query ) {
    if ( $query->is_main_query() && ! is_admin() && ( is_archive() || is_front_page() || is_search() ) ) { 
        if ( ! $query->is_post_type_archive() )
            $query->set( 'post_type', $this->post_types );

        $query->set( 'tax_query', array( get_region_tax_query() ) ); 

        $query = apply_filters( 'emusic_pre_get_posts', $query );	
    }
    return $query;
}

....

}

The above will regionalize posts in search results and on archive pages. If you want to alter a query inline, now all you have to do is something like this:

$results = new WP_Query( array(
    'tax_query'	=> array( get_region_tax_query() ),
    'orderby' => 'comment_count',
    'order' => 'DESC',
    'posts_per_page'=> $limit
) );

In Action

View eMusic in multiple regions:

From the US
From France in the EU
From Canada (CA)
From Great Britain, mate (UK)

Installing PHP 5.4 (like a boss) with MacPorts

PHP 5.4.7 is the latest stable release of PHP. WordPress has a minimum required version of 5.2.6. Most developers aren’t using the PHP 5.4 branch. Actually, most aren’t even rocking PHP 5.3. This disgusts me.

PHP 5.3 added support for closures. If you come from the world of JavaScript, you know how useful they can be. If you have used PHP 5.3 and closures in classes, you will be happy to know that PHP 5.4 allows you to use $this in closures in class methods.

If you haven’t messed around with PHP 5.3, you can install these MacPorts to get started:

php5 +apache2+fastcgi+pear 
php5-apc
php5-curl
php5-gd
php5-http
php5-iconv
php5-imagick
php5-mbstring
php5-mcrypt
php5-memcached
php5-mysql +mysqlnd 
php5-openssl
php5-tidy

If you already have PHP 5.3 and want to upgrade to PHP 5.4, these are some tricks to get you on the right path:

sudo -s // use sudo mode throughout

port uninstall php5 
// won't work if you have extensions installed, 
// so uninstall everything that has PHP5 as a dependency first

port install php54
cd /opt/local/etc/php54 && sudo cp php.ini-development php.ini

Install a bunch of PHP extensions:

port install php54-apc php54-curl php54-gd php54-http php54-iconv php54-imagick php54-mbstring php54-mcrypt php54-memcached php54-mysql php54-openssl php54-tidy

To use mysqlnd with a local MySQL server, edit /opt/local/etc/php54/php.ini and set

mysql.default_socket, mysqli.default_socket and pdo_mysql.default_socket
to

/opt/local/var/run/mysql5/mysqld.sock

Make sure PHP 5.4.6 is the default PHP binary:

which php

If it’s something like /usr/bin/php:

cd /usr/bin && sudo rm -rf php
sudo ln -s /opt/local/bin/php54 php

You now have PHP 5.4.6 and your extensions, but you no longer have the apache variant.

port install php54-apache2handler

cd /opt/local/apache2/modules
sudo /opt/local/apache2/bin/apxs -a -e -n php5 mod_php54.so

vi /opt/local/apache2/conf/httpd.conf (remove the old php5.so)

You now have PHP 5.4 and the apache handler, but you no longer have the PEAR variant. You can try to make this work:

port install pear-PEAR

Or you can do the following:

cd #
curl http://pear.php.net/go-pear.phar -o go-pear.phar
sudo php go-pear.phar

You will prompted to specify config vars, we want to change #1 and #4.

Press 1 – Installation base ($prefix) – and enter:

/opt/local/lib/php54

Press 4 – Binaries directory – and enter:

/opt/local/bin

More checks for PEAR:

pear info pear && rm go-pear.phar
pear config-set auto_discover 1

// make sure PEAR is in the PHP include path
pear config-get php_dir

// if you don't see "/opt/local/lib/php54/share/pear" in there
php54 -i|grep 'php.ini'
// you should see "/opt/local/etc/php54" - if you don't:
sudo vi php.ini
// change include_path to:
include_path = ".:/opt/local/lib/php54/share/pear"

PEAR is installed, let’s install some PEAR stuffs:

// Unit tests
pear install pear.phpunit.de/PHPUnit
// Documentation generator
pear install pear.apigen.org/apigen

Restart Apache:

sudo /opt/local/apache2/bin/apachectl restart

You can start Apache and Memcached, et al by using commands like:

sudo port load apache2
sudo port unload apache2

sudo port load memcached
sudo port unload memcached

// memcached debugging, start with:
memcached -vv

WP + You + OOP

A lot of the programming associated with WordPress is inherently not object-oriented. Most of the theme mods or plugins that developers write for their blog(s) are one-off functions here or there, or a small set of filters and actions with associated callback functions. As WordPress matures into an “application framework” (everyone else’s words, not mine), the need for better code organization, greater maintainability, and the self-documenting powers of Object Oriented Programming become immediately apparent.

Because a majority of WordPress sites aren’t complex, the audience for discussions like this are small. Probably 99% of WordPress installs don’t need extra PHP code for them to work how their site owner(s) want. A majority of sites don’t use Multisite. A majority of sites have no need for Web Services, and when they do: they just install some Twitter widget or the like that does the heavy lifting for them. But I don’t think anyone involved with WordPress core wants that to be the future of WordPress. When people talk about the future of WordPress, they talk about how it can run any web application, but there aren’t a large number of compelling examples of WP doing that yet.

Almost by accident, I think eMusic has become a great example of how to not only run WordPress at scale, but how to write a site using WordPress as an application framework, and I have many examples of how we organize our code that can help anyone else who is struggling to make sense of a pile of code that an entire team needs to decipher and maintain.

Before we dig into how to write better OO code, we need to first figure out how we are going to organize our codebase.

Some Setups Tips

  • Run WordPress as an svn:external: This should almost be mandatory. You want your directory structure to look like so:
    /index.php
    /wp-config.php
    /wordpress
    /wp-content
    /wp-content/themes
    /wp-content/plugins
    /wp-content/mu-plugins
    /wp-content/requests
    /wp-content/site-configs
    /wp-content/sunrise.php
    
    // in the root of your site
    svn propedit svn:externals .
    
    // add this code
    wordpress http://core.svn.wordpress.org/branches/3.4/

    This is important so that you never overwrite core, and so you can’t check-in whatever hacks you have added while debugging core code.

    Because we are using an external, you need to add these lines to wp-config.php:

    define( 'WP_CONTENT_URL', 'http://' . DOMAIN_CURRENT_SITE . '/wp-content' );
    define( 'WP_CONTENT_DIR', $_SERVER['DOCUMENT_ROOT'] . '/wp-content' );

    You also need to alter index.php to look like this:

    define( 'WP_USE_THEMES', true );
    
    /** Loads the WordPress Environment and Template */
    require( './wordpress/wp-blog-header.php' );
  • Use Sunrise:
    If you are using Multisite, Sunrise is the best super-early place to hook in and alter WordPress. In wp-config.php:

    define( 'SUNRISE', 1 );

    You then need to a file in wp-content called sunrise.php.

  • Use Site Configs:
    One of the main things we use sunrise.php for is site-specific configuration code. I made a folder in wp-content called site-configs that houses files like global.php (all sites), emusic.php (site #1), emusic-bbpress.php (site #2), etc
  • Separate HTTP Requests:
    I made a folder in wp-config called requests that houses site and page-specific HTTP logic. Because a big portion of our main site is dynamic and populates its data from Web Services, it makes sense to organize all of that logic in one place.
  • Use “Must Use” plugins:
    If you have classes or code that are mandatory for your application, you can autoload them by simply placing each file in your wp-content/mu-plugins folder. If your plugin requires a bunch of extra files: it is not a good candidate for mu-plugins.

Use classes to encapsulate plugins and theme configs

MOST plugins in the WordPress plugin repository are written using procedural code – meaning, a bunch of function and global variables (whadup, Akismet!). Hopefully you know enough about programming to know that is a bad idea. 1) Global variables suck and are easily over-writable and 2) PHP will throw a fatal error if you try to overload a function (declare a function with the same name twice).

Because you have to protect your function names against this, most procedural plugin authors namespace their functions by prepending an identifier to their function names:

function my_unique_plugin_name_woo_hoo( ) {
    return 'whatever';
}
// call me maybe
my_unique_plugin_name_woo_hoo( );

If you declare a bunch of function callbacks for actions and filters in your plugin, you can see how this would be gross:

function my_unique_plugin_name_alter_the_content() { ... }
add_filter( 'the_content', 'my_unique_plugin_name_alter_the_content' );

function my_unique_plugin_name_red() { ... }
add_filter( 'the_content', 'my_unique_plugin_name_red' );

function my_unique_plugin_name_green() { ... }
add_filter( 'the_content', 'my_unique_plugin_name_green' );

function my_unique_plugin_name_blue() { ... }
add_filter( 'the_content', 'my_unique_plugin_name_blue' );

If we instead use an OO approach, we can add all of our functions to a class as methods, ditch the namespacing, and group our filters and actions together into one “init” method.

class MyUniquePlugin {
    function init() {
        add_filter( 'the_content', array( $this, 'alter_the_content' ) );
        add_filter( 'the_content', array( $this, 'red' ) );
        add_filter( 'the_content', array( $this, 'green' ) );
        add_filter( 'the_content', array( $this, 'blue' ) );
    }

    function alter_the_content() { ... }
    function red() { ... }
    function green() { ... }
    function blue() { ... }
}

How we call this class is up for debate, and I will discuss this at length later. But for right now, let’s call it like this:

$my_unique_plugin = new MyUniquePlugin;
$my_unique_plugin->init();

The init method is used like a constructor, but it ditches any ambiguity between __construct() and class MyPlugin { function MyPlugin() {} }. Could you use __construct() in any of my examples throughout instead of init() now that we are all in PHP5 syntax heaven? Probably. However, we don’t really want to use either, because we don’t want to give anyone the impression that our plugin classes can be called at will. In almost every situation, plugin classes should only be called once, and this rule should be enforced in code. I’ll show you how.

The Singleton Pattern

The Singleton Pattern is one of the GoF (Gang of Four) Patterns. This particular pattern provides a method for limiting the number of instances of an object to just one.

class MySingletonClass {
    private static $instance;
    private function __construct() {}
    public static function get_instance() {
        if ( !isset( self::$instance ) )
            self::$instance = new MySingletonClass();    

        return self::$instance;
    }        
}

MySingletonClass::get_instance();

Why do we care about limiting the number of instances to one? Think about a class that encapsulates code used to connect to a database. If the database connection is made in the constructor, we should share that connection across all instances of the class, we shouldn’t try to open a connection every time we need to make a SQL query.

For WordPress plugin classes, we want to store all of our actions and filters in a constructor or a class’s init() method. We don’t want to register those filters and actions more than once. We also don’t need or want multiple instances of our plugin class. This makes a plugin class a perfect candidate to implement the Singleton pattern.

class MyUniquePlugin {
    private static $instance;
    private function __construct() {}
    public static function get_instance() {
        if ( !isset( self::$instance ) ) {
            $c = __CLASS__;
            self::$instance = new $c();    
        }

        return self::$instance;
    } 

    function init() {
        add_filter( 'the_content', array( $this, 'alter_the_content' ) );
        add_filter( 'the_content', array( $this, 'red' ) );
        add_filter( 'the_content', array( $this, 'green' ) );
        add_filter( 'the_content', array( $this, 'blue' ) );
    }

    function alter_the_content() { ... }
    function red() { ... }
    function green() { ... }
    function blue() { ... }
}

MyUniquePlugin::get_instance();

// or, store the value of the class invocation 
// to call public methods later
$my_plugin = MyUniquePlugin::get_instance();

Ok, so great, we implemented Singleton and limited our plugin to only one instance. We want to do this for all of our plugins, but it would be great if there was a way to not repeat code in every plugin, namely all of the class members / methods needed to implement Singleton:

    private static $instance;
    private function __construct() {}
    public static function get_instance() {
        if ( !isset( self::$instance ) ) {
            $c = __CLASS__;
            self::$instance = new $c();    
        }

        return self::$instance;
    }

To do so, we are going to need a base or intermediate class that can be extended. Here is a base class, but we have a few problems:

class BaseSingleton {
    // this won't work, since $instance will get overwritten 
    // every time BaseSingleton is instantiated by a sub-class
    private static $instance;

    // this won't work, because the child class 
    // needs to be able to call parent::__construct, 
    // meaning the parent constructor has to be as visible
    // as the child - the child has to have >= visibility
    private function __construct() {} 

    public static function get_instance() {
        if ( !isset( self::$instance ) ) {
            // this won't work, because __CLASS__ refers 
            // to BaseSingleton,
            // not the class extending it at runtime
            $c = __CLASS__;
            self::$instance = new $c();    
        }

        return self::$instance;
    }
}

Let’s try to fix is:

class BaseSingleton {
    // store __CLASS__ = (instance of class) as key => value pairs
    private static $instance = array();

    // let the extending class call the constructor
    protected function __construct() {} 

    public static function get_instance( $c = '' ) {
        if ( empty( $c ) ) 
            die( 'Class name is required' );
        if ( !isset( self::$instance[$c] ) )           
            self::$instance[$c] = new $c();    

        return self::$instance[$c];
    }
}

We’re getting closer, but we have some work to do. We are going to use OO features of PHP and some new stuff in PHP 5.3 to make a base class that implements Singleton and works the way we want (we don’t want to do this hack: ClassName::get_instance( 'ClassName' ) ).

Abstract Classes

Abstract classes are kinda like Interfaces:

Classes defined as abstract may not be instantiated, and any class that contains at least one abstract method must also be abstract. Methods defined as abstract simply declare the method’s signature – they cannot define the implementation.

When inheriting from an abstract class, all methods marked abstract in the parent’s class declaration must be defined by the child; additionally, these methods must be defined with the same (or a less restricted) visibility.

Here’s an example:

abstract class BaseClass {
    protected function __construct() {}
    abstract public function init();     
}

As we can see, BaseClass does nothing except provide a blueprint for how to write our extending class. Let’s alter it by adding our Singleton code:

abstract class BasePlugin {
    private static $instance = array();
    protected function __construct() {} 

    public static function get_instance( $c = '' ) {
        if ( empty( $c ) ) 
            die( 'Class name is required' );
        if ( !isset( self::$instance[$c] ) )           
            self::$instance[$c] = new $c();    

        return self::$instance[$c];
    }

    abstract public function init(); 
}

Our base class now has the following properties:

  • Declared abstract, cannot be called directly
  • Encapsulates Singleton code
  • Stores class instances in key => value pairs (we’re not done with this)
  • Instructs the extending / child class to define an init() method
  • Hides the constructor, can only be called by itself or a child class

Here’s an example of a plugin extending BasePlugin:

class MyPlugin extends BasePlugin {
    protected function __construct() {  
        // our parent class might
        // contain shared code in its constructor
        parent::__construct();
    }

    public function init() {
        // implemented, but does nothing  
    }     
}
// create the lone instance
MyPlugin::get_instance( 'MyPlugin' );

// store the instance in a variable to be retrieved later:
$my_plugin = MyPlugin::get_instance( 'MyPlugin' );

Here’s is what is happening:

  • MyPlugin is extending BasePlugin, inheriting all of its qualities
  • MyPlugin implements the required abstract function init()
  • MyPlugin cannot be instantiated with the new keyword, the constructor is protected
  • MyPlugin is instantiated with a static method, but because an instance is created, $this can be used throughout its methods

We’re almost done, but we want to call get_instance() without our hack (passing the class name).

Late Static Binding in PHP 5.3

get_called_class() is a function in PHP 5.3 that will give us the name of the child class that is calling a parent class’s method at runtime. The class name will not be resolved using the class where the method is defined but it will rather be computed using runtime information. It is also called a “static binding” as it can be used for (but is not limited to) static method calls.

Here’s an easy example to explain how this works:

class A {
    public static function name() {
        echo get_called_class();
    }     
}

class B extends A {}

Class C extends B {}

C::name(); // outputs "C"

get_called_class() is new to PHP 5.3. WordPress only requires PHP 5.2, so you might need to upgrade to be able to implement this class like so:

abstract class BasePlugin {
    private static $instance = array();
    protected function __construct() {}
    public static function get_instance() {
        $c = get_called_class();
        if ( !isset( self::$instance[$c] ) ) {
            self::$instance[$c] = new $c();
            self::$instance[$c]->init();     
        }

        return self::$instance[$c];
    }   

    abstract public function init();      
}

Now we can instantiate our plugin like so:

// create the lone instance
MyPlugin::get_instance();

// store the instance in a variable to be retrieved later:
$my_plugin = MyPlugin::get_instance();

Base Classes for Plugins and Themes

Just like it says: USE BASE CLASSES for plugins AND themes. If you aren’t using Multisite, you probably don’t have the problem of maintaining code across parent and child themes that might be shared for multiple sites. Each theme has a functions.php file, and that file, in my opinion, should encapsulate your theme config code (actions and filters, etc) in a class. Parent and Child themes are similar to base and sub classes.

Because we don’t want to repeat ourselves, code that would be copied / pasted into another theme to inherit functionality should instead be placed in a BaseTheme class. Our theme config classes should also implement Singleton.

One of the pieces of functionality we need to share across themes at eMusic is regionalization. Regionalization is accomplished by using a custom taxonomy “region” and some custom actions and filters. For a theme to be regionalized, it needs to override some class members and call BaseTheme::regionalize().

Here’s part of our BaseTheme class:

abstract class BaseTheme implements Singleton {
    var $regions_map;
    var $regions_tax_map;
    var $post_types = array( 'post' );

    private static $instance = array();

    public static function get_instance() {
        $c = get_called_class();
        if ( !isset( self::$instance[$c] ) ) {
            self::$instance[$c] = new $c();
            self::$instance[$c]->init();
        }

        return self::$instance[$c];
    }

    protected function __construct() {}
    abstract protected function init();

    protected function regionalize() {
	add_filter( 'manage_posts_columns', array( $this, 'manage_columns' ) );
	add_action( 'manage_posts_custom_column', array( $this, 'manage_custom_column' ), 10, 2 );
	add_filter( 'posts_clauses', array( $this, 'clauses' ), 10, 2 );
	add_filter( 'manage_edit-post_sortable_columns',array( $this, 'sortables' ) );
	add_filter( 'pre_get_posts', array( $this, 'pre_posts' ) );
    }

    ...
}

Here is a theme extending it in its functions.php file:

if ( ! isset( $content_width ) ) 
    $content_width = 448;

class Theme_17Dots extends BaseTheme {
    function __construct() {
        global $dots_regions_tax_map, $dots_regions_map;

        $this->regions_map = $dots_regions_map;  
        $this->regions_tax_map = $dots_regions_tax_map;          

        parent::__construct();
    }

    function init() {    
        $this->regionalize();

        add_action( 'init', array( $this, 'register' ) );
        add_action( 'after_setup_theme', array( $this, 'setup' ) );
        add_action( 'add_meta_boxes_post', array( $this, 'boxes' ) );
        add_action( 'save_post', array( $this, 'save' ), 10, 2 );
        add_filter( 'embed_oembed_html', '_feature_youtube_add_wmode' );
    } 

    ....
}

“Must Use” Plugins

Let’s assume we stored our BaseTheme and BasePlugin classes in files called class-base-theme.php and class-base-plugin.php. The next question is: where should these  files go? Probably the best place for them to go is wp-content/mu-plugins. The WordPress bootstrap routine will load files in that directory automatically. Because our BaseTheme class is not immediately invoked within the file, the loading of the file just makes the class available for our theme child class in functions.php to extend.

Sunrise

sunrise.php can be viewed as a functions.php for your whole network. Aside from letting you switch the context of $current_blog and $current_site, you can also start adding actions and filters.

In the eMusic sunrise.php, I take this opportunity to load “site configs,” mainly to filter which plugins are going to load for which site, and which plugins will load for the entire network of sites (plugins that load for every site). To accomplish this, we need a “global” config and then a config for each site like so:

require_once( 'site-configs/global.php' );

if ( get_current_blog_id() > 1 ) {
    switch ( get_current_blog_id() ) {
    case 2:
        require_once( 'site-configs/bbpress.php' );
	break;

    case 3:
        require_once( 'site-configs/dots.php' );
	break;

     case 5:
        require_once( 'site-configs/support.php' );
	break;
    }

    add_filter( 'pre_option_template', function () {
	return 'dark';
    } );
} else {
    require_once( 'site-configs/emusic.php' );
}

Site Configs

In our global site config, we want to filter our active network plugins. As I have said in previous posts and presentations, relying on active plugins being correct in the database is dangerous and hard to keep in sync across environments.

We can filter active network plugins by filtering the active_sitewide_plugins site option:

add_filter( 'pre_site_option_active_sitewide_plugins', function () {
    return array(
        'batcache/batcache.php'                         => 1,
        'akismet/akismet.php'                           => 1,
        'avatar/avatar.php'                             => 1,
        'bundle/bundle.php'                             => 1,
        'cloud/cloud.php'                               => 1,
        'download/download.php'                         => 1,
        'emusic-notifications/emusic-notifications.php' => 1,
        'emusic-ratings/emusic-ratings.php'             => 1,
        'emusic-xml-rpc/emusic-xml-rpc.php'             => 1,
        'johnny-cache/johnny-cache.php'                 => 1,
        'like-buttons/like-buttons.php'                 => 1,
        'members/members.php'                           => 1,
        'minify/minify.php'                             => 1,
        'movies/movies.php'                             => 1,
        'shuffle/shuffle.php'                           => 1,
        'apc-admin/apc-admin.php'                       => 1,
        //'debug-bar/debug-bar.php'                     => 1
    );
} );

Filtering plugins in this manner allows to do 2 main things:

  • We can turn plugins on and off by commenting / uncommenting
  • We can visually see what our base set of plugins is for our network without going to the admin or the database

We have a lot of other things in site-configs/global.php, but I mainly want to demonstrate how we can load classes in an organized manner. In a site-specific config, we will filter what plugins are active for that site only.

For the main eMusic theme, we use these plugins:

add_filter( 'pre_option_active_plugins', function () {
    return array(
        'artist-images/artist-images.php',
        'catalog-comments/catalog-comments.php',
        'emusic-post-types/emusic-post-types.php',
        'discography.php',
        'emusic-radio/emusic-radio.php',
        'gravityforms/gravityforms.php',
        'super-ghetto/super-ghetto.php'
        //,'theme-check/theme-check.php'
    );
} );

This is just another example of us not repeating ourselves. When we think about everything in an object-oriented manner, everything can be cleaner and more organized.

HTTP Requests

Another area where we need to organize our code is around HTTP requests. We have “pages” in WordPress that aren’t populated via content from the database, but from a combination of Web Service calls based on request variables.

We use the following files:

// base class
/wp-content/mu-plugins/request.php 

// extended by theme classes implementing the Factory pattern
// "dark" is a theme
/wp-content/requests/dark.php

// the theme class, where applicable, 
// loads a request class when required by a certain page
/wp-content/requests/home.php
/wp-content/requests/album.php
/wp-content/requests/artist.php
... etc ...

As an example, an album page loads AlbumRequest:

class AlbumRequest extends RequestMap { ... }
class RequestMap extends API { ... }

// the API class implements CURL
class API {}

// CURL implements the cURL PHP extension, curl_multi(), etc
class CURL {}

Our organization of HTTP files is example of OO principles in action, and includes ways to organize our codebase in a way that is not specified by WordPress out of the box.

This is also an example of the self-documenting nature of object-oriented programming. Rather than show you each line of AlbumRequest, we can assume that it deals with requests specific to the Album page, inheriting functionality from RequestMap, which calls methods available in API, which at its core is implementing cURL.

Conclusion

Hopefully after reading all of this, you can clearly see some of the benefits of organizing your code in an object-oriented manner. The basics of OOP are outside the scope of this post but are essential for all developers to learn, especially those who work on teams whose members have varying degrees of skill.

Organized and readable code is essential, and the patterns available to us from the history of computer programming so far should be used to help get it there.

A few notes on bbPress

I have been spending a lot of time off and on the past week creating a bbPress (message boards, or “forum software,” or “bulletin board”) theme for eMusic. Theming is fun because you get to touch almost every feature of the product, bbPress and/or WordPress. In the midst of this, I’ve been discovering how bbPress does things and making some modifications along the way. Those mods may only exist in eMusic and never make it into bbPress, but I have shared ideas with JJJ so I figured I would share them here as well for anyone to check out and supply feedback.

Queries within Queries with Queries

bbPress abstracts almost everything. For most WordPress-y things, there is a bbPress-y thing. One thing that will immediately confuse and potentially wreak havoc is the way “topics” are queried. Because the default WordPress query is the page or custom post type you are on, the topics for a forum are queried using a second mechanism: bbp_has_topics( ), which upon success, returns a WP_Query object.

That’s all well and good, but at any given time, there is only 1 bbPress query and no hardened reference to the original query. If you are using the default theme or just modifying its presentation, you probably don’t care. If you are implementing a design that has multiple “topics” queries, you are kinda up shit’s creek unless you roll your own code. The template tags / functions provided by bbPress will work to an extent, but they will also shift bbPress’s entire context every time you call bbp_has_topics( ).

The solution espoused to me was to roll my own WP_Query objects to replicate what bbPress is doing in the background, but I don’t want to do that. I want to use bbPress functions and fix whatever context is set along the way when necessary. WordPress maintains state and stores the main query by using $wp_the_query and $wp_query, for the main query and then the current query, respectively. bbPress clobbers $bbp->topic_query every time bbp_has_topics( ) is run. bbPress is treating bbp_has_topics( ) like query_posts( ) in WordPress, not like new WP_Query( ). Ask Nacin how he feels about query_posts( ).

bbPress makes an attempt to provide query context by providing the function bbp_set_query_name( $name )

Guess what it does internally… basically nothing. So I wanted to fix this, here’s some code I am using:

global $emusic_the_bbp_query;
$emusic_the_bbp_query = array();
function emusic_set_bbp_query( $name = 'main', $params = array() ) {
    global $emusic_the_bbp_query;
    $bbp = bbpress();

    bbp_set_query_name( $name );

    if ( !empty( $bbp->topic_query ) && empty( $emusic_the_bbp_query ) )
        $emusic_the_bbp_query['main'] = $bbp->topic_query;    

    if ( !empty( $name ) && isset( $emusic_the_bbp_query[$name] ) ) {
        $bbp->topic_query = $emusic_the_bbp_query[$name];
        return $bbp->topic_query;
    }

    if ( !empty( $params ) ) {
        bbp_has_topics( $params );
        $emusic_the_bbp_query[$name] = $bbp->topic_query;
        return $bbp->topic_query;
    }
}

function emusic_reset_bbp_query() {
    global $emusic_the_bbp_query;
    $bbp = bbpress();
    $bbp->topic_query = $emusic_the_bbp_query['main'];
    bbp_set_query_name();
}

My code does a few things:

  • Sets $emusic_the_bbp_query whenever you start to change context the first time
  • Always allows you to retrieve the default context when you are done with a sub-query for topics
  • Non-persistently caches topic queries by name

So where’s my use case? I want to show a “snapshot” of your subscribed-to topics, favorite topics, popular topics, and super-sticky topics in the sidebar on every page, and I want to reuse theme code (template parts) to do so. Imagine calling query_posts( ) 5 times in a template on the WordPress side…

Could I have accomplished this with WP_Query? Yeah, but… I want to use the bbPress stuff. So here’s what I do:

<?php if ( is_user_logged_in() ):   ?>
<div class="meta-block">
    <h3>Your Topics</h3>
    <span class="double-line-narrow"></span> 
    <?php 
    if ( bbp_is_subscriptions_active() ) : ?>    
        <h4 class="sub-head">
            Subscribed 
            <a class="aux" href="<?php bbp_user_profile_url( get_current_user_id() ) ?>">view all</a>
        </h4>
        <span class="double-line-narrow"></span>
        <?php       
        $subscriptions = bbp_get_user_subscribed_topic_ids( get_current_user_id() );
        if ( !empty( $subscriptions ) ):
            emusic_set_bbp_query( 'bbp_user_profile_subscriptions', array( 'post__in' => $subscriptions, 'posts_per_page' => 3 ) );

            while ( bbp_topics() ) : bbp_the_topic();

                bbp_get_template_part( 'loop' , 'single-sidebar-topic' );

            endwhile;
        else:
            printf( '<p>%s</p>', __( 'You haven\'t subscribed to any posts.' ) );
        endif;

        emusic_reset_bbp_query();

    endif; ?>

    <h4 class="sub-head">
        Favorites 
        <a class="aux" href="<?php bbp_user_profile_url( get_current_user_id() ) ?>">view all</a>
    </h4>
    <span class="double-line-narrow"></span>
    <?php
    $favorites = bbp_get_user_favorites_topic_ids( get_current_user_id() );
    if ( !empty( $favorites ) ):
        emusic_set_bbp_query( 'bbp_user_profile_favorites', array( 'post__in' => $favorites, 'posts_per_page' => 3 ) );

        while ( bbp_topics() ) : bbp_the_topic();

            bbp_get_template_part( 'loop' , 'single-sidebar-topic' );

        endwhile;
    else:
        printf( '<p>%s</p>', __( 'You haven\'t favorited any posts.' ) );
    endif;

    emusic_reset_bbp_query();        
?>
</div>
<?php endif ?>

<?php 
$super_stickies = get_option( '_bbp_super_sticky_topics', array() );
if ( !empty( $super_stickies ) ): ?>    
    <div class="meta-block">
        <h3>Featured Discussions</h3>
        <span class="double-line-narrow"></span> 
        <?php 
        emusic_set_bbp_query( 'bbp_super_stickies', array( 'post__in' => $super_stickies, 'posts_per_page' => 4 ) );

        while ( bbp_topics() ) : bbp_the_topic();

            bbp_get_template_part( 'loop' , 'single-sidebar-topic' );

        endwhile;
        ?>
    </div>
<?php endif ?>

Meta Queries like whoa

First of all, JJJ has done an awesome job making bbPress a plugin, and it is super sweet how seamlessly it integrates with everything else in WordPress. The two areas I would like to help make improvements in are 1) cache and non-persistent cache 2) Meta Query performance in WordPress as a whole. bbPress makes Meta Queries like (holy shit) whoa. For 99% of installs, who cares. For us, my current dataset is already 100s of 1000s of posts and 2-3 million rows of postmeta. A meta query basically says:

  • I need stuff from the posts table
  • I need stuff joined from the postmeta table
  • I have indexed columns in both
  • Fuck that noise, let’s join on an unindexable LONGTEXT column and pray for mercy

I wish meta_query never existed in WordPress. But it does, and bbPress sings its song throughout. I have a few ideas for reducing number of queries made and also splitting the query into 2, so that PHP can compare integers instead of trying to sort them as text in MySQL. I have done a lot of performance testing on meta_queries, and I actually ditched them in a few places for WP_Query because they do not scale out of the box.

But I’m not just gonna whine about it, I’m gonna try to contribute and I’ll report back when I do.

WordCamp NYC 2012: “Cloud, Cache, and Configs”

Here are the slides from my talk today:

I spoke for 40 minutes to a room full of people that had no idea what I was saying. Seriously.

Have any of you made a plugin before? Silence / crickets. Cool, well let me dive into scaling HTTP parallelization for 15 minutes…

Installing libmemcached on CentOS

I recently updated the Memcached WP Object Cache plugin to use the Memcached PHP extension – it currently uses the Memcache extension. My plugin is called Memcached Redux, and it’s delicious. The Memcached PHP extension implements methods from libmemcached, the C / C++ Memcached library.

Installing the extension on MacPorts is beyond easy:

port install php5-memached

Installing on Amazon EC2 instance running CentOS 5 is WAY more esoteric. I spent about 2 hours mucking around until I figured it out, so here it is (this assumes you are already using memcached and have libevent, libzlib, etc installed):

cd /etc/yum.repos.d/ 
wget http://rpms.famillecollet.com/remi-enterprise.repo 
wget http://syslogserver.googlecode.com/files/epel-release-5-3.noarch.rpm 
wget http://rpms.famillecollet.com/enterprise/remi-release-5.rpm 
rpm -Uvh remi-release-5*.rpm epel-release-5*.rpm 
yum --enablerepo=remi install libmemcached\* 
pecl install memcached 

vi /etc/php.ini 

// (under Dynamic Extensions) Add "extension=memcached.so": 

extension=apc.so 
extension=http.so 
extension=memcache.so 
extension=memcached.so

Servers have to be restarted – this has be done before code is deployed or the class won’t exist, which will cause a fatal error.

This process looks harmless, but I had to read 3.7 million blog posts before I found the right path forward.

Audio Redux + Updated Plugins

I had the pleasure recently of realizing that some of my plugins had disappeared from the WordPress dot org plugins repo because I haven’t updated them in 1.5ish years. Last year at WordCamp San Francisco, I used part of my talk to explain why plugins aren’t always awesome, and me not updating my own plugins is a great example of why. You might think to yourself, “Scott’s pretty good, I’m sure his plugins are awesome, I could probably get pregnant just by activating one of them!” To which I would reply: “If you are using my Movies plugin, I have absolutely no idea if it still works, and I’m sure every javascript library in it is 50-75 versions behind.”

So I am trying to right this wrong and update my code. Back in 2010, I was still under the impression that procedural programming was da bomb. Needless to say, the plugins should work a lot better now and will be maintained / are more maintainable.

Here are some of the plugins that are updated, along with their companion blog posts:

Minify
Plugin: http://wordpress.org/extend/plugins/minify/
Blog Post: http://scotty-t.com/2012/05/24/minify-redux/

Shuffle
Plugin: http://wordpress.org/extend/plugins/shuffle/
Blog Post: http://scotty-t.com/2010/11/15/my-first-plugin-shuffle/

Audio
Plugin: http://wordpress.org/extend/plugins/audio/
Blog Post: http://scotty-t.com/2010/11/22/new-plugin-audio/

Audio is awesome, and here’s why:

  • WordPress doesn’t come packaged with an mp3 player
  • Your player can be styled 100% with CSS – (drop in a replacement: STYLESHEETPATH . ‘/audio.css’)
  • If you use Shuffle, you can attach .ogg files to your .mp3s, and they will be used in native HTML5 browsers that don’t natively support .mp3
  • If you use your own styles, you can style a playlist automatically, and with Shuffle, attach image(s) to each MP3

BOOM. I will try to be a better man in the future and keep my plugins bleeding edge.

xoxo