Mantas Kaveckas @ .NFQ Academy
Twig is the flexible, fast, and secure template engine for PHP.
Key Features
Prerequisites
Twig needs at least PHP 7.0.0 to run.
Installation
composer require "twig/twig:^2.0"
Basic API Usage
require_once '/path/to/vendor/autoload.php';
$loader = new Twig_Loader_Array(array(
'index' => 'Hello {{ name }}!',
));
$twig = new Twig_Environment($loader);
echo $twig->render('index', array('name' => 'Fabien'));
Using Twig_Loader_Array
Basic API Usage
$loader = new Twig_Loader_Filesystem('/path/to/templates');
$twig = new Twig_Environment($loader, array(
'cache' => '/path/to/compilation_cache',
));
echo $twig->render('index.html', array('name' => 'Fabien'));
Using Twig_Loader_Filesystem
Twig is the default template engine for Symfony.
Synopsis
A template is simply a text file. It can generate any text-based format (HTML, XML, CSV, etc.). It doesn't have a specific extension.
Synopsis
A template contains variables or expressions, which get replaced with values when the template is evaluated, and tags, which control the logic of the template.
Synopsis
<!DOCTYPE html>
<html>
<head>
<title>My Webpage</title>
</head>
<body>
<ul id="navigation">
{% for item in navigation %}
<li><a href="{{ item.href }}">{{ item.caption }}</a></li>
{% endfor %}
</ul>
<h1>My Webpage</h1>
{{ a_variable }}
</body>
</html>
Minimal template
IDEs Integration
Many IDEs support syntax highlighting and auto-completion for Twig:
Variables
The application passes variables to the templates for manipulation in the template.
Variables
{{ foo.bar }}
{{ foo['bar'] }}
You can use a dot to access attributes of a variable (methods or properties of a PHP object, or items of a PHP array), or "subscript" syntax:
Setting Variables
{% set foo = 'foo' %}
{% set foo = [1, 2] %}
{% set foo = {'foo': 'bar'} %}
Variable assignments use the set tag:
Filters
The following example removes all HTML tags from the name and title-cases it:
{{ name|striptags|title }}
Filters
Filters that accept arguments have parentheses around the arguments. This example will join a list by commas:
{{ list|join(', ') }}
Filters
To apply a filter on a section of code, wrap it in the filter tag:
{% filter upper %}
This text becomes uppercase
{% endfilter %}
Functions
Functions can be called to generate content. Functions are called by their name followed by parentheses and may have arguments.
{% for i in range(0, 3) %}
{{ i }},
{% endfor %}
Control Structure
Conditionals (i.e. if/elseif/else), for-loops, as well as things like blocks.
Control Structure
For example, to display a list of users provided in a variable called users, use the for tag:
<h1>Members</h1>
<ul>
{% for user in users %}
<li>{{ user.username|e }}</li>
{% endfor %}
</ul>
Control Structure
The if tag can be used to test an expression:
{% if users|length > 0 %}
<ul>
{% for user in users %}
<li>{{ user.username|e }}</li>
{% endfor %}
</ul>
{% endif %}
Comments
{# note: disabled template because we no longer use this
{% for user in users %}
...
{% endfor %}
#}
No magic here:
Including other Templates
{{ include('sidebar.html') }}
The include function is useful to include a template and return the rendered content of that template into the current one:
Including other Templates
{% for box in boxes %}
{{ include('render_box.html') }}
{% endfor %}
Template Inheritance
The most powerful part of Twig is template inheritance.
Template Inheritance
Template inheritance allows you to build a base "skeleton" template that contains all the common elements of your site and defines blocks that child templates can override.
Template Inheritance
<!DOCTYPE html>
<html>
<head>
{% block head %}
<link rel="stylesheet" href="style.css" />
<title>{% block title %}{% endblock %} - My Webpage</title>
{% endblock %}
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
{% block footer %}
© Copyright 2011 by <a href="http://domain.invalid/">you</a>.
{% endblock %}
</div>
</body>
</html>
base.html
Template Inheritance
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ parent() }}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
{% block content %}
<h1>Index</h1>
<p class="important">
Welcome to my awesome homepage.
</p>
{% endblock %}
child.html
Template Inheritance
Template Inheritance
It's possible to render the contents of the parent block by using the parent function:
{% block sidebar %}
<h3>Table Of Contents</h3>
...
{{ parent() }}
{% endblock %}
Macros
Macros
{# forms.html #}
{% macro input(name, value, type, size) %}
<input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
{% endmacro %}
{# page.html #}
{% import "forms.html" as forms %}
<p>{{ forms.input('username') }}</p>
The world's most misunderstood programming language.
Building Blocks
Numbers
Numbers
Be a little careful with your arithmetic if you're used to math in C or Java:
0.1 + 0.2 == 0.30000000000000004;
Numbers
Although the standard arithmetic operators are supported:
Math.sin(3.5);
var circumference = 2 * Math.PI * r;
Numbers
You can convert a string to an integer using the built-in parseInt() function:
parseInt('123', 10); // 123
parseInt('010', 10); // 10
Numbers
If you want to convert a binary number to an integer, just change the base:
parseInt('11', 2); // 3
Numbers
A special value called NaN (short for "Not a Number") is returned if the string is non-numeric:
parseInt('hello', 10); // NaN
Numbers
You can test for NaN using the built-in isNaN() function:
isNaN(NaN); // true
Strings
Strings
To find the length of a string (in code units), access its length property:
'hello'.length; // 5
Strings
So strings like objects too? They have methods as well that allow you to manipulate the string and access information about the string:
'hello'.charAt(0); // "h"
'hello, world'.replace('hello', 'goodbye'); // "goodbye, world"
'hello'.toUpperCase(); // "HELLO"
Variables
New variables in JavaScript are declared using one of three keywords: let, const, or var.
Variables
let allows you to declare block-level variables. The declared variable is available from the function block it is enclosed in.
let a;
let name = 'Simon';
// myLetVariable is *not* visible out here
for (let myLetVariable = 0; myLetVariable < 5; myLetVariable++) {
// myLetVariable is only visible in here
}
// myLetVariable is *not* visible out here
Variables
const allows you to declare variables whose values are never intended to change. The variable is available from the function block it is declared in.
const Pi = 3.14; // variable Pi is set
Pi = 1; // will throw an error because you cannot change a constant variable.
Variables
var is the most common declarative keyword. A variable declared with the var keyword is available from the function block it is declared in.
var a;
var name = 'Simon';
// myVarVariable *is* visible out here
for (var myVarVariable = 0; myVarVariable < 5; myVarVariable++) {
// myVarVariable is visible to the whole function
}
// myVarVariable *is* visible out here
Operators
x += 5;
x = x + 5;
'hello' + ' world'; // "hello world"
'3' + 4 + 5; // "345"
3 + 4 + '5'; // "75"
123 == '123'; // true
1 == true; // true
123 === '123'; // false
1 === true; // false
Control structures
var name = 'kittens';
if (name == 'puppies') {
name += ' woof';
} else if (name == 'kittens') {
name += ' meow';
} else {
name += '!';
}
name == 'kittens meow';
Text
Conditional statements are supported by if and else; you can chain them together if you like:
Control structures
while (true) {
// an infinite loop!
}
var input;
do {
input = get_input();
} while (inputIsNotValid(input));
Text
Control structures
for (var i = 0; i < 5; i++) {
// Will execute 5 times
}
for (let value of array) {
// do something with value
}
for (let property in object) {
// do something with object property
}
Control structures
var name = o && o.getName();
var name = otherName || 'default';
The && and || operators use short-circuit logic, which means whether they will execute their second operand is dependent on the first.
Control structures
var allowed = (age > 18) ? 'yes' : 'no';
JavaScript has a ternary operator for conditional expressions:
Control structures
switch (action) {
case 'draw':
drawIt();
break;
case 'eat':
eatIt();
break;
default:
doNothing();
}
The switch statement can be used for multiple branches based on a number or string:
Objects
JavaScript objects can be thought of as simple collections of name-value pairs. As such, they are similar to:
Objects
There are two basic ways to create an empty object:
// Constructor syntax
var obj = new Object();
// Object literal syntax
var obj = {};
Objects
Object literal syntax can be used to initialize an object in its entirety:
var obj = {
name: 'Carrot',
'for': 'Max',
details: {
color: 'orange',
size: 12
}
}
Objects
Attribute access can be chained together:
obj.details.color; // orange
obj['details']['size']; // 12
Objects
The following example creates an object prototype - Person, and instance of that prototype - You.
function Person(name, age) {
this.name = name;
this.age = age;
}
// Define an object
var You = new Person('You', 24);
// We are creating a new person named "You"
// (that was the first parameter, and the age..)
Objects
Once created, an object's properties can again be accessed in one of two ways:
obj.name = 'Simon';
var name = obj.name;
// And...
obj['name'] = 'Simon';
var name = obj['name'];
Arrays
Arrays
One way of creating arrays is as follows:
var a = new Array();
a[0] = 'dog';
a[1] = 'cat';
a[2] = 'hen';
a.length; // 3
A more convenient notation is to use an array literal:
var a = ['dog', 'cat', 'hen'];
a.length; // 3
Arrays
Note that array.length isn't necessarily the number of items in the array. Consider the following:
var a = ['dog', 'cat', 'hen'];
a[100] = 'fox';
a.length; // 101
Arrays
If you query a non-existent array index, you'll get a value of undefined returned:
typeof a[90]; // undefined
Arrays
If you take the above into account, you can iterate over an array using the following:
for (var i = 0; i < a.length; i++) {
// Do something with a[i]
}
Arrays
You can iterate over an array using a for...in loop. Note that if someone added new properties to Array.prototype, they will also be iterated over by this loop. Therefore this method is "not" recommended.
Arrays
Another way of iterating over an array that was added with ECMAScript 5 is forEach():
['dog', 'cat', 'hen'].forEach(function(currentValue, index, array) {
// Do something with currentValue or array[index]
});
Arrays
If you want to append an item to an array simply do it like this:
a.push(item);
Arrays
Arrays come with a number of methods. See also the full documentation for array methods.
Functions
Along with objects, functions are the core component in understanding JavaScript. The most basic function couldn't be much simpler:
function add(x, y) {
var total = x + y;
return total;
}
Functions
The named parameters turn out to be more like guidelines than anything else. You can call a function without passing the parameters it expects, in which case they will be set to undefined.
add(); // NaN
// You can't perform addition on undefined
Functions
You can also pass in more arguments than the function is expecting:
add(2, 3, 4); // 5
// added the first two; 4 was ignored
Functions
That may seem a little silly, but functions have access to an additional variable inside their body called arguments, which is an array-like object holding all of the values passed to the function.
function add() {
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++) {
sum += arguments[i];
}
return sum;
}
add(2, 3, 4, 5); // 14
Functions
This is pretty useful, but it does seem a little verbose. To diminish this code a bit more we can look at substituting the use of the arguments array through Spread syntax.
function avg(...args) {
var sum = 0;
for (let value of args) {
sum += value;
}
return sum / args.length;
}
avg(2, 3, 4, 5); // 3.5
Functions
The avg() function takes a comma separated list of arguments — but what if you want to find the average of an array? You could just rewrite the function as follows:
function avgArray(arr) {
var sum = 0;
for (var i = 0, j = arr.length; i < j; i++) {
sum += arr[i];
}
return sum / arr.length;
}
avgArray([2, 3, 4, 5]); // 3.5
Functions
JavaScript lets you create anonymous functions.
var avg = function() {
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++) {
sum += arguments[i];
}
return sum / arguments.length;
};
Functions
It's extremely powerful, as it lets you put a full function definition anywhere that you would normally put an expression. This enables all sorts of clever tricks. Here's a way of "hiding" some local variables:
var a = 1;
var b = 2;
(function() {
var b = 3;
a += b;
})();
a; // 4
b; // 2
Functions
JavaScript allows you to call functions recursively. This is particularly useful for dealing with tree structures, such as those found in the browser DOM.
function countChars(elm) {
if (elm.nodeType == 3) { // TEXT_NODE
return elm.nodeValue.length;
}
var count = 0;
for (var i = 0, child; child = elm.childNodes[i]; i++) {
count += countChars(child);
}
return count;
}
Functions
This highlights a potential problem with anonymous functions: how do you call them recursively if they don't have a name?
Functions
JavaScript lets you name function expressions. You can use named IIFEs (Immediately Invoked Function Expressions) as shown below:
var charsInBody = (function counter(elm) {
if (elm.nodeType == 3) { // TEXT_NODE
return elm.nodeValue.length;
}
var count = 0;
for (var i = 0, child; child = elm.childNodes[i]; i++) {
count += counter(child);
}
return count;
})(document.body);
Custom objects
In classic OOP, objects are collections of data and methods that operate on that data.
Custom objects
Custom objects
Let's consider a person object with first and last name fields. There are two ways in which the name might be displayed: as "first last" or as "last, first".
Custom objects
Using the functions and objects discussed previously, we could display the data like this:
function makePerson(first, last) {
return {
first: first,
last: last
};
}
function personFullName(person) {
return person.first + ' ' + person.last;
}
function personFullNameReversed(person) {
return person.last + ', ' + person.first;
}
s = makePerson('Simon', 'Willison');
personFullName(s); // "Simon Willison"
personFullNameReversed(s); // "Willison, Simon"
Custom objects
This works, but it's pretty ugly. You end up with dozens of functions in your global namespace.
Custom objects
What we really need is a way to attach a function to an object. Since functions are objects, this is easy:
function makePerson(first, last) {
return {
first: first,
last: last,
fullName: function() {
return this.first + ' ' + this.last;
},
fullNameReversed: function() {
return this.last + ', ' + this.first;
}
};
}
s = makePerson('Simon', 'Willison');
s.fullName(); // "Simon Willison"
s.fullNameReversed(); // "Willison, Simon"
Custom objects
Used inside a function, this refers to the current object. What that actually means is specified by the way in which you called that function. If you called it using dot notation or bracket notation on an object, that object becomes this.
If dot notation wasn't used for the call, this refers to the global object.
Custom objects
Note that this is a frequent cause of mistakes. For example:
s = makePerson('Simon', 'Willison');
var fullName = s.fullName;
fullName(); // undefined undefined
Custom objects
When we call fullName() alone, without using s.fullName(), this is bound to the global object. Since there are no global variables called first or last we get undefined for each one.
Custom objects
We can take advantage of the this keyword to improve our makePerson function:
function Person(first, last) {
this.first = first;
this.last = last;
this.fullName = function() {
return this.first + ' ' + this.last;
};
this.fullNameReversed = function() {
return this.last + ', ' + this.first;
};
}
var s = new Person('Simon', 'Willison');
Custom objects
We have introduced another keyword: new. new is strongly related to this.
Custom objects
Custom objects
Functions that are designed to be called by new are called constructor functions. Common practice is to capitalize these functions as a reminder to call them with new.
Custom objects
Every time we create a person object we are creating two brand new function objects within it — wouldn't it be better if this code was shared?
function personFullName() {
return this.first + ' ' + this.last;
}
function personFullNameReversed() {
return this.last + ', ' + this.first;
}
function Person(first, last) {
this.first = first;
this.last = last;
this.fullName = personFullName;
this.fullNameReversed = personFullNameReversed;
}
Custom objects
We are creating the method functions only once, and assigning references to them inside the constructor. Can we do any better than that? The answer is yes:
function Person(first, last) {
this.first = first;
this.last = last;
}
Person.prototype.fullName = function() {
return this.first + ' ' + this.last;
};
Person.prototype.fullNameReversed = function() {
return this.last + ', ' + this.first;
};
Custom objects
Person.prototype is an object shared by all instances of Person. It forms part of a lookup chain: any time you attempt to access a property of Person that isn't set, JavaScript will check Person.prototype to see if that property exists there instead. As a result, anything assigned to Person.prototype becomes available to all instances of that constructor via the this object.
Custom objects
JavaScript lets you modify something's prototype at any time in your program, which means you can add extra methods to existing objects at runtime:
s = new Person('Simon', 'Willison');
s.firstNameCaps(); // TypeError on line 1: s.firstNameCaps is not a function
Person.prototype.firstNameCaps = function firstNameCaps() {
return this.first.toUpperCase();
};
s.firstNameCaps(); // "SIMON"
Custom objects
Interestingly, you can also add things to the prototype of built-in JavaScript objects. Let's add a method to String that returns that string in reverse:
var s = 'Simon';
s.reversed(); // TypeError on line 1: s.reversed is not a function
String.prototype.reversed = function reversed() {
var r = '';
for (var i = this.length - 1; i >= 0; i--) {
r += this[i];
}
return r;
};
s.reversed(); // nomiS
Custom objects
Our new method even works on string literals!
'This can now be reversed'.reversed(); // desrever eb won nac sihT