Frontend Vol. 2

Mantas Kaveckas @ .NFQ Academy

Twig

Twig is the flexible, fast, and secure template engine for PHP.

Twig

Key Features

  • Fast: Twig compiles templates down to plain optimized PHP code.
  • Secure: Twig has a sandbox mode to evaluate untrusted template code.
  • Flexible: Twig is allows the developer to define their own custom tags and filters.

Twig

Prerequisites

Twig needs at least PHP 7.0.0 to run.

Twig

Installation

composer require "twig/twig:^2.0"

Twig

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

Twig

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

Twig is the default template engine for Symfony.

Twig

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.

Twig

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.

Twig

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

Twig

IDEs Integration

Many IDEs support syntax highlighting and auto-completion for Twig:

  • PhpStorm (native as of 2.1)
  • Eclipse via the Twig plugin
  • Sublime Text via the Twig bundle
  • Many more...

Twig

Variables

The application passes variables to the templates for manipulation in the template.

Twig

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:

Twig

Setting Variables

{% set foo = 'foo' %}
{% set foo = [1, 2] %}
{% set foo = {'foo': 'bar'} %}

Variable assignments use the set tag:

Twig

Filters

The following example removes all HTML tags from the name and title-cases it:

{{ name|striptags|title }}

Twig

Filters

Filters that accept arguments have parentheses around the arguments. This example will join a list by commas:

{{ list|join(', ') }}

Twig

Filters

To apply a filter on a section of code, wrap it in the filter tag:

{% filter upper %}
    This text becomes uppercase
{% endfilter %}

Twig

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 %}

Twig

Control Structure

Conditionals (i.e. if/elseif/else), for-loops, as well as things like blocks.

Twig

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>

Twig

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 %}

Twig

Comments

{# note: disabled template because we no longer use this
    {% for user in users %}
        ...
    {% endfor %}
#}

No magic here:

Twig

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:

Twig

Including other Templates

{% for box in boxes %}
    {{ include('render_box.html') }}
{% endfor %}

Twig

Template Inheritance

The most powerful part of Twig is template inheritance.

Twig

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.

Twig

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

Twig

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

Twig

Template Inheritance

  • The extends tag is the key here. It tells the template engine that this template "extends" another template.
  • When the template system evaluates this template, first it locates the parent.
  • The extends tag should be the first tag in the template.

Twig

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 %}

Twig

Macros

  • Macros are comparable with functions in regular programming languages.
  • They are useful to reuse often used HTML fragments to not repeat yourself.

Twig

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>

JavaScript

The world's most misunderstood programming language.

JavaScript

Building Blocks

  • Number
  • String
  • Boolean
  • Symbol (new in ES2015)
  • Object
    • Function
    • Array
    • Date
    • RegExp
  • null
  • undefined

JavaScript

Numbers

  • Numbers in JavaScript are "double-precision 64-bit format IEEE 754 values".
  • There's no such thing as an integer in JavaScript.

JavaScript

Numbers

Be a little careful with your arithmetic if you're used to math in C or Java:

0.1 + 0.2 == 0.30000000000000004;

JavaScript

Numbers

Although the standard arithmetic operators are supported:

Math.sin(3.5);
var circumference = 2 * Math.PI * r;

JavaScript

Numbers

You can convert a string to an integer using the built-in parseInt() function:

parseInt('123', 10); // 123
parseInt('010', 10); // 10

JavaScript

Numbers

If you want to convert a binary number to an integer, just change the base:

parseInt('11', 2); // 3

JavaScript

Numbers

A special value called NaN (short for "Not a Number") is returned if the string is non-numeric:

parseInt('hello', 10); // NaN

JavaScript

Numbers

You can test for NaN using the built-in isNaN() function:

isNaN(NaN); // true

JavaScript

Strings

  • Strings in JavaScript are sequences of Unicode characters.
  • More accurately, they are sequences of UTF-16 code units.
  • Each code unit is represented by a 16-bit number. Each Unicode character is represented by either 1 or 2 code units.

