Symfony2 components overview: Finder

This is the 9th post in this series about the Symfony2 components. Today’s post covers one of the most popular PHP packages: the Finder component. According to Packagist it is the 14th most used PHP package, and the 3rd Symfony2 component, only after the EventDispatcher and Console ones.

The Finder component gives you an excellent lens to find files and directories

Installation

Installation is really easy using Composer:

{
    "require": {
        "symfony/finder": "2.4.*"
    }
}

If you have never used Composer before, check out our Composer 101 post.

The component

The Finder component helps us to find files and directories using a fluent interface. It provides useful methods to find what we are looking for, based on name, location, size, modification date as well as many other filters.

<?php

use Symfony\Component\Finder\Finder;

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

$finder = new Finder();
$finder->in(__DIR__)->files();

foreach ($finder as $file) {
    echo $file->getRealpath() . PHP_EOL;
}

In the above example, we create a Finder instance and configure it to find only files in the current directory, as we are using the files() method. Then, as the Finder instance is a PHP iterator (implements IteratorAggregate), we can use it in a foreach loop. For each of the files found we print its real path.

Instead of iterating over the Finder with the foreach loop, we can also use some of the functions that PHP provides to deal with iterators, such as iterator_to_array and iterator_count:

$filesNumber = iterator_count($finder);
$files = iterator_to_array($finder);

var_dump($filesNumber, $files);

Assuming we only have a PHP file in the current directory, we get as output:

int(1)
array(1) {
  '/Users/raulfraile/Sites/sgposts/finder/index.php' =>
  class Symfony\Component\Finder\SplFileInfo#13 (4) {
    private $relativePath =>
    string(0) ""
    private $relativePathname =>
    string(9) "index.php"
    private $pathName =>
    string(48) "/Users/raulfraile/Sites/sgposts/finder/index.php"
    private $fileName =>
    string(9) "index.php"
  }
}

Criteria

The official documentation for this component has a huge list of available options to find files and directories and it does not make sense to repeat all of them in this article. Here is an overview of a few important ones:

  • in($dirs): The location is the only mandatory criteria. Accepts one or more directories.
  • files() / directories(): Restricts the matching to files/directories only.
  • depth(): Restrict the depth of traversing. Accepts expressions such as ‘== 0′ or ‘<3′.
  • exclude($dirs): Exclude directories.
  • ignoreVCS($ignoreVCS): Ignores version control directories such as “.git”.
  • name($pattern): Find files that matches the pattern. Patterns can be globs, strings, or regular expressions.
  • contains($pattern): Find files with content that matches the pattern. Patterns can be strings or regular expressions.
  • filter($closure) / sort($closure): These methods accept a closure as input parameter, so we can customize filtering and sorting.

Under the hood

Adapters

The component relies on the Adapter pattern to provide different ways to find files/directories depending on the user’s machine. It ships with 3 adapters: PhpAdapter, GnuFindAdapter and BsdFindAdapter.

The PhpAdapter is always available and performs the search using PHP iterators, whilst GnuFindAdapter and BsdFindAdapter are only available in Unix/BSD operating systems. Their goal is to improve the performance by using shell commands like “find, “sort”, “cut” or “grep”. For example, these are the generated commands for some common criteria using the BsdFindAdapter (e.g. MacOS):

// find  -E '/dir' -noleaf -mindepth 1 -not \( -regex '.*(^|/)\..+(/|$).*' \)
$finder->in(__DIR__);

// find  -E '/dir' -noleaf -mindepth 1 -maxdepth 1 -type f -not \( -regex '.*(^|/)\..+(/|$).*' \)
$finder->in(__DIR__)->files()->depth(0);

// find  -E '/dir' -noleaf -mindepth 1
$finder->in(__DIR__)->ignoreDotFiles(false);

// find  -E '/dir' -noleaf -mindepth 1 -type f \( -name '*.php' \) -not \( -regex '.*(^|/)\..+(/|$).*' \)
$finder->in(__DIR__)->files()->name('*.php');

// find  -E '/dir' -noleaf -mindepth 1 -type f -not \( -regex '.*(^|/)\..+(/|$).*' \) | grep -v '^$' | xargs -I{} grep -I -l -Ee 'hello' {}
$finder->in(__DIR__)->files()->contains('hello');

All the adapters must implement the AdapterInterface interface, which defines the methods to set the criteria. Two of these methods are especially important so the Finder instance can best choose an adapter: isSupported() and getName(). If the adapters extend from the generic AbstractAdapter, it forces to implement the method canBeUsed() instead of isSupported() since the generic implementation provides a cache layer.

The IteratorAggregate interface

As we saw before, the Finder instance implements two PHP interfaces: IteratorAggregate and Countable.

The Countable interface is straightforward since it only defines the count() method. This method is called when we use the count() built-in function, but not with iterator_count().

The IteratorAggregate interface defines one method as well, getIterator(), which must return an object that implements the Traversable interface. Once we use the Finder instance in a foreach loop or as a parameter in iterator_count() or iterator_to_array() functions, this method is called and a new iterator is created.

It is important to note that when searching through multiple locations (passed through the in() method), an instance of AppendIterator is created to have a list of iterators (one for each directory), so iterates one after the other.

SplFileInfo

The component does not use the standard SplFileInfo class for the results, but a class extending it. This class adds support for relative paths and for getting the contents of the files directly with methods such as getRelativePath(), getRelativePathname() and getContents().

Globs

Globs are used in Unix-like environments to perform pattern matching based on wildcard characters. For example, “*.php” to find all the files having the “php” extension. The support for globs in PHP is limited (e.g. it does not work with remote files), but the component solves these limitations converting globs into regular expressions (a tradeoff between performance and usability). So, when using the PhpAdapter with globs, we are actually not using globs internally, but regular expressions.

For example, the following criteria:

$finder->in(__DIR__)->files()->name('*.php');

Generates this regular expression:

"^(?=[^\.])[^/]*\.php$"

Who’s using it

As I said before, it’s one of the most used PHP libraries according to Packagist, so it is not easy at all to list all the projects using it. Here is a short list of them:

