Skip to content

Latest commit

 

History

History
471 lines (368 loc) · 15.4 KB

view-fortune.md

File metadata and controls

471 lines (368 loc) · 15.4 KB

Fortune

Table of Contents

  1. Introduction
  2. How it works
  3. Tags
  4. Sanitized Tags
  5. Unsanitized Tags
  6. Directives
  7. If Statements
  8. Loops
  9. Including Views
    1. Variable Scope
  10. Extending Views
    1. Parents
  11. Parts
  12. Creating Directives
  13. Comments
  14. Functions
  15. Fortune Functions
  16. Custom Functions
  17. Using View Functions in PHP Code
  18. Delimiters
  19. Escaping Delimiters
  20. Changing Delimiters

Introduction

Fortune is Opulence's own view engine. It simplifies adding dynamic content to web pages. You can inject data into your pages, extend other views, prevent XSS attacks, and even extend the compiler. To get started using Fortune, simply create files with the extensions fortune or fortune.php, eg Master.fortune or Master.fortune.php. Opulence will detect that the file is a Fortune template and will use the Fortune compiler.

How It Works

Fortune uses a lexer to tokenize the raw template into a stream of tokens. The tokens are then parsed into an abstract syntax tree, which is transpiled into regular PHP code. The transpiler caches the generated PHP code to significantly speed up subsequent requests.

Tags

Tags are syntactic sugar for echoing PHP data in your view. You can put any printable value inside of tags, including PHP code:

{{ "foo" }}
{{ $bar }}
{{ isset($baz) ? "Baz is set" : "Baz is NOT set" }}

Sanitized Tags

Sanitized tags prevent malicious users from executing a cross-site scripting (XSS) attack:

{{ "A&W > water" }}

This will compile to A&W > water.

Unsanitized Tags

Unsanitized tags are useful for outputting HTML:

{{! "<title>My Title</title>" !}}

This will compile to <title>My Title</title>.

Note: Always sanitize user input before displaying it in your views.

Directives

Directives perform view logic. For example, they can be used to extend another view, perform if/else logic, and loop through data.

Note: You might be asking what the difference between tags and directives is. Tags are temporary placeholders for data that is inserted through a controller. Directives, on the other hand, provide a shorthand for executing logic entirely within a view.

If Statements

<% if ($user->isAdmin()) %>
    <a href="edit">Edit Post</a>
<% elseif ($user->canViewPosts()) %>
    <a href="view">View Post</a>
<% else %>
    You cannot view this post
<% endif %>

Loops

<% for ($i = 1;$i <= 5;$i++) %>
    {{ $i }}
<% endfor %>

<% foreach ($posts as $post) %>
    <a href="posts/{{ $post->getId() }}">{{ $post->getTitle() }}</a>
<% endforeach %>

// If there are any posts, display them
// Otherwise, display "There are no posts"
<% forif ($posts as $post) %>
    <a href="posts/{{ $post->getId() }}">{{ $post->getTitle() }}</a>
<% forelse %>
    There are no posts
<% endif %>

<% while (true) %>
    Still looping
<% endwhile %>

Including Views

