WordPress: Autowiring Custom Post Type Metadata

The New York Times Co.

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.

Best Albums of 75% of 2013 So Far

Muchaco

The Best

Kanye West – Yeezus
Phosphorescent – Muchacho
Drake – Nothing Was The Same
Volcano Choir – Repave
Disclosure – Settle
Chvrches – The Bones of What You Believe
Jason Isbell – Southeastern
The Weeknd – Kiss Land
Icona Pop – This is… Icona Pop
Flume – Flume
The National – Trouble Will Find Me
Yeah Yeah Yeahs – Mosquito
Kacey Musgraves – Same Trailer Different Park
Vampire Weekend – Modern Vampires of the City

The Rest

Yeah Yeah Yeahs – Mosquito
John Mayer – Paradise Valley
The National – Trouble Will Find Me
HAIM – Days are Gone
Kacey Musgraves – Same Trailer, Different Park
The Civil Wars – The Civil Wars
Vampire Weekend – Modern Vampires of the City
Daft Punk – Random Access Memories
AlunaGeorge – Body Music
Jim James – Regions of Light and Sound of God

Other things worth listening to:

Akron/Family – Sub Verses
Woodkid – The Golden Age
Daughn Gibson – Me Moan
Lightning Dust – Fantasy
Deptford Goth – Life After Defo
Little Boots – Nocturnes
Autre Ne Veut – Anxiety
Daughter – If You Leave
Flume – Flume