Getting the number of posts per category in WordPress

This morning JTk presented me a WordPress riddle:

What I would like to be able to do is ask the MySQL if a category has any posts in it or if it is empty.  So I can do one thing if there are posts there and another if that category is empty.  And try as I might, no matter how hard I beat my head against it, all I get is a bruised head.  And, yes, I have to do this conditional because WP is flawed and will break if I try and do certain things with empty categories…..

Here is the schema ( maybe it helps )
http://blog.kapish.co.in/wp-content/uploads/2008/01/wp_db.png

And the worst part of this, is – I know this is trivial so I hate to even ask, but I guess I don’t hate enough not to ask :)  I asked the “community” and got told that there is not a WP function for this ( um, yeah, thanks…. ) that I shouldn’t make straight db calls ( arrrggg ), and that there are plugins that accomplish similar things….  But no real help, so I decided to ask a ninja….

So we want to get the number of posts per category. JTk, you’re in luck! A quick look at the schema (if that diagram is current) tells me we could query the wp_post2cat table and do a COUNT and a GROUP BY query on the category of interest. Even better, if wp_categories.category_count is what I think it is, that’s a simpler query. But let’s see what the code gives us already. In category.php, there are some tasty sounding functions like get_categories(), get_category(), get_category_by_path(), get_category_by_slug() and so on. I installed a clean version of WordPress, added some categories and assigned some fake posts to them. Then I tried some of these functions to see what’s up.

<?php var_dump(get_categories()); ?>

Output:

array(3) {
  [1]=>
  object(stdClass)#68 (15) {
    ["term_id"]=>
    &string(1) "3"
    ["name"]=>
    &string(14) "First Category"
    ["slug"]=>
    &string(14) "first-category"
    ["term_group"]=>
    string(1) "0"
    ["term_taxonomy_id"]=>
    string(1) "3"
    ["taxonomy"]=>
    string(8) "category"
    ["description"]=>
    &string(0) ""
    ["parent"]=>
    &string(1) "0"
    ["count"]=>
    &string(1) "4"
    ["cat_ID"]=>
    &string(1) "3"
    ["category_count"]=>
    &string(1) "4"
    ["category_description"]=>
    &string(0) ""
    ["cat_name"]=>
    &string(14) "First Category"
    ["category_nicename"]=>
    &string(14) "first-category"
    ["category_parent"]=>
    &string(1) "0"
  }
  [2]=>
  object(stdClass)#69 (15) {
    ["term_id"]=>
    &string(1) "4"
    ["name"]=>
    &string(15) "Second Category"
    ["slug"]=>
    &string(15) "second-category"
    ["term_group"]=>
    string(1) "0"
    ["term_taxonomy_id"]=>
    string(1) "4"
    ["taxonomy"]=>
    string(8) "category"
    ["description"]=>
    &string(0) ""
    ["parent"]=>
    &string(1) "0"
    ["count"]=>
    &string(1) "2"
    ["cat_ID"]=>
    &string(1) "4"
    ["category_count"]=>
    &string(1) "2"
    ["category_description"]=>
    &string(0) ""
    ["cat_name"]=>
    &string(15) "Second Category"
    ["category_nicename"]=>
    &string(15) "second-category"
    ["category_parent"]=>
    &string(1) "0"
  }
  [3]=>
  object(stdClass)#90 (15) {
    ["term_id"]=>
    &string(1) "1"
    ["name"]=>
    &string(13) "Uncategorized"
    ["slug"]=>
    &string(13) "uncategorized"
    ["term_group"]=>
    string(1) "0"
    ["term_taxonomy_id"]=>
    string(1) "1"
    ["taxonomy"]=>
    string(8) "category"
    ["description"]=>
    &string(0) ""
    ["parent"]=>
    &string(1) "0"
    ["count"]=>
    &string(1) "1"
    ["cat_ID"]=>
    &string(1) "1"
    ["category_count"]=>
    &string(1) "1"
    ["category_description"]=>
    &string(0) ""
    ["cat_name"]=>
    &string(13) "Uncategorized"
    ["category_nicename"]=>
    &string(13) "uncategorized"
    ["category_parent"]=>
    &string(1) "0"
  }
}

OK, so it sounds like the person who told JTk there were no built-in WordPress functions for this was lying :)  This function tells us exactly what we need. A couple of interesting observations, though. I had a category named “Empty Category” with no posts, which does not show up here. The documentation page tells me that I can pass a parameter to include the empty ones easily enough:

<?php var_dump(get_categories(array(‘hide_empty’=>false))); ?>

