A lightweight template parser used by PyroCMS.

Related tags

Templating lex


Build Status

Lex is a lightweight template parser.

Lex is released under the MIT License and is Copyrighted 2011 - 2014 PyroCMS Team.

Change Log


  • Convert objects with ->toArray() at the beginning of the parser() method only.
  • As much as we want to say goodbye to PHP 5.3, we brought it back for now.


  • Added an ArrayableInterface that automatically converts objects to arrays. This allows an opportunity to define how an object will be converted to an array by adding a ->toArray() method.
  • Looped data is now checked if it is an array or if it implements \IteratorAggregate. This prevents "invalid arguments in foreach" errors while allowing arrays and Collection objects to be iterated.
  • Dropped support for PHP 5.3


  • Added support for self-closing Callback Tags (e.g. {{ foo.bar /}} instead of {{ foo.bar }}{{ /foo.bar }}).
  • Rolled back the change to make Callback Tags less greedy. Callback Tags are now greedy again. If you want to use both Single and Block tags in the same template, you must close the Single tags.


  • Fixes issue which caused all callbacks to be processed as single tags, even if they were a block.


  • Fixed #7 - Conditionals inside looped data tags now work as expected.


  • Changed injectNoparse to be static.


  • Fixed a test which was PHP 5.4 only.
  • Added PHPUnit as a composer dev requirement.
  • Added a Lex\ParsingException class which is thrown when a parsing exception occurs.


  • Fixed an issue where strings returned by callbacks inside a comparison conditional were being processed incorrectly, causing the conditional to always fail.


  • Undefined variables in a conditional now evaluate to NULL, so {{ if foo }} now works properly.
  • Added the exists keyword.
  • Added the not keyword.


  • Fixes composer autoloading.
  • Moved classes into lib folder.


  • Fixed a bug introduced in 2.0.1 where NULL variables were not being displayed.


  • Fixed a bug where variables with a "falsey" (e.g. 0, "0", -1, etc.) value were not displayed.


  • All code now follows PSR-0, 1 and 2.
  • Lex_Parser has been moved to the Lex namespace and renamed to Parser.
  • Lex_Autoloader has been removed. It is now PSR-0 compliant.
  • Added the support for {{ unless }} and {{ elseunless }}.

Basic Usage

Using Lex

