The Evolution of WordPress Software Development at WordCamp Europe 2015

Update: the video of the presentation is now available on WordPress.tv:

My WordCamp Europe 2015 talk on The Evolution of WordPress Software Development:

When you start to use WordPress as an application platform, one thing is very soon clear: WordPress defines very little base structure for your application code, where to put and how to write tests, how application is loaded, or for example how dependencies should be managed. Because of this, applications or just complex sites built on top of WordPress tend to get really messy.

There are application frameworks built on top of WordPress, but none of them seem to have wide enough use for to build any serious applications on top of. Until any of those gets wide-spread adoption, combining existing, non-WordPress specific PHP technologies as a custom solution probably is a more future-proof solution. We will be looking into one possible stack of technologies.

Here are the slides of the presentation:

You can read about our approach in more detail in a three part series of posts:

My WordCamp Finland 2015 talk on page performance optimization

On May 8th, 2015 I did a talk at WordCamp Finland about WordPress page performance optimization. Here are the links for things that I mentioned on the talk.

Update: the slides of the presentation.

Update 2: the video of the presentation.

My WordPress origin story

WordPress logoWordPress.org profiles have this one question about your WordPress origin story. That question inspired me to write my WordPress story here.

My initial contact with WordPress was on the summer of 2005, close to 10 years ago. Before that, my personal site and blog was running on a custom built blogging engine I had created for myself.

Back then, WordPress was at version 1.5. Later that year 2.0 was released with a redesigned admin UI. That is when I actually started using WordPress.

Blogging era

From the beginning I created my own theme. I wanted to tinker with everything and was happy to find out WordPress did not prevent that. I created a lot of functionality on top of it, mostly for my personal use. Like a database of books I had read and was going to read.

While working on my day job as a .Net developer, I did some freelancing on the side to build a couple of small sites on WordPress. That made me learn WordPress even better.

WordPress was also used on some trivial projects on my day jobs. Install, customize lightly and forget. Latter one being a bad idea, now that I think of it.

My day job at the time was a lot about creating a custom CMS for a media company and later for a web development company. (At the time, that made some sense. Now it does not.)

.Net web development back then was Web Forms development. Web Forms is this crazy model that totally disregards how the web actually works. I kept creating ways around it. WordPress was totally different. For a web geek, it made sense as is.

WordPress gave me ideas. For six years, working with different CMSs and other projects, I took a lot ideas out of WordPress and used them with commercial projects.

Until I started H1 in 2010 and was finally able to work with the real thing.

H1 era

Back in 2010, just a couple of months before H1 was founded, WordPress 3.0 was released. 3.0 was actually something that made WordPress a lot more useful in terms of developing interesting websites. Mainly because of the addition of custom post types and taxonomies.

It was not my idea back then to create a company that would do WordPress development. I though I was going to do some .Net consulting. Which I did, but not for long. Before long, I was asked to build websites and to my taste, there was no good .Net CMS available. At least not one with a reasonable sticker price. Sticking to what I knew second best, I chose WordPress.

Soon, H1 was a three person company and somehow we had managed to hire the number one WordPress name in Finland, Daniel Koskinen. Just his presence made me more interested in WordPress and thus also made the company focus fully on WordPress. Now we have eight people and our commitment to WordPress is like no other company in Finland.

During these close to five years at H1 my relationship with WordPress has grown a lot.

I’ve created many sites on WordPress I am really proud of.

I’ve released my first plugin on the official repository, though most of the coding has been for clients.

I have submitted at least one patch to core WordPress. I have enhanced documentation of WordPress. I’ve translated Gravity Forms, one of the most popular plugins out there.

I’ve been to three WordCamps, one PressNomics and many local WordPress meetups.

I’ve spoken about WordPress. This year I will speak at WordCamp Finland and WordCamp Europe.

I have written about WordPress – here and on the company blog.

I will keep doing these things. I do this because I believe in WordPress.

I’m sure my WordPress story will not end any time soon.

Mobile first – are you really?

You did just use the buzzword “mobile first” to describe the design of your latest website. But did you really do it right? Try my 5-second mobility test (ok, it could take a lot more seconds):

Open up Chrome Developer Tools (cmd-alt-I), click the Network tab and make sure the Disable cache checkbox is checked. Adjust the browser width to mobile size. Then, reload the page. Wait for the page to download. Really, wait. See the bottom status bar and especially the amount of data transferred.

