Skip to content

Component View

alex_prokopenko edited this page Jan 12, 2018 · 2 revisions

View component is defined with Web/View.php class.

Understanding Views

After reading through this guide, you will:

  • Understand the Layouts and recognize how it extends and complements the WordPress template hierarchy.
  • Know what is meant by the DRY Principle, why being DRY beats being WET, and see how most WordPress themes are WET.

Template Hierarchy

WordPress is pretty smart. Every time you load up a request it will search for the most relevant template available in your theme and load it. This is the Template Hierarchy in action and it enables us to easily customize the appearance of our sites.

Want to customize a specific page named "About"? Just copy page.php, to page-about.php and edit away to your heart's content. You only need to look at the success of WordPress to realize that this system works because of its simplicity and accessibility. But it doesn't work perfectly.

To prevent each new template from having a duplicate header, footer and sidebar, WordPress encourages you to separate that code into other templates and include them with the get_header(), get_footer() and get_sidebar() functions (all of which are based on get_template_part). Whilst this makes your code more manageable, with only one edit needed to implement a change across all templates, it still duplicates code that simply doesn't need to be duplicated; the code which calls each of those templates.

Layoutless templates

In your typical layoutless theme, every page template will look something like the following:

<?php get_header(); ?>
  <div class="main">
    <div class="content">
      <?php // Our page specific markup and loop goes here ?>
    </div>
    <?php get_sidebar(); ?>
  </div>
<?php get_footer(); ?>

Even though we know that every template will take this base format and render the header, footer, sidebar calls each time, we still need to continuously repeat the code to keep WordPress happy; it's laborious and unnecessary.

Enter DRY

DRY simply means Don't Repeat Yourself and conforming to the DRY Principle means:

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.

So whilst we have a base format for our pages, this "knowledge" is written countless times, spread across numerous files and has no authoritative representation. This is the opposite of DRY code and it's usually described as being WET, meaning that you Write Everything Twice.

As you can see from the standard template, WordPress goes beyond Continously Repeating Yourself. Whatever you want to call it, it wastes your time when creating the code, when reading the code and when editing the code; it's a lose-lose-lose situation (plus repetition is only fun in rhetoric) but it's easy enough to avoid.

Create layout and stay DRY

The goal of a layout is to remove any repeated markup from individual templates and put it into a single file. This file, {theme}/views/layouts/main.php becomes the single, unambiguous, authoritative representation of knowledge (i.e. the main format code). By doing this we can put the focus entirely on the page specific markup and loop, simplifying our templates to look like this:

<?php 
    $this->extends('layouts/main');
    
    // Our page specific markup and loop goes here 
?>

It's neat. It's tidy. You never need to make calls to get_header(), get_footer() or get_sidebar() again. You can also refactor the main format of your site by editing {theme}/views/layouts/main.php. But best of all, it takes less than 50 lines of code to do so.

This pattern is used in all modern PHP Frameworks: Laravel, Yii 2 and Symfony (Symfony uses Twig, and Twig also has layouts).

First realization of similar feature for WordPress was described in 2011 and was called a Theme Wrapper (read article).

Let's take a closer look into layouts.

Step 1: WordPress figures out which template to use

This is done using the standard WordPress Template Hierarchy, which, as mentioned before, selects the most relevant template as our starting point. Once this template has been chosen, but before it's loaded, WordPress runs the template_include($template) filter.

We use this filter to run our wrap function that saves the real $template path and always return root {theme}/index.php file as a template. This file will be an entry point of our templates system.

<?php
namespace JustCoded\WP\Framework\Web;

use JustCoded\WP\Framework\Objects\Singleton;

/**
 * Views base class.
 * Used for layouts and render partials
 */
class View {
	use Singleton;

	/**
	 * Layouts call chain.
	 *
	 * @var array
	 */
	private $extends = array();

	/**
	 * Theme template path to be loaded.
	 *
	 * @var string
	 */
	public $template;

	/**
	 * View constructor.
	 *
	 * Executed immediately before WordPress includes the predetermined template file
	 * Override WordPress's default template behavior.
	 */
	protected function __construct() {
		add_filter( 'template_include', array( $this, 'init_template' ), 999999 );
	}

	/**
	 * Implement template wrappers. For this we need to remember real template to be loaded.
	 * Then we return the object itself to be able to manipulate the loading process.
	 *
	 * @param string $template Template to be included inside theme.
	 *
	 * @return $this
	 */
	public function init_template( $template ) {
		$this->template = $template;

		return $this;
	}

	/**
	 * Convert object to string magic method.
	 * We replaced string with object inside `template_include` hook, so to support next include statement we need
	 * to add magic method, which make object to string conversion.
	 *
	 * Here we will just return theme index.php file, which will be the entry point of our views engine.
	 *
	 * @return string
	 */
	public function __toString() {
		return locate_template( array( 'index.php' ) );
	}
	
	// ...
}

This code makes WordPress template loader to load our theme root {theme}/index.php file. Furthermore, after including {theme}/index.php we have a global variable $template inside, which is actually our View instance. So we can find out the real template and run a method to include templates and process template inheritance.

{theme}/index.php

<?php
/**
 * Main template in WordPress theme.
 * Used to load views engine
 *
 * @see /views/ folder instead
 * @var $template \JustCoded\WP\Framework\Web\View
 */

$template->run();

Step 2: Specify layout template to use

Inside each specific template you should specify the layout to extend at the beginning of the file:

{theme}/views/page/page.php

<?php
/* @var \JustCoded\WP\Framework\Web\View $this */

$this->extends( 'layouts/main' );

// Our page specific markup and loop goes here 

We include all templates inside a View class instance, that's why $this variable is available inside view files and is pointing to a View class instance.

extends() method simply memorize the layout to inherit from and starts the output buffer.

View component

<?php
class View {

    // ...
    
	/**
	 * Registers parent template.
	 * Parent template will be rendered just after current template execution.
	 *
	 * To use current template generated html use `$content` variable inside the parent view
	 *
	 * @param string $layout  View name to register.
	 *
	 * @return bool
	 * @throws \Exception If no parent view template found.
	 */
	public function extends( $layout = 'layouts/main' ) {
		if ( false === $layout ) {
			return false;
		}

		// WordPress compatibility to still process usual headers.
		if ( empty( $this->extends ) ) {
			do_action( 'get_header', null );
		}

		// check that we have required template.
		$template = $this->locate( $layout, true );

		// memorize the template.
		array_push( $this->extends, $template );

		// start buffer.
		ob_start();
		return true;
	}
	
	// ...

Step 3: The layout files serve the content

After processing the main template View component will get the output buffer as $content variable and include the layout template (actually it will include any template, which was specified inside $this->extend() directive).

Layout file will looks like this:

{theme}/views/layouts/main.php

<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
	<!-- Your head part -->
</head>
<body <?php body_class(); ?>>
<div id="page">

	<?php $this->include( 'partials/header' ); ?>
    
    	<div id="content" class="site-content">
    
    		<div id="primary" class="content-area">
    			<main id="main" class="site-main" role="main">
    
    				<?php echo $content; ?>
    
    				<?php $this->include( 'partials/sidebar' ); ?>
    
    			</main><!-- #main -->
    		</div><!-- #primary -->
    
    	</div><!-- #content -->
    
    <?php $this->include( 'partials/footer' ); ?>


</div><!-- #page -->

<?php wp_footer(); ?>
</body>
</html>

Furthermore, you're not limited to use only one extends() directive. You can inherit several templates inside each other. This is the main advantage of Layouts over Theme Wrapper.

Layout files are loaded after main content is generated. Template inheritance loading process is activated inside View::run() method, which is called inside {theme}/index.php file:

View.php

<?php
class View {
	// ...
	
	public function run() {
		// add alias.
		$template = $this->template;

		include $this->template;

		$this->wrap();
	}
	
}

wrap() method checks for existed output buffers and registered templates through extends() method and then includes them:

View.php

<?php
class View {
	// ...
	
	public function wrap() {
		if ( empty( $this->extends ) ) {
			return false;
		}

		while( ob_get_contents() && $template = array_pop( $this->extends ) ) {
			$content = ob_get_contents();

			// clean view file buffer.
			ob_clean();

			// reset query to protect header from unclosed query in the content.
			wp_reset_postdata();

			// render under the existing context.
			include $template;
		}
	}
}

Step 4: Include partials

Even when we use layouts as base wrapper html, it can be pretty big. Or different layouts can share similar parts. To stay DRY we create small templates, which can be included in other templates and call them partials. Generic partials are placed in {theme}/views/partials/ folder.

To include another template you can use View component method include():

<?php $this->include( 'folder/template-name', $params ); ?>

This method replace WordPress core get_template_part() function. It support child themes as well, so if you created a child theme you can re-write specific view files inside child theme as usual WordPress templates.

$params variable allows you to pass some already defined variables in partial template (for example $model).

Share variables between views

If you need to share some data from main template to layout you can use $this->params class property inside views. Let's check an example:

{theme}/views/page/page.php

<?php
$this->extends('layouts/main');
$this->params['subheading'] = get_post_meta('subheading');

{theme}/views/post/category.php

<?php
$this->extends('layouts/main');
$this->params['subheading'] = get_term_meta('subheading', get_queried_object_id());

{theme}/views/layouts/main.php (or any other view)

<!-- ... -->
<h3><?php echo esc_html($this->params['subheading']); ?></h3>
<!-- ... -->

Layouts load process

During daily usage of layouts we found several issues, which can happen with some 3d-party plugins. Some plugins try to use javascript directly inside the_content() and they think the script is already registered by wp_head(). With layouts wp_head() is called AFTER the content part is generated!

Standard request cycle

HTTP Request → WP index.php → wp-config → WP core → Theme functions.php   
    → WP Hooks (setup_theme, init …) → WP Query 
    → {template} → get_header → CONTENT → get_footer

Layout-based request cycle

HTTP Request → WP index.php → wp-config → WP core 
    → Theme functions.php → /app/Theme.php → Theme component objects  
    → WP Hooks (setup_theme, init …) → WP Query 
    → {view} → {Model} → CONTENT
    → {layout file} → get_header / get_footer