Including another view (like PHP's include) is an easy way to not repeat yourself. Here's an example of how to include a view:

Included.fortune.php
Hello, world!
Master.fortune.php
<div id="important-message">
    <% include("Included") %>
</div>

This will compile to:

<div id="important-message">
    Hello, world!
</div>
Variable Scope

Included views' variables have their own scope, meaning they're not available outside of the included view. If you need to pass variables to the included view, you may do so in the second parameter of the include directive:

<% foreach($posts as $post) %>
    <% include("Post", compact("post")) %>
<% endforeach %>

Extending Views

Most views extend some sort of master view. To make your life easy, Fortune builds support for this functionality into its views.

Master.fortune.php
Hello, world!
Child.fortune.php
<% extends("Master") %>
Hello, Dave!

When the child view gets compiled, the Master.fortune.php view is automatically created by an Opulence\Views\Factories\IViewFactory and inserted into the view to produce the following output:

Hello, world!
Hello, Dave!

Note: When extending a view, the child view inherits all of the parent's parts and variable values. If A extends B, which extends C, parts and variables from part B will overwrite any identically-named parts and variables from part C.

Parents

Sometimes, you'll want to add to a parent view's part. To do so, use the <% parent %> directive:

Master.fortune.php
<% part("greeting") %>
    Hello
<% endpart %>
Child.fortune.php
<% extends("Master") %>
<% part("greeting") %>
    <% parent %>, world!
<% endpart %>

This will get compiled down to:

Hello, world!

Parts

Another common case is a master view that is leaving a child view to fill in some information. For example, let's say our master has a sidebar, and we want to define the sidebar's contents in the child view. Use the <% show("NAME_OF_PART") %> directive:

Master.fortune.php
<div id="sidebar">
    <% show("sidebar") %>
</div>
Child.fortune.php
<% extends("Master") %>
<% part("sidebar") %>
    <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/about">About</a></li>
    </ul>
<% endpart %>

We created a part named "sidebar". When the child gets compiled, the contents of that part will be shown in any <% show() %> directive whose parameter matches the name of the part. We will get the following:

<div id="sidebar">
    <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/about">About</a></li>
    </ul>
</div>

You can also define a part, end it, and show it by not passing anything into the <% show %> directive:

<% part("Nav") %>
    <a href="/">Home</a>
<% show %>

Creating Directives

To create your own Fortune directives, simply register them to the Fortune transpiler using registerDirectiveTranspiler(). The first argument is the name of the directive, and the second is a callback that returns transpiled PHP code. The callback optionally accepts an expression, which can be used when transpiling to PHP.

Note: Registering your directive transpiler is most easily done in a Bootstrapper.

Let's take a look at Fortune's if statement directive transpiler:

use Opulence\Ioc\Bootstrappers\Bootstrapper;
use Opulence\Views\Compilers\Fortune\ITranspiler;

class MyDirectives extends Bootstrapper
{
    public function run(ITranspiler $transpiler)
    {
        $transpiler->registerDirectiveTranspiler('if', function ($expression) {
            // The expression will contain the surrounding parentheses
            return '<?php if' . $expression . ': ?>';
        });
    }
}

If in our template we have <% if ($user->isAdmin()) %>, Fortune will transpile it to <?php if($user->isAdmin()): ?>.

Comments

When writing complex views, you can easily leave comments:

{# This is an example of a comment #}

Fortune will transpile this to:

<?php /* This is an example of a comment */ ?>

Note: Fortune comments do not show up client-side; they only exist server-side.

Functions

Fortune supports using functions in views. They are a great way to reuse logic or formatting throughout your views. You can use PHP functions, functions defined by Fortune, or your very own functions. For example, {{ strtoupper("Dave") }} will output DAVE.

Fortune Functions

Fortune supplies some built-in functions in the view library:

  • charset()
    • Returns HTML used to select a character set
    • Accepts the following arguments:
      1. string $charset - The character set to use
  • css()
    • Returns HTML used to link to a CSS stylesheet
    • Accepts the following arguments:
      1. array|string $paths - The path or list of paths to the stylesheets
  • favicon()
    • Returns HTML used to display a favicon
    • Accepts the following arguments:
      1. string $path - The path to the favicon image
  • httpEquiv()
    • Returns HTML used to create an http-equiv attribute
    • Accepts the following arguments:
      1. string $name - The name of the http-equiv attribute, eg "refresh"
      2. mixed $value - The value of the attribute
  • httpMethodInput()
    • Returns HTML used to spoof HTTP request methods besides POST and GET
    • Accepts the following arguments:
      1. string $httpMethod - The HTTP request method to spoof, eg "DELETE"
  • metaDescription()
    • Returns HTML used to display a meta description
    • Accepts the following arguments:
      1. string $metaDescription - The meta description to use
  • metaKeywords()
    • Returns HTML used to display meta keywords
    • Accepts the following arguments:
      1. array $metaKeywords - The list of meta keywords to use
  • pageTitle()
    • Returns HTML used to display a title
    • Accepts the following arguments:
      1. string $title - The title to use
  • script()
    • Returns HTML used to link to a script file
    • Accepts the following arguments:
      1. array|string $paths - The path or list of paths to the scripts
      2. string $type - The script type, eg "text/javascript"

If you're using the skeleton project, Fortune also supplies the following functions:

  • csrfInput()
    • Returns a hidden input containing the CSRF token
  • csrfToken()
    • Returns the current CSRF token
  • currentRouteIs()
    • Returns whether or not the current route is the input route
    • Accepts the following arguments:
      1. string $routeName - The name of the route to check
  • route()
    • Returns a URL that is created using the rules of the input route name
    • Accepts the following arguments:
      1. string $routeName - The name of the route whose URL we're creating
      2. ...$args - The variable-length arguments to pass into the UrlGenerator to fill any host or path variables in the route (learn more about the UrlGenerator)

Since these functions output HTML, use them inside unsanitized tags. Here's an example of how to use these functions:

View.fortune.php
<!DOCTYPE html>
<html>
    <head>
        {{! charset("utf-8") !}}
        {{! httpEquiv("content-type", "text/html") !}}
        {{! pageTitle("My Website") !}}
        {{! metaDescription("An example website") !}}
        {{! metaKeywords(["Opulence", "sample"]) !}}
        {{! favicon("favicon.ico") !}}
        {{! css("stylesheet.css") !}}
    </head>
    <body>
        Hello, World!
        {{! script(["jquery.js", "angular.js"]) !}}
    </body>
</html>

This will be compiled to:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="content-type" content="text/html">
        <title>My Website</title>
        <meta name="description" content="An example website">
        <meta name="keywords" content="Opulence,sample">
        <link href="favicon.ico" rel="shortcut icon">
        <link href="stylesheet.css" rel="stylesheet">
    </head>
    <body>
        Hello, World!
        <script type="text/javascript" src="jquery.js"></script>
        <script type="text/javascript" src="angular.js"></script>
    </body>
</html>

Custom Functions

It's possible to add custom functions to your view. For example, you might want to add a salutation to a last name in your view. This salutation would need to know the last name, whether or not the person is a male, and if s/he is married. You could set tags with the formatted value, but this would require a lot of duplicated formatting code in your application. Instead, save yourself some work and register the function to the Fortune transpiler.

Note: This is most easily done in a Bootstrapper.

use Opulence\Ioc\Bootstrappers\Bootstrapper;
use Opulence\Views\Compilers\Fortune\ITranspiler;

class MyFunctions extends Bootstrapper
{
    public function run(ITranspiler $transpiler)
    {
        // Our function simply needs to have a printable return value
        $transpiler->registerViewFunction(
            'salutation',
            function ($last, $isMale, $isMarried) {
                if ($isMale) {
                    $salutation = 'Mr.';
                } elseif ($isMarried) {
                    $salutation = 'Mrs.';
                } else {
                    $salutation = 'Ms.';
                }

                return $salutation . ' ' . $last;
            }
        );
    }
}

Compiling Hello, {{ salutation("Young", false, true) }} will give us Hello, Mrs. Young.

Using View Functions in PHP Code

You may call view functions in your PHP code by calling Opulence\Views\Compilers\Fortune\ITranspiler::callViewFunction(). Let's take a look at an example that displays a pretty HTML page title formatted like NAME_OF_PAGE | My Site:

$transpiler->registerViewFunction('myPageTitle', function ($title) use ($transpiler, $view) {
    // Take advantage of the built-in view function
    return $transpiler->callViewFunction('pageTitle', $view, $title . ' | My Site');
});

Compiling {{! myPageTitle("About") !}} will give us <title>About | My Site</title>.

Delimiters

Delimiters are the characters that surround tags, directives, and comments, eg {{ }}, {{! !}}, <% %>, and {# #}. Fortune allows you to escape delimiters as well as change delimiters.

Escaping Delimiters

Lots of JavaScript frameworks use similar syntax to Fortune to display data. To display a raw tag, directive, or comment, escape it using the \ character:

\<% foo %>
\{{ bar }}
\{{! baz !}}
\{# blah #}

This will compile to:

<% foo %>
{{ bar }}
{{! baz !}}
{# blah #}

Changing Delimiters

Sometimes, it might just be easier to change the tag and/or directive delimiters used in a view. To do so, use the setDelimiters() method on your view:

use Opulence\Views\View;

$view = new View();
// Set new open and close delimiters for directives
$view->setDelimiters(View::DELIMITER_TYPE_DIRECTIVE, ['{%', '%}']);
// Set new open and close delimiters for sanitized tags
$view->setDelimiters(View::DELIMITER_TYPE_SANITIZED_TAG, ['^^', "$$"]);
// Set new open and close delimiters for unsanitized tags
$view->setDelimiters(View::DELIMITER_TYPE_UNSANITIZED_TAG, ['++', '--']);
// Set new open and close delimiters for comments
$view->setDelimiters(View::DELIMITER_TYPE_COMMENT, ['((', '))']);

Because delimiters are set for each view, you can have one view with one set of delimiters and another view with other delimiters. This is useful if only one or a couple of views' delimiters conflict with JavaScript framework delimiters.