Phug

Phug (unified pug engine for Pug-php and Tale-pug)

Latest Stable Version Latest Stable Version    |   Build status Build status

Overview

What is Phug?

Phug is the pug template engine for PHP.

Phug offer you a clean way to write your templates (such as HTML pages).

Example:

body
  h1 Phug

Instead of writing verbose tags syntax, Phug is based on the indentation (like Python, CoffeeScript or Stylus). Here <h1> is inside <body> because h1 has one more indent level. Try to remove the spaces, you will see <h1> becoming a sibling of <body> instead of a child. You can indent with any tabs or spaces you want, Phug will always try to guess the structure by detecting indent level. However we recommend you to have consistent indentation in you files.

As a template engine, Phug also provide an optimized way to handle dynamic values.

Example:

- $var = true
if $var
  p Displayed only if $var is true
else
  p Fallback

Try to set $var to false to see how the template react to it.

Variables can be set outside of your template (for example in a controller):

label Username
input(value=$userName)
[
  'userName' => 'Bob',
]

Phug is written in PHP, so by default, expressions are written in PHP, but you can plug modules such as js-phpize or wrapper like pug-php that enable this module by default to get js expressions working in your templates. See the comparison below:

Phug:

p=$arr['obj']->a . $arr['obj']->b
[
  'arr' => [
    'obj' => (object) [
      'a' => 'A',
      'b' => 'B',
    ],
  ],
]

Pug-php:

p=arr.obj.a + arr.obj.b
[
  'arr' => [
    'obj' => (object) [
      'a' => 'A',
      'b' => 'B',
    ],
  ],
]

Now you know what Phug is you can:

If you are not sure you should use Phug or not, please check our Why section below:

Why Phug?

HTML is born in 1989 in the CERN, and was sufficient for its purpose: write pages with titles and links. Nowadays, we build user interfaces for various devices and the final HTML to send to the browser can be very verbose. And even if we must send HTML to the browser, we can use an other cleaner language and compile it.

A lot of template engines just allow to insert dynamic elements inside HTML, with an engine like Phug, you will no longer write HTML at all and will benefit many features: layouts, mixins, conditions, iterations, etc.

Note that Phug supports several versions and doctypes of HTML but also XML, and you can easily create any format you would need; as you can customize expressions handling, etc. Phug has much options and extension possibilities, very much.

Why a template engine?

Most PHP frameworks have a templates system. It's an efficient way to separate the view layer. Delegate view responsibility to template engine is a best practice since it avoids mixing logic (calculation, retrieving and parsing data) with presentation (display, formatting) and it will help you to respect the PSR (principal of single responsibility, that aims to not giving an entity multiple responsibilities), so you will be able to organize your views into pages and visual components with no constraint from your PHP code.

Finally, if you respect this principle (by avoiding, among others, inserting treatments in your templates), then your templates will not contain complex code but only variable inserts. This make this code easy to modify for someone who don't know your application back-end and even neither the PHP language.

Why not to use pugjs?

Isn't it possible to use the JavaScript pug package in a PHP application? Yes it is. There are even many ways to achieve this goal. See the alternatives section for more details. But know this approach has limits. Most important is the data flattening. Any class instance become a flat object when passed through pugjs, it means the object will lost its methods.

Here is a example of what's working well with Phug but would not be possible with pugjs if called via a proxy or a command:

p=today.format('d/m/Y H:i')
[
  'today' => new DateTime('now'),
]

Why upgrade/migrate to Phug?

You may use already a PHP library supporting Pug syntax.

First of all, if you do not use composer, I only can encourage you to switch to this dependencies management system. It's the most used in the PHP ecosystem and it will help you keeping your dependencies up to date. So I invite you to to pick a library among available ones in composer (see https://packagist.org/), prefer most starred ones with regular and recent releases.

Then know that if you use a project containing "jade" in its name, it's probably obsolete as jade is the old name of pug, for example packages kylekatarnls/jade-php, ronan-gloo/jadephp, opendena/jade.php, jumplink/jade.php, dz0ny/jade.php and everzet/jade are all no longer maintained projects that fork the same origin, they are not pugjs 2 compliant and miss a lot of pug features. The most up-to-date project that replace them all is pug-php/pug. In its version 2 it still use the same original engine. Same goes for talesoft/tale-jade replaced with talesoft/tale-pug and its version 2 will also use Phug.

talesoft/tale-pug and pug-php/pug are the most used Pug ports and are actively maintained. By using the last version of these projects, you will therefore automatically migrate to the Phug engine and as contributors of these both projects are now gather in the Phug project and will develop Phug in priority, you will benefit from the best possible support.

To upgrade pug-php/pug and benefit all the features described in this documentation, run the following command in your project:

composer require pug-php/pug:"^3.0"

To be informed about talesoft/tale-pug version 2 release, you can use https://www.versioneye.com/ and add talesoft/tale-pug to your watchlist.

At least, we can assure you Phug surpass others existing implementations on many subjects:

  • Extensibility, customization, formats, options
  • Integration and easy install in different frameworks
  • Documentation
  • Live test tool
  • Expression handling (js-phpize or any custom language)
  • Assets handling and minification (pug-assets)
  • Error tracking
  • Profiling
  • Community reactivity (issues and pull-request on GitHub, and [pug] [php] keywords on https://stackoverflow.com/search?q=pug php)

Installation

In your favorite framework

If you use one of the following frameworks, click on the link to see how to install phug directly in your application.

The framework adapters above are based on Pug-php 3, that means expressions should be written in JS style by default, but you can use PHP native style by setting expressionLanguage to php.

If you want us to support some other framework, please open an issue here: https://github.com/phug-php/phug/issues/new and if your issue get some votes, we'll work on it.

In your favorite CMS

Installation from scratch

First you need composer if you have'nt yet: https://getcomposer.org/download/

Then run in your application directory:

composer require phug/phug

Replace composer with php composer.phar if you installed composer locally. And same goes for every composer commands mentioned in this documentation.

Create a PHP file with the following content:

<?php

include_once __DIR__ . '/vendor/autoload.php';

Phug::display('p=$message', [
  'message' => 'Hello',
]);

You can edit first and second arguments of Phug::display in the code editors below and see the results in the right panel.

p=$message
[
  'message' => 'Hello',
]

Phug::display take a template string as first argument, variables values as second optional argument and a third optional argument allow you to specify options (see Options chapter).

You can use Phug::displayFile to display a template file:

Phug::displayFile('views-directory/my-pug-template.pug');

The same optional variables and option arguments are available.

You can also return the result instead of displaying it with Phug::render and Phug::renderFile.

The Phug class will also act like a facade for the Renderer class, it means you can call statically on Phug\Phug any Phug\Rebderer's method. For example, it makes compile and compileFile available:

file_put_contents('cache/my-compiled-page.php', Phug::compileFile('view/my-template.pug'));

This code will compile the template file view/my-template.pug and save it into cache/my-compiled-page.php, this is basically what we do when the cache option is set.

You may notice the PHP file contain debug code, this code allow us to provide you accurate error trace (give you matching line and offset in the pug source) and profiling tools to check performance.

In production, you can easily disable that stuff with setOption:

Phug::setOption('debug', false);

echo Phug::compile('p=$userName');

This will display the PHP compiled code with no debug code.

See all available methods in the API reference:

Use JavaScript expressions

By default, Phug and Tale-pug use PHP expressions. And Pug-php use JS expressions, but you can easily change the default behavior.

To handle js-style expressions on Phug and Tale-pug, install the js-phpize extension for Phug:

composer require js-phpize/js-phpize-phug

Replace composer with php composer.phar if you installed composer locally.

Then enable the extension before calling the render/display method:

<?php

use JsPhpize\JsPhpizePhug;

include_once __DIR__ . '/vendor/autoload.php';

Phug::addExtension(JsPhpizePhug::class);

Phug::display('p=userName', [
  'userName' => 'Bob',
]);

label Username
input(value=userName)
[
  'userName' => 'Bob',
]

To use PHP expressions in Pug-php, use the option expressionLanguage:

<?php

use Pug\Pug;

include_once __DIR__ . '/vendor/autoload.php';

$pug = new Pug([
    'expressionLanguage' => 'php',
]);

$pug->display('p=$user->name', [
  'user' => (object) [
    'name' => 'Bob',        
  ],
]);
label Username
input(value=$user->name)
[
  'user' => (object) [
    'name' => 'Bob',        
  ],
]

Switch expression language inside template

Since js-phpize-phug 2.1.0, it's now possible to switch between both styles inside templates.

body
  //- Whatever the options, we swtich to js mode
  language js
  - counter = 0
  
  node-language php
  div
    //- This node (the div tag) and its children
    //- will use php mode by default
    - $counter++
    span= $counter++
    //- Switch to js mode until new order
    language js
    - counter++
    - counter++
    //- And php again
    language php
    p= $counter

  section
    //- Outside the node (div tag), we go back to
    //- the previous mode
    p= counter
    //- language and node-language can also be called
    //- throught comments
    //- @language php
    p= $counter

Usage

Create one or more templates directories, and create pug files in them.

For example, imagine that myView.pug is in the views/directory directory and contains:

h1=$title
[
  'title' => 'Header',
]

You can display the matching HTML of this file like this:

<?php

include_once __DIR__ . '/vendor/autoload.php';

$variables = [
   'title' => 'Header',
];

$options = [
  'paths' => [
    'views/directory',
  ],
];

Phug::displayFile('myView', $variables, $options);

We recommend to use displayFile as much as possible for performances (displayFile is faster than echo renderFile) and files can be cached faster than a raw content so in production displayFile('myView.pug') is faster than display('the content of the file').

In production, we also recommend to use the Optimizer and the cache:

<?php

include_once __DIR__ . '/vendor/autoload.php';

// Replace with your own environment calculation
$environment = getenv('ENVIRONMENT') ?: 'production';

$variables = [
   'title' => 'Header',
];

$options = [
  'debug'     => false,
  'cache_dir' => 'cache/directory', 
  'paths'     => [
    'views/directory',
  ],
];

if ($environment === 'production') {
    \Phug\Optimizer::call('displayFile', ['myView', $variables], $options);

    exit;
}

$options['debug'] = true;
$options['cache_dir'] = null;

Phug::displayFile('myView', $variables, $options);

The Optimizer is a tool that avoid to load the Phug engine if a file is available in the cache. In counterpart, it does not allow to change the adapter or user post-render events.

If you use Pug-php, just replace in the code above \Phug\Optimizer with \Pug\Optimizer and Phug::displayFile with \Pug\Facade::displayFile.

The cache can be used in development too to save time.

In production, you should use the --optimize-autoloader option of composer to optimize the autoloader when installing dependencies. Then you should cache all your templates to get benefit of the up_to_date_check option

composer update --optimize-autoloader
./vendor/bin/phug compile-directory views/directory cache/directory '{"debug":"false"}'

By doing this for each deployment on your server, you will be able to set up_to_date_check option to true to directly load cache file with no file check.

In development environment, you can update automatically the cache and auto-refresh the page using the watcher.

See CLI for more information about the command line.

API reference

This is a summary of main methods available. You also can view the auto-generated documentation by clicking the link below:

Complete API documentation

displayFile

$template = '/my/template/file.pug';
$localVariables = [
  'variableName' => 'value',
];
$options = [
  'cache_dir' => '/path/to/a/cache/folder',
];


// Facade way (with Phug)
Phug::displayFile($template, $localVariables, $options);


// Instance way (with Phug)
$renderer = new Phug\Renderer($options);
$renderer->displayFile($template, $localVariables);


// Facade way (with Pug-php)
Pug\Facade::displayFile($template, $localVariables, $options);


// Instance way (with Pug-php)
$renderer = new Pug($options);
$renderer->displayFile($template, $localVariables);

This output the renderer template file given to the standard output. This is the recommended way to use Phug.

Check the usage section to see how to optimize it in production.

Check the options section to see the complete list of available options.

display

Behave like displayFile but takes pug source code as first argument:

$template = '
div: p.examples
  em Foo
  strong(title="Bar") Bar
';


// Facade way (with Phug)
Phug::display($template, $localVariables, $options);


// Instance way (with Phug)
$renderer = new Phug\Renderer($options);
$renderer->display($template, $localVariables);


// Facade way (with Pug-php)
Pug\Facade::displayString($template, $localVariables, $options);


// Instance way (with Pug-php)
$renderer = new Pug($options);
$renderer->displayString($template, $localVariables);

Note: If you use Pug-php, it will try to detect if the given string is a file path and fallback on displayFile. This behavior is for backward compatibility but you're encouraged to disabled it by setting the "strict" option to true or use displayString.

renderFile

renderFile is the same as displayFile but returns the output instead of display it.

render

render is the same as display but returns the output instead of display it.

Note: If you use Pug-php, it will try to detect if the given string is a file path and fallback on renderFile. This behavior is for backward compatibility but you're encouraged to disabled it by setting the "strict" option to true or use renderString.

renderAndWriteFile

Render a pug file then write the final result (often the HTML) in a file.

Returns true in case of success, false else.

$input = '/mon/fichier/template.pug';
$output = '/ma/page.html';
$variablesLocales = [
  'nomDeVariable' => 'valeur',
];
$options = [/* ... */];


// Facade way (with Phug)
Phug::setOptions($options);
if (Phug::renderAndWriteFile($input, $output, $localVariables)) {
  echo 'File written successfully.';
}


// Instance way (with Phug)
$renderer = new Phug\Renderer($options);
if ($renderer->renderAndWriteFile($input, $output, $localVariables)) {
  echo 'File written successfully.';
}


// Facade way (with Pug-php)
Pug\Facade::setOptions($options);
Pug\Facade::renderAndWriteFile($input, $output, $localVariables)) {
  echo 'File written successfully.';
}


// Instance way (with Pug-php)
$renderer = new Pug($options);
if ($renderer->renderAndWriteFile($input, $output, $localVariables)) {
  echo 'File written successfully.';
}

renderDirectory

Render all pug template files (recursively) in an input directory and output in an output directory. If no output directory specified, the same directory is used for input and output.

Returns an array with success count and error count.

$renderer = new Phug\Renderer($options); // or $renderer = new Pug($options);

$renderer->renderDirectory('./my-views'); // render with no local variables all templates in ./my-views
                                          // and write .html files next to pug files
$renderer->renderDirectory('./my-views', ['foo' => 'bar']); // the same with local variables
$renderer->renderDirectory('./my-views', './my-pages'); // render with no local variables all templates in ./my-views
                                                       // and write .html files in ./my-pages
$renderer->renderDirectory('./my-views', './my-pages', ['foo' => 'bar']); // the same with local variables
$renderer->renderDirectory('./my-views', './my-pages', '.xml'); // use .xml extension instead of the default .html
$renderer->renderDirectory('./my-views', './my-pages', '.xml', ['foo' => 'bar']); // the same with local variables

compileFile

Compile a file and return the rendering code. It means when renderFile typically returns HTML, compileFile mostly returns PHP, by executing this PHP code, you would get the final HTML.

This may allow you for example to delegate the view rendering and caching to an other engine/framework.

$template = '/my/template/file.pug';
$options = [
  // only compilation options, since rendering options
  // will not be used.
];

// Facade way (with Phug)
Phug::setOptions($options);
Phug::compileFile($template);


// Instance way (with Phug)
$renderer = new Phug\Renderer($options);
$renderer->compileFile($template);


// Facade way (with Pug-php)
Pug\Facade::setOptions($options);
Pug\Facade::compileFile($template);


// Instance way (with Pug-php)
$renderer = new Pug($options);
$renderer->compileFile($template);

compile

Behave like compileFile but takes pug source code as first argument.