Lex is a Composer package named pyrocms/lex. To use it, simply add it to the require section of you composer.json file.

    "require": {
        "pyrocms/lex": "2.2.*"

After adding Lex to your composer.json file, simply use the class as normal.

$parser = new Lex\Parser();

Using Lex

Basic parsing of a file:

$parser = new Lex\Parser();
$template = $parser->parse(file_get_contents('template.lex'), $data);

You can also set the Scope Glue (see "Scope Glue" under Syntax below):

$parser = new Lex\Parser();
$template = $parser->parse(file_get_contents('template.lex'), $data);

To allow noparse extractions to accumulate so they don't get parsed by a later call to the parser set cumulativeNoparse to true:

$parser = new Lex\Parser();
$template = $parser->parse(file_get_contents('template.lex'), $data);
// Second parse on the same text somewhere else in your app
$template = $parser->parse($template, $data);
// Now that all parsing is done we inject the contents between the {{ noparse }} tags back into the template text

If you only want to parse a data array and not worry about callback tags or comments, you can do use the parseVariables() method:

$parser = new Lex\Parser();
$template = $parser->parseVariables(file_get_contents('template.lex'), $data);

PHP in Templates

By default PHP is encoded, and not executed. This is for security reasons. However, you may at times want to enable it. To do that simply send true as the fourth parameter to your parse() call.

$parser = new Lex\Parser();
$template = $parser->parse(file_get_contents('template.lex'), $data, $callback, true);



All Lex code is delimeted by double curly braces ({{ }}). These delimeters were chosen to reduce the chance of conflicts with JavaScript and CSS.

Here is an example of some Lex template code:

Hello, {{name}}

Scope Glue

Scope Glue is/are the character(s) used by Lex to trigger a scope change. A scope change is what happens when, for instance, you are accessing a nested variable inside and array/object, or when scoping a custom callback tag.

By default a dot (.) is used as the Scope Glue, although you can select any character(s).

Setting Scope Glue



Whitespace before or after the delimeters is allowed, however, in certain cases, whitespace within the tag is prohibited (explained in the following sections).

Some valid examples:

{{ name }}
{{name }}
{{ name}}
{{  name  }}

Some invalid examples:

{{ na me }}
{ {name} }


You can add comments to your templates by wrapping the text in {{# #}}.


{{# This will not be parsed or shown in the resulting HTML #}}

    They can be multi-line too.

Prevent Parsing

You can prevent the parser from parsing blocks of code by wrapping it in {{ noparse }}{{ /noparse }} tags.


{{ noparse }}
    Hello, {{ name }}!
{{ /noparse }}

Variable Tags

When dealing with variables, you can: access single variables, access deeply nested variables inside arrays/objects, and loop over an array. You can even loop over nested arrays.

Simple Variable Tags

For our basic examples, lets assume you have the following array of variables (sent to the parser):

    'title'     => 'Lex is Awesome!',
    'name'      => 'World',
    'real_name' => array(
        'first' => 'Lex',
        'last'  => 'Luther',

Basic Example:

{{# Parsed: Hello, World! #}}
Hello, {{ name }}!

{{# Parsed: <h1>Lex is Awesome!</h1> #}}
<h1>{{ title }}</h1>

{{# Parsed: My real name is Lex Luther!</h1> #}}
My real name is {{ real_name.first }} {{ real_name.last }}

The {{ real_name.first }} and {{ real_name.last }} tags check if real_name exists, then check if first and last respectively exist inside the real_name array/object then returns it.

Looped Variable Tags

Looped Variable tags are just like Simple Variable tags, except they correspond to an array of arrays/objects, which is looped over.

A Looped Variable tag is a closed tag which wraps the looped content. The closing tag must match the opening tag exactly, except it must be prefixed with a forward slash (/). There can be no whitespace between the forward slash and the tag name (whitespace before the forward slash is allowed).

Valid Example:

{{ projects }} Some Content Here {{ /projects }}

Invalid Example:

{{ projects }} Some Content Here {{/ projects }}

The looped content is what is contained between the opening and closing tags. This content is looped through and output for every item in the looped array.

When in a Looped Tag you have access to any sub-variables for the current element in the loop.

In the following example, let's assume you have the following array/object of variables:

    'title'     => 'Current Projects',
    'projects'  => array(
            'name' => 'Acme Site',
            'assignees' => array(
                array('name' => 'Dan'),
                array('name' => 'Phil'),
            'name' => 'Lex',
            'contributors' => array(
                array('name' => 'Dan'),
                array('name' => 'Ziggy'),
				array('name' => 'Jerel')

In the template, we will want to display the title, followed by a list of projects and their assignees.

<h1>{{ title }}</h1>
{{ projects }}
    <h3>{{ name }}</h3>
    {{ assignees }}
        <li>{{ name }}</li>
    {{ /assignees }}
{{ /projects }}

As you can see inside each project element we have access to that project's assignees. You can also see that you can loop over sub-values, exactly like you can any other array.


Conditionals in Lex are simple and easy to use. It allows for the standard if, elseif, and else but it also adds unless and elseunless.

The unless and elseunless are the EXACT same as using {{ if ! (expression) }} and {{ elseif ! (expression) }} respectively. They are added as a nicer, more understandable syntax.

All if blocks must be closed with the {{ endif }} tag.

Variables inside of if Conditionals, do not, and should not, use the Tag delimeters (it will cause wierd issues with your output).

A Conditional can contain any Comparison Operators you would do in PHP (==, !=, ===, !==, >, <, <=, >=). You can also use any of the Logical Operators (!, not, ||, &&, and, or).


{{ if show_name }}
    <p>My name is {{real_name.first}} {{real_name.last}}</p>
{{ endif }}

{{ if user.group == 'admin' }}
    <p>You are an Admin!</p>
{{ elseif user.group == 'user' }}
    <p>You are a normal User.</p>
{{ else }}
    <p>I don't know what you are.</p>
{{ endif }}

{{ if show_real_name }}
    <p>My name is {{real_name.first}} {{real_name.last}}</p>
{{ else }}
    <p>My name is John Doe</p>
{{ endif }}

{{ unless age > 21 }}
    <p>You are to young.</p>
{{ elseunless age < 80 }}
    <p>You are to old...it'll kill ya!</p>
{{ else }}
    <p>Go ahead and drink!</p>
{{ endif }}

The not Operator

The not operator is equivilent to using the ! operator. They are completely interchangable (in-fact not is translated to ! prior to compilation).

Undefined Variables in Conditionals

Undefined variables in conditionals are evaluated to null. This means you can do things like {{ if foo }} and not have to worry if the variable is defined or not.

Checking if a Variable Exists

To check if a variable exists in a conditional, you use the exists keyword.


{{ if exists foo }}
    Foo Exists
{{ elseif not exists foo }}
    Foo Does Not Exist
{{ endif }}

You can also combine it with other conditions:

{{ if exists foo and foo !== 'bar' }}
    Something here
{{ endif }}

The expression exists foo evaluates to either true or false. Therefore something like this works as well:

{{ if exists foo == false }}
{{ endif }}

Callback Tags in Conditionals

Using a callback tag in a conditional is simple. Use it just like any other variable except for one exception. When you need to provide attributes for the callback tag, you are required to surround the tag with a single set of braces (you can optionally use them for all callback tags).

Note: When using braces inside of a conditional there CANNOT be any whitespace after the opening brace, or before the closing brace of the callback tag within the conditional. Doing so will result in errors.


{{ if user.logged_in }} {{ endif }}

{{ if user.logged_in and {user.is_group group="admin"} }} {{ endif }}

Callback Tags

Callback tags allow you to have tags with attributes that get sent through a callback. This makes it easy to create a nice plugin system.

Here is an example

{{ template.partial name="navigation" }}

You can also close the tag to make it a Callback Block:

{{ template.partial name="navigation" }}
{{ /template.partial }}

Note that attributes are not required. When no attributes are given, the tag will first be checked to see if it is a data variable, and then execute it as a callback.

{{ template.partial }}

The Callback

The callback can be any valid PHP callable. It is sent to the parse() method as the third parameter:

$parser->parse(file_get_contents('template.lex'), $data, 'my_callback');

The callback must accept the 3 parameters below (in this order):

$name - The name of the callback tag (it would be "template.partial" in the above examples)
$attributes - An associative array of the attributes set
$content - If it the tag is a block tag, it will be the content contained, else a blank string

The callback must also return a string, which will replace the tag in the content.


function my_callback($name, $attributes, $content)
    // Do something useful
    return $result;

Closing Callback Tags

If a Callback Tag can be used in single or block form, then when using it in it's singular form, it must be closed (just like HTML).


{{ foo.bar.baz }}{{ /foo.bar.baz }}

{{ foo.bar.baz }}
{{ /foo.bar.baz }}

Self Closing Callback Tags

You can shorten the above by using self-closing tags, just like in HTML. You simply put a / at the end of the tag (there MUST be NO space between the / and the }}).


{{ foo.bar.baz /}}

{{ foo.bar.baz }}
{{ /foo.bar.baz }}

Recursive Callback Blocks

The recursive callback tag allows you to loop through a child's element with the same output as the main block. It is triggered by using the recursive keyword along with the array key name. The two words must be surrounded by asterisks as shown in the example below.


function my_callback($name, $attributes, $content)
	$data = array(
			'url' 		=> 'url_1',
			'title' 	=> 'First Title',
			'children'	=> array(
					'url' 		=> 'url_2',
					'title'		=> 'Second Title',
					'children' 	=> array(
							'url' 	=> 'url_3',
							'title'	=> 'Third Title'
					'url'		=> 'url_4',
					'title'		=> 'Fourth Title',
					'children'	=> array(
							'url' 	=> 'url_5',
							'title'	=> 'Fifth Title'

	$parser = new Lex\Parser();
	return $parser->parse($content, $data);

In the template set it up as shown below. If children is not empty Lex will parse the contents between the {{ navigation }} tags again for each of children's arrays. The resulting text will then be inserted in place of {{ *recursive children* }}. This can be done many levels deep.

	{{ navigation }}
		<li><a href="{{ url }}">{{ title }}</a>
			{{ if children }}
					{{ *recursive children* }}
			{{ endif }}
	{{ /navigation }}


	<li><a href="url_1">First Title</a>
			<li><a href="url_2">Second Title</a>
					<li><a href="url_3">Third Title</a></li>

			<li><a href="url_4">Fourth Title</a>
					<li><a href="url_5">Fifth Title</a></li>
  • Not handling invalid variable gracesfully

    Not handling invalid variable gracesfully

    If a variable isn't defined it should return false:

    {{ if navType == 'forum' }} {{ navigation:links group="subnavforum" }} {{ else }} {{ navigation:links group="subnav" }} {{ endif }}

    results in: A PHP Error was encountered Severity: Notice Message: Use of undefined constant navType - assumed 'navType' Filename: Lex/Parser.php(725) : eval()'d code

    See Smarty example: http://www.smarty.net/syntax_comparison


     <a href="<?=$bar['zig']?>"><?=$bar['zag']?></a>
     <a href="<?=$bar['zig2']?>"><?=$bar['zag2']?></a> 
     <a href="<?=$bar['zig3']?>"><?=$bar['zag3']?></a> 

    There were no rows found.

    Smarty {foreach $foo as $bar} {$bar.zag} {$bar.zag2} {$bar.zag3} {foreachelse} There were no rows found. {/foreach}

    The empty check should be in the foreach or if checks

    opened by andrew13 11
  • Using variables within callbacks arguments

    Using variables within callbacks arguments

    Currently it's impossible to use a variable inside a callback argument. I know it's a lightweight templating engine but could we have the ability to do something like that:

    {{ foo:bar arg="herp/{baz}" }}

    Or at least something similar.

    (Link: https://forum.pyrocms.com/discussion/23562/using-variable-in-fileslisting-does-not-work)

    opened by kureuil 7
  • Double tags are greedy

    Double tags are greedy

    This was opened as an issue on PyroCMS with this test code:

    {{ plugin:some_method attribute="value" }}
    {{ plugin:some_method attribute="value" }}
        Some Content
    {{ /plugin:some_method }}

    Lex matches the outermost tags instead of the innermost so the {{ plugin:some_method attribute="value" }} on line 3 is considered a single tag. Would it be better if Lex was lazy instead?

    opened by jerel 7
  • How to access variables from out loop?

    How to access variables from out loop?

    How can I access variables from out loop inside loop?


    $projects = [
                ['name'=> 'Project1']
    $date = '2017-06-21';
    {{ projects }}
        <h3>{{ name }}</h3> <small>{{date}}</small>
    {{ /projects }}
    opened by LuanMaik 5
  • phpunit dependency?

    phpunit dependency?

    Hey guys, I'm trying to get your phpunit tests running locally. I have a few features we've added i'd love to get test coverage on and possibly send a few pull requests your way, but i'm really having some issues here getting the tests to run.

    The composer package doesn't have any phpunit requirements, so i've added those:

        "name": "fuel/lex",
        "type": "library",
        "description": "A lightweight template parser.",
        "keywords": ["template","parser"],
        "license": "MIT",
        "authors": [
                "name": "Dan Horrigan",
                "email": "[email protected]",
                "role": "Lead Developer"
        "require": {
            "php": ">=5.3.0"
        "require-dev": {
            "phpunit/phpunit": "3.7.*"
        "autoload": {
            "psr-0": { "Lex": "lib/" }

    And then run the composer --dev install, after which running phpunit --configuration phpunit.xml results in:

    Fatal error: Cannot redeclare class Composer\Autoload\ClassLoader in /Users/jack/Projects/lex/vendor/composer/ClassLoader.php on line 44

    I can't say for sure if this is something in my own environment or in the Lex test library, but i'm hoping one of you guys might have some insight here.

    opened by jackmcdade 4
  • Conditionals in loop

    Conditionals in loop

    Conditionals in loop doesn't work for me.. Ex:



    {{products}} {{if cond=="yes"}}YES!{{endif}} // not work {{cond}} //work {{/products}}

    opened by peet86 3
  • Lex thinks it needs a close tag?

    Lex thinks it needs a close tag?

    I'm writing a PyroCMS plugin to handle variables. I wrote the bit of "test" code below to test each of the plugin functions but it seems to be jacking up Lex.

    (I plan to make this open source free for Pyro on the modules page once done)

    It seems everything between line 1's {{ var:set name="first" value="John" }}

    and line 3's {{ /var:set }} is being treated as a single tag

    in the example below line 1 is 1 tag and line 3 is a completely separate tag both used to set a variable.

    I guess I can call one set and the other cset (content set) or something?

    1 <p>Set variable named first to John {{ var:set name="first" value="John" }}</p>
    2 <p>Set variable named middle to leon {{ var:middle value="Leon" }}</p>
    3 <p>Set variable named last to Doe {{ var:set name="last" }}Doe{{ /var:set }}</p>
    4 <p>Set variable named age to 41 {{ var:age }}41{{ /var:age }}</p>
    5 <p>Variable named first = '{{var:get name="first"}}'</p>
    6 <p>Variable named middle = '{{var:middle}}'</p>
    7 <p>Variable named last = '{{var:get name="last"}}'</p>
    8 <p>Variable named age = '{{var:age}}'</p>
    9 <p>Clear Variable age {{var:clear name="age"}}</p>
    10 <p>Variable named age = '{{var:age}}' (Should now be empty)</p>
    opened by dmyers2004 1
  • Hyphens in Variable Names

    Hyphens in Variable Names

    Just wondering if there's any reason why a hyphen is not allowed in a variable name? The parser just ignores a variable with a hyphen because the RegEx doesn't catch it.

    I've implemented a change locally which allows hyphenated variables but was wondering if this was a deliberate choice (i.e. am I letting myself in for a whole world of pain ;-))?

    opened by mtudor 1
  • variables in a callback function

    variables in a callback function

    I am stuck in such a situation, i have a tab like

    {{ function op1="option" op2=op op3="option" }}

    things work fine when op is set, what should be done when op is not set other than an if-else ??

    opened by hardfire 1
Build better Laravel websites and applications faster with Pyro.
Twig, the flexible, fast, and secure template language for PHP

Twig, the flexible, fast, and secure template language for PHP Twig is a template language for PHP, released under the new BSD license (code and docum

Twig 7.3k Nov 24, 2021
Smarty is a template engine for PHP, facilitating the separation of presentation (HTML/CSS) from application logic.

Smarty 3 template engine smarty.net Documentation For documentation see www.smarty.net/docs/en/ Requirements Smarty can be run with PHP 5.2 to PHP 7.4

Smarty PHP Template Engine 1.9k Nov 23, 2021
Native PHP template system

Plates Plates is a native PHP template system that's fast, easy to use and easy to extend. It's inspired by the excellent Twig template engine and str

The League of Extraordinary Packages 1.3k Nov 19, 2021
☕ Latte: the intuitive and fast template engine for those who want the most secure PHP sites.

Latte: amazing template engine for PHP Introduction Latte is a template engine for PHP which eases your work and ensures the output is protected again

Nette Foundation 731 Nov 27, 2021
View template engine of PHP extracted from Laravel

Blade 【简体中文】 This is a view templating engine which is extracted from Laravel. It's independent without relying on Laravel's Container or any others.

刘小乐 145 Aug 13, 2021
PHP template engine for native PHP templates

FOIL PHP template engine, for PHP templates. Foil brings all the flexibility and power of modern template engines to native PHP templates. Write simpl

Foil PHP 163 Nov 20, 2021
A complete and fully-functional implementation of the Jade template language for PHP

Tale Jade for PHP Finally a fully-functional, complete and clean port of the Jade language to PHP — Abraham Lincoln The Tale Jade Template Engine brin

Talesoft 93 May 4, 2021
Laravel package template

REPLACE Simple and flexible package template. Usage Replace all occurances of REPLACE (case sensitive) with the name of the package namespace. E.g. th

ARCHTECH 47 Nov 24, 2021
A ready-to-use Model View Controller template in PHP

PHP-MVC-Template A ready-to-use Model View Controller template in PHP Use this repo as a template! (Or clone it) Start to configure your MVC file Afte

Loule | Louis 6 Nov 19, 2021
The Templating component provides all the tools needed to build any kind of template system.

Templating Component The Templating component provides all the tools needed to build any kind of template system. It provides an infrastructure to loa

Symfony 983 Nov 10, 2021
Provides a GitHub repository template for a PHP package, using GitHub actions.

php-package-template Installation ?? This is a great place for showing how to install the package, see below: Run $ composer require ergebnis/php-pack

null 260 Nov 24, 2021
The free-to-use template for your Imagehost-website made with PHP, HTML and CSS!

The free-to-use template for your Imagehost-website made with PHP, HTML and CSS! Some information before we start This repo is only code related, to a

Ilian 5 Nov 3, 2021
Document templates Laravel package is intended for creating/managing user editable document template

Document Templates Introduction Document templates Laravel package is intended for creating/managing user editable document templates, with ability to

42coders 99 Nov 14, 2021
The free-to-use template for your Imagehost-website made with PHP, HTML and CSS!

The free-to-use template for your Imagehost-website made with PHP, HTML and CSS! Some information before we start This repo is only code related, to a

Ilian 5 Nov 3, 2021
A SilverStripe Module with template methods to quickly make use of FocusPoint, LazySizes, and Object-fit

LazyFocusFit A SilverStripe module with template methods to quickly make use of FocusPoint, LazySizes and object-fit. Requirements PHP FocusPoint JS/C

Evans Hunt 8 Oct 12, 2021
Astroid Framework - Powerful Joomla Template Framework

Powerful framework for designers and developers to create responsive, fast & robust Joomla based websites and templates.

TemPlaza 9 Nov 23, 2021
⚡️ Simple and fastly template engine for PHP

EasyTpl ⚡️ Simple and fastly template engine for PHP Features It's simple, lightweight and fastly. No learning costs, syntax like PHP template It is s

PHPPkg 9 Nov 26, 2021
DBML parser for PHP8. It's a PHP parser for DBML syntax.

DBML parser written on PHP8 DBML (database markup language) is a simple, readable DSL language designed to define database structures. This page outli

Pavel Buchnev 19 Nov 22, 2021
A lightweight lexical string parser for BBCode styled markup.

Decoda A lightweight lexical string parser for BBCode styled markup. Requirements PHP 5.6.0+ Multibyte Composer Contributors "Marten-Plain" emoticons

Miles Johnson 192 Sep 1, 2021
Simple handler system used to power clients and servers in PHP (this project is no longer used in Guzzle 6+)

RingPHP Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function. RingPHP be used to power HTTP clie

Guzzle 847 Nov 17, 2021
TrailLamp is a lightweight, easy-to-use Php MVC framework that can be used to build web applications and REST APIs.

TrailLamp Introduction TrailLamp is a lightweight, easy-to-use Php MVC framework that can be used to build web applications and REST APIs. Installatio

Etorojah Okon 11 Nov 9, 2021
PHP Template Attribute Language — template engine for XSS-proof well-formed XHTML and HTML5 pages

PHPTAL - Template Attribute Language for PHP Requirements If you want to use the builtin internationalisation system (I18N), the php-gettext extension

PHPTAL 165 Nov 17, 2021
Template for repository helper, library - Basic, Simple and Lightweight

Template start helper, library Template for repository helper, library - Basic, Simple and Lightweight Use this Template First, you can Use this templ

Hung Nguyen 2 Sep 23, 2021
A PHP parser written in PHP

PHP Parser This is a PHP 5.2 to PHP 8.0 parser written in PHP. Its purpose is to simplify static code analysis and manipulation. Documentation for ver

Nikita Popov 15k Nov 19, 2021
An object-oriented option parser library for PHP, which supports type constraints, flag, multiple flag, multiple values, required value checking

GetOptionKit Code Quality Versions & Stats A powerful option parser toolkit for PHP, supporting type constraints, flag, multiple flag, multiple values

Yo-An Lin 139 Nov 22, 2021
Another Command Line Argument Parser

Optparse — Another Command Line Argument Parser Install 1. Get composer. 2. Put this into your local composer.json: { "require": { "chh/optparse

Christoph Hochstrasser 18 Nov 1, 2019
A PHP parser for TOML

TOML parser for PHP A PHP parser for TOML compatible with TOML v0.4.0. Support: Installation Requires PHP >= 7.1. Use Composer to install this package

Yo! Symfony 157 Nov 19, 2021
Better Markdown Parser in PHP

Parsedown Better Markdown Parser in PHP - Demo. Features One File No Dependencies Super Fast Extensible GitHub flavored Tested in 5.3 to 7.3 Markdown

Emanuil Rusev 13.9k Nov 26, 2021