Skip to main content
Version: CANARY 🚧

Using views

Introduction​

Views contain the HTML provided by your application and separate your controller/application logic from your presentation logic.

Configuration​

Template engines

BowPHP implements 2 template engines and the default template is Tintin.

The view configuration is located in the view.php file in the config/ folder.

Specify the name of the template to use with the configuration's engine option. This option can take the following values: tintin, twig and php. By default, Bow uses tintin.

Note

Note that if you set php, views will be rendered without a template engine.

You can also change the template extension by modifying the value of the extension entry. You will also notice that views are stored in the templates directory by default.

Creating a view​

A simple view might look like this:

<!-- View stored in templates/greeting.tintin.php -->
<html>
<body>
<h1>Hello, {{ $name }}</h1>
</body>
</html>

After editing and saving your view in templates/greeting.tintin.php, you can now send it to users with the view helper like this:

$app->get('/', function() {
return view('greeting', ['name' => 'Tintin']);
});

Usage​

Example usage with the static class View::parse:

View::parse(string $view, array $data = []): View
ParameterTypeDescription
$viewstringThe name of the view, without extension, relative to the views folder
$dataarrayThe data passed to the template
use Bow\View\View;

echo View::parse('nom-de-la-vue-sans-extension');

To pass variables to the view:

use Bow\View\View;

echo View::parse('nom-de-la-vue-sans-extension', ['name' => 'bow']);
Helper

You can use the view helper, which is used in the same way.

To set an HTTP status code, use response()->render('vue', $data, $code) instead (see the HTTP responses documentation). View::parse() does not take a code.

With the following view:

<!-- View stored in templates/greeting.tintin.php -->
<html>
<body>
<h1>Hello, {{ $name }}</h1>
</body>
</html>

Example in a controller:

namespace App\Controllers;

use App\Controllers\Controller;

class HomeController extends Controller
{
/**
* Show hello page
*
* @return mixed
*/
public function show()
{
return view('greeting', ['name' => 'Bowphp']);
// Or
return $this->render('greeting', ['name' => "Bowphp"]);
// Or
return response()->render('greeting', ['name' => "Bowphp"]);
}
}

Introduction to Tintin​

Tintin

Tintin is a PHP template engine designed to be very simple and extensible. It can be used in any PHP project. By default, Tintin is the template engine used by BowPHP.

Hello, {{ $name }}.

Of course, you are not limited to displaying the content of the variables passed to the view. You can also echo the results of any PHP function. In fact, you can insert any PHP code into a Tintin echo statement:

Hello, {{ strtoupper($name) }}.

Tintin {{ }} statements are automatically sent through the PHP htmlspecialchars function to prevent XSS attacks.

Displaying unescaped data​

By default, Tintin {{ }} statements are automatically sent through the PHP htmlspecialchars function to prevent XSS attacks. If you do not want your data to be protected, you can use the following syntax:

Hello, {{{ $name }}}.

Adding a comment​

