__get() post_meta from a WP_Post Object

Josh Pollock - June 15, 2015

Ok, maybe I’m the last on to arrive at the party on this, but I somehow never realized that you can get any post_meta field by using its key as a property of a WP_Post object. Pretty useful. It means that if you have a WP_Post object, and the post it represents has a meta_key called “josh,” you don’t need to use get_post_meta(), you can use  $post->josh.

Here are some examples:

<?php
//Add meta fields
update_post_meta( 1, 'hats', 'bats' );
update_post_meta( 1, 'bread', array( 'rye', 'sourdough' ) );
//get the WP_Post object
$post = get_post( 1 );
//use it's __get to get its meta fields.
echo $post->hats; // "bats"
print_r( $post->bread ); //the array
var_dump( $post->not_a_real_key ); //empty string
view raw __get-meta-field.php hosted with ❤ by GitHub

How does it work? It works thanks to the way the __get() magic method is used in the WP_Post class. If a class has this magic method, it will run anytime a property that doesn’t actually exist is called. In WP_Post it is used for a few fields that are not in the post table, and therefore are not retrieved by the main query for the post.

As always, taking a look at the source, and then trying it out, is the best way to learn. Here is the __get() from WP_Post, copied from the source for your convenience:

<?php
//how it works in core
//Copied from post.php ~L718 in WordPress 4.3 (much GPL)
/**
* Getter.
*
* @param string $key Key to get.
* @return mixed
*/
public function __get( $key ) {
if ( 'page_template' == $key && $this->__isset( $key ) ) {
return get_post_meta( $this->ID, '_wp_page_template', true );
}
if ( 'post_category' == $key ) {
if ( is_object_in_taxonomy( $this->post_type, 'category' ) )
$terms = get_the_terms( $this, 'category' );
if ( empty( $terms ) )
return array();
return wp_list_pluck( $terms, 'term_id' );
}
if ( 'tags_input' == $key ) {
if ( is_object_in_taxonomy( $this->post_type, 'post_tag' ) )
$terms = get_the_terms( $this, 'post_tag' );
if ( empty( $terms ) )
return array();
return wp_list_pluck( $terms, 'name' );
}
// Rest of the values need filtering.
if ( 'ancestors' == $key )
$value = get_post_ancestors( $this );
else
$value = get_post_meta( $this->ID, $key, true );
if ( $this->filter )
$value = sanitize_post_field( $key, $value, $this->ID, $this->filter );
return $value;
}
view raw wp-post-__get.php hosted with ❤ by GitHub

Why am I thinking about this? In Caldera Forms we have the ability to auto-populate select fields from a specific post type, or taxonomy. I’m working on adding an autocomplete/ select2 field type that will be able to use this auto-population option.

As a result, I’m looking at how to make them more useful and flexible. My first step is adding filters for what fields of the WP_Post object, or the stdClass object for a taxonomy term is used for the option’s value and label. If you look at this commit, where I added those fields, you can see how much more flexible it is for posts, then for terms.

Why? Because of the WP_Post’s magic __get() you can use this filter to use meta_fields, or even the post’s category as the option label or value. Since get_terms() returns a stdClass object, not a specific WordPress object, with this kind of flexibility in mind, no such luck there. Those using these filters will be limited to using regular taxonomy term fields.

Use It In Your Own Classes

Studying how the __get() is used in WP_Post shows you a fun way to use a WP_Post object. But it also, should give you ideas on how to use it in your own work.

What’s cool about __get() is that it only runs when needed. In WP_Post, the basic post fields are queried for every time. But the extra fields available through the __get() method, which are not just limited to meta fields are only queried for when asked for specifically. It’s smart class architecture that we can all learn from when writing classes for doing specific queries or API calls that in some, but not all cases, you might need additional data.