Wordpress Post Type Models

Matt Hue
3 min readJan 1, 2019

--

In most of our Wordpress projects, we usually encapsulate Wordpress posts using php classes. The reason for doing this is to reduce code across templates and make it more readable and reusable.

I personally hate using get_post_meta across each templates. And worse, if you have multiple taxonomies attach to the post, you’ll end up having multiple function calls inside your templates.

Custom fields, taxonomies, post data and other custom data are all parts of a single entity, why not encapsulate them into a single PHP class?

The best part about representing WP posts with a php class is that we can categorise each post type into a more descriptive PHP model class.

Imagine having a location meta field and genre taxonomy attach to the model class and being able to write below:

// News Post Type Templateglobal $post;$news = new News($post);
echo $news->location; // 'USA' from meta field
echo $news->genre; // ['comedy', 'latest', 'international'];
echo $news->customData // defined separately inside the php class

So how do we go about implementing the above code?

First, we create a PHP class and we call this the PostModel class which takes care of all the boilerplate code like fetching meta, categories, post items, etc.

The code below is one variation of our implementation:

<?php 
/**
* Post Model
*/
abstract class PostModel
{
protected $post;
protected $meta;
protected $terms;

function __construct(WP_Post $post)
{
/**
* Check if passed post is of
* the correct post type
*/
if (defined('static::POST_TYPE')) {
if ($post->post_type !== static::POST_TYPE) {
throw new Exception('Invalid Post Type for PostModel Class');
}
}

$this->post = $post;
$this->meta = get_post_meta($this->post->ID, null);
$this->terms = wp_get_object_terms($this->post->ID, get_post_taxonomies($this->post));
}
public function __get($name)
{
if (is_callable([$this, "_get{$name}"])) {
return $this->{"_get{$name}"}();
}
if (isset($this->post->{$name})) {
return $this->post->{$name};
}
if (isset($this->meta[$name])) {
return $this->meta[$name][0] ?? null;
}
return null;
}
protected function _terms(string $taxonomy) : array
{
return array_values(array_filter($this->terms, function($el) use ($taxonomy) {
return $el->taxonomy === $taxonomy;
}));
}
protected function _meta($key, $single = true)
{
return get_post_meta($this->post->ID, $key, $single);
}
protected function _metaWildCard(array $keys, $count = 0)
{
$meta = $this->meta;
$result = [];
for ($i = 0; $i < $count; $i++) {
$key = implode("_{$i}_", $keys);
if (isset($meta[$key])) {
$result[] = $meta[$key][0] ?? "";
}
}
return $result;
}
}

Upon examining the code above, we can see that upon instantiation, it fetches all custom fields and taxonomies associated to the post and save it in memory via the $meta and $terms class properties. It also enforces ‘extending classes’ to have constant POST_TYPE defined.

Then we implement magic method __get to auto resolve post properties. This is the actual function that gets called when we do things like $news->location etc. In this specific magic method, we wanted to be able to fetch items from custom fields or custom data or post data. First it checks for custom data and return the function invocation if it finds it. If it doesn’t find anything, it looks in the cached$meta property looking for custom fields with key $name and return that value. Finally, if meta lookup is unsuccessful, we looked into the post data. If all else fails, then we fallback to null.

That’s basically the meat of it. Now you would ask, how do we implement the taxonomies? You can add new line in the magic ‘get’ method to search for taxonomies but what we do is we put it in the ‘custom data’ function in the extending class. More like so:

<?php 
/**
* News Model
*/
class News extends PostModel
{
const POST_TYPE = "news";
const TAX_TAG = "tags";
public function _gettags()
{
$terms = $this->_terms(self::TAX_TAG);
if (count($terms)) {
return array_values($terms);
}
return null;
}
}

And that’s it! Now if you want to add custom data that needs processing etc, you can add it like so:

public function _getlocation_formatted()
{
return "Location: " . $this->location;
}

And you can use it anywhere in templates like: $news->location_formatted.

Hope this helps.

--

--