More info

Photo: Investigating Nana & Grampas House, by Jae Malone

marzo 26 / 2014

Upcoming Conferences

WeCamp

PHP New Zealand

PHP Summer Camp Croatia

PHPNE

MadisonPHP

brnoPHP

SymfonyLiveLondon

ZgPHP

PHPSouthAfrica

PHPNWUK

SymfonyLiveNYC

PHPForumParis

PHPARG

PHPWorld

TechMeetupUY

SymfonyConMadrid

Introducción a Composer

logo-composer-transparent

Composer es una herramienta para la gestión de dependencias en PHP. Nos permite definir, de una manera muy simple, las librerías (paquetes a partir de ahora) que necesita nuestra aplicación y las descarga por nosotros. Sin duda, ha sido una de las mejores noticias de los últimos años en el mundo PHP, ya que ha cambiado radicalmente la forma en la que construimos los proyectos. Es posible encontrar paquetes de calidad para casi cualquier funcionalidad en Packagist.

En este tutorial mostraremos como instalarlo y empezar a utilizarlo.

Instalación

Windows

La forma más sencilla de instalar Composer en Windows es descargar y ejecutar su instalador. También se encargará de actualizar la variable %PATH% para que pueda ser llamado desde cualquier directorio en línea de comandos.

*nix

En sistemas Unix como Linux o Mac OS, la instalación se realiza descargando el instalador con cURL, que no es más que un script PHP que debe ejecutarse. Éste se asegurará que Composer puede ser usado en el sistema. Después, descargará automáticamente el fichero composer.phar, que es el ejecutable de Composer. La instrucción para realizar la instalación sería la siguiente:

$ curl -sS https://getcomposer.org/installer | php

Para instalar Composer de forma global en el sistema y poder usarlo como cualquier otro comando, es tan sencillo como copiar el archivo composer.phar en alguno de los directorios incluidos en la variable de entorno PATH, por ejemplo en /usr/local/bin:

$ mv composer.phar /usr/local/bin/composer

Como funciona

Composer es un gestor de dependencias por proyecto, por lo que los paquetes necesarios para nuestra aplicación no se instalarán globalmente como ocurre con PEAR, sino en un directorio especial llamado vendor. Después, simplemente añadiendo un require_once del archivo de autoload que genera Composer tendremos acceso a todas nuestras dependencias.

Las dependencias se definen en un archivo llamado composer.json. Un ejemplo simple sería el siguiente:

    &quot;require&quot;: {
        &quot;fabpot/goutte&quot;: &quot;v1.0.5&quot;
    }

En el ejemplo anterior definimos una única dependencia, el paquete “fabpot/goutte”, que sirve para hacer screen scrapping y web crawling en PHP.

Cuando ejecutamos “composer install”, Composer comprueba las dependencias que hemos definido y descarga los paquetes necesarios para satisfacerlas:

$ composer install
Loading composer repositories with package information
Installing dependencies (including require-dev)
  - Installing symfony/event-dispatcher (v2.4.2)
    Downloading: 100%
  - Installing guzzle/common (v3.8.1)
    Downloading: 100%
  ...
  - Installing symfony/browser-kit (v2.4.2)
    Downloading: 100%
  - Installing fabpot/goutte (v1.0.5)
    Downloading: 100%

symfony/event-dispatcher suggests installing symfony/dependency-injection ()
symfony/event-dispatcher suggests installing symfony/http-kernel ()
Writing lock file
Generating autoload files

Como puedes comprobar, Composer no solo ha descargado “fabpot/goutte”, también algunos paquetes adicionales. Esto se debe a que el paquete “fabpot/goutte” define a su vez otras dependencias – se pueden comprobar en su archivo composer.json -, pero no nos tenemos que preocupar de esto, Composer se encarga automáticamente de calcular el grafo de dependencias. Este es un proceso bastante costoso si el número de dependencias crece, por lo que Composer intenta minimizar el problema creando el archivo composer.lock, que contiene las versiones exactas de todos los paquetes a instalar. Así, la próxima vez que ejecutemos “composer install”, Composer simplemente leerá el archivo composer.lock y descargará los paquetes. Si modificamos el archivo composer.json, necesitamos ejecutar “composer update” para que el archivo composer.lock se calcule de nuevo.

“composer update” es el nuevo “compilando…” :)

Una vez que los paquetes se han descargado, Composer genera un archivo php que debemos incluir en el proyecto para poder utilizar las dependencias. Éste archivo registra un autoloader PSR-0/PSR-4, lo que facilita hacer uso de las dependencias:

&lt;?php

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

use Goutte\Client;

$client = new Client();
$crawler = $client-&gt;request('GET', 'http://www.servergrove.com');
$title = $crawler-&gt;filter('head &gt; title')-&gt;text();
echo $title;

En el ejemplo anterior, incluímos el archivo autogenerado autoload.php, y a continuación creamos un objeto de la clase Goutte\Client, que forma parte del paquete “fabpot/goutte”. Por último, utilizamos el objeto para extraer el contenido de la etiqueta title de la página web de ServerGrove.

El comando composer

Ya hemos hablado de los comandos “install” y “update”, pero Composer dispone de algunos comandos adicionales. “composer list” muestra una lista de todos los comandos disponibles:

$ composer list
   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/

Available commands:
  about            Short information about Composer
  archive          Create an archive of this composer package
  config           Set config options
  create-project   Create new project from a package into given directory.
  depends          Shows which packages depend on the given package
  diagnose         Diagnoses the system to identify common errors.
  dump-autoload    Dumps the autoloader
  dumpautoload     Dumps the autoloader
  global           Allows running commands in the global composer dir ($COMPOSER_HOME).
  help             Displays help for a command
  init             Creates a basic composer.json file in current directory.
  install          Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json.
  licenses         Show information about licenses of dependencies
  list             Lists commands
  require          Adds required packages to your composer.json and installs them
  run-script       Run the scripts defined in composer.json.
  search           Search for packages
  self-update      Updates composer.phar to the latest version.
  selfupdate       Updates composer.phar to the latest version.
  show             Show information about packages
  status           Show a list of locally modified packages
  update           Updates your dependencies to the latest version according to composer.json, and updates the composer.lock file.
  validate         Validates a composer.json