JavaScript

Strings

To find the length of a string (in code units), access its length property:

'hello'.length; // 5

JavaScript

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"

JavaScript

Variables

New variables in JavaScript are declared using one of three keywords: let, const, or var.

JavaScript

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

JavaScript

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.

JavaScript

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

JavaScript

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

JavaScript

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:

JavaScript

Control structures

while (true) {
  // an infinite loop!
}

var input;
do {
  input = get_input();
} while (inputIsNotValid(input));

Text

JavaScript

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
}

JavaScript

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.

JavaScript

Control structures

var allowed = (age > 18) ? 'yes' : 'no';

JavaScript has a ternary operator for conditional expressions:

JavaScript

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:

JavaScript

Objects

JavaScript objects can be thought of as simple collections of name-value pairs. As such, they are similar to:

 

  • Dictionaries in Python.
  • Hashes in Perl and Ruby.
  • Hash tables in C and C++.
  • HashMaps in Java.
  • Associative arrays in PHP.

 

JavaScript

Objects

There are two basic ways to create an empty object:

// Constructor syntax
var obj = new Object();

// Object literal syntax
var obj = {};

JavaScript

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
  }
}

JavaScript

Objects

Attribute access can be chained together:

obj.details.color; // orange
obj['details']['size']; // 12

JavaScript

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..)

JavaScript

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'];

JavaScript

Arrays

  • Arrays in JavaScript are actually a special type of object.
  • They work very much like regular objects (numerical properties can naturally be accessed only using [] syntax) but they have one magic property called 'length'.
  • This is always one more than the highest index in the array.

JavaScript

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

JavaScript

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

JavaScript

Arrays

If you query a non-existent array index, you'll get a value of undefined returned:

typeof a[90]; // undefined

JavaScript

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]
}

JavaScript

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.

JavaScript

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]
});

JavaScript

Arrays

If you want to append an item to an array simply do it like this:

a.push(item);

JavaScript

Arrays

Arrays come with a number of methods. See also the full documentation for array methods.

  • a.toString()
  • a.concat(item1[, item2[, ...[, itemN]]])
  • a.join(sep)
  • a.pop()
  • a.reverse()
  • a.slice(start[, end])
  • etc...

JavaScript

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;
}

JavaScript

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

JavaScript

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

JavaScript

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

JavaScript

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

JavaScript

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

JavaScript

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;
};

JavaScript

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

JavaScript

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;
}

JavaScript

Functions

This highlights a potential problem with anonymous functions: how do you call them recursively if they don't have a name?

JavaScript

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);

JavaScript

Custom objects

In classic OOP, objects are collections of data and methods that operate on that data.

JavaScript

Custom objects

  • JavaScript is a prototype-based language that contains no class statement, as you'd find in C++ or Java.
  • Instead, JavaScript uses functions as classes.

JavaScript

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".

JavaScript

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"

JavaScript

Custom objects

This works, but it's pretty ugly. You end up with dozens of functions in your global namespace.

JavaScript

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"

JavaScript

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.

JavaScript

Custom objects

Note that this is a frequent cause of mistakes. For example:

s = makePerson('Simon', 'Willison');
var fullName = s.fullName;
fullName(); // undefined undefined

JavaScript

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.

JavaScript

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');

JavaScript

Custom objects

We have introduced another keyword: new. new is strongly related to this.

JavaScript

Custom objects

  • new creates a brand new empty object.
  • And then calls the function specified, with this set to that new object.

JavaScript

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.

JavaScript

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;
}

JavaScript

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;
};

JavaScript

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.

JavaScript

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"

JavaScript

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

JavaScript

Custom objects

Our new method even works on string literals!

'This can now be reversed'.reversed(); // desrever eb won nac sihT

Mind === Blown.

QA & Thank You!

Twig

By Mantas Kaveckas

Twig

  • 240
Loading comments...

More from Mantas Kaveckas