The {## ##} clause lets you add a comment to your tintin code.

{## This comment will not be present in the rendered HTML ##}

The %verbatim directive​

If you are displaying JavaScript variables in a large portion of your template, you can wrap the HTML in the %verbatim directive so that you don't have to prefix each Tintin echo statement with a % symbol:

%verbatim
<div class="container">
Hello, {{ name }}.
</div>
%endverbatim

The %if directives​

These are the clauses that let you build conditional branches, as in most programming languages.

You can build if statements using the %if, %elseif, %elif, %else and %endif directives. These directives work the same way as their PHP counterparts:

%if ($name == 'tintin')
{{ $name }}
%elseif ($name == 'template')
{{ $name }}
%else
{{ $name }}
%endif

You can use %elif instead of %elseif.

One small specificity: %unless lets you write a condition that is the inverse of %if. To keep it simple, here is an example:

%unless ($user->isAdmin())
// do something else
%endunless

In addition to the conditional directives already discussed, the %isset, %empty and %notempty directives can be used as convenient shortcuts for their respective PHP functions:

%isset($records)
// $records is defined and is not null...
%endisset

%empty($records)
// $records is "empty"...
%endempty

%notempty($records)
// $records is not "empty"...
%endnotempty

You can add %else to perform an opposite action.

The %loop / %for / %while directives​

You will often need to create lists or repeat over elements. For example, displaying all the users of your platform.

Using %loop​

This clause does exactly what foreach does.

%loop($names as $name)
Bonjour {{ $name }}
%endloop

This clause can also be combined with any other clause such as %if. A quick example.

%loop($names as $name)
%if($name == 'tintin')
Bonjour {{ $name }}
%stop
%endif
%endloop

You may have noticed the %stop, which lets you stop the loop's execution. There is also its companion %jump, which stops execution at its level and starts the next iteration of the loop.

The %jump and %stop syntactic sugar​

Often the developer needs to write loop-stopping conditions in a %loop like this:

%loop($names as $name)
%if($name == 'tintin')
%stop
// Or
%jump
%endif
%endloop

With syntactic sugar, you can shorten the code like this:

%loop($names as $name)
%stop($name == 'tintin')
// Or
%jump($name == 'tintin')
%endloop

Using %for and %while​

This clause does exactly what for does.

%for($i = 0; $i < 10; $i++)
// ..
%endfor

This clause does exactly what while does.

%while($name != 'tintin')
// ..
%endwhile

Conditional classes and styles​

The %class directive conditionally compiles a CSS class string. The directive accepts an array of classes where the array key contains the class or classes you want to add, while the value is a boolean expression. If the array element has a numeric key, it will always be included in the rendered class list:

%php
$isActive = false;
$hasError = true;
%endphp

<span %class([
'p-4',
'font-bold' => $isActive,
'text-gray-500' => ! $isActive,
'bg-red' => $hasError,
])></span>

<span class="p-4 text-gray-500 bg-red"></span>

Similarly, the %style directive can be used to conditionally add inline CSS styles to an HTML element:

%php
$isActive = true;
%endphp

<span %style([
'background-color: red',
'font-weight: bold' => $isActive,
])></span>

<span style="background-color: red; font-weight: bold;"></span>

Including sub-views​

Often when developing your code, you will need to split up your application's views to be more flexible and write less code.

%include lets you include another template file into another.

<div id="container">
%include('filename', %data)
</div>

If you try to include a view that does not exist, Tintin will throw an error. If you want to include a view that may or may not be present, you should use the %includeIf directive:

%includeIf("filename", ["name" => "Tintin"])

If you want to %include a view when a given boolean expression evaluates to true or false, you can use the %includeWhen and %includeUnless directives:

%includeWhen($user->isAdmin(), "include-file-name", ["name" => "Tintin"])

Raw PHP​

In some situations, it is useful to embed PHP code in your views. You can use the Tintin %php or %raw directive to execute a block of plain PHP in your template:

%php
$counter = 1;
%endphp

%raw
$counter = 1;
%endraw

Flash session​

If you want to display a flash message directly in a view, you can use %flash. And to check whether a flash message exists, use %hasflash and %endhasflash:

%hasflash("error")
<div class="alert alert-danger">
%flash("error")
</div>
%endhasflash

Service injection​

The %service directive can be used to retrieve a service from the container. The first argument passed to %service is the name of the variable in which the service will be placed, while the second argument is the class or interface name of the service you want to resolve:

%service('user_service', 'App\Services\UserService')

<div>
%loop($user_service->all() as $user)
<p>{{ $user->name }}</p>
%endloop
</div>

Authentication directives​

The %auth and %guest directives can be used to quickly determine whether the current user is authenticated or is a guest:

%auth
// The user is authenticated...
%endauth

%guest
// The user is not authenticated...
%endguest

If needed, you can specify the authentication guard that should be checked when using the %auth and %guest directives:

%auth('admin')
// The user is authenticated...
%endauth

%guest('admin')
// The user is not authenticated...
%endguest

Environment Guidelines​

You can check whether the application is running in the production environment using the %production directive:

%production
// Production specific content...
%endproduction

Or, you can determine whether the application is running in a specific environment using the %env directive:

%env('staging')
// The application is running in "staging"...
%endenv

%env(['staging', 'production'])
// The application is running in "staging" or "production"...
%endenv

CSRF Field​

Whenever you define an HTML form in your application, you must include a hidden CSRF token field in the form so that the CSRF protection middleware can validate the request. You can use the Tintin %csrf directive to generate the token field:

<form method="POST" action="/profile">
%csrf
</form>

Method Field​

Since HTML forms cannot make PUT, PATCH or DELETE requests, you will need to add a hidden _method field to spoof these HTTP verbs. The Tintin %method directive can create this field for you:

<form action="/foo/bar" method="POST">
%method('PUT')
</form>

Inheritance with %extends, %block and %inject​

Like any good template system, tintin supports sharing code between files. This makes your code flexible and maintainable.

Consider the following tintin code:

# le fichier `layout.tintin.php`
<!DOCTYPE html>
<html>
<head>
<title>Hello, world</title>
</head>
<body>
<h1>Page header</h1>
<div>
%inject('content')
</div>
<p>Page footer</p>
</body>
</html>

And we also have another file that inherits the code from the layout.tintin.php file.

# le fichier se nomme `content.tintin.php`
%extends('layout')

%block('content')
<p>This is the page content</p>
%endblock

Explanation​

The content.tintin.php file inherits the code from layout.tintin.php, and if you look closely, the layout.tintin.php file has the %inject clause whose parameter is the name of the %block in content.tintin.php, which is content. This means that the content of the content %block will be substituted by %inject. This will ultimately give the following:

<!DOCTYPE html>
<html>
<head>
<title>Hello, world</title>
</head>
<body>
<h1>Page header</h1>
<div>
<p>This is the page content</p>
</div>
<p>Page footer</p>
</body>
</html>

Custom directive​

Tintin can be extended with its custom directive system. To do this, use the directive method. Let's create some directives to handle a form:

$tintin->directive('input', function (string $type, string $name, ?string $value) {
return '<input type="'.$type.'" name="'.$name.'" value="'.$value.'" />';
});

$tintin->directive('button', function (string $type, string $label) {
return '<button type="'.$type.'">'.$label.'"</button>';
});

$tintin->directive('form', function (string $action, string $method, string $enctype = "multipart/form-data") {
return '<form action="'.$action.'" method="'.$method.'" enctype="'.$enctype.'">';
});

$tintin->directive('endform', function () {
return '</form>';
});

Using these directives could not be simpler. Write the name of the directive preceded by %. Then, if the directive takes parameters, call the directive the same way you call functions in your program.

<div class="container">
%form("/posts", "post", "multipart/form-data")
%input("text", "name")
%button('submit', 'Add')
%endform
</div>

Compilation works as usual; for more information about compilation.

echo $tintin->render('form');

Output after compilation:

<form action="/posts" method="post" enctype="multipart/form-data">
<input type="text" name="name" value="" />
<button type="submit">Add</button>
</form>

Adding your directives via configuration​

To register your directives at BowPHP startup, create (or modify) a provider in app/Configurations/ that extends Bow\Configuration\Configuration:

app/Configurations/ApplicationConfiguration.php
namespace App\Configurations;

use Bow\Configuration\Configuration;
use Bow\Configuration\Loader as Config;

class ApplicationConfiguration extends Configuration
{
/**
* Launch configuration
*/
public function create(Config $config): void
{
$tintin = app('view')->getEngine();

$tintin->directive('super', function () {
return "Super !";
});
}

public function run(): void
{
//
}
}

Don't forget to register this provider in Kernel::configurations() so that it is executed at boot.

Now the %super directive is available and you can use it.

return $tintin->render('%super');
// => Super !

The %macro directive​

Often, you will need to use or reuse a block of template to streamline the writing of your application. That is exactly what macros are for.

Macros must be defined in a separate file.

To use %macro, you must pass the name of the macro as the first parameter, followed by the macro's parameters.

Consider the file user-macro.tintin.php.

%macro('users', array $users)
%loop($users as $user)
<div>{{ $user }}</div>
%endloop
%endmacro

To use the macro, you must import it into another file with %import. We will call the file app.tintin.php, which contains the following template:

%import('user-macro')

%extends('layout')

%block('content')
<div class="container">
{{ users($users) }}
</div>
%endblock

For compilation, we will pass the following list of users:

$users = ["franck", "lucien", "brice"];

$tintin->render('app', compact('users'));

After compiling the file:

<div>franck</div>
<div>lucien</div>
<div>brice</div>

Is something missing?

If you run into problems with the documentation or have suggestions to improve the documentation or the project in general, please open an issue for us, or send a tweet mentioning the Twitter account @bowframework or directly on github.