cacheFile

Compile a pug file and save it in the cache directory specified via the options.

cacheFileIfChanged

Compile a pug file and save it in the cache directory specified via the options except if an up-to-date file is already present in it.

cacheDirectory

Cache all pug files (recursively) in the directory passed as argument.

Advanced methods

See the complete API documentation.

Language reference

As Phug aims to be pugjs compliant, the language reference is nearly the same as the one you can find for pugjs and this chapter follow the pugjs one content except for Phug specificities and live code editors that allows you to test with the Phug engine.

Attributes

Tag attributes look similar to HTML (with optional commas), but their values are just expressions.

a(href='google.com') Google
="\n"
a(class='button' href='google.com') Google
="\n"
a(class='button', href='google.com') Google

(="\n" are just here to add whitespace between links for an easier reading of the output HTML).

Normal PHP expressions work fine by default on Phug and Tale-pug; and on Pug-php with expressionLanguage option set to php:

- $authenticated = true
body(class=$authenticated ? 'authed' : 'anon')

Normal JavaScript expressions work fine too with js-phpize-phug extension installed (see how to install) or Pug-php last version with default options (or expressionLanguage set to js):

- var authenticated = true
body(class=authenticated ? 'authed' : 'anon')

Multiline Attributes

If you have many attributes, you can also spread them across many lines:

input(
  type='checkbox'
  name='agreement'
  checked
)

Multiline texts are not at all a problem either you use JS or PHP expressions:

