Timber Documentation

repository·2.x·Indexed 26 days ago

https://github.com/timber/timber

A WordPress theme development tool that integrates the Twig template engine to separate PHP logic from HTML presentation. This documentation covers installation via Composer for Timber 1.x and 2.0, using the Timber Starter Theme, and implementing Twig features such as template inheritance, blocks, and data rendering using Timber::render() and Timber::get_posts().

Tokens
76.6K
Snippets
279
Records
422
Agent score
89%

What's inside Timber

  1. Overview of Timber and Twig integration

    2.x

    Timber allows you to build WordPress themes by separating logic from presentation. Instead of using standard WordPress PHP loops, you pass data from PHP files to .twig files using the Twig Template Engine. This allows developers to focus on data/logic in PHP and designers to focus on HTML/display in Twig.

    {% extends "base.twig" %}
    
    {% block content %}
      <h1 class="big-title">{{ foo }}</h1>
      <h2 class="post-title">{{ post.title }}</h2>
    
      <img src="{{ post.thumbnail.src }}" />
    
      <div class="body">
    	{{ post.content }}
      </div>
    {% endblock %}
  2. Extend Timber classes using Class Maps

    2.x

    Timber uses Class Maps to determine which PHP class to instantiate for specific WordPress objects (posts, terms, comments, etc.). To use your own custom classes instead of the default Timber\Post or Timber\Term classes, you must register them via the appropriate filter.

    Available Class Map filters:

    • timber/post/classmap: For posts and attachments.
    • timber/term/classmap: For taxonomies and terms.
    • timber/comment/classmap: For comments.
    • timber/menu/classmap: For menu objects based on location.
    • timber/menuitem/classmap: For menu items based on location.
    • timber/user/class: For user objects.
  3. Register ACF Blocks in WordPress

    2.x

    Register your blocks during the WordPress init action using register_block_type(). You can register blocks individually or dynamically by iterating through your blocks directory.

    // Individual registration
    function register_acf_blocks() {
        register_block_type( __DIR__ . '/blocks/my-block' );
    }
    add_action( 'init', 'register_acf_blocks' );
    
    // Dynamic registration for all blocks in the directory
    function register_acf_blocks() {
        foreach ($blocks = new DirectoryIterator( __DIR__ . '/blocks' ) as $item) {
            if ($item->isDir() && !$item->isDot() && file_exists($item->getPathname() . '/block.json')) {
                register_block_type($item->getPathname());
            }
        }
    }
    add_action('init', 'register_acf_blocks');
  4. Optimize Timber post queries for performance

    2.x

    To improve performance when working with Timber posts, follow these best practices:

    1. Use pre_get_posts: If you need to modify the query before WordPress determines the template, use the standard WordPress pre_get_posts action instead of passing arguments to Timber::get_posts() or Timber::get_post(), which always trigger a database hit.
    2. Disable row counting: When querying a collection where pagination is not required, set 'no_found_rows' => true in your query arguments to avoid the overhead of counting total matching rows.
    3. Eagerly realize collections for caching: Timber collections are lazy-loaded (containing raw WP_Post objects until accessed). If you are caching a collection via a transient, call $query->realize() to convert all raw posts into Timber\Post objects immediately. This ensures that when the cached data is retrieved later, the expensive instantiation work is already done.
    // Disable row counting for better performance
    $posts = Timber::get_posts([
        'no_found_rows' => true,
    ]);
    
    // Eagerly realize a collection before caching it in a transient
    $eager_posts = \Timber\Helper::transient('my_posts', function () {
        $query = \Timber\Timber::get_posts([
            'post_type' => 'some_post_type',
        ]);
    
        // Run Post::setup() up front.
        return $query->realize();
    }, HOUR_IN_SECONDS);
  5. Use Timber\PostExcerpt instead of Timber\PostPreview

    2.x

    In Timber 2.0, Timber\PostPreview has been renamed to Timber\PostExcerpt to align with WordPress terminology.

    • Use Timber\Post::excerpt() instead of Timber\Post::preview().
    • The post.excerpt Twig function now accepts arguments via array/hash notation.

    PHP Usage:

    $post->excerpt([
        'words' => 50,
        'chars' => false,
        'end' => '&hellip;',
        'force' => false,
        'strip' => true,
        'read_more' => 'Read More',
    ]);

    Twig Usage:

    {{ post.excerpt({
        words: 50,
        chars: false,
        end: "&hellip;",
        force: false,
        strip: true,
        read_more: "Read More"
    }) }}

    New Excerpt Parameters:

    • always_add_read_more: Controls whether a read more link is added even if the excerpt isn't trimmed. (Default: false)
    • always_add_end: Controls whether the end string is added even if the excerpt isn't trimmed. (Default: false)

    Global Defaults: You can filter the default excerpt options using the timber/post/excerpt/defaults filter:

    add_filter('timber/post/excerpt/defaults', function ($defaults) {
        $defaults['always_add_read_more'] = false;
        $defaults['words'] = 240;
        return $defaults;
    });
  6. Setup a testing environment with PHPUnit (Timber v1)

    2.x

    To test Timber v1, you must use a Vagrant-based environment via VVV (Varying Vagrant Vagrants). This process involves installing VVV, cloning the repository into the VVV www directory, and running Composer to install dependencies.

    Note: This guide is specific to Timber v1 and uses VVV/Vagrant. It may not be applicable to Timber 2.0.

    # 1. Navigate to your VVV www directory and clone Timber
    cd ~/vagrant-local/www/
    git clone git@github.com:timber/timber.git
    
    # 2. Install dependencies
    cd timber
    composer install
    
    # 3. Run tests via SSH
    vagrant ssh
    cd /srv/www/timber
    phpunit
  7. Work with users in Twig

    2.x

    Timber provides Twig functions to handle user objects directly in templates:

    • get_user(user_id): Converts a user ID into a Timber\User object.
    • get_users(user_ids): Converts an array of user IDs into an array of Timber\User objects.
    {# Get a single user #}
    {% set user = get_user(user_id) %}
    
    {# Loop through multiple users #}
    {% for user in get_users(user_ids) %}
        {{ user.name }}
    {% endfor %}
    
    {# Check login state #}
    {% if user %}
        Hello {{ user.name }}!
    {% else %}
        Hello visitor!
    {% endif %}
  8. Pass custom data to Twig templates via Timber::context()

    2.x

    In your PHP template files (e.g., single.php), you can extend the available Twig data by modifying the $context array returned by Timber::context(). Any key added to this array becomes available as a variable in your Twig template.

    $context = Timber::context();
    $post = $context['post'];
    
    // Add custom data to the context
    $context['reading_time'] = reading_time($post);
    
    Timber::render('single.twig', $context);
  9. Implement Product Teasers in loops

    2.x

    To display products in a loop (e.g., views/partials/tease-product.twig), you must call the timber_set_product helper function to ensure each product in the loop has the correct WooCommerce context. Without this, products may incorrectly display data from the first item in the loop.

    1. Add the helper to functions.php:
    <?php
    function timber_set_product( $post ) {
        global $product;
    
        if ( is_woocommerce() ) {
            $product = wc_get_product( $post->ID );
        }
    }
    1. Call the helper in your Twig template:
    {{ fn('timber_set_product', post) }}
    <article {{ fn('post_class', ['$classes', 'entry'] ) }}>
    
        {{ fn('timber_set_product', post) }}
    
        <div class="media">
    
            {% if showthumb %}
                <div class="media-figure {% if not post.thumbnail %}placeholder{% endif %}">
                    <a href="{{ post.link }}">
                        {% if post.thumbnail %}
                            <img src="{{ post.thumbnail.src|resize(post_thumb_size[0], post_thumb_size[1]) }}" />
                        {% else %}
                            <span class="thumb-placeholder"><i class="icon-camera"></i></span>
                        {% endif %}
                    </a>
                </div>
            {% endif %}
    
            <div class="media-content">
    
                {% do action('woocommerce_before_shop_loop_item_title') %}
    
                {% if post.title %}
                    <h3 class="entry-title"><a href="{{ post.link }}">{{ post.title }}</a></h3>
                {% else %}
                    <h3 class="entry-title"><a href="{{ post.link }}">{{ fn('the_title') }}</a></h3>
                {% endif %}
    
                {% do action( 'woocommerce_after_shop_loop_item_title' ) %}
                {% do action( 'woocommerce_after_shop_loop_item' ) %}
    
            </div>
    
        </div>
    
    </article>