Algunos comandos interesantes:

  • init: Crea un archivo composer.json de forma interactiva.
  • validate: Comprueba si el archivo composer.json es válido.
  • selfupdate: Actualiza Composer a la última versión disponible. Si la versión de Composer es demasiado antigua – 30 días o más -, se muestra un mensaje de alerta cuando se ejecuta cualquier comando.
  • search: Busca paquetes en Packagist – por ejemplo, “composer search yaml” -.
  • show: Muestra información sobre los paquetes. Por ejemplo, “composer show –installed” muestra un listado de los paquetes instalados.
  • create-project: Crea un nuevo proyecto a partir de un paquete. Es bastante útil para frameworks – por ejemplo, para crear un nuevo proyecto Symfony2: “php composer.phar create-project symfony/framework-standard-edition path/ 2.4.2″.

Versiones de los paquetes

Hay varias formas de definir qué versión o versiones aceptamos en nuestro proyecto:

  • Versión exacta: “1.4.2″, “v1.4.2″.
  • Rangos: “>=1.0,<2.0", ">=1.0,<1.1 | >=1.2″.
  • Comodines: “1.0.*”, “1.*”.
  • Next Significant Release: “~1.2″ es equivalente a “>=1.2,<2.0".

Estabilidad

Cada una de las versiones de los paquetes dispone de un stability flag, que indica si la versión es “final” o si en cambio se trata de una versión previa (p.ej. beta). Por defecto, solo se tienen en cuenta las versiones finales, por lo que si necesitamos alguna versión no stable, debemos cambiar el parámetro minimum-stability. Los valores disponibles son “dev”, “alpha”, “beta”, “RC” y “stable”. También es posible definir el minimum stability por dependencia, añadiendo “@flag” a la versión del paquete – por ejemplo “”fabpot/goutte”: “1.0.*@dev””.

El directorio “vendor”

Composer crea automáticamente el directorio vendor, que debemos ignorar en el sistema de control de versiones (VCS) – por ejemplo, añadiéndolo al .gitignore en GIT-. Es recomendable incluir el archivo composer.lock en el VCS para que todo el equipo de desarrollo utilice exactamente las mismas versiones de los paquetes. Se acabó la excusa de “funciona en mi ordenador…”!

Más información

Éste artículo es solamente una introducción a Composer y como empezar a utilizarlo en nuestros proyectos para gestionar sus dependencias. Si deseas profundizar más, Composer dispone de una excelente documentación, tanto para creadores de paquetes como para los consumidores.

marzo 19 / 2014
Author Raul Fraile
Category PHP
Comments No Comments

Upcoming Conferences

WeCamp

PHP New Zealand

PHP Summer Camp Croatia

PHPNE

MadisonPHP

brnoPHP

SymfonyLiveLondon

ZgPHP

PHPSouthAfrica

PHPNWUK

SymfonyLiveNYC

PHPForumParis

PHPARG

PHPWorld

TechMeetupUY

SymfonyConMadrid

Symfony2 components overview: Translation

This post covers the Symfony Translation component. The component  provides tools to internationalize our applications.

In today’s globalized world internationalizing our applications is a must

The Translation component

Modern applications need to be internationalized and localized to be able to reach people from all over the world. Internationalization – aka i18n – is the process of designing a software application so that it can be adapted to various languages and regions without engineering changes. Localization – aka l10n – is the process of adapting internationalized software for a specific region or language by adding locale-specific components such as dates or currency. The Translation component provides different tools to get your application internationalized, but not localized.

The component is split in three main parts:

  • Catalogues: These are key-value collections of messages. The translator class looks in catalogues to find the required matching key.
  • Loaders: Loaders convert translation resources such as YAML, XLIFF or JSON files into PHP array.
  • Dumpers: Dumpers export a catalogue into a given format.

The component ships with several loaders and dumpers for the most common formats: PHP, XLIFF, JSON, YAML, PO, MO, INI and CSV. It it can also load PHP arrays.

Simple example

Let’s start with a very simple example. What we want to do here is translate a few strings into Spanish and Portuguese. Take a look at the code:

<?php

use Symfony\Component\Translation\Loader\ArrayLoader;
use Symfony\Component\Translation\Translator;

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

$translator = new Translator('es_ES');

$translator->addLoader('array', new ArrayLoader());

$translator->addResource('array', array(
    'hello world!' => '¡hola mundo!',
    'hello %name%!' => '¡hola %name%!'
), 'es_ES');

$translator->addResource('array', array(
    'hello world!' => 'Olá mundo!',
    'hello %name%!' => 'Olá %name%!'
), 'pt');

var_dump($translator->trans('hello world!'));
var_dump($translator->trans('hello %name%!', array('%name%' => 'Raul')));
var_dump($translator->trans('hello %name%!', array('%name%' => 'Raul'), null, 'pt_PT'));

We created the $translator object, which is an instance of Translator, and set “es_ES” as default locale, which is the combination of an ISO 639-1 code for the language and ISO 3166-1 alpha-2 for the country. This will be the default locale.

Then, we add the ArrayLoader loader which allows us to pass the PHP code for the translation messages (instead of messing around with translation files in this basic example). Using this loader, we register translation messages for “es_ES” and “pt”.

Finally, we call the trans() method of the translator object to get the translated strings. The first string will output “¡hola mundo!”, as the default locale is “es_ES”. The second string will call “¡hola Raul!” as we use placeholders, while the last call will display “Olá Raul!”, in portuguese, as the translator first checks for “pt_PT” and then “pt”:

string(13) "¡hola mundo!"
string(12) "¡hola Raul!"
string(10) "Olá Raul!"

Instead of full strings, using keys are recommended:

$translator->addResource('array', array(
    'hello_world' => '¡hola mundo!',
    'hello_name!' => '¡hola %name%!'
), 'es_ES');