array(4) {
  [0]=>
  object(stdClass)#67 (15) {
    ["term_id"]=>
    &string(1) "5"
    ["name"]=>
    &string(14) "Empty Category"
    ["slug"]=>
    &string(14) "empty-category"
    ["term_group"]=>
    string(1) "0"
    ["term_taxonomy_id"]=>
    string(1) "5"
    ["taxonomy"]=>
    string(8) "category"
    ["description"]=>
    &string(0) ""
    ["parent"]=>
    &string(1) "0"
    ["count"]=>
    &string(1) "0"
    ["cat_ID"]=>
    &string(1) "5"
    ["category_count"]=>
    &string(1) "0"
    ["category_description"]=>
    &string(0) ""
    ["cat_name"]=>
    &string(14) "Empty Category"
    ["category_nicename"]=>
    &string(14) "empty-category"
    ["category_parent"]=>
    &string(1) "0"
  }
  [1]=>
  object(stdClass)#68 (15) {
    ["term_id"]=>
    &string(1) "3"
    ["name"]=>
    &string(14) "First Category"
    ["slug"]=>
    &string(14) "first-category"
    ["term_group"]=>
    string(1) "0"
    ["term_taxonomy_id"]=>
    string(1) "3"
    ["taxonomy"]=>
    string(8) "category"
    ["description"]=>
    &string(0) ""
    ["parent"]=>
    &string(1) "0"
    ["count"]=>
    &string(1) "4"
    ["cat_ID"]=>
    &string(1) "3"
    ["category_count"]=>
    &string(1) "4"
    ["category_description"]=>
    &string(0) ""
    ["cat_name"]=>
    &string(14) "First Category"
    ["category_nicename"]=>
    &string(14) "first-category"
    ["category_parent"]=>
    &string(1) "0"
  }
  [2]=>
  object(stdClass)#69 (15) {
    ["term_id"]=>
    &string(1) "4"
    ["name"]=>
    &string(15) "Second Category"
    ["slug"]=>
    &string(15) "second-category"
    ["term_group"]=>
    string(1) "0"
    ["term_taxonomy_id"]=>
    string(1) "4"
    ["taxonomy"]=>
    string(8) "category"
    ["description"]=>
    &string(0) ""
    ["parent"]=>
    &string(1) "0"
    ["count"]=>
    &string(1) "2"
    ["cat_ID"]=>
    &string(1) "4"
    ["category_count"]=>
    &string(1) "2"
    ["category_description"]=>
    &string(0) ""
    ["cat_name"]=>
    &string(15) "Second Category"
    ["category_nicename"]=>
    &string(15) "second-category"
    ["category_parent"]=>
    &string(1) "0"
  }
  [3]=>
  object(stdClass)#90 (15) {
    ["term_id"]=>
    &string(1) "1"
    ["name"]=>
    &string(13) "Uncategorized"
    ["slug"]=>
    &string(13) "uncategorized"
    ["term_group"]=>
    string(1) "0"
    ["term_taxonomy_id"]=>
    string(1) "1"
    ["taxonomy"]=>
    string(8) "category"
    ["description"]=>
    &string(0) ""
    ["parent"]=>
    &string(1) "0"
    ["count"]=>
    &string(1) "1"
    ["cat_ID"]=>
    &string(1) "1"
    ["category_count"]=>
    &string(1) "1"
    ["category_description"]=>
    &string(0) ""
    ["cat_name"]=>
    &string(13) "Uncategorized"
    ["category_nicename"]=>
    &string(13) "uncategorized"
    ["category_parent"]=>
    &string(1) "0"
  }
}

Sure enough,there is the empty category. I also noticed that the article count appears in both ->count and ->category_count. I’ll just assume the cached version, ->category_count, will remain correct through the code, but I’m just guessing it doesn’t matter which member we use.

So, that’s fine if we want the whole collection, but what about individual categories? Fortunately, we can extract this same data for both the category ID as well as the name of the category. Here’s an example of each, querying my “First Category” category:

Using ->category_count

<?php var_dump( (int) get_category(’3′)->category_count); ?>

<?php var_dump( (int) get_category_by_slug(‘First Category’)->category_count; ?>

<?php var_dump( (int) get_category_by_slug(‘first-category’)->category_count); ?>

Using ->count

<?php var_dump( (int) get_category(’3′)->count); ?>

<?php var_dump( (int) get_category_by_slug(‘First Category’)->count; ?>

<?php var_dump( (int) get_category_by_slug(‘first-category’)->count); ?>

All of these return the correct result, int(4). Good on ya, WordPress.

Now let’s see how robust the function is. Will it choke on a non-existent category, or return a zero like it should?

<?php var_dump( (int) get_category(’6969′)->category_count); ?>

<?php var_dump( (int) get_category_by_slug(‘This Damn Category’)->category_count); ?>

Both of these do indeed return zero, which is nice and correct.

In practical terms, you can use this code stub to do something will all categories depending on whether or not there are posts:

foreach (get_categories(array('hide_empty'=>false)) as $category){
	if ($category->count > 0){
		//has posts, do something
	}else{
		//no posts, do something else
	}
}

That’s all there is to it!  There’s definitely a built in function to inspect categories, and even some nice examples to go with. Happy WordPress hacking, and don’t forget that now’s the time to upgrade to PHP 5 if you haven’t already :)

About these ads

About John Herren

John Herren is a developer and technical consultant with focus on web applications. He currently serves as Director of Development for Primetime US, the company behind the hit movie and book The Secet. John was formerly staff writer and developer community evangelist for Zend Technologies. Along with founding neat experiments like TagCloud.com, John is an active member in the mashup community, working with API providers and speaking at conferences. He is a published author of Linux certification study material. John enjoys using open source software like PHP and Ruby on Rails to bend the web into exciting new chimeras of hyperlinked goodness. View all posts by John Herren

23 responses to “Getting the number of posts per category in WordPress

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: