-
Notifications
You must be signed in to change notification settings - Fork 17
Views: Layouts
After reading through this guide, you will:
- Understand the Layouts and recognize how they extend and complement 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.
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 customize the appearance of our sites easily.
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). While it 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.
In your typical layoutless theme, every page template will look something like this:
<?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.
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 while we have a base format for our pages, this "knowledge" is written countless times, spread across numerous files, and has no authoritative representation. It 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 Continuously 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 it.
The goal of a layout is to remove any repeated markup from individual templates and put it into a single file. This file, 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 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.
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 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' ) );
}
// ...
}
* View component is a part of Theme Framework, which is required by Boilerplate theme
This code makes WordPress template loader to load our theme root index.php
file.
Furthermore, after including 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.
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();
Inside each specific template you should specify the layout to extend at the beginning of the file:
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.
(for detailed explanation see Theme Framework documentation)
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:
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.
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 views/partials/
folder.
To include another template you can use View component method include()
:
<?php $this->include( 'folder/template-name', $params ); ?>
This method replaces WordPress core get_template_part()
function. It supports 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.
Effectively we've started and ended with the standard WordPress Template Hierarchy, but grabbed the base format from the appropriate layout file in between. The markup of our content is wrapped between our base markup, the cycle completes, and the layout's job is now done. In fact, because the layout system starts and ends with the output from the standard Template Hierarchy, the vast majority of issues can be resolved just by looking through and understanding the Template Hierarchy Codex Page.
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
Next: Data Models