input(data-json='
  {
    "very-long": "piece of ",
    "data": true
  }
')

Note: if you migrate templates from a JavaScript project you might have to replace ` by simple quotes ' or double quotes ".

Quoted Attributes

If your attribute name contains odd characters that might interfere with expressions syntax, either quote it using "" or '', or use commas to separate different attributes. Examples of such characters include [] and () (frequently used in Angular 2).

//- In this case, `[click]` is treated
//- as an offset getter, resulting
//- in the unusual error.
div(class='div-class' [click]='play()')
div(class='div-class', [click]='play()')
div(class='div-class' '[click]'='play()')

Attribute Interpolation

A small note about a syntax you may hav known in pugjs 1: a(href="/#{url}") Link, this syntax is no longer valid in pugjs 2 and so we decided to not support it in Phug. You can PHP interpolation:

- $btnType = 'info'
- $btnSize = 'lg'
button(type="button" class="btn btn-$btnType btn-$btnSize")
- $btn = (object) ['size' => 'lg']
button(type="button" class="btn btn-{$btn->size}")

For JS expressions, use concatenation " + btnType + ".

Unescaped Attributes

By default, all attributes are escaped to prevent attacks (such as cross site scripting). If you need to use special characters, use != instead of =.

div(escaped="&lt;code>")
div(unescaped!="&lt;code>")

Caution, unescaped buffered code can be dangerous. You must be sure to sanitize any user inputs to avoid cross-site scripting.

Unchecked attributes

Here is a specific concept of Phug you would not find in pugjs, it's about checking a variable exist.

In PHP, when error are displayed and error level enable notices, calling a non-existing variable will throw an error.

By default, we will hide those error, but this could hide some bug, so you can use ?= operator to avoid this behavior:

- $wrong = ''
img(src?=$wronk)

In this example, the ?= operator will reveal the miss typo of "wrong". Click on the [Preview] button to see the error.

Attributes can be both unchecked and unescaped:

- $html = '&lt;strong>OK&lt;/strong>'
img(alt?!=$html)

To disable globally the checking (always throw an error if the variable called is not defined), use the php_token_handlers option:

Phug::setOption(['php_token_handlers', T_VARIABLE], null);

Boolean Attributes

Boolean attributes are mirrored by Phug. Boolean values (true and false) are accepted. When no value is specified true is assumed.

input(type='checkbox' checked)
="\n"
input(type='checkbox' checked=true)
="\n"
input(type='checkbox' checked=false)
="\n"
input(type='checkbox' checked='true')

If the doctype is html, Phug knows not to mirror the attribute, and instead uses the terse style (understood by all browsers).

doctype html
="\n"
input(type='checkbox' checked)
="\n"
input(type='checkbox' checked=true)
="\n"
input(type='checkbox' checked=false)
="\n"
input(type='checkbox' checked='checked')

Style Attributes

The style attribute can be a string, like any normal attribute; but it can also be an object or an array.

PHP-style:

a(style=['color' => 'red', 'background' => 'green'])
="\n"
a(style=(object)['color' => 'red', 'background' => 'green'])

JS-style:

a(style={color: 'red', background: 'green'})

Class Attributes

The class attribute can be a string, like any normal attribute; but it can also be an array of class names, which is handy when generated.

- $classes = ['foo', 'bar', 'baz']
a(class=$classes)
="\n"
//- the class attribute may also be repeated to merge arrays
a.bang(class=$classes class=['bing'])

It can also be an array which maps class names to true or false values. This is useful for applying conditional classes.

PHP-style:

- $currentUrl = '/about'
a(ref='/' class=['active' => $currentUrl === '/']) Home
="\n"
a(href='/about' class=['active' => $currentUrl === '/about']) About

JS-style:

- var currentUrl = '/about'
a(href='/' class={active: currentUrl === '/'}) Home
="\n"
a(href='/about' class={active: currentUrl === '/about'}) About

Class Literal

Classes may be defined using a .classname syntax:

a.button

Since div's are such a common choice of tag, it is the default if you omit the tag name:

.content

ID Literal

IDs may be defined using a #idname syntax:

a#main-link

Since div's are such a common choice of tag, it is the default if you omit the tag name:

#content

&attributes

Pronounced as “and attributes”, the &attributes syntax can be used to explode an array into attributes of an element.

PHP-style:

div#foo(data-bar="foo")&attributes(['data-foo' => 'bar'])

JS-style:

div#foo(data-bar="foo")&attributes({'data-foo': 'bar'})

The above examples uses an array literal. But you can also use a variable whose value is an array, too. (See also: Mixin Attributes).

- $attributes = []
- $attributes['class'] = 'baz'
div#foo(data-bar="foo")&attributes($attributes)

Caution, attributes applied using &attributes are not automatically escaped. You must be sure to sanitize any user inputs to avoid cross-site scripting (XSS). If passing in attributes from a mixin call, this is done automatically.

Case

The case statement is a shorthand for the PHP switch statement. It takes the following form:

- $friends = 10
case $friends
  when 0
    p you have no friends
  when 1
    p you have a friend
  default
    p you have #{$friends} friends

Case Fall Through

You can use fall through, just as you would in a PHP switch statement.

- $friends = 0
case $friends
  when 0
  when 1
    p you have very few friends
  default
    p you have #{$friends} friends

The difference, however, is a fall through in PHP happens whenever a break statement is not explicitly included; in Phug, it only happens when a block is completely missing.

If you would like to not output anything in a specific case, add an explicit unbuffered break:

- $friends = 0
case $friends
  when 0
    - break
  when 1
    p you have very few friends
  default
    p you have #{$friends} friends

Block Expansion

Block expansion may also be used:

- $friends = 1
case $friends
  when 0: p you have no friends
  when 1: p you have a friend
  default: p you have #{$friends} friends

Code

Phug allows you to write inline PHP or JavaScript code in your templates. The code can be buffered or not. When it is buffered, it can be escaped or not, checked or not the same way as attributes.

Unbuffered Code

Unbuffered code starts with -. It does not directly add anything to the output.

- for ($x = 0; $x &lt; 3; $x++)
  li item

Phug also supports block unbuffered code:

-
  $list = ["Uno", "Dos", "Tres",
          "Cuatro", "Cinco", "Seis"]
each $item in $list
  li= $item

Buffered Code

Buffered code starts with =. It evaluates the PHP or JavaScript expression and outputs the result. For security, buffered code is first HTML escaped.

p
  = 'This code is &lt;escaped>!'

It can also be written inline, and supports the full range of expressions:

p= 'This code is' . ' &lt;escaped>!'

Note: if you use JavaScript expressions, concatenations must use the + operator:

p= 'This code is' + ' &lt;escaped>!'

Unescape/escape code

Precede the = with ! to not escape HTML entities, with ? to not check variables to be set and ?! for both:

- $start = '&lt;strong>'
- $end = '&lt;/strong>'
//- This one is escaped
div= $start . 'Word' . $end
//- This one is not escaped
div!= $start . 'Word' . $end
//- Both are checked
div= 'start' . $middle . 'end'
div!= 'start' . $middle . 'end'

Uncheck/check code

Checked code does not throw an error when variables are undefined.

Unchecked code throws an error when variables are undefined. See the examples below with on the one hand an existing variable, and on the other hand a missing variable:

- $middle = ' middle '
div?= 'start' . $middle . 'end'
div?!= 'start' . $middle . 'end'
div?= 'start' . $middle . 'end'
div?!= 'start' . $middle . 'end'

Caution: unescaped buffered code can be dangerous. You must be sure to sanitize any user inputs to avoid cross-site scripting (XSS).

Commentaires

Buffered comments look the same as single-line JavaScript comments. They act sort of like markup tags, producing HTML comments in the rendered page.

Like tags, buffered comments must appear on their own line.

// just some paragraphs
p foo
p bar
// each line
// will produce
// a HTML comment
footer

Phug also supports unbuffered comments (they will be not compiled so you can add many with no impact on the cache file size). Simply add a hyphen (-) to the start of the comment.

//- will not output within markup
p foo
p bar

Block Comments

Block comments work, too:

body
  //-
    Comments for your template writers.
    Use as much text as you want.
  //
    Comments for your HTML readers.
    Use as much text as you want.

Conditional Comments

Phug does not have any special syntax for conditional comments. (Conditional comments are a peculiar method of adding fallback markup for old versions of Internet Explorer.)

However, since all lines beginning with < are treated as plain text, normal HTML-style conditional comments work just fine.

doctype html

&lt;!--[if IE 8]>
&lt;html lang="fr" class="lt-ie9">
&lt;![endif]-->
&lt;!--[if gt IE 8]>&lt;!-->
&lt;html lang="fr">
&lt;!--&lt;![endif]-->

body
  p Supporting old web browsers is a pain.

&lt;/html>

Conditionals

Conditionals look like the following if you use PHP-style:

- $user = [ 'description' => 'foo bar baz' ]
- $authorised = false
#user
  if $user['description']
    h2.green Description
    p.description= $user['description']
  else if $authorised
    h2.blue Description
    p.description.
      User has no description,
      why not add one...
  else
    h2.red Description
    p.description User has no description

If you use JS-style:

- var user = { description: 'foo bar baz' }
- var authorised = false
#user
  if user.description
    h2.green Description
    p.description= user.description
  else if authorised
    h2.blue Description
    p.description.
      User has no description,
      why not add one...
  else
    h2.red Description
    p.description User has no description

Phug also provides the conditional unless, which works like a negated if.

PHP-style example:

unless $user['isAnonymous']
  p You're logged in as #{$user['name']}.
//- is equivalent to
if !$user['isAnonymous']
  p You're logged in as #{$user['name']}.
[
  'user' => [
    'isAnonymous' => false,
    'name' => 'Jefferson Airplane',
  ],
]

JS-style example:

unless user.isAnonymous
  p You're logged in as #{user.name}.
//- is equivalent to
if !user.isAnonymous
  p You're logged in as #{user.name}.
[
  'user' => [
    'isAnonymous' => false,
    'name' => 'Jefferson Airplane',
  ],
]

Doctype

doctype html

Doctype Shortcuts

Shortcuts to commonly used doctypes:

doctype html
doctype xml
doctype transitional
doctype strict
doctype frameset
doctype 1.1
doctype basic
doctype mobile
doctype plist

Custom Doctypes

You can also use your own literal custom doctype:

doctype html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"

Doctype Option

In addition to being buffered in the output, a doctype in Phug can affect compilation in other ways. For example, whether self-closing tags end with /> or > depends on whether HTML or XML is specified. The output of boolean attributes may be affected as well.

If, for whatever reason, it is not possible to use the doctype keyword (e.g., just rendering HTML fragments), but you would still like to specify the doctype of the template, you can do so via the doctype option.

$source = 'img(src="foo.png")';

Phug::render($source);
// => '<img src="foo.png"/>'

Phug::render($source, [], [
  'doctype' => 'xml',
]);
// => '<img src="foo.png"></img>'

Phug::render($source, [], [
  'doctype' => 'html',
]);
// => '<img src="foo.png">'

Filters

Filters let you use other languages in Pug templates. They take a block of plain text as an input.

To pass options to the filter, add them inside parentheses after the filter name (just as you would do with tag attributes): :less(compress=false).

All JSTransformer modules can be used as Pug filters. Popular filters include :babel, :uglify-js, :scss, and :markdown-it. Check out the documentation for the JSTransformer for the options supported for the specific filter.

To use JSTransformer with Phug, install the following extension:

composer require phug/js-transformer-filter

Then enable it:

use Phug\JsTransformerExtension;

Phug::addExtension(JsTransformerExtension::class);

With Pug-php, this extension is installed and enabled by default since 3.1.0 version.

You can also use any of the following PHP projects as filter in all Phug and Pug-php projects: http://pug-filters.selfbuild.fr

If you can't find an appropriate filter for your use case, you can write your own custom filter. It can be any callable (closure, function or object with __invoke method).

Phug::setFilter('upper-start', function ($inputString, $options) {
  return strtoupper(mb_substr($inputString, 0, $options['length'])).
    mb_substr($inputString, $options['length']);
});

Phug::display('
div
  :upper-start(length=3)
    gggggg
');

This will output:

<div>GGGggg</div>

The same goes for Pug-php:

$pug = new Pug();
$pug->setFilter('upper-start', function ($inputString, $options) {
  return strtoupper(mb_substr($inputString, 0, $options['length'])).
    mb_substr($inputString, $options['length']);
});

$pug->display('
div
  :upper-start(length=3)
    gggggg
');

The current compiler is passed as third argument so you can get any information from it:

Phug::setFilter('path', function ($inputString, $options, CompilerInterface $compiler) {
  return $compiler->getPath();
});

Phug::displayFile('some-template.pug');

In this example, if some-template.pug contains :path filter calls, it will display the full path of the current file compiling (example: /directory/some-template.pug or some other file if called inside an extended/included file).

Phug comes with cdata filter pre-installed:

data
  :cdata
    Since this is a CDATA section
    I can use all sorts of reserved characters
    like > &lt; " and &
    or write things like
    &lt;foo>&lt;/bar>
    but my document is still well formed!

Pug-php pre-install JsTransformerExtension and embed cdata, css, escaped, javascript, php, pre, script and if you not set the filterAutoLoad to false, it will load automatically as filter any invokable class in the Pug\Filter namespace:

doctype html
html
  head
    :css
      a {
        color: red;
      }
  body
    :php
      $calcul = 9 + 8
    p=calcul
    :escaped
      &lt;foo>
    :pre
      div
        h1 Example of Pug code
    :javascript
      console.log('Hello')
    :script
      console.log('Alias of javascript')

Includes

Includes allow you to insert the contents of one Pug file into another.

//- index.pug
doctype html
html
  include includes/head.pug
  body
    h1 My Site
    p Welcome to my super lame site.
    include includes/foot.pug
//- includes/head.pug
head
  title My Site
  script(src='/javascripts/jquery.js')
  script(src='/javascripts/app.js')
//- includes/foot.pug
footer#footer
  p Copyright (c)
    =date(' Y')

If the path is absolute (e.g., include /root.pug), it is resolved from paths option. This option works like the basedir in pugjs but allow you to specify multiple directories. The basdir option also exists in Pug-php to provide full pugjs options supports but we recommend you prefer paths.

Otherwise, paths are resolved relative to the current file being compiled.

If no file extension is given, .pug is automatically appended to the file name.

Including Plain Text

Including non-Pug files simply includes their raw text.

//- index.pug
doctype html
html
  head
    style
      include style.css
  body
    h1 My Site
    p Welcome to my super lame site.
    script
      include script.js
/* style.css */
h1 {
  color: red;
}
// script.js
console.log('You are awesome');

Including Filtered Text

You can combine filters with includes, allowing you to filter things as you include them.

//- index.pug
doctype html
html
  head
    title An article
  body
    include:markdown article.md
# article.md

This is an article written in markdown.

Template Inheritance

Phug supports template inheritance. Template inheritance works via the block and extends keywords.

In a template, a block is simply a “block” of Pug that a child template may replace. This process is recursive.

Phug blocks can provide default content, if appropriate. Providing default content is purely optional, though. The example below defines block scripts, block content, and block foot.

//- layout.pug
html
  head
    title My Site - #{$title}
    block scripts
      script(src='/jquery.js')
  body
    block content
    block foot
      #footer
        p some footer content
[
  'title' => 'Blog',
]

*No $ needed if you use JS-style

To extend this layout, create a new file and use the extends directive with a path to the parent template. (If no file extension is given, .pug is automatically appended to the file name.) Then, define one or more blocks to override the parent block content.

Below, notice that the foot block is not redefined, so it will use the parent's default and output “some footer content”.

//- page-a.pug
extends layout.pug

block scripts
  script(src='/jquery.js')
  script(src='/pets.js')

block content
  h1= $title
  each $petName in $pets
    include pet.pug
[
  'title' => 'Blog',
  'pets'  => ['cat', 'dog']
]
//- pet.pug
p= $petName

*No $ needed if you use JS-style

It's also possible to override a block to provide additional blocks, as shown in the following example. As it shows, content now exposes a sidebar and primary block for overriding. (Alternatively, the child template could override content altogether.)

//- sub-layout.pug
extends layout.pug

block content
  .sidebar
    block sidebar
  .primary
    block primary
//- page-b.pug
extends sub-layout.pug

block sidebar
  p something

block primary
  p something else

Block append / prepend

Phug allows you to replace (default), prepend, or append blocks.

Suppose you have default scripts in a head block that you wish to use on every page, you might do this:

//- page-layout.pug
html
  head
    block head
      script(src='/vendor/jquery.js')
      script(src='/vendor/caustic.js')
  body
    block content

Now, consider a page of your JavaScript game. You want some game related scripts as well as these defaults. You can simply append the block:

//- page.pug
extends page-layout.pug

block append head
  script(src='/vendor/three.js')
  script(src='/game.js')

When using block append or block prepend, the word “block” is optional:

//- page.pug
extends page-layout.pug

append head
  script(src='/vendor/three.js')
  script(src='/game.js')

Common mistakes

Phug's template inheritance is a powerful feature that allows you to split complex page template structures into smaller, simpler files. However, if you chain many, many templates together, you can make things a lot more complicated for yourself.

Note that only named blocks and mixin definitions can appear at the top (unindented) level of a child template. This is important! Parent templates define a page's overall structure, and child templates can only append, prepend, or replace specific blocks of markup and logic. If a child template tried to add content outside of a block, Phug would have no way of knowing where to put it in the final page.

This includes unbuffered code, which the placement can have an impact and buffered comments as they produce HTML comments which would have nowhere to go in the resulting HTML. (Unbuffered Pug comments, however, can still go anywhere.)

Interpolation

Phug provides operators for a variety of your different interpolative needs.

String Interpolation, Escaped

Consider the following code:

- $title = "On Dogs: Man's Best Friend";
- $author = "enlore";
- $theGreat = "&lt;span>escape!&lt;/span>";

h1= $title
p Written with love by #{$author}
p This will be safe: #{$theGreat}

title follows the basic pattern for evaluating a template local, but the code in between #{ and } is evaluated, escaped, and the result buffered into the output of the template being rendered.

This can be any valid expression, so you can do whatever feels good.

- $msg = "not my inside voice";
p This is #{strtoupper($msg)}

Phug is smart enough to figure out where the expression ends, so you can even include } without escaping.

p No escaping for #{'}'}!

If you need to include a verbatim #{, you can either escape it, or use interpolation.

p Escaping works with \#{$interpolation}
p Interpolation works with #{'#{$interpolation}'} too!

String Interpolation, Unescaped

- $riskyBusiness = "&lt;em>Some of the girls are wearing my mother's clothing.&lt;/em>";
.quote
  p Joel: !{$riskyBusiness}

Caution ! Keep in mind that buffering unescaped content into your templates can be mighty risky if that content comes fresh from your users. Never trust user input!

Tag Interpolation

Interpolation works with Phug as well. Just use the tag interpolation syntax, like so:

p.
  This is a very long and boring paragraph that spans multiple lines.
  Suddenly there is a #[strong strongly worded phrase] that cannot be
  #[em ignored].
p.
  And here's an example of an interpolated tag with an attribute:
  #[q(lang="es") ¡Hola Mundo!]

Wrap an inline Phug tag declaration in #[ and ], and it'll be evaluated and buffered into the content of its containing tag.

Whitespace Control

The tag interpolation syntax is especially useful for inline tags, where whitespace before and after the tag is significant.

By default, however, Phug removes all spaces before and after tags. Check out the following example:

p
  | If I don't write the paragraph with tag interpolation, tags like
  strong strong
  | and
  em em
  | might produce unexpected results.
p.
  If I do, whitespace is #[strong respected] and #[em everybody] is happy.

See the section Plain Text for more information and examples on this topic.

Iteration

Phug supports two primary methods of iteration: each and while.

each

each is the easiest way to iterate over arrays and objects in a template:

PHP-style:

ul
  each $val in [1, 2, 3, 4, 5]
    li= $val

JS-style:

ul
  each val in [1, 2, 3, 4, 5]
    li= val

You can also get the index as you iterate:

PHP-style:

ul
  each $val, $index in ['zero', 'one', 'two']
    li= $index . ': ' . $val

JS-style:

ul
  each val, index in ['zero', 'one', 'two']
    li= index + ': ' + val

Phug also lets you iterate over the keys in an object:

PHP-style:

ul
  each $val, $index in (object) ['one' => 'ONE', 'two' => 'TWO', 'three' => 'THREE']
    li= $index . ': ' . $val

JS-style:

ul
  each val, index in {one: 'ONE', two: 'TWO', three: 'THREE'}
    li= index + ': ' + val

The object or array to iterate over can be a variable, or the result of a function call, or almost anything else.

PHP-style:

- $values = []
ul
  each $val in count($values) ? $values : ['There are no values']
    li= $val

JS-style:

- var values = [];
ul
  each val in values.length ? values : ['There are no values']
    li= val

One can also add an else block that will be executed if the array or object does not contain values to iterate over. The following is equivalent to the example above:

PHP-style:

- $values = []
ul
  each $val in $values
    li= $val
  else
    li There are no values

JS-style:

- var values = [];
ul
  each val in values
    li= val
  else
    li There are no values

You can also use for as an alias of each.

A special feature of Phug (not available in pugjs) also allow you to use for like a PHP for loop:

ul
  for $n = 0; $n &lt; 4; $n++
    li= $n

Last note about the iterations variables scope: be aware they erase previous variables with the same names:

- $index = 'foo'
- $value = 'bar'
each $value, $index in ['key' => 'value']
| $index = #{$index}
| $value = #{$value}

while

You can also use while to create a loop:

- $n = 0
ul
  while $n &lt; 4
    li= $n++

Mixins

Mixins allow you to create reusable blocks of Phug.

//- Declaration
mixin list
  ul
    li foo
    li bar
    li baz
//- Use
+list
+list

Mixins are compiled to functions, and can take arguments:

mixin pet($name)
  li.pet= $name
ul
  +pet('cat')
  +pet('dog')
  +pet('pig')

No need to prefix parameters/variables with $ if you use JS-style

Mixin Blocks

Mixins can also take a block of Phug to act as the content:

mixin article($title)
  .article
    .article-wrapper
      h1= $title
      if $block
        block
      else
        p No content provided

+article('Hello world')

+article('Hello world')
  p This is my
  p Amazing article

No need to prefix parameters/variables with $ if you use JS-style

Important: in pugjs, the block variable is a function representation of the block. In Phug, we just pass a boolean as an helper to know if the mixin call has children elements. So you can use $block anywhere inside the mixin declaration (or just block if you use JS-style) and you will get true if the block is filled, false if it's empty.

Mixin Attributes

Mixins also get an implicit attributes argument, which is taken from the attributes passed to the mixin:

PHP-style:

mixin link($href, $name)
  //- attributes == {class: "btn"}
  a(class!=$attributes['class'], href=$href)= $name

+link('/foo', 'foo')(class="btn")

JS-style:

- var values = [];
mixin link(href, name)
  //- attributes == {class: "btn"}
  a(class!=attributes.class href=href)= name

+link('/foo', 'foo')(class="btn")

Note: The values in attributes by default are already escaped! You should use != to avoid escaping them a second time. (See also unescaped attributes.)

You can also use mixins with &attributes:

PHP-style:

mixin link($href, $name)
  a(href=$href)&attributes($attributes)= $name

+link('/foo', 'foo')(class="btn")

JS-style:

mixin link(href, name)
  a(href=href)&attributes(attributes)= name

+link('/foo', 'foo')(class="btn")

Note: The syntax +link(class="btn") is also valid and equivalent to +link()(class="btn"), since Phug tries to detect if parentheses’ contents are attributes or arguments. Nevertheless, we encourage you to use the second syntax, as you pass explicitly no arguments and you ensure the first parenthesis is the arguments list.

Rest Arguments

You can write mixins that take an unknown number of arguments using the “rest arguments” syntax.

PHP-style:

mixin list($id, ...$items)
  ul(id=$id)
    each $item in $items
      li= $item

+list('my-list', 1, 2, 3, 4)

JS-style:

mixin list(id, ...items)
  ul(id=id)
    each item in items
      li= item

+list('my-list', 1, 2, 3, 4)

Plain Text

Phug provides four ways of getting plain text — that is, any code or text content that should go, mostly unprocessed, directly into the rendered HTML. They are useful in different situations.

Plain text does still allow tag and string interpolation, but because plain text is not escaped, you can also include literal HTML.

One common pitfall here is managing whitespace in the rendered HTML. We'll talk about that at the end of this chapter.

Inline in a Tag

The easiest way to add plain text is inline. The first term on the line is the tag itself. Everything after the tag and one space will be the text contents of that tag. This is most useful when the plain text content is short (or if you don't mind lines running long).

p This is plain old &lt;em>text&lt;/em> content.

Literal HTML

Whole lines are also treated as plain text when they begin with a left angle bracket (<), which may occasionally be useful for writing literal HTML tags in places that could otherwise be inconvenient. For example, one use case is conditional comments. Since literal HTML tags do not get processed, they do not self-close, unlike Phug tags.

&lt;html>

body
  p Both open and close of the html tag are considered
  p as a line of literal HTML.

&lt;/html>

Warning: In pugjs, indent the content (body in this example) has no incidence. In pugjs, only lines starting with &lt; are literal HTML no mater the indent.

But in Phug (and so pug-php too), we consider that indented content after a line starting with &lt; is also literal HTML and so not processed, so if you indent body in this example, it will become unprocessed text content of the <html> tag.

This feature allow you to copy-paste indented HTML as it from anywhere to your templates:

.foo
  #bar
    <p>
      Unprocessed text #{'except for interpolations'}
    </p>
    <p>
      Title
      <a href="/link">
        Button
      </a>
    </p>
    p This is #[strong Phug] code again

Piped Text

Another way to add plain text to templates is to prefix a line with a pipe character (|). This method is useful for mixing plain text with inline tags, as we discuss later, in the Whitespace Control section.

p
  | The pipe always goes at the beginning of its own line,
  | not counting indentation.

Block in a Tag

Often you might want large blocks of text within a tag. A good example is writing JavaScript and CSS code in the script and style tags. To do this, just add a . right after the tag name, or after the closing parenthesis, if the tag has attributes.

There should be no space between the tag and the dot. Plain text contents of the tag must be indented one level:

script.
  if (usingPug)
    console.log('you are awesome')
  else
    console.log('use pug')

You can also create a dot block of plain text after other tags within the parent tag.

div
  p This text belongs to the paragraph tag.
  br
  .
    This text belongs to the div tag.

Whitespace Control

Managing the whitespace of the rendered HTML is one of the trickiest parts about learning Pug. Don't worry, though, you'll get the hang of it soon enough.

You just need to remember two main points about how whitespace works. When compiling to HTML:

  1. Phug removes indentation, and all whitespace between elements.
    • So, the closing tag of an HTML element will touch the opening tag of the next. This is generally not a problem for block-level elements like paragraphs, because they will still render as separate paragraphs in the web browser (unless you have changed their CSS display property). See the methods described below, however, for when you do need to insert space between elements.
  2. Phug preserves whitespace within elements, including:
    • all whitespace in the middle of a line of text,
    • leading whitespace beyond the block indentation,
    • trailing whitespace,
    • line breaks within a plain text block.

So… Phug drops the whitespace between tags, but keeps the whitespace inside them. The value here is that it gives you full control over whether tags and/or plain text should touch. It even lets you place tags in the middle of words.

| You put the em
em pha
| sis on the wrong syl
em la
| ble.

The trade-off is that it requires you to think about and take control over whether tags and text touch.

If you need the text and/or tags to touch — perhaps you need a period to appear outside the hyperlink at the end of a sentence — this is easy, as it’s basically what happens unless you tell Phug otherwise.

a ...sentence ending with a link
| .

If you need to add space, you have a few options:

Recommended Solutions

You could add one or more empty piped lines — a pipe with either spaces or nothing after it. This will insert whitespace in the rendered HTML.

| Don't
|
button#self-destruct touch
|
| me!

If your inline tags don’t require many attributes, you may find it easiest to use tag interpolation, or literal HTML, within a plain text block.

p.
  Using regular tags can help keep your lines short,
  but interpolated tags may be easier to #[em visualize]
  whether the tags and text are whitespace-separated.

Not recommended

Depending on where you need the whitespace, you could add an extra space at the beginning of the text (after the block indentation, pipe character, and/or tag). Or you could add a trailing space at the end of the text.

NOTE the trailing and leading spaces here:

| Hey, check out 
a(href="http://example.biz/kitten.png") this picture
|  of my cat!

The above solution works perfectly well, but is admittedly perhaps a little dangerous: many code editors by default will remove trailing whitespace on save. You and all your contributors may have to configure your editors to prevent automatic trailing whitespace removal.

Tags

By default, text at the start of a line (or after only white space) represents an HTML tag. Indented tags are nested, creating the tree structure of HTML.

ul
  li Item A
  li Item B
  li Item C

Phug also knows which elements are self-closing:

img

Block Expansion

To save space, Phug provides an inline syntax for nested tags.

a: img

Self-Closing Tags

Tags such as img, meta, and link are automatically self-closing (unless you use the XML doctype).

You can also explicitly self close a tag by appending the / character. Only do this if you know what you're doing.

foo/
foo(bar='baz')/

Rendered Whitespace

Whitespace is removed from the beginning and end of tags, so that you have control over whether the rendered HTML elements touch or not. Whitespace control is generally handled via plain text.

JS-style expressions

By using JS-style You no longer need $ in front of your variables but this is just the beginning of the amazing transformations provided by js-phpize. Here is a list of more advanced features:

Chaining

- foo = {bar: [{biz: [42]}]}

p=foo.bar[0].biz[0]

Array/object access

:php
  $obj = (object) ['foo' => 'bar'];
  $arr = ['foo' => 'bar'];

p=obj.foo
p=arr.foo
p=obj['foo']
p=arr['foo']

Method post-pone call

:php
  class Foo
  {
    public function bar()
    {
      return 42;
    }
  }

  $foo = new Foo;

- method = foo.bar

p=method()

Getter call

:php
  class Foo
  {
    public function __isset($name)
    {
      return $name === 'abc';
    }

    public function __get($name)
    {
      return "magic getter for $name";
    }

    public function getBar()
    {
      return 42;
    }
  }

  $foo = new Foo;

p=foo.bar
p=foo['bar']
p=foo.abc
p=foo['abc']
p=foo.nothandled
p=foo['nothandled']
:php
  class Machin implements ArrayAccess
  {
    public function offsetExists($name)
    {
      return $name === 'abc';
    }

    public function offsetGet($name)
    {
      return "magic getter for $name";
    }

    public function offsetSet($name, $value) {}

    public function offsetUnset($name) {}

    public function getTruc()
    {
      return 42;
    }
  }

  $machin = new Machin;

p=machin.truc
p=machin['truc']
p=machin.abc
p=machin['abc']
p=machin.nongere
p=machin['nongere']

Closure

p=implode(', ', array_filter([1, 2, 3], function (number) {
  return number % 2 === 1;
}))

Array.prototype emulation

- arr = [1, 2, 3]
p=arr.length
p=arr.filter(nombre => nombre & 1).join(', ')
p=arr.indexOf(2)
p=arr.slice(1).join('/')
p=arr.reverse().join(', ')
p=arr.splice(1, 2).join(', ')
p=arr.reduce((i, n) => i + n)
p=arr.map(n => n * 2).join(', ')
- arr.forEach(function (num) {
  div=num
- })
//- Yes all that is converted into PHP

String.prototype emulation

- text = "abcdef"
p=text.length
p=text[1]
p=text[-1]
p=text.substr(2, 3)
p=text.charAt(3)
p=text.indexOf('c')
p=text.toUpperCase()
p=text.toUpperCase().toLowerCase()
p=text.match('d')
p=text.split(/[ce]/).join(', ')
p=text.replace(/(a|e)/, '.')

Options

Equivalents for pugjs options

filename string

The name of the file being compiled. This is used if no file specified for path resolve and exception file detail:

Phug::compile("\n  broken\nindent");

By default, when you compile, render or display an input string, Phug give no source file info in exception and cannot resolve relative include/extend.

Failed to parse: Failed to outdent: No parent to outdent to. Seems the parser moved out too many levels.
Near: indent

Line: 3
Offset: 1 

With the filename option, it provides a fallback:

Phug::setOption('filename', 'foobar.pug');
Phug::compile("\n  broken\nindent");
...
Line: 3
Offset: 1
Path: foobar.pug

But, this option is ignored if you specify locally the filename:

Phug::setOption('filename', 'foobar.pug');
Phug::compile("\n  broken\nindent", 'something.pug');
...
Line: 3
Offset: 1
Path: something.pug 

The same goes for compileFile, renderFile and displayFile:

Phug::displayFile('something.pug');
...
Line: 3
Offset: 1
Path: something.pug 

basedir string

The root directory of all absolute inclusion. This option is supported for pugjs compatibility reason, but we recommend you to use the paths option instead. It has the same purpose but you can specify an array of directories that will be all tried (in the order you give them) to resolve absolute path on include/extend and the first found is used.

doctype string

If the doctype is not specified as part of the template, you can specify it here. It is sometimes useful to get self-closing tags and remove mirroring of boolean attributes. See doctype documentation for more information.

Note: the XML doctype can break when open short tags are enabled, that's why by default, we replace <?xml with <<?= "?" ?>xml when short_open_tag or hhvm.enable_short_tags is On (this is the behavior when short_open_tag_fix option is set to its default value: "auto") but you can switch this option to true (to always enable the fix) or false (to always disable it).

pretty boolean | string

[Deprecated.] Adds whitespace to the resulting HTML to make it easier for a human to read using ' ' as indentation. If a string is specified, that will be used as indentation instead (e.g. '\t'). We strongly recommend against using this option. Too often, it creates subtle bugs in your templates because of the way it alters the interpretation and rendering of whitespace, and so this feature is going to be removed. Defaults to false.

filters array

Associative array of custom filters. Defaults to [].

Phug::setOption('filters', [
  'my-filter' => function ($text) {
    return strtoupper($text);
  },
]);
Phug::render("div\n  :my-filter\n    My text");

Returns:

<div>MY TEXT</div>

You can also use the method setFilter and your callback can take options:

Phug::setFilter('add', function ($text, $options) {
  return $text + $options['value'];
});
Phug::render("div\n  :add(value=4) 5");

Returns:

<div>9</div>

And note that you can also use callable classes (classes that contains an __invoke method) instead of a simple callback function for your custom filters.

self boolean | string

Use a self namespace to hold the locals. Instead of writing variable you will have to write self.variable to access a property of the locals object. Defaults to false.

Phug::setOption('self', true);
Phug::render('p=self.message', [
    'message' => 'Hello',
]);

Will output:

<p>Hello</p>

And you can pass any string as namespace as long as it's a valid variable name, so the following is equivalent:

Phug::setOption('self', 'banana');
Phug::render('p=banana.message', [
    'message' => 'Hello',
]);

debug boolean

If set to true, when an error occurs at render time, you will get a complete stack trace including line and offset in the original pug source file.

In production, you should set it to false to fasten the rendering and hide debug information. It's done automatically if you use framework adapters such as pug-symfony or laravel-pug.

shared_variables / globals array

List of variables stored globally to be available for any further calls to render, renderFile, display or displayFile.

globals and shared_variables are 2 different options merged together, globals only exists to provide an equivalent to the pugjs option. And methods like ->share and ->resetSharedVariables only impact the the shared_variables option, so we recommend you to use shared_variables.

Phug::setOptions([
    'globals' => [
        'top' => 1,
        'right' => 1,
        'bottom' => 1,
        'left' => 1,
    ],
    'shared_variables' => [
        'right' => 2,
        'bottom' => 2,
        'left' => 2,
    ],
]);

Phug::share([
    'bottom' => 3,
    'left' => 3,
]);

Phug::display('="$top, $right, $bottom, $left"', [
    'left' => 4,
]);

Phug::resetSharedVariables();

Phug::display('="$top, $right, $bottom, $left"', [
    'left' => 5,
]);

The first display will outputs:

1, 2, 3, 4

The second display will outputs:

1, 1, 1, 5

As you can note in this examples, locals always have the precedence and shared_variables has the precedence on globals.

The same code would look like this using pug-php:

$pug = new Pug([
    'globals' => [
        'top' => 1,
        'right' => 1,
        'bottom' => 1,
        'left' => 1,
    ],
    'shared_variables' => [
        'right' => 2,
        'bottom' => 2,
        'left' => 2,
    ],
]);

// or $pug->setOptions([...])

$pug->share([
    'bottom' => 3,
    'left' => 3,
]);

$pug->display('=top + ", " + right + ", " + bottom + ", " + left', [
    'left' => 4,
]);

$pug->resetSharedVariables();

$pug->display('=top + ", " + right + ", " + bottom + ", " + left', [
    'left' => 5,
]);

cache_dir boolean | string

If set to true, compiled templates are cached. filename must be set as the cache key (automatically done when using renderFile or displayFile). Defaults to false.

This option can also be a directory path where cached files will be stored.

This option is automatically handled when you use framework adapters such as pug-symfony or laravel-pug.

pug-php also provide a cache alias for this option to match pug-php 2 and pugjs options. It can also provide a better semantic when using boolean value, and cache_dir stay more appropriate when passing a string.

See compile-directory command to cache a whole directory.

We recommend to use this command when you deploy your applications in production, it also allow you to set the option up_to_date_check to false and get better performance.

Language

paths array

Specify list of paths to be used for include and extend with absolute paths. Example:

Phug::setOption('paths', [
  __DIR__.'/bundle-foo',
  __DIR__.'/resources/views',
]);

Phug::render('include /directory/file.pug');

As /directory/file.pug starts with a slash, it's considered as an absolute path. Phug will first try to find it in the first directory you specified: __DIR__.'/bundle-foo', if it does not exist in it, it will search in the next __DIR__.'/resources/views'.

extensions array

List of file extensions Phug will consider as pug files. ['', '.pug', '.jade'] by default.

This means:

//- my-file.pug
p Foo
// non-pug-file.js
alert('foo');
//- index.pug
//-
  my-file.pug can be imported (included or extended)
  with or without extension, and its content will be
  parsed as pug content.
  (cannot be tested in this live editor as includes
  are emulated)
include my-file.pug
//- include my-file
//-
  non-pug-file.js will be included as text
include non-pug-file.js

So the extensions extension allow you to pass an other list of extensions to be handled by Phug and added automatically to include/extend paths if missing.

default_doctype string

Doctype to use if not specified as argument. "html" by default.

This means:

doctype
//- will automatically fallback to:
doctype html

default_tag string

By default, when you do not specify a tag name, Phug fallback to a div tag:

.foo
#bar(a="b")
(c="d") Hello

The same code with Phug::setOption('default_tag', 'section') would render as:

<section class="foo"></section>
<section id="bar" a="b"></section>
<section c="d">Hello</section>

attributes_mapping array

This option allow you to replace attributes by others:

Phug::setOption('attributes_mapping', [
  'foo' => 'bar',
  'bar' => 'foo',
  'biz' => 'zut',
]);
Phug::display('p(foo="1" bar="2" biz="3" zut="4" hop="5")');

Will output:

<p bar="1" foo="2" zut="3" zut="4" hop="5"></p>

Profiling

Phug embed a profiler module to watch, debug or limit memory consumption and execution time.

memory_limit integer

Set a memory limit usage. -1 by default would mean no limit. But if the debug option is true, it automatically pass to 50*1024*1024 (50MB).

If the profiler detect the memory usage exceed the limit, it will throw an exception. But be aware, if this limit is greater than the machine limit or the PHP limit, the Phug limit will have no effect.

execution_max_time integer

Set an execution time limit. -1 by default would mean no limit. But if the debug option is true, it automatically pass to 30*1000 (30 seconds).

If the profiler detect Phug is running for a longer time than the specified limit, it will throw an exception. But be aware, if this limit is greater than the PHP limit, the Phug limit will have no effect.

enable_profiler boolean

When set to true, it will output on render a timeline you can inspect in your browser to see wich token/node take longer to lex/parse/compile/render.

When enabled, it comes with a subset of options you can also edit, these are the default values:

'profiler' => [
    'time_precision' => 3,     // time decimal precision
    'line_height'    => 30,    // timeline height
    'display'        => true,  // output the result
                               // can be true or false
    'log'            => false, // log the result in a file
                               // can be a file path or false
],

detailed_dump boolean

The lexer, the parser and the compiler all have a ->dump() method that allow you to see the state of each process.

By setting detailed_dump to true you can dump more details (right now this option is only available for the parser).

Errors

error_reporting callable | int

Allow to handle PHP errors display that comes up during the template execution. By default, errors raised by current PHP setting (see error_reporting) are turned into exception Phug is able to trace the origine in the template pug code. Other errors are hidden.

You can pass an custom error level that will override the PHP setting.

$renderer = new Renderer([
    'error_reporting' => E_ALL ^ E_NOTICE,
]);

$renderer->render('p=$foo["bar"]', ['foo' => []]);
// Outputs <p></p> since E_NOTICE are ignored

$renderer = new Renderer([
    'error_reporting' => E_ALL,
]);

$renderer->render('p=$foo["bar"]', ['foo' => []]);
// Throws an E_NOTICE error wrapped in Phug exception

You also can pass a callback for a finest handling:

$renderer = new Renderer([
    'error_reporting' => function ($number, $message, $file, $line) {
        if (strpos($message, 'hidden') !== false) {
            return null; // No errors displayed
        }

        if ($number === E_NOTICE) {
            return false; // Display the error in the template
        }

        // Stop the execution and throws an exception for any other error
        throw new \ErrorException($message, 0, $number, $file, $line);
    },
]);

error_handler callable

Set a callback method to handle Phug exceptions. null by default.

html_error boolean

Display errors as HTML (by default, it's false when run on CLI, true when run in browser).

color_support boolean

Used to enable color in CLI errors output, by default we will try to detect if the console used support colors.

error_context_lines integer

We give you some context on error code dump, 7 lines above and below the error line by default. But you can pass to this option any number to get more or less context.

exit_on_error

When debug option is true, errors not handled will quit the process using exit(1), to disable this behavior, your can set the option exit_on_error to false.

Events

Events are a very useful way to intercept different process steps to get, change or manipulate objects and parameters.

Example:

$renderer = new \Phug\Renderer([
    'on_render' => function (\Phug\Renderer\Event\RenderEvent $event) {
        // Get current parameters
        $parameters = $event->getParameters();
        // If you pass laurel in your parameters
        if (isset($parameters['laurel'])) {
            // Then you will need hardy
            $parameters['hardy'] = '45 Minutes from Hollywood';
        }

        // Set new parameters
        $event->setParameters($parameters);
    },
]);

$renderer->display('p=$hardy', [
    'laurel' => true,
]);

Will output:

<p>45 Minutes from Hollywood</p>

The same works with pug-php:

$renderer = new Pug([
    'on_render' => function (\Phug\Renderer\Event\RenderEvent $event) {
        // Get new parameters
        $parameters = $event->getParameters();
        // If you pass laurel in your parameters
        if (isset($parameters['laurel'])) {
            // Then you will need hardy
            $parameters['hardy'] = '45 Minutes from Hollywood';
        }

        // Set new parameters
        $event->setParameters($parameters);
    },
]);

$renderer->display('p=hardy', [
    'laurel' => true,
]);

Note that all on_* options are initial options, it means you cannot set them after renderer initialization or using the facade (Phug::setOption() or Pug\Facade::setOption()).

However, you can attach/detach events this way (using facade or not):

function appendHardy(\Phug\Renderer\Event\RenderEvent $event) {
    // Get new parameters
    $parameters = $event->getParameters();
    // If you pass laurel in your parameters
    if (isset($parameters['laurel'])) {
        // Then you will need hardy
        $parameters['hardy'] = '45 Minutes from Hollywood';
    }

    // Set new parameters
    $event->setParameters($parameters);
}

Phug::attach(\Phug\RendererEvent::RENDER, 'appendHardy');

Phug::display('p=$hardy', [
    'laurel' => true,
]);

Phug::detach(\Phug\RendererEvent::RENDER, 'appendHardy');

Phug::display('p=$hardy', [
    'laurel' => true,
]);

Will output <p>45 Minutes from Hollywood</p> then <p></p>.

So for all the on_* options below, we will give you the initial option name, the event constant (to attach/detach) and the event class with a link to the API documentation that give you all methods available for this event (all values you can get and set).

Before listing all the events, here is a overview of the processes timeline:

Phug processes timeline

Plain lines are active process, dotted line are waiting for an other process.

So you can see the rendering start event will be triggered before other events even if the real active rendering process only start after all other processes end.

The same goes for compilation that first wait for the parser to give him the complete nodes tree before starting the active compilation, then it will wait for the formatting process before calling the output event.

Parsing, lexing and reading are parallel processes, the reader identify string chunk such as 2 spaces at the beginning of the line, the lexer convert this string into a indent token, then so parser know it have to enter one level and append next nodes as children of the upper node. Then this process is repeated token by token until all the input string is consumed.

on_render callable

Is triggered before a file or a string being rendered or displayed.

In some cases you may hesitate between on_render and on_compile, you should maybe check on_compile option.

Event constant: \Phug\RendererEvent::RENDER

Event type: \Phug\Renderer\Event\RenderEvent

Parameters you can get/set:

  • input: input string if render/display has been called
  • path: input file if renderFile/displayFile has been called
  • method: the method that have been called "render", "display", "renderFile" or "displayFile".
  • parameters: local variables passed for the view rendering

on_html callable

Is triggered after a file or a string being rendered or displayed.

Event constant: \Phug\RendererEvent::HTML

Event type: \Phug\Renderer\Event\HtmlEvent

Parameters you can get/set:

  • renderEvent: link to the initial RenderEvent (see above)
  • result: returned result (by render or renderFile)
  • buffer: output buffer (what display or displayFile is about to display) typically the HTML code, but can be XML or any custom format implemented
  • error: the exception caught if an error occured

on_compile callable

Is triggered before a file or a string being compiled.

This option is different from on_render in the following points:

  • compile() and compileFile() methods will trigger a compile event but not the render event,
  • compile event is triggered after the render event,
  • and render and display methods will always trigger a render event but will trigger a compile event only except when compiled template is served from cache (cache settings in use and template up to date).

The compile process transform pug code into PHP code that is always the same for a given template no matter the locals values, when render process execute that PHP code to get HTML, XML or any final output with locals replaced with their values. That's why the render event also have parameters with locals variables values you can get and set.

Event constant: \Phug\CompilerEvent::COMPILE

Event type: \Phug\Compiler\Event\CompileEvent

Parameters you can get/set:

  • input: input string/source file content
  • path: input file if compileFile/renderFile/displayFile has been called

on_output callable

Is triggered after a file or a string being compiled.

Event constant: \Phug\CompilerEvent::OUTPUT

Event type: \Phug\Compiler\Event\OutputEvent

Parameters you can get/set:

  • compileEvent: link to the initial CompileEvent (see above)
  • output: output PHP code that can be executed to get the final output

on_node callable

Is triggered for each node before its compilation.

To well understand what is a node, you can use the parse mode of the live editor, here is an example:

doctype
html
  head
    title=$var
  body
    h1 Text
    footer
      | Text
      =date('Y')

You can see the Phug parser transform the pug code into a tree of nodes. Then the compiler will compile each of these nodes into elements recursively. And the formatter will turn the tree of elements into the compiled PHP code.

Event constant: \Phug\CompilerEvent::NODE

Event type: \Phug\Compiler\Event\NodeEvent

Parameters you can get/set:

  • node: the node instance created by the parser that is about to be compiled

on_element callable

Is triggered for each node after its compilation.

Event constant: \Phug\CompilerEvent::ELEMENT

Event type: \Phug\Compiler\Event\ElementEvent

Parameters you can get/set:

  • nodeEvent: link to the initial NodeEvent (see above)
  • element: the compiled element

on_format callable

Is triggered for each element before being formatted.

The formatting step is a process that take elements tree (output object representation as stored after the compilation, not to be confused with the nodes tree that represent the input and is given by the parsing before the compilation) and convert these elements into a compiled PHP capable to render them.

The formatting is recursive, when passing an element, it also format its children inside.

Before formatting process

p=$var

After formatting process

p=$var

Event constant: \Phug\FormatterEvent::FORMAT

Event type: \Phug\Formatter\Event\FormatEvent

Parameters you can get/set:

  • element: the compiled element (implements ElementInterface)
  • format: the format in use (implements FormatInterface) will be by default BasicFormat or the format set for your doctype (for example, if you have set doctype html) the format here is HtmlFormat (see format and default_format options)

on_stringify callable

Is triggered for each element after being formatted.

Event constant: \Phug\FormatterEvent::STRINGIFY

Event type: \Phug\Formatter\Event\StringifyEvent

Parameter you can get/set:

  • output: output string (HTML and PHP code) Parameter you can get:
  • formatEvent: reference to the source format event (FormatEvent), modifying properties on this event will have no effect has it has already been triggered.

on_new_format callable

Is triggered when the format change (for example when you set the doctype).

Event constant: \Phug\FormatterEvent::NEW_FORMAT

Event type: \Phug\Formatter\Event\NewFormatEvent

Parameters you can get/set:

  • formatter: the current formatter in use (implements Formatter)
  • format: the new format instance (implements FormatInterface)

on_dependency_storage callable

Is triggered when a dependency is retrieved from the dependency storage.

Dependency storage is used to store functions needed at rendering time and dump them in the compiled PHP code, it's used for example in assignments:

p&attributes(['foo' => 'bar'])

Those methods are stored by default in $pugModule (see dependencies_storage option to change it).

Event constant: \Phug\FormatterEvent::DEPENDENCY_STORAGE

Event type: \Phug\Formatter\Event\DependencyStorageEvent

Parameters you can get/set:

  • dependencyStorage: storage variable as string (for example: $pugModule['Phug\\Formatter\\Format\\BasicFormat::attributes_assignment'])

on_parse callable

Is triggered before the parsing process.

Event constant: \Phug\ParserEvent::PARSE

Event type: \Phug\Parser\Event\ParseEvent

Parameters you can get/set:

  • input: input string/source file content
  • path: input file if compileFile/renderFile/displayFile has been called
  • stateClassName: class name of the state object that is about be created for parsing
  • stateOptions: options array that will be passed to the sate at its creation

on_document callable

Is triggered when the parser finished to parse a whole document.

Event constant: \Phug\ParserEvent::DOCUMENT

Event type: \Phug\Parser\Event\NodeEvent

Parameters you can get/set:

  • node: the document as parser node instance

on_state_enter callable

Is triggered when the parser enter a node.

Event constant: \Phug\ParserEvent::STATE_ENTER

Event type: \Phug\Parser\Event\NodeEvent

Parameters you can get/set:

  • node: the node the parser entered in

on_state_leave callable

Is triggered when the parser leave a node.

Event constant: \Phug\ParserEvent::STATE_LEAVE

Event type: \Phug\Parser\Event\NodeEvent

Parameters you can get/set:

  • node: the node the parser left out

on_state_store callable

Is triggered when the parser store and append a node to the document tree.

Event constant: \Phug\ParserEvent::STATE_STORE

Event type: \Phug\Parser\Event\NodeEvent

Parameters you can get/set:

  • node: the node the parser stored

on_lex callable

Is triggered when the lexer starts to tokenize an input string.

Event constant: \Phug\LexerEvent::LEX

Event type: \Phug\Lexer\Event\LexEvent

Parameters you can get/set:

  • input: input string/source file content
  • path: input file if compileFile/renderFile/displayFile has been called
  • stateClassName: class name of the state object that is about be created for lexing
  • stateOptions: options array that will be passed to the sate at its creation

on_lex_end callable

Is triggered when the lexer finished to tokenize an input string.

Event constant: \Phug\LexerEvent::LEX_END

Event type: \Phug\Lexer\Event\EndLexEvent

Parameters you can get/set:

  • lexEvent: link to the initial LexEvent

on_token callable

Is triggered each time the lexer is about to yield a token and send it to the parser.

Event constant: \Phug\LexerEvent::TOKEN

Event type: \Phug\Lexer\Event\TokenEvent

Parameters you can get/set:

  • token: the token created by the lexer
  • tokenGenerator: is null by default, but if you set an iterator for this property, it will replace the token

Some examples:

$renderer = new \Phug\Renderer([
  'on_token' => function (\Phug\Lexer\Event\TokenEvent $event) {
    $token = $event->getToken();
    if ($token instanceof \Phug\Lexer\Token\TagToken) {
      $token->setName('a');
    }
  },
]);
$renderer->display('div'); // <a></a>

$renderer = new \Phug\Renderer([
  'on_token' => function (\Phug\Lexer\Event\TokenEvent $event) {
    if ($event->getToken() instanceof \Phug\Lexer\Token\TagToken) {
      $text = new \Phug\Lexer\Token\TextToken();
      $text->setValue('Hello');
      $event->setToken($text);
    }
  },
]);
$renderer->display('div'); // Hello

$renderer = new \Phug\Renderer([
  'on_token' => function (\Phug\Lexer\Event\TokenEvent $event) {
    if ($event->getToken() instanceof \Phug\Lexer\Token\TextToken) {
      $event->setTokenGenerator(new \ArrayIterator([
        (new \Phug\Lexer\Token\TagToken())->setName('div'),
        (new \Phug\Lexer\Token\ClassToken())->setName('foo'),
        (new \Phug\Lexer\Token\IdToken())->setName('bar'),
      ]));
    }
  },
]);
$renderer->display('| Hello'); // <div id="bar" class="foo"></div>

function replaceTextToken(\Phug\Lexer\Token\TextToken $token) {
  if (preg_match('/^(\D+)(\d+)$/', $token->getValue(), $match) {
    list(, $chars, $digit) = $match;
    for ($i = 0; $i < $digit; $i++) {
      yield (new \Phug\Lexer\Token\TagToken())->setName($chars);
    }
  }
}

$renderer = new \Phug\Renderer([
  'on_token' => function (\Phug\Lexer\Event\TokenEvent $event) {
    $token = $event->getToken();
    if ($token instanceof \Phug\Lexer\Token\TextToken) {
      $event->setTokenGenerator(replaceTextToken($token));
    }
  },
]);
$renderer->display("|i2\n|bk3"); // <i></i><i></i><bk></bk><bk></bk><bk></bk>

Add-ons

Here are a list of entry points to add contents and custom behaviors:

macros array

macros option or the macro method allow you to add methods on both Phug facade and Phug\Renderer instances (and if you use Pug-php, it also add them to the Pug\Facade facade and Pug instances).

Phug::macro('displayMessage', function ($message) {
  static::display('p=$message', [
    'message' => $message,
  ]);
});

Phug::displayMessage('Hello');

This will display:

<p>Hello</p>

Or an example with the option syntax:

Phug::setOption('macros', [
  'getOne' => function () {
    return 1;
  },
  'getTwo' => function () {
    return 2;
  },
]);

echo Phug::getOne() + Phug::getTwo();

Outputs: 3.

modules array

Modules can add anything else by manipulating options and events. For example if you use Pug-php with default options, you already have the js-phpize module for Phug enabled. This would be equivalent to require it with composer and add it this way to Phug:

$renderer = new \Phug\Renderer([
    'modules' => [
        \JsPhpize\JsPhpizePhug::class,
    ],
]);

$renderer->display('p=userName.substr(0, 3)', [
    'userName' => 'Bobby',
]);

This code output <p>Bob</p> but it would failed without the module added because JS expressions are not natively handled.

You also can create your own module by extending one of the following class:

They all extend the \Phug\Util\AbstractModule class

Here is an example:

class MyModule extends \Phug\AbstractCompilerModule
{
    public function __construct(\Phug\Util\ModuleContainerInterface $container)
    {
        // Here you can change some options
        $container->setOption('default_tag', 'p');

        parent::__construct($container);
    }

    public function getEventListeners()
    {
        // Here you can attach some events
        return [
            \Phug\CompilerEvent::NODE => function (\Phug\Compiler\Event\NodeEvent $event) {
                // Do something before compiling a node
                $node = $event->getNode();
                if ($node instanceof \Phug\Parser\Node\MixinNode) {
                    $tag = new \Phug\Parser\Node\ElementNode(
                        $event->getNode()->getToken()
                    );
                    $attribute = new \Phug\Parser\Node\AttributeNode();
                    $attribute->setName('mixin');
                    $attribute->setValue('"'.$node->getName().'"');
                    $tag->getAttributes()->attach($attribute);
                    $event->setNode($tag);
                }
            },
        ];
    }
}

$renderer = new \Phug\Renderer([
    'modules' => [
        MyModule::class,
    ],
]);

$renderer->display('mixin foo()');

This will output:

<p mixin="foo"></p>

Modules are dispatched to the renderer/compiler/formatter/parser/lexer according to the interface implemented (or the abstract module class extended).

But you can specify explicitly a target with following options:

compiler_modules array

Modules reserved to the compiler (see modules).

formatter_modules array

Modules reserved to the formatter (see modules).

parser_modules array

Modules reserved to the parser (see modules).

lexer_modules array

Modules reserved to the lexer (see modules).

includes array

It simply includes pug files before each compilation:

// Add to previous includes to avoid erase existing includes if any
Phug::setOption('includes', Phug::getOption('includes') + [
    'mixins.pug',
]);

Phug::display('+foo()');

If the file mixins.pug contains a foo mixin declaration, this code will properly call it.

It's a good way to make a mixins library available in any template.

filter_resolvers array

Set a dynamic way to resolve a filter by name when not found.

Phug::setOption('filter_resolvers', Phug::getOption('filter_resolvers') + [
    function ($name) {
        if (mb_substr($name, 0, 3) === 'go-') {
            return function ($content) use ($name) {
                return '<a href="'.mb_substr($name, 3).'">'.$content.'</a>';
            };
        }
    },
]);

Phug::display(':go-page');
// <a href="page"></a>
Phug::display(':go-action Action');
// <a href="action">Action</a>

The resolver is used only if the given filter name does not exists and if previous resolver did return nothing. If no resolver return anything, then an Unknown filter error is thrown.

keywords array

Allow you to create your own custom language keywords:

Phug::setOption('keywords', Phug::getOption('keywords') + [
    'myKeyword' => function ($value, \Phug\Formatter\Element\KeywordElement $element, $name) {
        $nodes = isset($element->nodes) ? count($element->nodes) : 0;

        return "This is a $name keyword with $value value and $nodes children.";
    },
]);


Phug::display('
div
  myKeyword myValue
    span 1
    span 2
');

Display:

<div>This is a myKeyword keyword with myValue value and 2 children.</div>

When the keyword callback returns a string, it replaces the whole keyword with its children.

But you can also return an array with begin and end that will preserves the children nodes:

Phug::setOption('keywords', Phug::getOption('keywords') + [
    'foo' => function ($value) {
        return [
            'begin' => '<div class="'.$value.'">',
            'end'   => '</div>',
        ];
    },
]);


Phug::display('
myKeyword myValue
  span 1
  span 2
');

Display:

<div class="myValue">
  <span>1</span>
  <span>2</span>
</div>

Or you can specify an array with beginPhp and endPhp to wrap children with a begin string and an end string both wrapped themselves in <?php ?>.

Phug::setOption('keywords', Phug::getOption('keywords') + [
    'repeat' => function ($value) {
        return [
            'beginPhp' => 'for ($i = 0; $i < '.$value.'; $i++) {',
            'endPhp'   => '}',
        ];
    },
]);


Phug::display('
repeat 3
  section
');

Display:

<section></section>
<section></section>
<section></section>

php_token_handlers array

This setting allow you to intercept any PHP token and replace it with an other string of PHP code. It also works with expressions inside each loop, if statements, etc. and even if the expression come from a translation. For example, if you use js-phpize ](#utiliser-des-expressions-javascript) and write a(href=route('profile', {id: 3})), then {id: 3} is converted to array('id' => 3) and the array( token can be intercepted with its PHP identifier T_ARRAY.

Phug::setOption('php_token_handlers', [
    T_DNUMBER => 'round(%s)',
]);

Phug::display('
- $floatingNumber = 9.54
- $text = "9.45"
strong=$floatingNumber
| !=
strong=$text
');

Output:

<strong>10</strong>!=<strong>9.45</strong>

If you pass a string, sprintf is used to handle it, so if it contains %s, it will be replaced with the input token string.

You can also use a callback function:

Phug::setOption('php_token_handlers', [
    '(' => function ($tokenString, $index, $tokens, $checked, $formatInstance) {
        return '__call_something('.mt_rand(0, 4).', ';
    },
]);

echo Phug::compile('b=($variable)');

This will call the callback function for each ( token found in an expression and replace it with the result returned by the function (for example __call_something(2, ).

This callback function receives 5 arguments:

  • $tokenString is the input token as string;
  • $index is the position of the token in the expression;
  • &$tokens is an array with all tokens of the expression, it is passed by reference, it means you can modify/add/remove tokens from it (will works only for tokens come after the current token handled);
  • $checked is the check flag of the expression (=exp() is checked, ?=exp() is not checked);
  • $formatInstance the format instance that format the current expression (implements FormatInterface).

Be careful, we use php_token_handlers to handle checked expressions. It means you can replace the T_VARIABLE PHP token handler with your own like in the example below:

Phug::setOption('php_token_handlers', [
    T_VARIABLE => function ($variable) {
        if (mb_substr($variable, 0, 5) === '$env_') {
            return '$_'.strtoupper(mb_substr($variable, 5));
        }

        return $variable;
    },
]);

Phug::display('
b=$normalVariable
i=$env_super["METHOD"]
', [
    'normalVariable' => 'foo',
    '_SUPER' => [
        'METHOD' => 'bar',
    ],
]);

Output:

<b>foo</b><i>bar</i>

But if you do, it erase the initial checked expressions handling:

Phug::display('p=$missing'); // output <p></p>

Phug::setOption('php_token_handlers', [
    T_VARIABLE => function ($variable) {
        return $variable;
    },
]);

Phug::display('p=$missing'); // Throw: Undefined variable: missing

But you still can recall the native variable handler before or after your own treatments:

Phug::setOption('php_token_handlers', [
    T_VARIABLE => function ($tokenString, $index, $tokens, $checked, $formatInstance) {
        // Do something before the check process
        $tokenString .= 'Suffix';
        // Do the check process:
        $tokenString = $formatInstance->handleVariable($tokenString, $index, $tokens, $checked);
        // Do something after the check process
        $tokenString = 'strtoupper('.$tokenString.')';

        return $tokenString;
    },
]);

Phug::display('p=$foo', [
    'fooSuffix' => 'bar',
]);

Output:

<p>BAR</p>

Mixins

mixin_merge_mode string

Alias of allowMixinOverride in Pug-php.

It determines if a new mixin declaration with an existing name will replace the previous one or be ignored:

Phug::setOption('mixin_merge_mode', 'replace');

Phug::display('
mixin foo
  p A

mixin foo
  p B

+foo
');

// Output <p>B</p>

Phug::setOption('mixin_merge_mode', 'ignore');

Phug::display('
mixin foo
  p A

mixin foo
  p B

+foo
');

// Output <p>A</p>

This option is set to "replace" by default.

Formatting

patterns array

Defines how to dump specific parts of elements in PHP.

Default value:

[
  'class_attribute'        => '(is_array($_pug_temp = %s) ? implode(" ", $_pug_temp) : strval($_pug_temp))',
  'string_attribute'       => '
        (is_array($_pug_temp = %s) || is_object($_pug_temp) && !method_exists($_pug_temp, "__toString")
            ? json_encode($_pug_temp)
            : strval($_pug_temp))',
  'expression_in_text'     => '(is_bool($_pug_temp = %s) ? var_export($_pug_temp, true) : $_pug_temp)',
  'html_expression_escape' => 'htmlspecialchars(%s)',
  'html_text_escape'       => 'htmlspecialchars',
  'pair_tag'               => '%s%s%s',
  'transform_expression'   => '%s',
  'transform_code'         => '%s',
  'transform_raw_code'     => '%s',
  'php_handle_code'        => '<?php %s ?>',
  'php_display_code'       => '<?= %s ?>',
  'php_block_code'         => ' {%s}',
  'php_nested_html'        => ' ?>%s<?php ',
  'display_comment'        => '<!-- %s -->',
  'doctype'                => '<!DOCTYPE %s PUBLIC "%s" "%s">',
  'custom_doctype'         => '<!DOCTYPE %s>',
  'debug_comment'          => "\n// PUG_DEBUG:%s\n",
  'debug'                  => function ($nodeId) {
    return $this->handleCode($this->getDebugComment($nodeId));
  },
]

Formats can add patterns (like XmlFormat):

class XmlFormat extends AbstractFormat
{
    //...

    const DOCTYPE = '<?xml version="1.0" encoding="utf-8" ?>';
    const OPEN_PAIR_TAG = '<%s>';
    const CLOSE_PAIR_TAG = '</%s>';
    const SELF_CLOSING_TAG = '<%s />';
    const ATTRIBUTE_PATTERN = ' %s="%s"';
    const BOOLEAN_ATTRIBUTE_PATTERN = ' %s="%s"';
    const BUFFER_VARIABLE = '$__value';

    public function __construct(Formatter $formatter = null)
    {
        //...

        $this->addPatterns([
            'open_pair_tag'             => static::OPEN_PAIR_TAG,
            'close_pair_tag'            => static::CLOSE_PAIR_TAG,
            'self_closing_tag'          => static::SELF_CLOSING_TAG,
            'attribute_pattern'         => static::ATTRIBUTE_PATTERN,
            'boolean_attribute_pattern' => static::BOOLEAN_ATTRIBUTE_PATTERN,
            'save_value'                => static::SAVE_VALUE,
            'buffer_variable'           => static::BUFFER_VARIABLE,
        ])

For example, you can see BOOLEAN_ATTRIBUTE_PATTERN = ' %s="%s"' which mean input(checked) become <input checked="checked">.

And HtmlFormat override it:

class HtmlFormat extends XhtmlFormat
{
    const DOCTYPE = '<!DOCTYPE html>';
    const SELF_CLOSING_TAG = '<%s>';
    const EXPLICIT_CLOSING_TAG = '<%s/>';
    const BOOLEAN_ATTRIBUTE_PATTERN = ' %s';

    public function __construct(Formatter $formatter = null)
    {
        parent::__construct($formatter);

        $this->addPattern('explicit_closing_tag', static::EXPLICIT_CLOSING_TAG);
    }
}

BOOLEAN_ATTRIBUTE_PATTERN = ' %s' so input(checked) become <input checked>.

The same way you can extend a format to create your own custom format and override it with formats and default_format options.

Patterns can be strings where %s is replaced with the input or callback functions receiving the input as argument.

Some patterns have multiple input (like pair_tag which takes $open, $content and $close).

Usage example: you can intercept and modify expressions:


Phug::setOption('patterns', [
  'transform_expression' => 'strtoupper(%s)',
]);

Phug::display('p="AbcD"'); // Output <p>ABCD</p>

Or you can use a custom escape function:

Phug::setOption('patterns', [
  'html_expression_escape' => 'htmlentities(%s)',
  'html_text_escape'       => 'htmlentities',
]);

pattern callable

The pattern option is the way patterns are handled.

Default value :

function ($pattern) {
  $args = func_get_args();
  $function = 'sprintf';
  if (is_callable($pattern)) {
    $function = $pattern;
    $args = array_slice($args, 1);
  }

  return call_user_func_array($function, $args);
}

This function will take at least one argument (the pattern) and as many values as needed for this pattern will come as rest arguments.

You can see in the default behavior that if the pattern is callable, we simply call it with input values: $pattern($value1, $value2, ...), else we call sprintf($pattern, $value1, $value2, ...)

By changing the pattern option, you can handle patterns in the way you want and support any other pattern types.

formats array

Array of format classes by doctype, default value is:

[
  'basic'        => \Phug\Formatter\Format\BasicFormat::class,
  'frameset'     => \Phug\Formatter\Format\FramesetFormat::class,
  'html'         => \Phug\Formatter\Format\HtmlFormat::class,
  'mobile'       => \Phug\Formatter\Format\MobileFormat::class,
  '1.1'          => \Phug\Formatter\Format\OneDotOneFormat::class,
  'plist'        => \Phug\Formatter\Format\PlistFormat::class,
  'strict'       => \Phug\Formatter\Format\StrictFormat::class,
  'transitional' => \Phug\Formatter\Format\TransitionalFormat::class,
  'xml'          => \Phug\Formatter\Format\XmlFormat::class,
]

You can add/modify/remove any format by doctype:

class FooFormat extends \Phug\Formatter\Format\HtmlFormat
{
    const DOCTYPE = '#FOO';
    // Here you can change options/methods/patterns
}

Phug::setOption('formats', [
    'foo' => FooFormat::class,
] + Phug::getOption('formats'));
// Add foo but keep Phug::getOption('formats')
// So both array will be merged

Phug::display('
doctype foo
test
');

// Output #FOO<test></test>

You can also remove a format this way:

$formats = Phug::getOption('formats');
unset($formats['xml']); // remove custom XML format

Phug::setOption('formats', $formats);

Phug::display('
doctype xml
test
');

// Output <!DOCTYPE xml><test></test>
// Instead of <?xml version="1.0" encoding="utf-8" ?><test></test>

default_format string

It's the format used when there is no doctype; \Phug\Formatter\Format\BasicFormat::class by default.

$renderer = new \Phug\Renderer([
  'default_format' => \Phug\Formatter\Format\XmlFormat::class,
]);
$renderer->display('input'); // <input></input>

$renderer = new \Phug\Renderer([
  'default_format' => \Phug\Formatter\Format\HtmlFormat::class,
]);
$renderer->display('input'); // <input>

dependencies_storage string

Variable name that will contain dependencies in the compiled PHP code; "pugModule" by default.

formatter_class_name string

Allow you to extend the Formatter class

class CustomFormatter extends \Phug\Formatter
{
    public function format(\Phug\Formatter\ElementInterface $element, $format = null)
    {
        // Add a space everywhere
        return parent::format($element, $format).' ';
    }
}

Phug::display('
span foo
span bar
');

// <span>foo</span><span>bar</span>

$renderer = new Phug\Renderer([
    'formatter_class_name' => CustomFormatter::class,
]);

$renderer->display('
span foo
span bar
');

// <span>foo </span> <span>bar </span>

Rendering

adapter_class_name string

This option need the adapter to be reinitialized to be taken into account. So you can use it as an initial option (passed as options array when you construct a new Renderer instance or a new Pug instance if you use Pug-php) else you can simply use the ->setAdapterClassName() method to change this option and reinitialize the adapter.

Phug::getRenderer()->setAdapterClassName(\Phug\Renderer\Adapter\StreamAdapter::class);
// Phug::getRenderer()->getAdapter() instanceof \Phug\Renderer\Adapter\StreamAdapter

There are 3 adapters available and you can create your own by extending one of them or the AbstractAdapter class.

The adapter role is to take formatted compiled code and turn it into the final rendered code. So most often it means execute PHP code to get HTML code.

FileAdapter

FileAdapter is the only adapter to implement CacheInterface so when you enable or use any cache feature, this adapter is automatically selected if the current adapter does not implement CacheInterface. ->display() with the FileAdapter is equivalent to:

file_put_contents('file.php', $phpCode);
include 'file.php';

EvalAdapter

EvalAdapter is the default adapter and uses eval. You may have heard that eval is dangerous. And yes, if you don't filter user/external input the string you pass to eval may contain, this is unsafe. But this does not happen when you render a template. Your local/global variables are never executed, only the Pug code converted in PHP code is, so if you do not write dangerous code in your Pug code, there is nothing dangerous in the final PHP code. This is perfectly as safe as the 2 other adapters, you will always get the exact same executions and results no matter which adapter is used.

Take a look at the following:

p?!=$dangerousContent
[
  'dangerousContent' => 'file_get_contents("index.php")',
]

As you see, variables can contain anything and be display in any way, it will not be evaluated by PHP, only displayed. Danger only appears if you write it down directly in your Pug template, so it's the same danger than in any template engine or if you would have written it directly in your PHP files.

EvalAdapter is the faster and easier to setup way as well. In this mode ->display() is equivalent to:

eval('?>'.$phpCode);

StreamAdapter

StreamAdapter Stream is an alternative between both. In this mode ->display() is equivalent to:

include 'pug.stream://data;'.$phpCode;

Stream have some constraints. The stream size is limited by the RAM memory. And server config (like php.ini) can disallow stream inclusion.

stream_name string

Default to "pug". It determines the stream name when you use the stream adapter (see above).

stream_suffix string

Default to ".stream". It determines the stream suffix when you use the stream adapter (see above).

File system

tmp_dir string

The directory to use to store temporary files. sys_get_temp_dir() by default.

tempnam callable

The function to use to create a temporary file. "tempnam" by default.

get_file_contents callable

The function to use to get file contents. "file_get_contents" by default.

$storage = new Memcached();
$storage->addServer('localhost', 11211);
Phug::setOption('get_file_contents', function ($path) use ($storage) {
  return $storage->get($path);
});

In this example, instead of searching templates (rendered, included or extended) in the file matching the path, we search it in a Memcached storage.

up_to_date_check boolean

true by default, when set to false, cache files never expire until a manual cache clear.

keep_base_name boolean

If true, it will prepend template name to cache file name. It can be useful to debug if you need to see quickly which template a cache file come from.

locator_class_name string

The locator is used by the compiler to locate files to compile/include/extend.

By default we use FileLocator.

But you can change it for any class that implement LocatorInterface.

Lexing

Lexing options are handled and altered by the lexer at very low process level. There are no particular interest to change (except encoding) them but getting them at some events can be interesting:

$lexer = Phug::getRenderer()->getCompiler()->getParser()->getLexer();
$lexer->attach(\Phug\LexerEvent::TOKEN, function (\Phug\Lexer\Event\TokenEvent $event) use ($lexer, &$output) {
    $state = $lexer->getState();
    $state->getLevel(); // level
    $state->getIndentStyle(); // indent_style
    $state->getIndentWidth(); // indent_width
    $state->getReader()->getEncoding(); // encoding
});

Warning: do not confuse options below with formatting options, the options below have no effect on the output, they are how the lexer get the input.

level integer

Count of indentation spaces/tabs.

indent_style string

Indentation string (spaces, tabs or any custom string).

indent_width indent_width

Number strings occurrences to step an indentation.

allow_mixed_indent integer

true by default. If set to false, mixin tabs and spaces throw an exception.

encoding string

Encoding of the input ("UTF-8" by default).

Extending core classes

Core classes can be replaced thanks to options. This allow you to extend Phug classes and methods. These options are initial, so you have to reset/recreate the renderer if you want to change them after a render.

compiler_class_name string

Allow to replace Compiler

parser_class_name string

Allow to replace Parser

lexer_class_name string

Allow to replace Lexer

lexer_class_name string

Allow to replace Lexer

lexer_state_class_name string

Allow to replace the lexer state class

parser_state_class_name string

Allow to replace the parser state class

node_compilers array

Allow to change compilers for each kind of node, the default map is:

[
    \Phug\Parser\Node\AssignmentListNode::class => \Phug\Compiler\NodeCompiler\AssignmentListNodeCompiler::class,
    \Phug\Parser\Node\AssignmentNode::class     => \Phug\Compiler\NodeCompiler\AssignmentNodeCompiler::class,
    \Phug\Parser\Node\AttributeListNode::class  => \Phug\Compiler\NodeCompiler\AttributeListNodeCompiler::class,
    \Phug\Parser\Node\AttributeListNode::class  => \Phug\Compiler\NodeCompiler\AttributeNodeCompiler::class,
    \Phug\Parser\Node\BlockNode::class          => \Phug\Compiler\NodeCompiler\BlockNodeCompiler::class,
    \Phug\Parser\Node\YieldNode::class          => \Phug\Compiler\NodeCompiler\YieldNodeCompiler::class,
    \Phug\Parser\Node\CaseNode::class           => \Phug\Compiler\NodeCompiler\CaseNodeCompiler::class,
    \Phug\Parser\Node\CodeNode::class           => \Phug\Compiler\NodeCompiler\CodeNodeCompiler::class,
    \Phug\Parser\Node\CommentNode::class        => \Phug\Compiler\NodeCompiler\CommentNodeCompiler::class,
    \Phug\Parser\Node\ConditionalNode::class    => \Phug\Compiler\NodeCompiler\ConditionalNodeCompiler::class,
    \Phug\Parser\Node\DoctypeNode::class        => \Phug\Compiler\NodeCompiler\DoctypeNodeCompiler::class,
    \Phug\Parser\Node\DocumentNode::class       => \Phug\Compiler\NodeCompiler\DocumentNodeCompiler::class,
    \Phug\Parser\Node\DoNode::class             => \Phug\Compiler\NodeCompiler\DoNodeCompiler::class,
    \Phug\Parser\Node\EachNode::class           => \Phug\Compiler\NodeCompiler\EachNodeCompiler::class,
    \Phug\Parser\Node\KeywordNode::class        => \Phug\Compiler\NodeCompiler\KeywordNodeCompiler::class,
    \Phug\Parser\Node\ElementNode::class        => \Phug\Compiler\NodeCompiler\ElementNodeCompiler::class,
    \Phug\Parser\Node\ExpressionNode::class     => \Phug\Compiler\NodeCompiler\ExpressionNodeCompiler::class,
    \Phug\Parser\Node\FilterNode::class         => \Phug\Compiler\NodeCompiler\FilterNodeCompiler::class,
    \Phug\Parser\Node\ForNode::class            => \Phug\Compiler\NodeCompiler\ForNodeCompiler::class,
    \Phug\Parser\Node\ImportNode::class         => \Phug\Compiler\NodeCompiler\ImportNodeCompiler::class,
    \Phug\Parser\Node\MixinCallNode::class      => \Phug\Compiler\NodeCompiler\MixinCallNodeCompiler::class,
    \Phug\Parser\Node\MixinNode::class          => \Phug\Compiler\NodeCompiler\MixinNodeCompiler::class,
    \Phug\Parser\Node\TextNode::class           => \Phug\Compiler\NodeCompiler\TextNodeCompiler::class,
    \Phug\Parser\Node\VariableNode::class       => \Phug\Compiler\NodeCompiler\VariableNodeCompiler::class,
    \Phug\Parser\Node\WhenNode::class           => \Phug\Compiler\NodeCompiler\WhenNodeCompiler::class,
    \Phug\Parser\Node\WhileNode::class          => \Phug\Compiler\NodeCompiler\WhileNodeCompiler::class,
]

element_handlers array

Allow to change how to format each kind of element, the default map is:

[
    \Phug\Formatter\Element\AssignmentElement::class => [$this, 'formatAssignmentElement'],
    \Phug\Formatter\Element\AttributeElement::class  => [$this, 'formatAttributeElement'],
    \Phug\Formatter\Element\CodeElement::class       => [$this, 'formatCodeElement'],
    \Phug\Formatter\Element\CommentElement::class    => [$this, 'formatCommentElement'],
    \Phug\Formatter\Element\ExpressionElement::class => [$this, 'formatExpressionElement'],
    \Phug\Formatter\Element\DoctypeElement::class    => [$this, 'formatDoctypeElement'],
    \Phug\Formatter\Element\DocumentElement::class   => [$this, 'formatDocumentElement'],
    \Phug\Formatter\Element\KeywordElement::class    => [$this, 'formatKeywordElement'],
    \Phug\Formatter\Element\MarkupElement::class     => [$this, 'formatMarkupElement'],
    \Phug\Formatter\Element\MixinCallElement::class  => [$this, 'formatMixinCallElement'],
    \Phug\Formatter\Element\MixinElement::class      => [$this, 'formatMixinElement'],
    \Phug\Formatter\Element\TextElement::class       => [$this, 'formatTextElement'],
    \Phug\Formatter\Element\VariableElement::class   => [$this, 'formatVariableElement'],
]

Where $this is the current format instance.

token_handlers array

Allow to change how lexer token are parsed, the default map is:

[
    \Phug\Lexer\Token\AssignmentToken::class             => \Phug\Parser\TokenHandler\AssignmentTokenHandler::class,
    \Phug\Lexer\Token\AttributeEndToken::class           => \Phug\Parser\TokenHandler\AttributeEndTokenHandler::class,
    \Phug\Lexer\Token\AttributeStartToken::class         => \Phug\Parser\TokenHandler\AttributeStartTokenHandler::class,
    \Phug\Lexer\Token\AttributeToken::class              => \Phug\Parser\TokenHandler\AttributeTokenHandler::class,
    \Phug\Lexer\Token\AutoCloseToken::class              => \Phug\Parser\TokenHandler\AutoCloseTokenHandler::class,
    \Phug\Lexer\Token\BlockToken::class                  => \Phug\Parser\TokenHandler\BlockTokenHandler::class,
    \Phug\Lexer\Token\YieldToken::class                  => \Phug\Parser\TokenHandler\YieldTokenHandler::class,
    \Phug\Lexer\Token\CaseToken::class                   => \Phug\Parser\TokenHandler\CaseTokenHandler::class,
    \Phug\Lexer\Token\ClassToken::class                  => \Phug\Parser\TokenHandler\ClassTokenHandler::class,
    \Phug\Lexer\Token\CodeToken::class                   => \Phug\Parser\TokenHandler\CodeTokenHandler::class,
    \Phug\Lexer\Token\CommentToken::class                => \Phug\Parser\TokenHandler\CommentTokenHandler::class,
    \Phug\Lexer\Token\ConditionalToken::class            => \Phug\Parser\TokenHandler\ConditionalTokenHandler::class,
    \Phug\Lexer\Token\DoToken::class                     => \Phug\Parser\TokenHandler\DoTokenHandler::class,
    \Phug\Lexer\Token\DoctypeToken::class                => \Phug\Parser\TokenHandler\DoctypeTokenHandler::class,
    \Phug\Lexer\Token\EachToken::class                   => \Phug\Parser\TokenHandler\EachTokenHandler::class,
    \Phug\Lexer\Token\ExpansionToken::class              => \Phug\Parser\TokenHandler\ExpansionTokenHandler::class,
    \Phug\Lexer\Token\ExpressionToken::class             => \Phug\Parser\TokenHandler\ExpressionTokenHandler::class,
    \Phug\Lexer\Token\FilterToken::class                 => \Phug\Parser\TokenHandler\FilterTokenHandler::class,
    \Phug\Lexer\Token\ForToken::class                    => \Phug\Parser\TokenHandler\ForTokenHandler::class,
    \Phug\Lexer\Token\IdToken::class                     => \Phug\Parser\TokenHandler\IdTokenHandler::class,
    \Phug\Lexer\Token\InterpolationStartToken::class     => \Phug\Parser\TokenHandler\InterpolationStartTokenHandler::class,
    \Phug\Lexer\Token\InterpolationEndToken::class       => \Phug\Parser\TokenHandler\InterpolationEndTokenHandler::class,
    \Phug\Lexer\Token\ImportToken::class                 => \Phug\Parser\TokenHandler\ImportTokenHandler::class,
    \Phug\Lexer\Token\IndentToken::class                 => \Phug\Parser\TokenHandler\IndentTokenHandler::class,
    \Phug\Lexer\Token\MixinCallToken::class              => \Phug\Parser\TokenHandler\MixinCallTokenHandler::class,
    \Phug\Lexer\Token\MixinToken::class                  => \Phug\Parser\TokenHandler\MixinTokenHandler::class,
    \Phug\Lexer\Token\NewLineToken::class                => \Phug\Parser\TokenHandler\NewLineTokenHandler::class,
    \Phug\Lexer\Token\OutdentToken::class                => \Phug\Parser\TokenHandler\OutdentTokenHandler::class,
    \Phug\Lexer\Token\TagInterpolationStartToken::class  => \Phug\Parser\TokenHandler\TagInterpolationStartTokenHandler::class,
    \Phug\Lexer\Token\TagInterpolationEndToken::class    => \Phug\Parser\TokenHandler\TagInterpolationEndTokenHandler::class,
    \Phug\Lexer\Token\KeywordToken::class                => \Phug\Parser\TokenHandler\KeywordTokenHandler::class,
    \Phug\Lexer\Token\TagToken::class                    => \Phug\Parser\TokenHandler\TagTokenHandler::class,
    \Phug\Lexer\Token\TextToken::class                   => \Phug\Parser\TokenHandler\TextTokenHandler::class,
    \Phug\Lexer\Token\VariableToken::class               => \Phug\Parser\TokenHandler\VariableTokenHandler::class,
    \Phug\Lexer\Token\WhenToken::class                   => \Phug\Parser\TokenHandler\WhenTokenHandler::class,
    \Phug\Lexer\Token\WhileToken::class                  => \Phug\Parser\TokenHandler\WhileTokenHandler::class,
]

scanners array

Allow to change how to scan and detect each string sequence to get the given token, the default map is:

[
    'new_line'    => \Phug\Lexer\Scanner\NewLineScanner::class,
    'indent'      => \Phug\Lexer\Scanner\IndentationScanner::class,
    'import'      => \Phug\Lexer\Scanner\ImportScanner::class,
    'block'       => \Phug\Lexer\Scanner\BlockScanner::class,
    'yield'       => \Phug\Lexer\Scanner\YieldScanner::class,
    'conditional' => \Phug\Lexer\Scanner\ConditionalScanner::class,
    'each'        => \Phug\Lexer\Scanner\EachScanner::class,
    'case'        => \Phug\Lexer\Scanner\CaseScanner::class,
    'when'        => \Phug\Lexer\Scanner\WhenScanner::class,
    'do'          => \Phug\Lexer\Scanner\DoScanner::class,
    'while'       => \Phug\Lexer\Scanner\WhileScanner::class,
    'for'         => \Phug\Lexer\Scanner\ForScanner::class,
    'mixin'       => \Phug\Lexer\Scanner\MixinScanner::class,
    'mixin_call'  => \Phug\Lexer\Scanner\MixinCallScanner::class,
    'doctype'     => \Phug\Lexer\Scanner\DoctypeScanner::class,
    'keyword'     => \Phug\Lexer\Scanner\KeywordScanner::class,
    'tag'         => \Phug\Lexer\Scanner\TagScanner::class,
    'class'       => \Phug\Lexer\Scanner\ClassScanner::class,
    'id'          => \Phug\Lexer\Scanner\IdScanner::class,
    'attribute'   => \Phug\Lexer\Scanner\AttributeScanner::class,
    'assignment'  => \Phug\Lexer\Scanner\AssignmentScanner::class,
    'variable'    => \Phug\Lexer\Scanner\VariableScanner::class,
    'comment'     => \Phug\Lexer\Scanner\CommentScanner::class,
    'filter'      => \Phug\Lexer\Scanner\FilterScanner::class,
    'expression'  => \Phug\Lexer\Scanner\ExpressionScanner::class,
    'code'        => \Phug\Lexer\Scanner\CodeScanner::class,
    'markup'      => \Phug\Lexer\Scanner\MarkupScanner::class,
    'expansion'   => \Phug\Lexer\Scanner\ExpansionScanner::class,
    'dynamic_tag' => \Phug\Lexer\Scanner\DynamicTagScanner::class,
    'text_block'  => \Phug\Lexer\Scanner\TextBlockScanner::class,
    'text_line'   => \Phug\Lexer\Scanner\TextLineScanner::class,
]

assignment_handlers array

Allow to change how to handle assignments and allow to create your owns, example:

Phug::display('img&foo()', [], [
    'assignment_handlers' => [
        function (AssignmentElement $assignment) {
            if ($assignment->getName() === 'foo') {
                $assignment->detach();

                yield new AttributeElement('data-foo', '123');
            }
        },
    ],
]);

Output:

<img data-foo="123" />

attribute_assignments array

Allow to change how to handle attributes, example:


Phug::display('img&attributes(["foo" => "bar", "biz" => true])', [], [
    'attribute_assignments' => [
        'foo' => function () {
            return 'not-bar';
        },
    ],
]);

Output:

<img foo="not-bar" biz="biz" />

CLI

Phug and Pug-php can be run as CLI commands:

./vendor/bin/phug render 'p=$msg' '{"msg": "Hello"}'
./vendor/bin/pug render 'p=msg' '{"msg": "Hello"}'

./vendor/bin/pug is available only if you installed Pug-php, ./vendor/bin/phug is always available if you use any of both.

Globals options

2 globals options are available for all commands:

--output-file (or -o) redirect success output to the specified file, it allow for example to write rendered HTML in a file:

./vendor/bin/phug render-file my-source-file.pug --output-file my-destination-file.html

--bottstrap (or -b) allow you to include a PHP file to be executed before the command. For example you can define your variables in a dynamic way:

Let say you have the following file: set-variables.php

Phug::share([
  'time' => date('H:i'),
]);
./vendor/bin/phug render 'p=$time' -b set-variables.php -o page.html

page.html will contains a paragraph with the time inside (example <p>17:47</p>).

The bootstrap file can run any PHP code and have all classes available thanks to composer autoload.

If a file is named phugBootstrap.php in the current directory, then it will be used as default bootstrap file.

Both options above can be set using space delimiter or equal operator, so all the following are equivalent:

./vendor/bin/phug render-file a.pug --output-file a.html
./vendor/bin/phug render-file a.pug --output-file=a.html
./vendor/bin/phug render-file a.pug -o a.html
./vendor/bin/phug render-file a.pug -o=a.html

Commands

Commands are the same for both phug and pug and will call the same methods. The lonely difference is phug call them on the Phug facade (that use Phug\Renderer with no particular extensions and settings) and pug call them on the Pug\Facade facade (that use Pug\Pug that comes with js-phpize and the pug-php default settings). For both, you can use --bootstrap to set more options, add extensions, share variables, etc.

render (or display)

Call ::render() and take 1 required argument pug code input (as string), and 2 optional arguments: local variables (as JSON string) and options (as JSON string)

./vendor/bin/phug render 'p(foo="a")' '{}' '{"attributes_mapping":{"foo":"bar"}}'

Will output:

<p bar="a"></p>

render-file (or display-file)

Call ::renderFile(), it's exactly like render expect it take a file path as first argument:

./vendor/bin/phug render-file /directory/file.pug '{"myVar":"value"}' '{"self":true}'

render-directory (or display-directory)

Call ::renderDirectory(), render each file in a directory and its subdirectories. It take 1 required argument: the input directory, and 3 optional arguments:

  • the output directory (if not specified, input directory is used instead, so rendered file are generated side-by-side with input files)
  • output files extension (.html by default)
  • local variables (as JSON string)
./vendor/bin/phug render-directory ./templates ./pages '.xml' '{"foo":"bar"}' 

Supposing you have the following ./templates directory:

templates
  some-view.pug
  some-subdirectory
    another-view.pug

You will get the following .pages:

pages
  some-view.xml
  some-subdirectory
    another-view.xml

And in this example, all rendered files will get $foo = "bar" as available local.

compile

Call ::compile(). Compile a pug string without render it. It take 1 required argument: the pug code and 1 optional: the filename (can also be provided via options), it will be used to resolve relative imports.

./vendor/bin/phug compile 'a(href=$link) Go' 'filename.pug' -o file.php

This will write something like this in file.php:

<a href="<?= htmlspecialchars($link) ?>">Go</a>

compile-file

Call ::compileFile(). Compile a pug input file without render it.

./vendor/bin/phug compile-file views/index.pug -o public/index.php

compile-directory (or cache-directory)

Call ::cacheDirectory() (via ::textualCacheDirectory()). Compile each file in a directory and its subdirectories.

It's the perfect way to cache all your pug files when you deploy an new version of your application in a server in production.

If you call this command each time you put something new in production, you can disable the up-to-date check with the option 'up_to_date_check' => false to optimize performance.

./vendor/bin/phug compile-directory views cache '{"option":"value"}'

Only the first argument is required (input directory where are stored your pug files).

As a second optional argument, you can specify the cache directory, else cache_dir specified in options will be used, if not specified, system temporary directory will be used.

The third argument (optional too) allows you to pass options as a JSON string if needed.

custom commands

You can create your own commands thanks to the commands option:

For example if you write this in a phugBootstrap.php file (in the directory you enter your commands, typically at the project root) or in any other file that you load with the CLI option --bootstrap:

<?php

Phug::setOption('commands', [
  'renderDate' => function () {
    return Phug::render('p=date("d/m/Y")');
  },
]);

Then you will be able to execute the following command:

./vendor/bin/phug render-date

And it will display the date in a paragraph:

<p>09/02/2018</p>

The commands option must be an array listing custom commands, each command can be described with one of the 3 ways below:

<?php

Phug::setOption('commands', [
  'cacheFile',
    // Make the Phug::cacheFile() method
    // available as its kebab case name:
    // ./vendor/bin/phug cache-file

  'storeFile' => 'cacheFile',
    // Make the Phug::cacheFile() method
    // with an other name:
    // ./vendor/bin/phug store-file

  'myFunction' => function () {
    return 'Hello';
  },
    // Execute the function with the given
    // name:
    // ./vendor/bin/phug my-function
]);

The command can also call a macro.

Watch changes and autocompile

To use the watch command, you will need to install phug/watcher:

composer require phug/watcher

And you can use the --init command to create a phugBoostrap.php file used as default bootstrap file by the phug CLI.

./vendor/bin/watcher --init

In this file, you can change the list of the directories to watch (./views and ./templates by default) and you can change the cache path (by default, it creates a phug-cache in your system temporary storage directory).

Then this file enable the watcher extension and set Phug options.

To properly work, you need to use here the same options as in you application. To keep them synchronized you can use a common config file.

For example, let say you have the following structure for you app:

- vendor
- config
  - phug.php
- bootstrap
  - cli.php
  - web.php
- views
  - home.pug
- cache
  - views
composer.json
index.php

Then you can have the following contents:

phug.php

<?php return [
  'cache_dir' => __DIR__.'/../cache/views',
  'paths'     => [
    __DIR__.'/../views',
  ],
  // Any other option you use in you app:
  'debug'     => true,
];

cli.php

<?php

$options = include __DIR__.'/../config/phug.php';

if (!file_exists($options['cache_dir']) && !@mkdir($options['cache_dir'], 0777, true)) {
    throw new \RuntimeException(
        $options['cache_dir'].' cache directory could not be created.'
    );
}

Phug::addExtension(\Phug\WatcherExtension::class);

Phug::setOptions($options);

web.php

<?php

include_once __DIR__.'/../vendor/autolod.php';

$options = include __DIR__.'/../config/phug.php';

Phug::setOptions($options);

index.php

<?php

include_once __DIR__.'/bootstrap/web.php';

Phug::displayFile('home');

And you can run the watcher with:

./vendor/bin/phug watch -b bootstrap/cli.php

When you will edit any file in the views directory and save it, it will automatically refresh the cache (as long as the command is running).

If you CLI bootstrap has the default location (phugBootstrap.php), you can simply do:

./vendor/bin/phug watch

Automatically reload the browser on change

Browser auto-reloading also need the phug/watcher package to be installed (see above).

It allows you to start a development server and a watcher in parallel on 2 different ports with the following command:

./vendor/bin/phug listen 9000 index.php

It will start a dev server as if you did:

php -S localhost:9000 index.php

Supposing you load the \Phug\WatcherExtension in index.php, it will add a <script> tag in the rendering to watch changes and refresh the page when they happen (communicating on a second port, by default 8066).

For example if you did some basic watcher install:

composer require phug/watcher
./vendor/bin/watcher --init

And have the following index.php:

<?php

include_once __DIR__ . '/vendor/autoload.php';

include_once __DIR__ . '/phugBootstrap.php';

Phug::displayFile('views/basic.pug');

Then by runing ./vendor/bin/phug listen 9000 index.php, you will be able to load http://localhost:9000 in a browser and the page will auto-refresh if you change the views directory.

Unit tests and coverage

You can try out our experimental testing CLI tool using PHPUnit and xdebug including Pug files coverage reports and all-in-one PHP and Pug unit tests helpers:

https://github.com/phug-php/tester

Frequently asked questions

Why do I get error with UPPER_CASE variables?

This can happen when you use use JS-style (js-phpize module or Pug-php) simply because nothing in the JS syntax allow to distinguish a constant from a variable so we chose to follow the most used convention: a all-upper-case name is a constant, everything else is a variable:

:php
  $fooBar = 9;
  define('FOO_BAR', 8);
  $d = 9;
  define('D', 8);
  $_ = '_';
  define('_K', 5);

p=fooBar
p=FOO_BAR
p=d
p=D
p=_
p=_K

Is it still possible to disable constants this way:

<?php

use Pug\Pug;

include 'vendor/autoload.php';

$pug = new Pug([
    'module_options' => [
        'jsphpize' => [
            'disableConstants' => true,
        ],
    ],
]);

$pug->display('p=FOO', [
    'FOO' => 'variable',
]);

How to use namespaces in a pug template?

By default, templates are executed with no namespace and so you need to write full paths to access functions and classes:

p=\SomeWhere\somefunction()
p=call_from_root_namespace()
- $a = new \SomeWhere\SomeClass()

You cannot set a namespace at the beginning of a pug template as it is not the beginning of the PHP compiled file (we prepend debug stuff, dependencies, mixins functions, etc.)

However you can apply globally a namespace to all templates this way:

Phug::setOption('on_output', function (OutputEvent $event) {
  $event->setOutput(
    '<?php namespace SomeWhere; ?>'.
    $event->getOutput()
  );
});

With this output event interceptor, the previous code become:

p=somefunction()
p=\call_from_root_namespace()
- $a = new SomeClass()

How to run JS scripts inside templates?

There are different possible approaches:

  • First of all, avoid mixing PHP and JS in your back-end so if you already have a PHP app and find some node.js package you would use, check there is no equivalent in PHP.

  • If you don't need to call PHP functions/methods/objects in your templates, then you can use the native npm pugjs package. Pug-php have a wrapper for that:

    <?php
    

use Pug\Pug;

include 'vendor/autoload.php';

$pug = new Pug([

'pugjs' => true,

]);

// Phug engine skipped, pugjs used instead $pug->display('p=9..toString()');

// So this way, you can require any JS file or npm package: $pug->display('

  • moment = require("moment") p=moment("20111031", "YYYYMMDD").fromNow() ');

    
    
  • You can pass a helper function as any other variable via share or render that can call a CLI program (so node or anything else):

    $pug->share('dateDisplay', function ($date) {
      return shell_exec('node your-js-script.js ' . escapeshellarg($date));
    });
    
  • You can use the V8Js engine (http://php.net/manual/en/book.v8js.php):

    $pug->share('dateDisplay', function ($date) {
      $v8 = new V8Js('values', array('date' => '2016-05-09'));
    
      return $v8->executeString('callJsFunction(values.date)');
    });
    

How to use helper functions with pugjs engine?

When you use Pug-php with pugjs option to true all the data you pass to the view is encoded as JSON. So you loose your class typing and you loose closures functions.

Nevertheless, you can write JS functions inside you templates and use any local or shared variable in it :

-
  function asset(file) {
    return assetDirectory + '/' + file + '?v' + version;
  }

script(href=asset('app'))
[
  'assetDirectory' => 'assets',
  'version' => '2.3.4',
]

How to disable errors on production?

In production, you should set the debug option to false. Then you should have a global exception handler for your PHP application to hide errors from the user.

Best practice would be to log them (in a file for example) using exception handler (see set_exception_handler).

A more radical way is to hide them completely with error_reporting(0); or the same setting in php.ini.

How to includes files dynamically?

The include and extend statements only allow static paths: include myFile but disallow dynamic imports such as: include $myVariable.

But custom keyword come to the rescue:

Phug::addKeyword('dyninclude', function ($args) {
    return array(
        'beginPhp' => 'echo file_get_contents(' . $args . ');',
    );
});

This allow to include files as raw text:

- $styleFile = 'foo.css'
- $scriptFile = 'foo.js'

style
  // Include foo.css as inline content
  dyninclude $styleFile
  
script
  // Include foo.js as inline content
  dyninclude $scriptFile

Warning: you must be sure of the variables content. If $styleFile contains "../../config.php" and if config.php contains some DB passwords, session secret, etc. it will disclose those private information.

You must be even more prudent if you allow to include PHP files:

Phug::addKeyword('phpinclude', function ($args) {
    return array(
        'beginPhp' => 'include ' . $args . ';',
    );
});

It can be helpful and safe, for example if you do:

each $module in $userModules
  - $module = 'modules/'.preg_replace('/[^a-zA-Z0-9_-]/', '', $module).'.php'
  phpinclude $module

In this example, by removing all characters except letters, digits and dashes, no matter what contains $userModules and where it come from, you're sure it can only include an existing file from the directory modules. So you just have to check what you put in this directory.

Finally you can also include files dynamically and render them with Phug (or any transformer):

Phug::addKeyword('puginclude', function ($args) {
    return array(
        'beginPhp' => 'Phug::display((' . $args . ') . ".pug");',
    );
});
- $path = '../dossier/template'
puginclude $path

It includes ../directory/template.pug as we concat the extension in the keyword callback.

How to handle internationalization?

Translations functions such as __() in Laravel or _() with gettext can be called as any other function in expressions and codes.

The gettext parser does not support pug files but the python mode give pretty good results with pug files.

Init method such as textdomain can be called in a simple code block: - textdomain("domain") for example as your first file line.

Last, be sure all needed extension (such as gettext) is well installed on the PHP instance that render your pug files.

How to clear the cache?

If you use laravel-pug the cache is handled by Laralve and so you can refer to the framework documentation for cache operations. Empty it for example can be done with php artisan cache:clear

Else in production, you should use the cache-directory command and disable up_to_date_check option.

In development environment, if you have any trouble with the cache, you can just safely disable cache with some code like this:

Phug::setOption('cache', $prod ? 'cache/directory' : false);

What is the equivalent of Twig filter?

If you know Twig, you may know this syntax:

<div>{{ param1 | filter(param2, param3) }}</div>

Or if you know AngularJS, those filters:

<div>{{ param1 | filter : param2 : param3 }}</div>

Pug filters are a bit different since there are allowed only outside expressions:

div
  :filter(param2=param2 param3=param3)
    param1

Inside tag content, it's technically usable even if this syntax would not be so relevant in this case.

For attributes values or inside mixin arguments, there is no such thing like filters available because simple functions works just fine:

<div>{{ filter(param1, param2, param3) }}</div>

Twig/AngularJS filters are nothing more than a first argument-function name swap. Most of Twig filters are available as native PHP functions (split: explode, replace: strtr, nl2br: nl2br, etc.).

Moreover, you can pass functions as closures inside your locals or the shared variables.

And remember the | yet exists in PHP, it's the OR bitwise operator.

How to solve Warning: include() read X bytes more data than requested (Y read, Z max)?

This happens probably because you have the php.ini mbstring.func_overload settings with the 2 flag on.

As it's an obsolete setting, the best thing to do is to set it to 0 and replace manually functions in your app rather than overload them.

If you really need to keep this setting, you can still use the FileAdapter that will not have this problem:

Phug::setOption('adapter_class_name', FileAdapter::class);

How to solve Maximum function nesting level of 'X' reached?

This error means you overhead the xdebug.max_nesting_level php.ini setting. The default value is 100 and it might be not enough. With many includes and nested mixins call in your pug template, you could reach 500 nesting level. So first, try to increase this setting.

If you still get this error, then you probably have an infinite recursion. It can happen in pug when a template a.pug include b.pug then b.pug include a.pug or directly a.pug including a.pug (obviously the same goes for PHP files). If you do not have conditions to avoid the recursion to be infinite, you will get a nesting level error, or a timeout exceeded. It can also happen when a mixin calls it self (or indirectly via other mixins) or the same with PHP functions.

Note: xdebug is a debug extension, so don't forget to disable it in production.

Should I use render or renderFile?

In old versions of Pug-php, only was the render method that rendered a file if the given argument matched an existing file, else it rendered as pug string. While Tale-pug always rendered a file.

None of these behaviour was consistent with Pugjs so we chose to change it in Phug. With Phug, render only take strings, no file, and renderFile has been added to render files.

Pug-php keep the old behaviour for the moment to ease the upgrade but you're strongly encouraged to use the strict option to get the new behaviour:

$pug = new Pug([
  'strict' => true,
]);

This way $pug->render() will always take first argument as a pug string no matter it mach a file path or not. This can avoid some unexpected behaviors.

If for some backward compatibility reason, you can't use this option, then you should avoid using render and use renderString for a pug string and renderFile for a file.

How to debug a code from the documentation not working in my app?

First, we assume you use the last version of Phug (or Pug-php >= 3).

You can check your Phug version with composer show phug/phug and update it with: composer update

Last Phug stable version is Latest Stable Version

If you're using a version < 3.0 of Pug-php (check it with composer show phug/phug) this documentation is not accurate for your and you're strongly encouraged to upgrade to Pug-php Latest Stable Version

If you're using Tale-jade or Tale-pug, you're advised to migrate to Phug.

Then, examples in this documentation are for Phug if not specified otherwise. If you're using Pug-php you have to adapt it. For example:

Phug::setOption('cache_dir', 'directory');
Phug::display('template.pug', $vars);

Should have to be written as one of these 2 syntaxes to get Pug-php features working:

\Pug\Facade::setOption('cache_dir', 'directory');
\Pug\Facade::display('template.pug', $vars);

\Pug\Facade allow to replace easily Phug keeping the same syntax. But you could prefer the instance syntax:

$pug = new Pug([
  'cache_dir' => 'directory',
]);
$pug->display('template.pug', $vars);

Alternatives

It exists alternative ways to get pug templates with a PHP back-end.

V8Js

http://php.net/manual/fr/book.v8js.php

You can run the V8 engine from PHP. You might need to use the setModuleLoader to require the pug node module and its dependencies in an automated way.

Then you could require the native pug engine with executeString and call pug using PHP data. That could be a very fast way to get the exact pugjs behavior with PHP data.

As far as we know, no such wrapper out-of-the-box yet exists. If you made or know one, please use the button [Edit] to submit a pull-request.

Pug-php with pugjs option

When you install Pug-php, you will be asked for installing pug-cli node package. If you enter Y, it will use npm if available on the machine to install the official pug-cli package, and Pug-php has an option to use it instead of its own engine:

<?php

include 'vendor/autoload.php';

$pug = new Pug([
  'pugjs' => true,
]);

$html = $pug->render('p=9..toString()');

Here you call the native pug package using directly node.js, that's why you can use any JS syntax.

If you do this, be aware we are not responsible of what happens inside templates, it's no longer PHP used and the documentation to refer to is https://pugjs.org

Optionally, you can specify the path to the node and pug-cli programs with following tools:

$pug = new Pug([
  'pugjs' => true,
  'nodePath' => __DIR__ . '/../bin/node',
]);
NodejsPhpFallback::setModulePath('pug-cli', __DIR__ . '/../node_modules/pug-cli');

Resources

 Edit