A Journey Through WordPress Templating
Russell Heimlich
(like the maneuver)
Senior Developer at nclud
<h2>
<a href="<?php the_permalink() ?>" rel="bookmark">
<?php the_title(); ?>
</a>
</h2>
<small>
<?php the_time(__('F jS, Y', 'kubrick')) ?>
</small>
<div class="entry">
<?php the_content(); ?>
</div>
the_title()
prints the title
get_the_title()
returns the value of the title
<header class="entry-header">
<?php
if ( 'post' === get_post_type() ) :
echo '<div class="entry-meta">';
if ( is_single() ) :
twentyseventeen_posted_on();
else :
echo twentyseventeen_time_link();
twentyseventeen_edit_link();
endif;
echo '</div><!-- .entry-meta -->';
endif;
if ( is_single() ) {
the_title( '<h1 class="entry-title">', '</h1>' );
} else {
the_title( '<h2 class="entry-title"><a href="' . esc_url( get_permalink() ) . '" rel="bookmark">', '</a></h2>' );
}
?>
</header><!-- .entry-header -->
{{ ... }}
if
, for
, while
)
{% ... %}
{# ... #}
<?php // Loop over some things ?>
<?php foreach ( $things as $thing ) { ?>
<?php echo $thing; ?>
<?php } ?>
{# Loop over some things #}
{% for thing in things %}
{{ thing }}
{% endfor %}
extends
{% extends "file.twig" %}
{% block content %}
This is how you extend a template in Twig
{% endblock %}
{{ 'fab'|upper }}
FAB
<?php
$context = array(
'foo' => 'bar',
'baz' => 'bat',
'url' => get_permalink(),
);
echo $twig->render( 'template.twig', $context );
Why not just use PHP short tags?
<?= $foo ?>
(Don't do this)
Why so much reinventing of PHP?
{% set currentPageNum = paginationCurrentPageNumber %}
{% set totalItems = paginationTotalItems %}
{% set itemsPerPage = paginationResultsPerPage %}
{% set pageCount = (totalItems / itemsPerPage)|round(0, 'ceil') %}
{% set pageRange = 5 %}
{% if pageCount < currentPageNum %}
{% set currentPageNum = pageCount %}
{% endif %}
{% if pageRange > pageCount %}
{% set pageRange = pageCount %}
{% endif %}
{% set delta = (pageRange / 2)|round(0, 'ceil') %}
{% if (currentPageNum - delta) > (pageCount - pageRange) %}
{% set pages = range((pageCount - pageRange + 1), pageCount) %}
{% else %}
{% if (currentPageNum - delta) < 0 %}
{% set delta = currentPageNum %}
{% endif %}
{% set offset = currentPageNum - delta %}
{% set pages = range(offset + 1, offset + pageRange) %}
{% endif %}
partials/search-tools.twig
partials/stream/stream-home.twig
partials/stream/featured-posts.twig
partials/stream/standard/featured.twig
partials/stream/item-img.twig
partials/overview/overline.twig
partials/item-title.twig
partials/meta-info/meta-info-empty.twig
partials/meta-info/meta-info-stream.twig
partials/meta-info/img-' ~ item.get_type ~ '.twig
partials/meta-info/img.twig
partials/meta-info/stack.twig
partials/meta-info/source-name-' ~ item.get_type ~ '.twig
partials/meta-info/source-name.twig
partials/meta-info/timestamp-' ~ item.get_type ~ '.twig
partials/meta-info/timestamp.twig
partials/meta-info/source-name-' ~ item.get_type ~ '.twig
partials/meta-info/source-name.twig
I just hated the way it was being used
So how can we use Twig with WordPress?
Timber unites Twig and WordPress
single.php
$context = Timber::get_context();
$context['foo'] = 'Bar!';
$context['post'] = Timber::query_post();
Timber::render( 'single.twig', $context );
single.twig
{% extends "base.twig" %}
{% block content %}
{{foo}}
{{post.title}}
{{post.content}}
{% endblock %}
<a href="{{ post.get_field('custom_link')|e('esc_url') }}"></a>
Bare bones Twig templating support for WordPress
PHP files | Lines of Code | |
---|---|---|
Timber | 132 | 18,313 |
Sprig | 2 | 214 |
Sprig::render()
will return a string of a compiled template
$context = array(
'title' => 'Sprig is awesome!',
'url' => 'https://github.com/kingkool68/sprig/',
);
// Render the template and return a string
$thing = Sprig::render( 'example.twig', $context );
Sprig::out()
will echo a compiled template
$context = array(
'title' => 'Sprig is awesome!',
'url' => 'https://github.com/kingkool68/sprig/',
);
// Echo out the rendered template
Sprig::out( 'example.twig', $context );
Sprig::do_action()
will capture the output of a WordPress action and return a string
$context = array(
'scripts' => Sprig::do_action( 'wp_print_scripts' ),
);
// Echo out the rendered template
Sprig::out( 'example.twig', $context );
function filter_sprig_twig_filters( $filters = array() ) {
$filters['sanitize_title'] = 'sanitize_title';
return $filters;
}
add_filter( 'sprig/twig/filters', 'filter_sprig_twig_filters' );
<input
type="text"
name="chart-options[yaxis_label]"
id="chart-options-yaxis-label"
value="{{ yaxis_label|esc_attr }}"
>
<a href="{{ link_url|esc_url }}">{{ link_text }}</a>
function filter_sprig_twig_functions( $functions = array() ) {
$functions['wp_nonce_field'] = 'wp_nonce_field';
return $functions;
}
add_filter( 'sprig/twig/functions', 'filter_sprig_twig_functions' );
<select name="chart-options[zoomtype]">
<option value="x" {{ selected( 'x', zoomtype ) }}>x-Axis Only</option>
<option value="y" {{ selected( 'y', zoomtype ) }}>y-Axis Only</option>
<option value="xy" {{ selected( 'xy', zoomtype ) }}>x & y-Axis</option>
<option value="none" {{ selected( 'none', zoomtype ) }}>None&ly;/option>
</select>
<select name="chart-options[zoomtype]">
<option value="x" selected="selected">x-Axis Only</option>
<option value="y">y-Axis Only</option>
<option value="xy">x & y-Axis</option>
<option value="none">None&ly;/option>
</select>
Data for a component can come from different sources
“You must be shapeless, formless, like water. When you pour water in a cup, it becomes the cup. When you pour water in a bottle, it becomes the bottle. When you pour water in a teapot, it becomes the teapot. Water can drip and it can crash. Become like water my friend.”—Bruce Lee
Don't tie your components to the data
<?php
/**
* Handle everything for Archive Items
*/
class RH_Archive_Items {
/**
* Get an instance of this class
*/
public static function get_instance() {
static $instance = null;
if ( null === $instance ) {
$instance = new static();
$instance->setup_actions();
}
return $instance;
}
/**
* Hook into WordPress via actions
*/
public function setup_actions() { ... }
}
RH_Archive_Items::get_instance();
/**
* Render an individual archive item
*
* @param array $args Values to pass to the template to render
* @return string HTML of rendered archive item
*/
public static function render_item( $args = array() ) {
$defaults = array(
'url' => '',
'title' => '',
'excerpt' => '',
'date' => '',
'display_date' => '',
'machine_date' => '',
);
$context = wp_parse_args( $args, $defaults );
$context['title'] = apply_filters( 'the_title', $context['title'] );
$context['excerpt'] = apply_filters( 'the_content', $context['excerpt'] );
// More logic for handling data
return Sprig::render( 'archive-item.twig', $context );
}
<article class="archive-item">
<h2>
{% if url %}
<a href="{{ url|esc_url }}">{{ title }}</a>
{% else %}
{{ title }}
{% endif %}
</h2>
{% if display_date %}
<time datetime="{{ machine_date|esc_attr }}">{{ display_date }}</time>
{% endif %}
{{ excerpt }}
</article>
/**
* Render an archive item from post data
*
* @param WP_Post|integer $post WP Post object or post ID to get data from
* @param array $args Values to override what gets rendered
* @return string HTML of rendered archive item
*/
public static function render_item_from_post( $post = null, $args = array() ) {
$post = get_post( $post );
$args = array(
'url' => get_permalink( $post ),
'title' => get_the_title( $post ),
'excerpt' => get_the_excerpt( $post ),
'date' => $post->post_date,
);
return static::render_item( $args );
}
/**
* Render archive items from a WP_Query object
*
* @param object $the_query A WP_Query object
* @return string HTML of all archive items
* @throws Exception If $the_query isn't a WP_Query object then bail
*/
public static function render_items_from_wp_query( $the_query = false ) {
global $wp_query;
if ( ! $the_query ) {
$the_query = $wp_query;
}
if ( ! $the_query instanceof WP_Query ) {
throw new Exception( '$the_query is not a WP_Query object!' );
}
$output = [];
while ( $the_query->have_posts() ) :
$post = $the_query->the_post();
$output[] = static::render_item_from_post( $post );
endwhile;
wp_reset_postdata();
return implode( "\n", $output );
}
<?php
$context = array(
'the_loop' => RH_Archive_Items::render_item_from_wp_query(),
'pagination' => RH_Pagination::render_from_wp_query(),
);
Sprig::out( 'index.twig', $context );
<?php
$basic = RH_Archive_Items::render_item(
array(
'title' => 'Basic Archive Item',
'excerpt' => 'This is an excerpt to provide more context about what this archive items is all about.',
'date' => date( 'Y-m-d' ),
'url' => 'https://example.com',
)
);
$no_date = RH_Archive_Items::render_item(
array(
'title' => 'No Date Archive Item',
'excerpt' => 'This archive item has no date associated with it.',
'url' => 'https://example.com',
)
);
$no_url = RH_Archive_Items::render_item(
array(
'title' => 'No URL Archive Item',
'excerpt' => 'You can\'t click the headlines to go somewhere else',
'date' => date( 'Y-m-d' ),
)
);
$context = array(
'items' => array(
'Basic' => $basic,
'No Date' => $no_date,
'No URL' => $no_url,
),
);
Sprig::out( 'styleguide-archive-items.twig', $context );
๐
Separate data processing from rendering the output
๐
Use separate files for each template
๐
Only pass the data needed to render the template
๐งถ
Don't constrain templates to their data sources