Load translations from translation files

Normally, we will have translation files instead of plain PHP arrays. Loading these files is as easy as registering the loader we want to use – specific for the file format – and adding the resource. For example, to load the following YAML file:

messages:
    good_morning: "Buenos días"

errors:
    invalid_email: "Email no válido"

We register a YamlFileLoader instance with the key yaml and then we add the full path of the YAML:

<?php

use Symfony\Component\Translation\Loader\YamlFileLoader;
use Symfony\Component\Translation\Translator;

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

$translator = new Translator('es_ES');
$translator->addLoader('yaml', new YamlFileLoader());

$translator->addResource('yaml', __DIR__ . '/translations/es_ES.yml' , 'es_ES');

var_dump($translator->trans('messages.good_morning'));
var_dump($translator->trans('errors.invalid_email'));

The output, as expected, is:

string(12) "Buenos días"
string(16) "Email no válido"

Keep in mind that all provided loaders are dependent on the Config component, and YamlFileLoader has a dependency on the Yaml component too.

Translation domains

In the previous example, we used the same YAML file to define “normal” and error messages, and as nested keys are flatten, we used “messages.good_morning” and “errors.invalid_email” as keys. While this works, it is usually a good idea to have different translation domains, so we can separate translation messages in different files by their purpose. It is common to have domains for normal messages, errors, form messages or validations.

In our example, we could split the YAML file into two files and load them separately indicating the domain. If we don’t provide a domain name, “messages” is the default one.

<?php

use Symfony\Component\Translation\Loader\YamlFileLoader;
use Symfony\Component\Translation\Translator;

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

$translator = new Translator('es_ES');
$translator->addLoader('yaml', new YamlFileLoader());

$translator->addResource('yaml', __DIR__ . '/translations/messages.es_ES.yml' , 'es_ES');
$translator->addResource('yaml', __DIR__ . '/translations/errors.es_ES.yml' , 'es_ES', 'errors');

var_dump($translator->trans('good_morning'));
var_dump($translator->trans('invalid_email', array(), 'errors'));

Pluralization

Pluralization is a really hard task to achieve in some languages such as Russian or Polish, as rules can be quite complex. Anyway, the component provides the mechanisms to define these rules and be able to pluralize messages.

For example, to show the message “There is 1 cat” if there is actually only 1 cat, and “There are X cats” if there are two or more, it is defined using options and placeholders:

cats: "There is 1 cat|There are %number% cats"

Then, the transChoice() method of the translator instance takes care of it. It gets the translation key, the number of cats in this example and optionally the placeholder values:

var_dump($translator->transChoice('cats', 1));
var_dump($translator->transChoice('cats', 2, array('%number%' => 2)));

This code will output:

string(14) "There is 1 cat"
string(16) "There are 2 cats"

You can find more examples of pluralization in the Symfony documentation.

Dump messages

The component is also shipped with a few dumpers that allow us to export catalogues into different formats. This can be useful for using third-party translation files or if you just to have them in an easier to read format for a human.

In the following example, we load a catalogue from a YAML file and export it to JSON, using a JsonFileDumper instance:

<?php

use Symfony\Component\Translation\Loader\YamlFileLoader;
use Symfony\Component\Translation\Dumper\JsonFileDumper;

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

$loader = new YamlFileLoader();
$catalogue = $loader->load(__DIR__ . '/translations/messages.es_ES.yml' , 'es_ES');

$dumper = new JsonFileDumper();
$dumper->dump($catalogue, array('path' => __DIR__.'/dumps'));

This script creates the file ./dumps/messages.es_ES.yml, with the messages in JSON format:

{
    "good_morning": "Buenos d\u00edas",
    "good_night": "Buenas noches",
    "welcome": "Bienvenido"
}

Custom loaders

So, this is really cool, but what happens when we are trying to modernize a legacy project using Symfony2 components and it uses a weird custom format for translation files? Could we still use the Translation component? Absolutely! We just have to create a loader for that format.

Imagine we have a custom format where translation messages are defined using one line for each translation and parenthesis to wrap the key and the message. A translation file would look like this:

(welcome)(Bienvenido)
(goodbye)(Adios)
(hello)(Hola)

To define a custom loader able to read this kind of files, we must implement the LoaderInterface interface, which defines a load() method. In our loader, this method will get a filename and parse it to create an array. Then, it will create the catalog that will be returned.

<?php

namespace RaulFraile\Loader;

use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Loader\LoaderInterface;

class CustomLoader implements LoaderInterface
{

    public function load($resource, $locale, $domain = 'messages')
    {
        $messages = array();
        $lines = file($resource);

        foreach ($lines as $line) {
            if (preg_match('/\(([^\)]+)\)\(([^\)]+)\)/', $line, $matches)) {
                $messages[$matches[1]] = $matches[2];
            }
        }

        $catalogue = new MessageCatalogue($locale);
        $catalogue->add($messages, $domain);

        return $catalogue;
    }

}

As you see, adding support for new formats is fast and painless, and once you have it, it can be used as any other loader:

<?php

use Symfony\Component\Translation\Translator;
use RaulFraile\Loader\CustomLoader;

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

$translator = new Translator('es_ES');
$translator->addLoader('custom', new CustomLoader());

$translator->addResource('custom', __DIR__.'/translations/messages.txt', 'es_ES');

var_dump($translator->trans('hello'));

It outputs:

string(4) "Hola"

That’s the magic of well designed software, extending it is like child’s play.

It is also possible to create a custom dumper for any made-up format invent. To achieve this, a new class implementing the DumperInterface interface must be created. To write the dump contents into a file, extending the FileDumper class is going to save us a few lines.

<?php

namespace RaulFraile\Dumper;

use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Dumper\FileDumper;

class CustomDumper extends FileDumper
{

    public function format(MessageCatalogue $messages, $domain = 'messages')
    {
        $output = '';

        foreach ($messages->all($domain) as $source => $target) {
            $output .= sprintf("(%s)(%s)\n", $source, $target);
        }

        return $output;
    }