241 requests | 3.1 MB transferred | 1.5 min (load 16.53 s, DOMContentLoaded 16.17 s)

If the amount of bytes transferred is something like less than a megabyte, you are ok, but obviously the less the better. If, on the other hand, the amount is several megabytes, then… well, you should reconsider your original statement about being mobile first – I’m not sure that would be first anything.

Sure, download size is just one factor of the whole page speed experience, but remember that this is just a quick test I like. You might like to use Google PageSpeed Insights instead, for example.

My point is this:

Whatever design approach you use, it is not really worth a damn unless you make sure the approach is actually executed. It really is the only way.

WordPress Software Development at H1. Part 3: Application Code

PostType classes

Since WordPress is a CMS, the most important thing is content. So let’s first look into what would a post type class look like.

namespace ProjectName\PostType;

/**
 * Page, a WordPress default post type
 */
class Page extends \WPlinth\PostType {

	const TYPE = 'page';

	public function set_registration_parameters() {
	}

	public function set_meta_boxes() {
	}

}

Both WordPress default post types, Page and Post, can be defined as a class that extends the WPlinth PostType class. This is not necessary, if your application does nothing to extend the functionality of those types. However, from the simplistic example above, we can see how a PostType class is constructed. We define the post type name as a class level constant. Then we have two functions, set_registration_parameters and set_meta_boxes that will be called automatically, if they are defined, by the base class. Below, you can see a more realistic example:

namespace ProjectName\PostType;

/**
 * Service post type
 * Represents a service provided to a customer,
 * e. g. Project Management or Maintenance.
 */
class Service extends \WPlinth\PostType {

	const TYPE = 'projectname_service';

	public function set_registration_parameters() {
		$this->labels = array( 
			'menu_name'          => __( 'Services', 'projectname-application' ),
			'name'               => _x( 'Services', 'post type general name', 'projectname-application' ),
			'singular_name'      => _x( 'Service', 'post type singular name', 'projectname-application' ),
			'name_admin_bar'     => _x( 'Service', 'add new on admin bar', 'projectname-application' ),
			'add_new'            => _x( 'Add New', 'service', 'projectname-application' ),
			'add_new_item'       => __( 'Add New Service', 'projectname-application' ),
			'new_item'           => __( 'New Service', 'projectname-application' ),
			'edit_item'          => __( 'Edit Service', 'projectname-application' ),
			'view_item'          => __( 'View Service', 'projectname-application' ),
			'all_items'          => __( 'All Services', 'projectname-application' ),
			'search_items'       => __( 'Search Services', 'projectname-application' ),
			'parent_item_colon'  => __( 'Parent Services:', 'projectname-application' ),
			'not_found'          => __( 'No services found.', 'projectname-application' ),
			'not_found_in_trash' => __( 'No services found in Trash.', 'projectname-application' ),
		);

		/**
		 * Define other arguments
		 */
		$this->args = array(
			'rewrite'  => array( 'slug' => 'services' ),
			'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt', 'revisions' ),
		);
	}

	public function set_meta_boxes() {
		$this->meta_boxes[] = array(
			'title' => 'Pricing',
			'pages' => array( self::TYPE ), // Array of post types
			'context'    => 'normal',
			'priority'   => 'high',
			'fields' => array(
				array(
					'id'   => 'projectname_service_hourly_price',
					'name' => __( 'Hourly price', 'projectname-application' ),
					'type' => 'text',
				),
				array(
					'id'   => 'projectname_service_daily_price',
					'name' => __( 'Daily price', 'projectname-application' ),
					'type' => 'text',
				),
			),
		);
	}

}

In set_registration_parameters we do two things. First we set the labels to an instance variable labels. Then we do that for other arguments of register_post_type. The base class takes care of the rest.

In set_meta_boxes we append to the meta_boxes array the meta fields we would like our post type have. We use the format shared with plugins Custom Meta Boxes and Meta Box. One of those plugins should be active. If you would like to use something else, you can extend ProjectName\PostType and take care of that part yourself.

Taxonomy classes

Taxonomies work a lot the same way than post types. Below is a sample. It should be pretty self-explanatory.

namespace ProjectName\Taxonomy;

class CaseType extends \WPlinth\Taxonomy {
	const TAXONOMY = 'projectname_casetype';

	public function set_taxonomy_data() {

		$labels = array(
			'name'					=> _x( 'Case Type', 'Taxonomy plural name', 'projectname-application' ),
			'singular_name'			=> _x( 'Case Type', 'Taxonomy singular name', 'projectname-application' ),
			'search_items'			=> __( 'Search Case Types', 'projectname-application' ),
			'popular_items'			=> __( 'Popular Case Types', 'projectname-application' ),
			'all_items'				=> __( 'All Case Types', 'projectname-application' ),
			'parent_item'			=> __( 'Parent Case Type', 'projectname-application' ),
			'parent_item_colon'		=> __( 'Parent Case Type', 'projectname-application' ),
			'edit_item'				=> __( 'Edit Case Type', 'projectname-application' ),
			'update_item'			=> __( 'Update Case Type', 'projectname-application' ),
			'add_new_item'			=> __( 'Add New Case Type', 'projectname-application' ),
			'new_item_name'			=> __( 'New Case Type', 'projectname-application' ),
			'add_or_remove_items'	=> __( 'Add or remove Case Type', 'projectname-application' ),
			'choose_from_most_used'	=> __( 'Choose from most used Case Types', 'projectname-application' ),
			'menu_name'				=> __( 'Case Type', 'projectname-application' ),
		);
	
		$args = array(
			'labels'            => $labels,
			'public'            => true,
			'show_in_nav_menus' => true,
			'show_admin_column' => true,
			'hierarchical'      => true,
			'show_tagcloud'     => true,
			'show_ui'           => true,
			'query_var'         => true,
			'rewrite'           => array( 'slug' => 'case-type' ),
			'query_var'         => true,
		);

		$this->args = $args;

		$this->types = array( 'projectname_casestudy' );
	}

}

Connection classes

If we want to define a Posts to Posts connection, we extend the \WPlinth\Connection class. Here is a simple example:

namespace ProjectName\Connection;

class ServicesProvided extends \WPlinth\Connection {

	const NAME = 'services_provided';

	protected $connection_type;

	public function set_connection_type() {
		$this->connection_type = array(
			'name'  => self::NAME,
			'from'  => 'projectname_casestudy',
			'to'    => 'projectname_service',
			'cardinality' => 'one-to-many',
			'title' => array(
				'from' => __( 'Casestudies', 'projectname-application' ),
				'to' => __( 'The Service', 'projectname-application' )
			),
		);
	}
}

All the functionality that is related to the connection should be put into this class. We might, for example, want a function to fetch all the services provided for a case. That would look like something like this:

public function get_services( $casestudy_id ) {
		$services = p2p_get_connections(
			self::NAME,
			array( 'from' => $casestudy_id, 'to' => 'any' )
		);
		return $services;
}

Even this simple function does make sense to exist, since this way we are able to encapsulate all the logic that is touching Posts 2 Posts API and thus keep all other code not dependant on that particular implementation. The code that would need to consume this function would need to have an instance of our ServicesProvided class. And, you guessed it, that instance would be provided to your consuming class by the DependencyInjection container, provided that you had defined the dependency in the configuration file.

QueryFilter classes

We are using QueryFilters to modify the main query for specific views. Instead of having a mess of unreadable if else statements, we define separate classes for each modification. Let’s look at an example. Say we wanted to display case studies in addition to posts on the front page.

namespace ProjectName\QueryFilter;

/**
 * Display posts and case studies on front page
 */
class FrontPage extends \WPlinth\QueryFilter {
	const PRIORITY = 10;

	public function test( $query ) {
		return $query->is_home();
	}

	public function filter( $query ) {
		$query->set( 'post_type', array(
			ProjectName\PostType\Post::TYPE,
			ProjectName\PostType\CaseStudy::TYPE
		);
	}
}

A QueryFilter consists of three parts. First, we can define a priority. Filters are tested in the order of their priority and only one filter is run per page load. Lower number means earlier execution.

QueryFilters in the system are tested first. The test function should return true, if the query does match the view WordPress is executing. In our example, we are simply testing weather WordPress thinks the current page is home page.

If the test matches, filter function is run. There we have the opportunity to do the modifications to the query we like.

Going further

These are just the most common ways of modifying and extending WordPress for a project specific way. If your application does something else, say, defines multiple shortcodes, it would make sense to create a base class for those and group them in a namespace/folder, in a similar fashion than what I have demonstrated above.

Other than that, this is it, a better way to organize your application code.

In the next part, we are going to discuss writing unit test.