Skip to content

Twig to PHP Parser

Curtis Belt edited this page Feb 28, 2020 · 6 revisions

The "Twig to PHP Parser" (which more accurately might be named the Twig to PHP String Replacer) processes Twig and replaces a restricted set of syntax WordPress ready PHP templates. We use a limited set of Twig functionality in any templates that are meant to output to PHP with the appropriate escaping.

There should be a command npm run parser in package.json that runs this script.

Supported Features

The following are the "magic" syntax and words that are supported by the parser. Mustache Expressions

Mustache expressions correspond to variables and, depending on their name, will apply appropriate escaping.

All variables in mustache expressions must contain one of the following strings:

  • class, name, or attr
    • {{ o_nav_classes }}
    • {{ modifier_class }}
    • {{ c_input_name }}
    • {{ c_iframe_width_attr }}
    • output: <?php echo esc_attr( $variable_for_class_or_name ); ?>
  • url
    • {{ c_title_permalink_url }}
    • {{ c_figure_src_url }}
    • output: <?php echo esc_url( $variable_for_url ); ?>
  • text
    • {{ c_title_text }}
    • {{ o_figure_caption_text }}
    • output: <?php esc_html( $variable_for_text ); ?>
  • markup
    • {{ c_dek_markup }}
    • output: <?php wp_kses_post( $c_dek_markup ); ?>
  • svg

To provide a bit of context, here are the "magic words" as they are used in the parser:

<?php // in class-twig-parser.php

$is_attr       = strpos( $match, 'class' ) || strpos( $match, 'name' );
$is_url        = strpos( $match, 'url' );
$is_text       = strpos( $match, 'text' );

Implicit Coercion Conditionals

The parser supports one type of conditional where a single variable of any type (usually a string in Twig's case) is coerced into a boolean type, and evaluated in an if:

{% if c_figure_caption_text %}
	<p class="c-figure__caption">{{ c_figure_caption_text }}</p>
{% endif %}

This outputs the following PHP:

<?php if ( $c_figure_caption_text ) { ?>
	<p class="c-figure__caption"><?php echo esc_html( $c_figure_caption_text ); ?></p>
<?php } ?>

At present, the not keyword is, well, not supported, but this would be trivial to implement if it is needed.

For context, this is handled with we might call "dumb" string replacement vs. the "somewhat smarter" string replacement for mustaches and includes that is based on a regular expression. The replacement for if statements is part of a 1-to-1 replacement configuration that looks like this:

<?php // in class-twig-parser.php

$general_replacers = [
    '{#'              => '<?php /*' . "\n",
    '#}'              => '*/' . "\n" . '?>',
    '{% endif %}'     => '<?php } ?>',
    '{% endfor %}'    => '<?php } ?>',
    '{% if '          => '<?php if ( $',
    '{% for item in ' => '<?php foreach ( $item as $',
    '%}'              => ') { ?>',
];

One Dimensional For Loop

The Twig parser supports one dimensional for loops, that is, for loops that do not specify a key => value association. In Twig, this looks like:

<ul class="o-story-list {{ modifier_class }}">
    {% for item in o_story_list_teases %}
        <li class="o-story-list__item {{ o_story_list_item_classes }}">
            {% include "../o-tease/o-tease.twig" with item %}
        </li>
    {% endfor %}
</ul>

The term item must be used for the iterator, but the name of the array can be anything (though it should follow the name of the patterns as closely as possible).

This (will) output the following PHP:

<ul class="o-story-list <?php echo esc_attr( $modifier_class ); ?>">
    <?php foreach ( $o_story_list_teases as $item  ) { ?>
        <li class="o-story-list__item <?php echo esc_attr( $o_story_list_item_classes ); ?>">
            <?php \PMC::render_template( get_stylesheet_directory() . '/template-parts/objects/o-tease.php', $item, true ); ?>
        </li>
    <?php } ?>
</ul>

Note: as of 1/25/19 the parser does not switch the ordering of the item and array and it needs to - a fix is in the works, but it's something to be aware of. In the mean time, the values can be changed manually in the PHP templates.

Also be aware that the for loop parsing does not support dot notation or expanded objects after the with keyword.

Includes with Context Data

The parser supports the Twig include statement for including one template in another with context data. The context data must be a single word that maps to an object and, unless part of a for loop or necessitated by redundancy, should match the name of the component or object included for ease of understanding. The path in an include is the relative path referencing the pattern's Twig template.

An example Twig file containing includes:

<article class="o-story {{ modifier_class }}">
    <div class="o-story__primary {{ o_story_primary_classes }}">
        {% include "../../04-components/c-title/c-title.twig" with c_title %}   
        <p>{{ o_story_dek }}</p>
    </div>
    <div class="o-story__secondary {{ o_story_secondary_classes }}">
        {% include "../../04-components/c-figure/c-figure.twig" with c_figure %}
    </div>
</article>

And it's resulting PHP:

<?php
// This is a generated file. Refer to the file located at vip/pmc-deadline-2019/assets/src/patterns/05-objects/o-story/o-story.twig for adjusting this markup. (This comment is added to the top of every generated template)
?>
<article class="o-story <?php echo esc_attr( $modifier_class ); ?>">
    <div class="o-story__primary <?php echo esc_attr( $o_story_primary_classes ); ?>">
        <?php \PMC::render_template( get_stylesheet_directory() . '/template-parts/components/c-figure.php', $c_figure, true ); ?>  
        <p><?php echo esc_attr( $o_story_secondary_classes ); ?></p>
    </div>
    <div class="o-story__secondary <?php \PMC::render_template( get_stylesheet_directory() . '/template-parts/components/c-title.php', $c_title, true ); ?>">  
    </div>
</article>

Comments

Finally, comments in the {# .. #} Twig syntax are also supported by the parser and replaced using the "dumb" replacement strategy mentioned above.

Note that comments will likely impact the whitespace formatting to violate PHPCS. This can be fixed by running the templates through phpcbf to automatically fix the whitespace.