    protected function getExtension()
    {
        return 'txt';
    }
}

The format method creates the output string, that will be used by the dump() method of the FileDumper class to create the file. The dumper can be used like any other built-in dumper. In this example, we export the translation messages defined in the YAML file into a text file with our custom format:

<?php

use Symfony\Component\Translation\Loader\YamlFileLoader;
use RaulFraile\Dumper\CustomDumper;

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

$loader = new YamlFileLoader();
$catalogue = $loader->load(__DIR__ . '/translations/messages.es_ES.yml' , 'es_ES');

$dumper = new CustomDumper();
$dumper->dump($catalogue, array('path' => __DIR__.'/dumps'));

Conclusion

We have seen that the Translation component makes the arduous task of internazionalizating our application a bit easier, providing all the needed tools to do so: simple translations, placeholders, pluralization, loaders, and dumpers, as well as options to to extend it.

Who’s using it?

More info

Photo: World-map, by greyweed

marzo 18 / 2014

Upcoming Conferences

WeCamp

PHP New Zealand

PHP Summer Camp Croatia

PHPNE

MadisonPHP

brnoPHP

SymfonyLiveLondon

ZgPHP

PHPSouthAfrica

PHPNWUK

SymfonyLiveNYC

PHPForumParis

PHPARG

PHPWorld

TechMeetupUY

SymfonyConMadrid

Symfony2 components overview: Templating

In the 7th post of this series we are going to talk about a very important component for modern PHP frameworks. This component is usually hidden under a few abstractions to make the life of framework users easier, we are talking about the Templating component.

Templating engines allow to create representations of data

The Templating component

The Templating component provides an engine-agnostic infrastructure to load template files and render them using the input variables. Although the component provides methods for creating template systems, we are going to focus on how to integrate third-party template engines like Twig and Smarty, and how to use the right engine based on the templates file extension.

Templates using PHP

Using PHP to generate templates has been common practice in the PHP community and although this approach has a few advantages, such as not having to learn another language and that templates can be executed without having to compile them, there are also some drawbacks. Some of the drawbacks are:

  • PHP templates are usually more verbose, especially when dealing with output escaping:
    <?php echo htmlspecialchars($var, ENT_QUOTES, 'UTF-8') ?>
    
  • Missing features such as template inheritance, layouts, filters, and extensions.
  • Difficult for web designers who don’t know PHP.

The component provides a default engine to use PHP for templates improving some of these limitations and providing ways to escape output easily, include subtemplates, and common helpers.

Let’s create the simplest template possible, one that says hello using the variable $name, which is passed by the controller:

Hello <?php echo $name ?>! From a humble PHP template.

Then, to render the template:

<?php

use Symfony\Component\Templating\PhpEngine;
use Symfony\Component\Templating\TemplateNameParser;
use Symfony\Component\Templating\Loader\FilesystemLoader;

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

$loader = new FilesystemLoader(__DIR__.'/views/%name%');
$templating = new PhpEngine(new TemplateNameParser(), $loader);

echo $templating->render('hello.php', array('name' => 'Raul'));

It outputs:

Hello Raul! From a humble PHP template.

Here’s an overview of what happened under the hood. First, we create the PhpEngine instance with the required parameters, a couple of objects implementing the TemplateNameParserInterface and LoaderInterface interfaces. Don’t be afraid by all this code, it’s much simpler than it seems.

$loader is an instance of FilesystemLoader, it defines where the templates are – in our example, templates are in the view directory. The TemplateNameParser object converts a string containing the template filename to a TemplateReference object.

Finally, we execute the render method, which renders the template ‘views/hello.php’ with the ‘name’ variable set to ‘Raul’.

This complex setup is the price we pay when working with Symfony components, but as we will see in the next section, this design makes adding new templating engines really easy.

Adding other template engines

When adding new template engines, we create bridges that connect the engine itself with the component. This is done implementing the three methods defined in the EngineInterface interface:

  • render($name, array $parameters = array()): It’s the method in charge of actually rendering the template with the name $name passing the array $parameters as variables.
  • exists($name): Checks if the template with the name $name exists.
  • supports($name): Returns true if the engine bridge is able to process the template with the name $name. Usually, this is done checking the file extension. When there are several engine bridges this method is called to determine which one to use.

So, if we want to add support for Twig templates we just have to implement these methods.

<?php

namespace RaulFraile\Templating;

use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Templating\TemplateReferenceInterface;
use Symfony\Component\Templating\TemplateNameParserInterface;

use Twig_Loader_Filesystem;
use Twig_Environment;

class TwigEngine implements EngineInterface
{

    protected $environment;
    protected $parser;

    /**
     * Constructor.
     *
     * @param \Twig_Environment           $environment A \Twig_Environment instance
     * @param TemplateNameParserInterface $parser      A TemplateNameParserInterface instance
     * @param string                      $dir         Templates directory
     */
    public function __construct(\Twig_Environment $environment, TemplateNameParserInterface $parser, $dir)
    {
        $loader = new Twig_Loader_Filesystem($dir);

        $this->environment = $environment;
        $this->environment->setLoader($loader);

        $this->parser = $parser;
    }

    /**
     * Renders a template.
     *
     * @param string|TemplateReferenceInterface $name A template name or a TemplateReferenceInterface instance
     * @param array $parameters An array of parameters to pass to the template
     *
     * @return string The evaluated template as a string
     *
     * @throws \RuntimeException if the template cannot be rendered
     *
     * @api
     */
    public function render($name, array $parameters = array())
    {
        return $this->environment->loadTemplate((string) $name)->render($parameters);
    }

    /**
     * Returns true if the template exists.
     *
     * @param string|TemplateReferenceInterface $name A template name or a TemplateReferenceInterface instance
     *
     * @return Boolean true if the template exists, false otherwise
     *
     * @throws \RuntimeException if the engine cannot handle the template name
     *
     * @api
     */
    public function exists($name)
    {
        $loader = $this->environment->getLoader();

        return $loader->exists($name);
    }

