Working with the View class

One of my favorite features for the Mythic starter theme is the View class. Technically this is a part of the underlying Hybrid Core framework, but it’s an important part of how Mythic works.

The idea behind this was born from frustration with WordPress’ built-in template-loading functions. I’ve tried various methods over the years to address this in themes but was never fully satisfied. I needed to come up with a better solution.

There’s a number of primary features that I wanted to cover that just don’t work well with core WP’s get_template_part().

  • A hierarchy of possible templates.
  • The ability to pass any arbitrary data for use in the template.
  • Filter hooks so that child themes can change things.
  • The ability to return the template output as a string.

These aren’t extremely hard problems to solve. However, core WP has many Trac tickets/issues going back years that seem to be going nowhere.

Understanding the View class

I’m going to dive pretty deep into the code here to give a foundational example of how the View class works. This will walk you through understanding what’s going on behind the scenes. I promise things will be much simpler when you get to the end of this tutorial.

The contract

The Hybrid Core framework uses interfaces to describe most of its classes’ public-facing methods. If you’re unfamiliar with interfaces, it’s best to think of them as “contracts” that any implementing classes must follow. It makes the code self-documenting so that you can look at the contract and know exactly what you can do with the code.

So, we need to look into src/contracts/view/interface-view.php to see what’s going on:

namespace Hybrid\Contracts\View;

use Hybrid\Contracts\Fetchable;
use Hybrid\Contracts\Displayable;

interface View extends Fetchable, Displayable {

    /**
     * Returns the absolute path to the template file.
     *
     * @since  5.0.0
     * @access public
     * @return string
     */
    public function template();
}

What we see here is that View extends Fetchable and Displayable, which means that it inherits the public-facing methods of those contracts. It also adds a template() method.

Here’s what Fetchable and Displayable look like:

namespace Hybrid\Contracts;

interface Fetchable {

    /**
     * Returns an HTML string for output.
     *
     * @since  5.0.0
     * @access public
     * @return string
     */
    public function fetch();
}

interface Displayable {

    /**
     * Displays the HTML string.
     *
     * @since  5.0.0
     * @access public
     * @return void
     */
    public function display();
}

This means that we have three public methods that we can use:

  • template() to output a view template filename.
  • fetch() to return an HTML string of the view template.
  • display() to display the output of a view template.

These are just abstractions. However, without knowing anything about the implementation of the Hybrid\View\View class that we’ll get to next, we immediately know what methods will always be available by the contract. This means that you can do things like build a custom view class and implement things completely differently while maintaining the same public methods.

The implementation

If we have a contract, we need to implement it. That’s where the Hybrid\View\View class comes in. It’s the default implementation of the View contract from above.

The most important thing we need to know about this class is what to pass into the constructor, which looks like the following:

public function __construct( $name, $slugs = [], \Hybrid\Tools\Collection $data = null )

It accepts 3 parameters:

  • $name (required) The folder name of the view (or file name without slugs).
  • $slug (optional) A filename slug or array of filename slugs (without the .php).
  • $data (optional) An instance of the Collection class of data to pass to the template.

So, let’s create a new view for an entry/post (just assume we’re in The Loop here):

use Hybrid\View\View;
use Hybrid\Tools\Collection;

$view = new View(
    'entry',
    get_post_type(),
    new Collection( [ 'post_id' => get_the_ID() ] )
);

The above code will search for view templates in the following order and load the first that it finds.

resources/views/entry/{$post_type}.php
resources/views/entry/default.php
resources/view/entry.php

$view is an instance of the View class. Here’s some examples of how you can use that:

// Returns the template filename.
$template = $view->template();

// Returns the template output as HTML.
$output = $view->fetch();

// Displays the template output to the screen.
$view->display();

What about that $data variable? That’s a good question. You’ll notice we passed the post ID in. Let’s say a resources/views/entry/post.php template was found. Well, you can use $post_id in that template.

<?= esc_html( $post_id ) ?>

You can also access that variable as $data->post_id (object syntax) or $data['post_id'] (array syntax). Use your preferred method.

Helper functions

As a theme author, you’re accustomed to using a simple function for loading template parts instead of exposing all that PHP in your template files. That’s where the following helper functions will come in handy:

  • Hybrid\View\display() displays the template output.
  • Hybrid\View\fetch() returns the template output as a string.
  • Hybrid\View\view() returns a view instance.

99% of the time, you’ll want to use the display() function. Here’s an example of the above code in The Loop:

<?php while ( have_posts() ) : the_post(); ?>

    <?php Hybrid\View\display( 'entry', get_post_type(), [ 'post_id' => get_the_ID() ] ) ?>

<?php endwhile ?>

You’ll probably have noticed that the helper function accepts a simple array for $data. It’ll auto-build the Collection for you.

Filter hooks

The View class has a few filter hooks that can be handy, particularly for child theme authors.

A filter hook over the array of slugs:

$slugs = apply_filters( "hybrid/view/{$name}/slugs", $slugs, $view );

A filter hook over the collection of data:

$data = apply_filters( "hybrid/view/{$name}/data", $data, $view );

A filter hook over the hierarchy (array of filenames) prior to searching for a view template:

$hierarchy = apply_filters( "hybrid/view/{$name}/hierarchy", $templates, $slugs );

I could see the data filter hook being useful for things like always passing certain data to view templates without having to repeat it in your function calls. There’s loads of possibilities there.

Core compatibility

Are you concerned about core action hooks like get_header, get_footer, and others not firing? No need to worry about that. The View class handles compatibility with all of core’s action hooks for you.

For example, this will fire the core get_header action hook:

<?php Hybrid\View\display( 'header' ) ?>

This is important so that we’re not breaking plugins that might rely on these hooks.

Get started building templates

This is just the tip of the iceberg. The great thing about this feature is that you’re not tied down to any particular structure.

If you don’t like the default implementation, you can simply sub-class Hybrid\View\View and do your own thing.

You can even do things like build your own wrapper functions. Or, if you prefer a static helper class, you can do that too. The possibilities are endless.

Leave a Reply

Your email address will not be published. Required fields are marked *