Anatomy of a Post

Posts are the building blocks of a typical blog page and the most complex object type in the engine. Let's take a look at the structure of a post. This is what you'll see if you var_export() a Post object:

   'belongs_to' => 
  array (
    0 => 'user',
   'has_many' => 
  array (
    0 => 'comments',
    1 => 'likes',
    2 => 'pingbacks',
    3 => 'views',
   'has_one' => 
  array (
   'no_results' => false,
   'id' => '1',
   'feather' => 'text',
   'clean' => 'my-first-post',
   'url' => 'my-first-post',
   'pinned' => '0',
   'status' => 'public',
   'user_id' => '1',
   'created_at' => '2018-08-20 19:41:54',
   'updated_at' => '0000-00-00 00:00:00',
   'attribute_names' => 
  array (
    0 => 'body',
    1 => 'tags',
    2 => 'title',
   'attribute_values' => 
  array (
    0 => 'This is the body text of my first post.',
    1 => '{"foo":"foo","bar":"bar"}',
    2 => 'My first post',
   'queryString' => '
SELECT "posts".*,
       "post_attributes".name AS attribute_names,
       "post_attributes".value AS attribute_values
FROM "posts"
LEFT JOIN "post_attributes" ON ("post_attributes".post_id = "posts".id)
WHERE ("posts".feather IN (\'text\', \'photo\', \'audio\', \'uploader\', \'video\', \'link\', \'quote\'))
  AND (("posts".status IN (\'public\', \'draft\', \'registered\_only\', \'private\', \'scheduled\') OR "posts".status LIKE \'%{1}%\') OR ("posts".status LIKE \'%{%\' AND "posts".user_id = 1) OR ("posts".status = \'draft\' AND "posts".user_id = 1))
  AND ("posts".url = \'my-first-post\')
ORDER BY "posts".id DESC
   'updated' => false,
   'slug' => 'my-first-post',
   'filtered' => true,
   'attributes' => 
  array (
    'body' => 'This is the body text of my first post.',
    'tags' => '{"foo":"foo","bar":"bar"}',
    'title' => 'My first post',
   'body' => '<p>This is the body text of my first post.</p>
   'tags' => 
  array (
    'bar' => 'bar',
    'foo' => 'foo',
   'title' => 'My first post',
   'title_unfiltered' => 'My first post',
   'body_unfiltered' => 'This is the body text of my first post.',

A post is constructed by amalgamating data from two database tables: posts and post_attributes. The posts table provides the core attributes that every post has, for example id, user_id, created_at, the unique url and synonymous slug values. The post_attributes table provides the attributes that are specific to a particular feather or added to the post by an enabled module. The SQL statement that was used to construct this post instance is stored in $post->queryString.


By looking at queryString, we can see that a post's permission system is applied by its model. When a post is instantiated, the visitor's ID and group permissions will determine whether or not the post can be returned for this visitor. This is in contrast to the permission system for pages, which is much simpler and handled by MainController. If you are instantiating a post and you want to skip the permissions check, you can supply the option 'skip_where' => true to the constructor.

Attributes and Filters

The object property attributes stores the post attributes as they were fetched from the database; these post attributes are then set as direct properties of the post object, for example $post->attributes['title'] becomes $post->title. Unless you supply the constructor option 'filter' => false the attributes will be run through applicable Trigger filters and custom feather filters. The original unfiltered attribute will be retained in e.g. $post->title_unfiltered. Filtering may result in additional attributes being added to the object.

Object Interrelations

We can see from the property $post->has_many that multiple modules were enabled at the moment this post object was instantiated, and they have registered their relationship to this post. If we try to get any of these properties, for example $post->pingbacks, this will result in a query to the pingbacks database table for rows with a matching post_id value. You can read more about model interrelations in Working with Model.