    /**
     * Returns true if this class is able to render the given template.
     *
     * @param string|TemplateReferenceInterface $name A template name or a TemplateReferenceInterface instance
     *
     * @return Boolean true if this class supports the given template, false otherwise
     *
     * @api
     */
    public function supports($name)
    {
        if ($name instanceof \Twig_Template) {
            return true;
        }

        $template = $this->parser->parse($name);

        return 'twig' === $template->get('engine');
    }

}

To be able to use dependency injection later, the newly created TwigEngine needs an instance of the Twig_Environment and an object that implements TemplateNameParserInterface, which is used to get the engine name – based on the extension. It also accepts a directory where the templates can be found.

The same could be applied to any engine we want to support. For example, a simple engine bridge for Smarty templates would look like this:

<?php

namespace RaulFraile\Templating;

use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Templating\TemplateReferenceInterface;
use Symfony\Component\Templating\TemplateNameParserInterface;
use Smarty;

class SmartyEngine implements EngineInterface
{

    protected $smarty;
    protected $parser;

    /**
     * Constructor.
     *
     * @param Smarty                      $environment A Smarty instance
     * @param TemplateNameParserInterface $parser      A TemplateNameParserInterface instance
     * @param string                      $dir         Templates directory
     */
    public function __construct(Smarty $smarty, TemplateNameParserInterface $parser, $dir)
    {
        $this->smarty = $smarty;
        $this->smarty->setTemplateDir($dir);

        $this->parser = $parser;
    }

    /**
     * Renders a template.
     *
     * @param string|TemplateReferenceInterface $name A template name or a TemplateReferenceInterface instance
     * @param array $parameters An array of parameters to pass to the template
     *
     * @return string The evaluated template as a string
     *
     * @throws \RuntimeException if the template cannot be rendered
     *
     * @api
     */
    public function render($name, array $parameters = array())
    {
        $this->smarty->assign($parameters);

        return $this->smarty->fetch($name);
    }

    /**
     * Returns true if the template exists.
     *
     * @param string|TemplateReferenceInterface $name A template name or a TemplateReferenceInterface instance
     *
     * @return Boolean true if the template exists, false otherwise
     *
     * @throws \RuntimeException if the engine cannot handle the template name
     *
     * @api
     */
    public function exists($name)
    {
        return $this->smarty->templateExists($name);
    }

    /**
     * Returns true if this class is able to render the given template.
     *
     * @param string|TemplateReferenceInterface $name A template name or a TemplateReferenceInterface instance
     *
     * @return Boolean true if this class supports the given template, false otherwise
     *
     * @api
     */
    public function supports($name)
    {
        $template = $this->parser->parse($name);

        return 'tpl' === $template->get('engine');
    }

}

To use them in a controller:

<?php

use Symfony\Component\Templating\TemplateNameParser;
use RaulFraile\Templating\TwigEngine;
use RaulFraile\Templating\SmartyEngine;

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

$templatesDir = __DIR__.'/views/';
$templateNameParser = new TemplateNameParser();

$templating = new TwigEngine(new Twig_Environment(), $templateNameParser, $templatesDir),
// or $templating = new SmartyEngine(new \Smarty(), $templateNameParser, $templatesDir);

echo $templating->render('hello.php', array('name' => 'Raul'));
// or echo $templating->render('hello.tpl', array('name' => 'Raul'));

Choosing the right engine

We have a few engine bridges, but how to choose the right one? Frameworks usually allow us to use the template engine of our choice. For example, in Symfony2 full-stack framework, we can use both Twig and PHP templates simply by changing the filename:

$this->render('AcmeBundle:Hello:index.html.twig', array('name' => $name));

// or

$this->render('AcmeBundle:Hello:index.html.php', array('name' => $name));

Symfony knows which engine to use based on the extension. The component provides a way to follow this approach using the DelegatingEngine class and the supports() method of the engine bridges that we just implemented.

<?php

use Symfony\Component\Templating\PhpEngine;
use Symfony\Component\Templating\TemplateNameParser;
use Symfony\Component\Templating\Loader\FilesystemLoader;
use Symfony\Component\Templating\DelegatingEngine;

use RaulFraile\Templating\TwigEngine;
use RaulFraile\Templating\SmartyEngine;

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

$templatesDir = __DIR__.'/views/';
$templateNameParser = new TemplateNameParser();

$engines = new DelegatingEngine(array(
    new PhpEngine($templateNameParser, new FilesystemLoader($templatesDir . '%name%')),
    new TwigEngine(new Twig_Environment(), $templateNameParser, $templatesDir),
    new SmartyEngine(new \Smarty(), $templateNameParser, $templatesDir)
));

echo $engines->render('hello.php', array('name' => 'Raul')) . PHP_EOL;

The render() method of DelegatingEngine will iterate over all the defined engine bridges and call to their supports() method. As soon as it finds an engine capable of handling that template, executes the render() method of that engine.

An alternative for PHP templates: Plates

If you prefer to use native PHP templates but miss some features of compiled ones such as template inheritance, layouts or plugins/extensions, it is worth taking a look at Plates, which is a PHP templating system inspired by Twig.

If we want to provide Plates as the default engine for PHP templates, all we have to do is create a new engine that implements the EngineInterface interface and accepts ‘*.php’ templates.

<?php

namespace RaulFraile\Templating;

use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Templating\TemplateReferenceInterface;
use Symfony\Component\Templating\TemplateNameParserInterface;
use League\Plates;

class PlatesEngine implements EngineInterface
{

    protected $engine;
    protected $parser;

    public function __construct(Plates\Engine $engine, TemplateNameParserInterface $parser, $dir)
    {
        $this->engine = $engine;
        $this->engine->setDirectory($dir);
        $this->engine->setFileExtension(null);

        $this->parser = $parser;
    }

    public function render($name, array $parameters = array())
    {
        $template = new Plates\Template($this->engine);
        $template->data($parameters);

        return $template->render($name);
    }

    public function exists($name)
    {
        return $this->engine->pathExists($name);
    }

