Write Less Code
I recently did a project at the New York Times, a corporate website that was highly dynamic. A lot of the front-end work was done ahead of time with dummy content. I was brought in at the end to rewrite the core logic and set up all of the dynamic pieces. EVERYTHING had to be dynamic. There were several times that I had to quickly replace a dummy HTML list with content from a collection of objects belonging to a custom post type. I didn’t want to re-invent the wheel every time I added a new post type. I wanted to write one register_post_type()
call with a helper as the value for 'register_meta_box_cb'
.
Here’s How
Custom post types in WordPress are really object types, much like a blog post is an instance of the Post object type. When you register a custom post type, you are really registering a new “thing” that isn’t really a “post,” it’s something else. Once you have registered this thing, you will probably use the same API as Post to interact with your data: WordPress core functions to retrieve and save your data.
By far the most annoying things about custom post types are how much code it takes to register one and how much duplicate code it takes to save arbitrary metadata. An example:
I want to create a new object called “nyt_partner” – I am going to use the title, the content, and featured image, but I also need to associate some arbitrary data with each instance of “nyt_partner”: phone number, address, twitter account, etc. I am only going to read the data (not search for it), so object (post) metadata works just fine.
Here is some example code for how one currently registers the post type, then registers the metabox to display a form for new fields, and then saves the data when the post is saved:
All that code, and all we are doing is saving a twitter field. Gross. What if our site is very custom and we are using objects all over the place? What if everything on the site needs to be editable? This code is going to bloat almost immediately, so we need to find more ways to reuse.
The first thing we need to do is use a class to contain our logic, and ditch all of the procedural code from the last example. We are going to seriously optimize this code later, but here it is as a class:
This object is better, but it can still bloat very quickly. For each post type that has custom data, you have to add a meta box in one callback, and then register the UI in another. Every time your new object is saved, you have to run it through your own save logic, which adds even more bloat. For objects that are really complex, you actually might want to create a class per type, but most of the time, the data you are saving are attributes or simple fields. It would be great if we could create a few methods to autowire the creation and saving of a field.
In the next example, we will use closures and parent scope to dramatically decrease the necessary code to register a field:
For the time being, if I need to add another post type that has one field, I can just add these lines and be done with it:
All of the magic is rolled up into the NYT_Post_Types::create_field_box()
method. So, if you need to add a bunch of post types at once that only save a field, you only have to edit the init
method. This works if I have only one field. If I have several, I need to add a method:
To specify the fields while registering the post type:
Another piece of magic that we wired up – you can autowire a save method for a post type (that does not use autowiring for the UI) by adding a save_{post_type}
method to your class. If you create a post type called balloon
, all you have to do is add a method called save_balloon
to your class. Our one registered save_post
callback is smart enough to call it. This is great because you don’t have to duplicate the logic to determine if the post is eligible for save.
The autowiring methods (create_field_box()
and create_fields_box()
) dynamically create class properties with closures, but first look for an existing method. You can’t have both. Closures actually create properties on the class, not new class methods. This makes sense because you are really decorating your object with instances of the Closure class, which is what closures are. Closures should look very familiar to you if you write JavaScript with jQuery.
Some of your custom post types will need unique method callbacks for 'register_meta_box_cb'
, but my bet is that MOST of them can share logic similar to what I have demonstrated above. At eMusic, we had 56 custom post types powering various parts of the site. I used similar techniques to cut down the amount of duplicated logic across the codebase.
You may not need to use these techniques if your site is simple. And note: you can’t use closures in any version of PHP before 5.3.
Pingback: WordPress: Autowiring Custom Post Type Metadata | WordPress 2.0 Site
Nice tips on implementing a variety of custom post types. Thanks for this. I have a question: When do you reach a point where you have too much data to use the standard custom post type implementation for wordpress. What if you have tens of thousands of data driven pages like a business review site where you have 50,000 businesses and 100,000 reviews. I believe each business and each review is a row in the posts table and each one might have anywhere from 5-20 rows in the postmeta table for meta data for each object. At what point should you do some sort of custom table implementation to break up the data, if ever? Thanks!