The get_post_class()
function is a WordPress function commonly used within post “rivers”. For example, if I had a list of posts, WooCommerce products, or any content type really, I might have some code like this:
<div >
The get_post_class()
function is a WordPress function commonly used within post “rivers”. For example, if I had a list of posts, WooCommerce products, or any content type really, I might have some code like this:
<div >
Note: post_class()
just calls get_post_class()
and outputs it to the browser.
post_class()
will output something like class="post has-post-thumbnail type-POST-TYPE status-POST_STATUS tag-TAG1 tag-TAG2 category-CATEGORY1 category-CATEGORY2 ...."
The classes added make it easy to style content that has a specific taxonomy term, has a thumbnail, a particular status, etc.
However, the queries needed to determine all this information are not cheap. Moreover, this function is probably called for every post you’re listing. So if you have posts_per_page
set to 20, this function will be called 20 times.
Let’s take a look at the function’s code (I’ve trimmed some of the comments):
function get_post_class( $class = '', $post_id = null ) { $post = get_post( $post_id ); $classes = array(); if ( $class ) { if ( ! is_array( $class ) ) { $class = preg_split( '#s+#', $class ); } $classes = array_map( 'esc_attr', $class ); } else { // Ensure that we always coerce class to being an array. $class = array(); } if ( ! $post ) { return $classes; } $classes[] = 'post-' . $post->ID; if ( ! is_admin() ) $classes[] = $post->post_type; $classes[] = 'type-' . $post->post_type; $classes[] = 'status-' . $post->post_status; // Post Format if ( post_type_supports( $post->post_type, 'post-formats' ) ) { $post_format = get_post_format( $post->ID ); if ( $post_format && !is_wp_error($post_format) ) $classes[] = 'format-' . sanitize_html_class( $post_format ); else $classes[] = 'format-standard'; } $post_password_required = post_password_required( $post->ID ); // Post requires password. if ( $post_password_required ) { $classes[] = 'post-password-required'; } elseif ( ! empty( $post->post_password ) ) { $classes[] = 'post-password-protected'; } // Post thumbnails. if ( current_theme_supports( 'post-thumbnails' ) && has_post_thumbnail( $post->ID ) && ! is_attachment( $post ) && ! $post_password_required ) { $classes[] = 'has-post-thumbnail'; } // sticky for Sticky Posts if ( is_sticky( $post->ID ) ) { if ( is_home() && ! is_paged() ) { $classes[] = 'sticky'; } elseif ( is_admin() ) { $classes[] = 'status-sticky'; } } // hentry for hAtom compliance $classes[] = 'hentry'; // All public taxonomies $taxonomies = get_taxonomies( array( 'public' => true ) ); foreach ( (array) $taxonomies as $taxonomy ) { if ( is_object_in_taxonomy( $post->post_type, $taxonomy ) ) { foreach ( (array) get_the_terms( $post->ID, $taxonomy ) as $term ) { if ( empty( $term->slug ) ) { continue; } $term_class = sanitize_html_class( $term->slug, $term->term_id ); if ( is_numeric( $term_class ) || ! trim( $term_class, '-' ) ) { $term_class = $term->term_id; } // 'post_tag' uses the 'tag' prefix for backward compatibility. if ( 'post_tag' == $taxonomy ) { $classes[] = 'tag-' . $term_class; } else { $classes[] = sanitize_html_class( $taxonomy . '-' . $term_class, $taxonomy . '-' . $term->term_id ); } } } } $classes = array_map( 'esc_attr', $classes ); $classes = apply_filters( 'post_class', $classes, $class, $post->ID ); return array_unique( $classes ); }
Within this code, the following functions might result in database queries: get_post_format
, has_post_thumbnail
, is_sticky
, and get_the_terms
. The most expensive of these queries is get_the_terms
which for each taxonomy associated with the post type, selects all the terms attached to the post for that taxonomy. If there are four taxonomies associated with the post type being queried, get_post_class
could result in 7 extra database queries per post. With 20 posts per page, that’s an extra 140 queries per page load! On WooCommerce sites where there are many taxonomies and usually many products per page being shown, this is a huge performance killer. Yes, object caching (and page caching of course) will improve our eliminate some of the database queries, but people will still be hitting the cache cold sometimes.
Solution:
Don’t use get_post_class
or post_class
. It’s not that important. 99% of people don’t use the tags it generates. What I do is output the function, inspect the classes it adds using Chrome, and hardcode the classes actually referenced in CSS into the theme.
PS: body_class()
is much less query intensive and okay to use.
Today I am presenting on the JSON REST API for WordPress at the DC API User Group. This is a shorter talk geared at both developers and API users with or without WordPress experience.
With core integration coming in the near future, it’s important for developers of ALL backgrounds to understand it will be available on ~23% of the websites on the internet. Here are the slides for my talk:
The past few months I’ve had the opportunity to work on the new JSON REST API for WordPres. My biggest contribution as a WP API team member has been the Backbone client.
The JSON REST API’s Backbone client let’s you interact with a WordPress installation using Backbone.js collections and models. The client is an extremely useful tool in creating reactive web applications (which seems to be where the web is heading).
As a proof of concept, I created a WordPress starter theme based on Automattic’s _s named _s_backbone. Loops (or post streams) in _s_backbone are driven by Backbone.js collections. This means that posts are grabbed on the fly without a page reload. Pagination is accomplished through a “more” button which, again, does not require a page reload. This is commonly referred to as “infinite scroll”.
Please download _s_backbone from Github. Any feedback is appreciated.
Edit: Check out the WP Tavern article on _s_backbone.
<![CDATA[
The past few months I’ve had the opportunity to work on the new JSON REST API for WordPres. My biggest contribution as a WP API team member has been the Backbone client.
The JSON REST API’s Backbone client let’s you interact with a WordPress installation using Backbone.js collections and models. The client is an extremely useful tool in creating reactive web applications (which seems to be where the web is heading).
As a proof of concept, I created a WordPress starter theme based on Automattic’s _s named _s_backbone. Loops (or post streams) in _s_backbone are driven by Backbone.js collections. This means that posts are grabbed on the fly without a page reload. Pagination is accomplished through a “more” button which, again, does not require a page reload. This is commonly referred to as “infinite scroll”.
Please download _s_backbone from Github. Any feedback is appreciated.
Edit: Check out the WP Tavern article on _s_backbone.
]]>
Modifying core WordPress files is a quick way to introduce security vulnerabilities, break your site, and break future updates. From the perspective of a developer, nothing is worse than searching through a theme and plugins for a problem only to discover a previous developer modified a core WordPress file an introduced a bug.
I wrote a simple Node module that lets you easily check WordPress installations for modified or removed core files. Install the detect-wp-core-modifications npm package with the following shell command:
npm install -g detect-wp-core-modifications
You can run the command without any arguments from within the root of a WordPress installation with the following shell
command:
detect-wp-core-modifications
You can also specify a relative or absolute path to a WordPress installation:
detect-wp-core-modifications ../wordpress
Given the unmet need to allow users only access to edit specific pieces of content, I created a simple plugin called Editorial Access Manager.
By default in WordPress, we can create users and assign them to roles. Roles are automatically assigned certain capabilities. However, sometimes default roles are not enough, and we have one-off situations. Editorial Access Manager lets you set which users or roles have access to specific posts (as well as pages and custom post types).
Only the Administrator and Editor role have access to manage pages by default in WordPress. Making a user an Editor gives them a lot of power. I ran into the situation where we wanted a Contributor (less power than an Editor) to be able to edit only a few pages hence the need for this plugin.
My plan is to eventually integrate this plugin with EditFlow allowing people to assign User Groups to have access to specific pieces of content.
Any development help on this project would be much appreciated! Fork the Github repo.
Background:
Unit testing is the automated testing of units of source code against assumptions. The goal of unit testing is to write test cases with assumptions that test if a unit of code is truly working as intended. If an assumption fails, a potential issue is exposed, and code needs to be revised.
By definition unit tests do not have dependencies on outside systems; in other words only your code (a single unit of code) is being tested. Integration testing works similarly to unit tests but assumptions are tested against systems of code, moving parts, or an entire application. The phrases unit testing and integration testing are often misused to reference one another especially in the context of WordPress. This article will try to stay consistent with WordPress terminology whether correct or not. Personally, in writing tests for my WordPress plugins/themes I write integration tests which I feel are more useful for a number of reasons that are outside of the realm of this article.
PHPUnit is the defacto library used to run unit and integration tests in PHP and thus server-side WordPress. This article assumes you are familiar with basic concepts of unit testing with PHPUnit.
What is WP_UnitTestCase?
WP_UnitTestCase is a PHP class included in the WordPress core development repository. Using SVN, you can check out that repo like so:
svn co http://develop.svn.wordpress.org/trunk/ wordpress-develop
The WordPress core development repository contains the core WordPress unit tests. Cool, huh? You can setup these tests, run them, and even write your own! Let’s poke around a few WordPress core test files. Open up wordpress-develop/tests/phpunit/tests/actions.php.
Normally, PHPUnit tests are structured like so:
class MyTestSuite extends PHPUnit_Framework_TestCase { public function setUp() { // This code will run before each test! } public function tearDown() { // This code will run after each test } public function testBasic() { $this->assertTrue( true ); } }
However, when writing integration tests for WordPress based applications, there exists a cool API we can leverage: WP_UnitTestCase. Remember how I asked you to open the actions.php file? You will see the test class in there extends WP_UnitTestCase. WP_UnitTestCase provides shortcuts for doing a number of tasks when creating your test cases. The first thing you will need to do to use this API is to require the file in your PHPUnit bootstrap file:
require_once( $tests_dir . '/includes/bootstrap.php' );
Of course you will need to setup $tests_dir appropriately. Within the WordPress core development repository, this bootstrap file lives at /tests/phpunit/includes/bootstrap.php. Including this bootstrap file will “bootstrap setup” WordPress, setup some things for unit testing, and include a number of useful things one of which is WP_UnitTestCase. There are other ways you could include WP_UnitTestCase, but this is by far the easiest.
Let’s setup our test class to use WP_UnitTestCase:
class MyTestSuite extends WP_UnitTestCase { public function setUp() { parent::setUp(); // This code will run before each test! } public function tearDown() { parent::tearDown(); // This code will run after each test } public function testBasic() { $this->assertTrue( true ); } }
Notice our class is extending WP_UnitTestCase instead of PHPUnit_Framework_TestCase. You can probably guess that WP_UnitTestCase extends PHPUnit_Framework_TestCase so we can use everything in PHPUnit_Framework_TestCase by extending WP_UnitTestCase plus a bunch more. Also notice within setUp() and tearDown() we are calling the same methods in the parent class; this is extremely important to ensure WP_UnitTestCase is working correctly. WP_UnitTestCase::setUp() and WP_UnitTestCase::tearDown() do a bunch of magic such as cache clean up and global resets.
WP_UnitTestCase has an instance variable named $factory. Before each of your tests is run $factory is set to a newly instantiated object of type WP_UnitTest_Factory. The meat of WP_UnitTestCase is in this $factory variable. Let’s look some cools way that we can use WP_UnitTestCase.
Creating Blogs within a Network
public function testBasic() { $blog_id = $this->factory->blog->create(); $blog_id_array = $this->factory->blog->create_many( 4 ); }
This is extremely useful for writing test cases that make cross-site assumptions. As you can tell $this->factory->blog->create() creates a blog and returns the new blog ID. $this->factory->blog->create_many( 4 ), creates 4 new blogs and returns an array of the ID’s in order.
If multisite is not setup, this factory will not exist! You can ensure WordPress is bootstrapped as multisite by setting the following constant up in your phpunit.xml file:
<const name="WP_TESTS_MULTISITE" value="1" />
Creating Test Posts
public function testBasic() { $post_id = $this->factory->post->create(); $post_id_array = $this->factory->post->create_many( 4 ); }
Many times when I am writing tests I need to bulk create posts. WP_UnitTestCase let’s you accomplish this very easily. You can pass an array of post args (same as wp_insert_post) like so: create( $args ) and create_many( $num, $args ). If no arguments are passed, generic post_title/post_content info is generated. Post args passed to create_many will be applied to every post created.
Creating Test Comments
public function testBasic() { $comment_id = $this->factory->comment->create(); $comment_id_array = $this->factory->comment->create_many( 4 ); }
Again, we can leverage WP_UnitTestCase to bulk create comments. You can see the ways to use the class is very consistent :). Comment args (like wp_insert_comment) can be passed to create() and create_many() in the exact same way as the post factory.
Creating Test Users
public function testBasic() { $user_id = $this->factory->user->create( array( 'user_login' => 'test', 'role' => 'administrator' ) ); $user_id_array = $this->factory->user->create_many( 4 ); }
WP_UnitTestCase allows us to bulk create users as well. User args (same as wp_insert_user) can be passed to create() and create_many() in the same fashion as the post factory.
Creating Test Terms
public function testBasic() { $term_id = $this->factory->term->create( array( 'name' => 'Term Name', 'taxonomy' => 'category', 'slug' => 'term-slug' ) ); $term_id_array = $this->factory->term->create_many( 4, array( 'taxonomy' => 'category' ); }
Creating bulk terms is slightly different from other factories. Create() and create_many() do take arguments the same was as wp_insert_term(). However, we provide ‘taxonomy’ and ‘name’ as a part of the array rather than as separate parameters.
Conclusion
WP_UnitTestCase contains additional factories for categories, tags, networks, and attachments. They work in a similar fashion as the factories above, so I will let you explore these API’s yourself :). WordPress contains a bunch of other useful API’s for building your test suites such as a system for mocking actions and a test case class for AJAX hooks. I plan on outlining those tools and a few others in future blog posts.
10up just released a new plugin called the Post Customizer. I, along with John James Jacoby, John Bloch, Drew Jaynes, and Carl Danley led the charge on the plugin. The idea of the plugin is to mimic the theme customizer for posts. The plugin adds functionality that displays an overlay when you click “Preview” within the post editor. The overlay has a sidebar allowing you to edit the excerpt and featured post. In the middle of the overlay is a frame that shows the front of the website. Within that frame you can edit the post title and content. This enables you to make “live” changes and to see how they look as you make them. There are other plugins that offer similar behavior. However, Post Customizer closely follows the Theme Customizer and thus WordPress standards.
We want to make this plugin super extensible, and we need all the help we can get. We would appreciate contributions! Fork the plugin on Github.
So I’ve read all over the place that looking for posts based on a taxonomy is much quicker than looking up posts based on post meta. But why? I’ve yet to read any sort of technical explanation that satisfies my curiosity. I decided to do some research.
I wrote some example code and used the debug plugin to examine the resulting SQL queries. Here is a post meta query:
$args = array(
'meta_query' =>
array(
array(
'key' => 'test_key',
'value' => '1'
)
)
);
$query = new WP_Query( $args2 );
Here is the resulting SQL:
SELECT SQL_CALC_FOUND_ROWS trunk_posts.ID
FROM trunk_posts INNER JOIN trunk_postmeta ON (trunk_posts.ID = trunk_postmeta.post_id)
WHERE 1=1 AND
trunk_posts.post_type = 'post' AND
(trunk_posts.post_status = 'publish' OR trunk_posts.post_status = 'private') AND
( (trunk_postmeta.meta_key = 'test_key' AND
CAST(trunk_postmeta.meta_value AS CHAR) = '1') )
GROUP BY trunk_posts.ID
ORDER BY trunk_posts.post_date DESC
LIMIT 0, 10
As you can see the posts table is being joined with the post meta table. A join very basically creates a temporary table where every row in the posts table is matched with every row in the post meta table. The ON clause then narrows down that temporary table keeping only rows of posts that have been matched with post meta that apply to that post. Here is a database model of the post and post meta tables:
This joining of posts with corresponding post meta rows is done by comparing values across two indexed columns (trunk_posts.ID and trunk_postmeta.post_id) as shown in the diagram. Creating an index on a column in MySQL makes lookups on the column much quicker. Simply put, MySQL stores a B-Tree of the values in that column. A B-Tree is a data structure that allows search in O(log n) time (vs. O(n) time on an unindexed column). The disadvantage in indexing a column with a B-Tree type data structure is that inserting and deleting becomes much slower because parts of the tree must be rebuilt; this is generally a worthwhile sacrifice.
Next, the WHERE statement narrows down posts by meta key only keeping rows that have meta_key=’test_key’. Again, as shown in the diagram, trunk_postmeta.meta_key is an indexed column. So again we can do this in O(log n) time. Awesome, so where is the slow down? Well, the last thing we have to do is narrow the posts so we only have ones where meta_value=’1′. There is no index on the meta_value column. We have refined our posts as follows:
On the final refinement, if there are multiple post meta rows with the same key associated, then we will have to search through multiple values until we find a matching value. This search will happen in O(n) which is slow.
Now let’s look at how a taxonomy query works. Here is my taxonomy query code:
$args = array(
'tax_query' => array(
array(
'taxonomy' => 'category',
'field' => 'id',
'terms' => array( 2 ),
)
)
);
$query = new WP_Query( $args );
Here is the resulting SQL:
SELECT SQL_CALC_FOUND_ROWS trunk_posts.ID
FROM trunk_posts INNER JOIN trunk_term_relationships ON (trunk_posts.ID = trunk_term_relationships.object_id)
WHERE 1=1 AND
( trunk_term_relationships.term_taxonomy_id IN (2) ) AND
trunk_posts.post_type = 'post' AND
(trunk_posts.post_status = 'publish' OR trunk_posts.post_status = 'private')
GROUP BY trunk_posts.ID
ORDER BY trunk_posts.post_date DESC LIMIT 0, 10
As you can see the trunk_posts table is joined with the trunk_term_relationships table. The two tables are joined into a temporary table which is refined by the ON portion of the join statement matching each term row with it’s corresponding posts.
As shown in the diagram there is an index on trunk_posts.ID and trunk_term_relationships.object_id, so this first refinement can be done in O(log n) time. Next the WHERE clause refines the temporary table so that only posts matched with the term id 2 are left. There is an index on the term_taxonomy_id column so this can be done in O(log n) time. Obviously, there is more to the query then just this but we don’t care about anything else for now.
Summary:
Post meta queries will experience a major slowdown if the key(s) being searched are associated with a large amount of posts. Another big reason to avoid post meta queries is because the post meta table is typically much larger than the taxonomy_term_relationships table. Therefore doing a join with posts and postmeta is a much more expensive operation than joining posts and taxonomy_term_relationships. This analysis doesn’t take into account caching and is drastically simplified. If you have any questions or corrections, please leave a comment.