    public function supports($name)
    {
        $template = $this->parser->parse($name);

        return 'php' === $template->get('engine');
    }
}

Calling the setFileExtension() method with null is necessary in our example as we are already using the extension to choose the template engine, so we want Plates to use hello.php instead of hello.php.php.

Then, we just replace the PhpEngine object by the PlatesEngine one.

<?php

use Symfony\Component\Templating\TemplateNameParser;
use Symfony\Component\Templating\DelegatingEngine;

use RaulFraile\Templating\TwigEngine;
use RaulFraile\Templating\SmartyEngine;
use RaulFraile\Templating\PlatesEngine;

use League\Plates;

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

$templatesDir = __DIR__.'/views/';
$templateNameParser = new TemplateNameParser();

$engines = new DelegatingEngine(array(
    new PlatesEngine(new Plates\Engine(), $templateNameParser, $templatesDir),
    new TwigEngine(new Twig_Environment(), $templateNameParser, $templatesDir),
    new SmartyEngine(new \Smarty(), $templateNameParser, $templatesDir)
));

echo $engines->render('hello.php', array('name' => 'Raul')) . PHP_EOL;

Even though the template is the same, we can now use all the features of Plates.

Who’s using it?

Conclusion

The Templating component comes very handy when we need to provide different template engines, especially in frameworks and projects that can be extended somehow with templates, such as static blog generators or plugin systems.

More info

Photo: Matt Cornock

marzo 11 / 2014

Upcoming Conferences

WeCamp

PHP New Zealand

PHP Summer Camp Croatia

PHPNE

MadisonPHP

brnoPHP

SymfonyLiveLondon

ZgPHP

PHPSouthAfrica

PHPNWUK

SymfonyLiveNYC

PHPForumParis

PHPARG

PHPWorld

TechMeetupUY

SymfonyConMadrid

Symfony2 components overview: Validator

In the first five posts of this series we have been talking about key components for any PHP framework from the point of view of their internals, such as HttpFoundation to abstract the HTTP protocol, HttpKernel to convert a Request into a Response, Routing to map requests to controllers, EventDispatcher to add reusability and extensibility, and Config to load and validate configuration values. This time we’ll delve deeper in the user space to describe a component that is for specific apps rather than just for frameworks. Today’s topic will be the Validator component.

It is not always easy to know what and how to validate a given input

The problem

Every single application that accepts data from a source must implement some sort of validation. It does not matter that your data comes from users or from an online API, you always run the risk your data might be tainted.

We all know the catastrophic consequences we can face if we don’t filter input data, from inconsistency in our database to security disasters caused by SQL injections, Cross-Site Scripting (XSS) or Cross-Site Request Forgery (CSRF), to name just a few. Even though the Validator component responsibilities fall under the first set of problems – to keep data consistency -, any non-filtered input data can trigger security issues.

Any application must validate input data. Some of this validations like emails, urls or ISBNs are so common that is a waste of time to code them again, as probably someone else has already made it before. The Validator component provides an extensive list of validations and a way to use them easily.

Architecture

The component it is based on the JSR-303 specification and each validation is composed of one or more constraints, which are validated against a validator. For example, in your database, your employees table must have only email addresses from your company, so you can use the default email constraint to check that the given address is valid and a second constraint that checks the domain. With the Validator component, this is done in two parts:

  • Constraints
    A constraint describes the rule. It is just a PHP object that makes an assertive statement, such as “the email must be valid”.
  • Validators
    Validators implement the validation logic. In the email example, the validator class will use filter_var($email, FILTER_VALIDATE_EMAIL) to check if the email is valid.

Simple example

<?php 

use Symfony\Component\Validator\Validation; 
use Symfony\Component\Validator\Constraints\Range; 

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

$validator = Validation::createValidator(); 
$constraint = new Range(array(
    'min' => 1,
    'max' => 10
));

$violations = $validator->validateValue(15, $constraint);

The $violations variable will have a ConstraintViolationList object with one ConstraintViolation object for each violation. If there are no violations, the list will be empty, but it is important to remember that for consistency, we will always get a ConstraintViolationList object, not null, false or 0.

In the last example, the $violations variable will contain one violation:

class Symfony\Component\Validator\ConstraintViolationList#10 (1) {
  private $violations =>
  array(1) {
    [0] =>
    class Symfony\Component\Validator\ConstraintViolation#13 (8) {
      private $message =>
      string(32) "This value should be 10 or less."
      private $messageTemplate =>
      string(41) "This value should be {{ limit }} or less."
      private $messageParameters =>
      array(2) {
        ...
      }
      private $messagePluralization =>
      NULL
      private $root =>
      int(11)
      private $propertyPath =>
      string(0) ""
      private $invalidValue =>
      int(11)
      private $code =>
      NULL
    }
  }
}

The ConstraintViolationList class implements Traversable, Countable and ArrayAccess interfaces. That means that we can work with the object like it was an array:

var_dump(count($violations));
var_dump(empty($violations));

foreach ($violations as $violation) {
    var_dump($violation->getMessage());
}
int(1)
bool(false)
string(32) "This value should be 10 or less."

Built-in validations

The component is shipped with the most commonly-needed constraints. Basic constraints to check that an email is valid – even checking host and MX records if needed -, the length of a string, number ranges, list of choices and many more are available out of the box.

Internationalization

We will talk more about the Translation component in future posts, but for now you must know that any object implementing the TranslatorInterface interface can be injected into the validator. The trans method of the object will be called to get the translation. Let’s see an example:

<?php 

use Symfony\Component\Validator\Validation; 
use Symfony\Component\Validator\Constraints\Range; 
use Symfony\Component\Translation\Loader\XliffFileLoader; 
use Symfony\Component\Translation\Translator; 

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

$translator = new Translator('es_ES'); 
$translator->addLoader('xliff', new XliffFileLoader());
$translator->addResource('xliff', __DIR__ . '/vendor/symfony/validator/Symfony/Component/Validator/Resources/translations/validators.es.xlf', 'es');

$validator = Validation::createValidatorBuilder()
    ->setTranslator($translator)
    ->getValidator();

$rangeConstraint = new Range(array(
    'min' => 1,
    'max' => 10
));

$violations = $validator->validateValue(11, $rangeConstraint);

var_dump($violations[0]->getMessage());

In this example, we want to show error messages in Spanish. To do so, we create the translator object and configure it to use the default XLIFF translation files provided by the component. Obviously, for simplicity, this is a dirty way to reference the file and, based on your application, you should find a better way to do it without relying on a Composer folder called vendor or that the autoload method is going to be PSR-0. Finally, we inject the translator and use the validator as usual. We get as the output the error message in Spanish:

string(35) "Este valor debería ser 10 o menos."

Validating objects

Most of the times, you will need to validate full objects instead of single values before persisting it in the database. How does the validator know what rules apply to each property? We need to create a mapping that links class properties with sets of constraints. It can be done using YAML, XML, annotations or PHP, in our example we will do it using YAML:

RaulFraile\Foo:
    properties:
        email:
            - NotBlank: ~
            - Email: ~
        age:
            - Range:
                min: 18

Once we have the mapping we need to tell the validator what kind of mapping we are using and where to find it:

<?php 

use Symfony\Component\Validator\Validation; 
use Symfony\Component\Validator\Constraints\Range; 
use RaulFraile\Foo; 

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

$obj = new Foo(); $obj->setEmail('raul@servergrove');
$obj->setAge(17);

$validator = Validation::createValidatorBuilder()
    ->addYamlMapping(__DIR__ . '/src/RaulFraile/validation/Foo.yml')
    ->getValidator();

$violations = $validator->validate($obj);

var_dump($violations);

Finally, we call the validate method – not validateValue – and get the list of violations as usual. In this case, the email is not correct and the age is less than the minimum allowed:

class Symfony\Component\Validator\ConstraintViolationList#11 (1) {
  private $violations =>
  array(2) {
    [0] =>
    class Symfony\Component\Validator\ConstraintViolation#25 (8) {
      private $message =>
      string(40) "This value is not a valid email address."
      private $messageTemplate =>
      string(40) "This value is not a valid email address."
      private $messageParameters =>
      array(1) {
        ...
      }
      private $messagePluralization =>
      NULL
      private $root =>
      class RaulFraile\Foo#2 (2) {
        ...
      }
      private $propertyPath =>
      string(5) "email"
      private $invalidValue =>
      string(16) "raul@servergrove"
      private $code =>
      NULL
    }
    [1] =>
    class Symfony\Component\Validator\ConstraintViolation#29 (8) {
      private $message =>
      string(32) "This value should be 18 or more."
      private $messageTemplate =>
      string(41) "This value should be {{ limit }} or more."
      private $messageParameters =>
      array(2) {
        ...
      }
      private $messagePluralization =>
      NULL
      private $root =>
      class RaulFraile\Foo#2 (2) {
        ...
      }
      private $propertyPath =>
      string(3) "age"
      private $invalidValue =>
      int(17)
      private $code =>
      NULL
    }
  }
}

Custom validations

The list of built-in constraints is huge and keeps growing, but if you need custom validations, it’s as easy as creating the corresponding classes for the constraint and the validation logic. For example, let’s create a simple validation to check that a programming language starts with ‘p’, or is in a fixed list of allowed languages – I know it is weird, but it’s just an example :) -.

First, we create the constraint, which defines the error message and the list of available programming languages:

<?php

namespace RaulFraile\Constraints;

use Symfony\Component\Validator\Constraint;

class ProgrammingLanguage extends Constraint
{
    public $message = 'The string "%string%" is not an accepted programming language';
    public $languages = array('php', 'javascript');
}

Then, the validation logic:

<!?php 

namespace RaulFraile\Constraints; 

use Symfony\Component\Validator\Constraint; 
use Symfony\Component\Validator\ConstraintValidator; 

class ProgrammingLanguageValidator extends ConstraintValidator {
    
    public function validate($value, Constraint $constraint) 
    {
        if (!(!empty($value) && $value[0] == 'p') && !in_array($value, $constraint->languages)) {
            $this->context->addViolation(
                $constraint->message,
                array('%string%' => $value)
            );
        }
    }
}

That’s it! If it is not an accepted programming language, we add a new violation with the error message, which will be appended to the list.

If we follow the convention and the validator class has the same name as the constraint class appending “Validator” at the end, it will automatically work. In our previous example, we add a lang property to the Foo class and include the constraint in the class validation definition:

RaulFraile\Foo:
    properties:
        ...
        lang:
            - RaulFraile\Constraints\ProgrammingLanguage: ~

Voila! Our example will work if the lang property is not valid:

<?php

$obj = new Foo(); 
$obj->setEmail('raul@servergrove.com');
$obj->setAge(18);
$obj->setLang('perlang');

$violations = $validator->validate($obj);

Will output:

class Symfony\Component\Validator\ConstraintViolationList#11 (1) {
  private $violations =>
  array(1) {
    [0] =>
    class Symfony\Component\Validator\ConstraintViolation#33 (8) {
      private $message =>
      string(59) "The string "erlang" is not an accepted programming language"
      private $messageTemplate =>
      string(61) "The string "%string%" is not an accepted programming language"
      private $messageParameters =>
      array(1) {
        ...
      }
      private $messagePluralization =>
      NULL
      private $root =>
      class RaulFraile\Foo#2 (3) {
        ...
      }
      private $propertyPath =>
      string(4) "lang"
      private $invalidValue =>
      string(6) "erlang"
      private $code =>
      NULL
    }
  }
}

Who is using it

More info

Photo: Jack Spades – Security Features of the US Twenty Dollar Bill

marzo 03 / 2014

Upcoming Conferences

WeCamp

PHP New Zealand

PHP Summer Camp Croatia

PHPNE

MadisonPHP

brnoPHP

SymfonyLiveLondon

ZgPHP

PHPSouthAfrica

PHPNWUK

SymfonyLiveNYC

PHPForumParis

PHPARG

PHPWorld

TechMeetupUY

SymfonyConMadrid