HHVM & Hack available at ServerGrove

hhvm It is well known that Facebook is currently the largest site built using PHP, but not everyone knows that they have created their own new language called Hack. It’s based on PHP and contains several new features and improvements. You can start learning about Hack with their online tutorial.

To run Hack they created a new runtime platform, HHVM. HHVM can provide big performance gains, which allowed Facebook to run their site in less servers, saving lots of money. Both HHVM and Hack have been released to the public so everyone outside of Facebook can use them.

HHVM is not fully compatible with PHP, yet. The HHVM development team is working hard to increase compatibility and they already support most of the major PHP frameworks. Having said this, there is a lot of custom PHP code that may not run in HHVM, so before you go ahead and install HHVM in your production servers, test your applications and sites really well.

As we said, Hack is very similar to PHP, a simple script looks like this:

<?hh
echo "Hi, I'm a Hack script!";

There are other bigger differences, and lots of new features that are worth checking.

To run this script, you will use the hhvm command:

$ hhvm script.php

In fact, you can use composer with hhvm, which will provide a big boost in performance:

$ hhvm composer.phar update

HHVM also includes a web server and a FastCGI server, giving you the option to run a standalone HHVM server or configure your Apache or NGINX using FastCGI.

Installing HHVM & Hack on ServerGrove

We have created customized packages for our customers so they can install HHVM in their VPS following some simple steps. Currently HHVM is available for Centos 6.x and Ubuntu 12.04 VPSes. We are preparing packages for additional distributions.

For Centos 6.x:

$ yum install hhvm

For Ubuntu 12.04:

$ apt-get update && apt-get install hhvm

Once installed, you can find HHVM configuration files in /etc/hhvm. The log files will be located in /var/log/hhvm. All the HHVM commands are located in /opt/hhvm/bin which is added to your PATH configuration.

Configuring HHVM

The /etc/hhvm/server.hdf file will contain most configuration options pertaining to the web and fastcgi servers.

Once configured, you can start the HHVM server by running:

$ /etc/init.d/hhvm start

If you need more control, you can run:

# spawn daemon on the background
$ /opt/hhvm/bin/hhvm --config /etc/hhvm/server.hdf --user www-data --mode daemon
# keep server in foreground
$ /opt/hhvm/bin/hhvm --config /etc/hhvm/server.hdf --user www-data --mode server

HHVM & FastCGI

If you want to use the fastcgi mode, you will need to set this configuration in server.hdf

Server {
  Type = fastcgi
  FileSocket = /var/run/hhvm/sock
  Port = 9000
}

This will make the server available in port 9000 or /var/run/hhvm/sock. Then you need to configure Apache with mod_fastcgi support. Install and enable mod_fastcgi and add this to the Apache configuration:

 FastCgiExternalServer /var/www/html -host 127.0.0.1:9000

Our installation also includes hh_single_type_check, hh_client and hh_server which are used to run the typecheker.

Using our packages outside of ServerGrove

If you want to use our packages in your servers or VMs outside of ServerGrove, you are more than welcome. Follow the instructions on setting up our repository in your server and then install hhvm as described above:
- Centos 6.x
- Ubuntu 12.04

Conclusion

HHVM and Hack bring a new perspective into PHP and provide big performance improvements. We will keep updating our repository with new versions as they come out. At the time of publishing this post, you will install HHVM 3.0.1. And don’t forget to share with us your experience with HHVM & Hack!

April 03 / 2014
Author Pablo
Category PHP, VPS
Comments 1 Comment
Tags ,

New TLDs

NewTLDs

Upcoming Conferences

CraftingTour

CraftingTour

PHP New Zealand









Deployment of Symfony2 applications with Ansible

iStock_000017954524Small

Ansible is a powerful automation engine that simplifies deploying systems and apps. Its popularity has been rising rapidly as developers and system administrators look for simpler ways to manage servers and deploy applications.

The selling points of Ansible are:

  • simplicity: the configuration is done through INI and YAML files
  • agentless: there is no agent to install, making it dead easy to use on virtual servers and shared hosting
  • extensible: thanks to roles and modules, it is very easy to extend its functionality and reuse of configuration blocks

If you follow our blog you will know that here at ServerGrove we have been promoting the use of Capifony, an extension for Capistrano that is targeted to the deployment of Symfony2 applications. However Capifony & Capistrano are based on Ruby, and every time we run into an issue, we need to dig to find out where the problem lies. In addition, with every major upgrade of Capistrano, something breaks, especially in our case where we have a fairly complex deployment process. So, we decided to give Ansible a try.

Installing and setting up Ansible

Installing Ansible is simple. The only requirement is to have Python installed in the computer where you will be launching the Ansible process. You can follow the manual instructions which recommends several ways to get Ansible running.

Once it is installed, Ansible will connect over SSH to the remote servers that it controls. The servers that you want to control need to be listed in a simple text file following the INI format, this is called the inventory:

192.168.1.50
aserver.example.org
bserver.example.org

You can group servers in sets so you can define a list of servers for testing, qa, production, etc.

[testing]
test1.example.com

[prod]
www1.example.com
www2.example.com
www[10:50].example.com

There are more complex groupings you can do, so it covers pretty much any need you can think of.

Now, you can execute simple commands with Ansible:

# send a ping command to all servers
$ ansible all -m ping
# print hostname and logged in users
$ ansible all -a "hostname -f && w"

You can also copy files:

$ ansible prod -m copy -a "src=/etc/hosts dest=/tmp/hosts"

You can manage users, OS packages (rpm, deb, etc), services (ie. stop/start/restart apache) and much more thanks to modules.

The real power comes when you start defining these commands as tasks:

---
- hosts: webservers
  vars:
    http_port: 80
    max_clients: 200
  remote_user: root
  tasks:
  - name: ensure apache is at the latest version
    yum: pkg=httpd state=latest
  - name: write the apache config file
    template: src=/srv/httpd.j2 dest=/etc/httpd.conf
    notify:
    - restart apache
  - name: ensure apache is running
    service: name=httpd state=started
  handlers:
    - name: restart apache
      service: name=httpd state=restarted

The above code tells Ansible that these tasks apply to all hosts in the group “webservers”, then we define the variables, and then create a list of tasks that will be run in sequence. If any of these tasks fail, the process will stop.

All of these can be store in files called playbooks, and these playbooks can depend on other playbooks, including external files, etc, making it really extensible and easy.

Furthermore, you can combine these playbooks into “roles” (similar to Symfony2′s bundles), so you can reuse these across projects. These roles can be shared by uploading them to Ansible Galaxy, a site where people share roles for common needs.

There is a lot more that Ansible can do, but for this article we want to focus on Symfony2, for more information we recommend that you read the excellent Ansible documentation.

Deploying Symfony2

kittens

As you may know, successfully deploying a Symfony2 application involves several steps, the most common ones are:

  • define a directory structure in the server which will allow to setup the Symfony2 application before making it live and keep multiple releases, so you can rollback to a previous release if something fails.
  • clone your git repository
  • run composer install to get all your dependencies
  • warm up the cache / dump Assetic assets
  • update the web server document root to point the site to the new release
If you used Capifony before, these are more or less the steps that are done every time you run cap deploy.

In Ansible this is easily achievable by defining a set of tasks in a playbook and then running them and you can put all of these in a role, so you can reuse it in all your Symfony2 projects.

Introducing the Ansible Symfony2 Deployment Role

We have created a role that you can start using now to easily deploy your Symfony2 apps. It is available in the Ansible Galaxy and in Github so anyone can contribute enhancements and fixes.

To use the role in your project, once you installed Ansible, run the following command to make the role available in your computer:

$ ansible-galaxy install servergrove.symfony2

The next step is to define the inventory (the list of servers where you will deploy your application). Create a file app/config/hosts.ini

[prod]
server.example.com

Next, you need to create a YAML file that will contain the variables and basic instructions for Ansible, name it app/config/deploy.yml

---
- hosts: prod
  vars:
    symfony2_project_name: your_project
    symfony2_project_root: /var/www/vhosts/example.com
    symfony2_project_repo: git@github.com:username/your_repo.git
    ansible_ssh_user: your_ssh_username

  roles:
    - servergrove.symfony2

  tasks:
    - local_action: osx_say msg="Deployment complete." voice=Zarvox

We defined a configuration for the prod group of servers. We define the variables needed for the role to deploy our project. It will run the role and when it finishes it will run the osx_say task locally that will inform us that the process has completed (this only works in Mac OSX, but you can replace it with an action that will send an email or some other form of notification).

Once you have these, you are ready to deploy your application with Ansible, do it with the following command:

$ ansible-playbook -l prod -i app/config/hosts.ini -e "symfony2_project_release=1" app/config/deploy.yml -vvv
  • the -l option defines which list of servers we want to run this for
  • the -i option defines the inventory file that contains the list of servers
  • the -e option allows us to define variables that will be used in the playbook. In this case we tell Ansible that we will deploy a release named “1″. You can replace this with `date +%Y%m%d%H%M%S` to name our releases with the current date/time.
  • Then we define the playbook we will use and the -vvv option allows us to see what Ansible is doing.

The servergrove.symfony2 role will perform the following tasks:

  • creates the directory structure to host your app, following a similar structure from Capifony:
  • clones the git repository
  • if composer is not installed or is outdated, it will download the phar in the project directory
  • runs composer install –no-dev –optimize-autoload
  • runs app/console assetic:dump
  • creates/updates current symlink to point to new release

This is the first version of this role, we hope to improve the functionality with new features, if you have any suggestions, please open an issue or send your pull request.

As we said before, Ansible does not need anything special in the server, it only needs to be able to connect to the server using SSH. This means that it can be used with any of our VPS plans and in all Developer and Business shared hosting plans, making the deployment of Symfony2 apps really easy and predictable.

April 01 / 2014
Author Pablo
Category PHP, Symfony, Tutorials
Comments 7 Comments

New TLDs

NewTLDs

Upcoming Conferences

CraftingTour

CraftingTour

PHP New Zealand









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

March 26 / 2014

New TLDs

NewTLDs

Upcoming Conferences

CraftingTour

CraftingTour

PHP New Zealand









Composer 101

logo-composer-transparent

Composer is a tool for dependency management in PHP. It allows us to declare the libraries (packages from now on) on which our project depends on and downloads them for us. With many high quality packages available to us, the are redefining they way we are building PHP software. You can browse through the wide variety of packages at the composer main repository packagist.org.

Composer is a simple tool to use and this tutorial will go over the installation and usage basics.

Installation

Windows

The easiest way to install Composer in a Windows machine is download and run the Composer installer. It will also set up the %PATH% for you so it can be used from any directory in the command line.

*nix

In Unix-based systems like Linux or MacOS the installation is done downloading the installer – which is a PHP script, using cURL, and then executing it. The installer will check our PHP settings to make sure Composer can be installed in the machine. It will then download our composer.phar file, the Composer executable. Below is the code you need to run to accomplish this:

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

If you want to install Composer globally, you have to place the composer.phar file into one of the directories included in the PATH environment variable:

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

How it works

Composer is a per-project dependency manager, which means that the packages needed by our project are not going to be installed globally. Packages will be downloaded and placed in a special folder called vendor. Then, by adding a require_once to the Composer autoload file, you will provide access to all your dependencies.

Dependencies are defined in a file called composer.json, and in its simplest version looks like this:

{
    "require": {
        "fabpot/goutte": "v1.0.5"
    }
}

In the above example, we defined a dependency, the “fabpot/goutte” package, which is a screen scraping and web crawling library for PHP.

When we execute “composer install”, Composer checks the required dependencies and downloads the packages:

$ 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

As you see, Composer downloaded “fabpot/goutte”, but also a few other packages. This is because the “fabpot/goutte” package has other dependencies too – they can be found in the composer.json file of the package -, but we don’t have to worry about it, Composer takes care of it and calculates the dependency graph automatically. This can become an expensive process when the number of dependencies increases. Composer deals with this by creating a composer.lock file with the exact versions of all the packages that must be downloaded, so the next time we run “composer install”, Composer will just read the composer.lock file and will download the packages. If we edit the composer.json file, we will need to run “composer update” so the composer.lock file gets recalculated.

“composer update” is the new “compiling…” :)

Once the packages have been downloaded, Composer generates a php file we need to include in our projects to use the dependencies. This file registers a PSR-0/PSR-4-compliant autoloader, so we don’t need to require any class file separately:

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

use Goutte\Client;

$client = new Client();
$crawler = $client->request('GET', 'http://www.servergrove.com');

$title = $crawler->filter('head > title')->text();
echo $title;

In the above example, we require the autogenerated autoload.php file, and then used the Goutte\Client class of the “fabpot/goutte” package to extract the contents of the title tag from the ServerGrove website.

The composer command

We have talked about the “install” and “update” commands, but Composer provides a few more commands. “composer list” displays a list of all available commands:

$ 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

Some interesting commands:

  • init: Creates a basic composer.json interactively
  • validate: Checks whether the composer.json file is valid
  • selfupdate: Updates Composer itself to the latest version. If the Composer version is too old – 30 days or more -, a warning will be displayed when executing any command.
  • search: Look for packages in Packagist – e.g. “composer search yaml” -.
  • show: Displays information about packages. For example, “composer show –installed” to show installed packages.
  • create-project: Creates a new project from a package, especially useful for frameworks – e.g. to create a new project with the Symfony 2.4.2 framework: “php composer.phar create-project symfony/framework-standard-edition path/ 2.4.2″.

Package versions

There are several ways to define what version of a given package we want to use:

  • Exact version: “1.4.2″, “v1.4.2″.
  • Ranges: “>=1.0,<2.0″, “>=1.0,<1.1 | >=1.2″.
  • Wildcard: “1.0.*”, “1.*”.
  • Next Significant Release: “~1.2″ is equivalent to “>=1.2,<2.0″.

Stability

Every package version has a stability flag indicating whether it is “final” version or previous one such as, development, betas, alphas or release candidates. By default, only stable package versions are considered, so if we need something that is not in the stable level, the minimum-stability setting must be changed. Available values are “dev”, “alpha”, “beta”, “RC” and “stable”. It is also possible to set the minimum stability flag by dependency, appending “@flag” to the package version – e.g. “”fabpot/goutte”: “1.0.*@dev””.

The vendor directory

The vendor directory is created automatically by Composer and it should be ignored in your VCS system – e.g. .gitignore in GIT-. It is highly recommended to include the composer.lock file in the VCS, so everyone in the team have the exact same package versions. No more “it works on my machine…”!

More information

This is just a simple introduction of using Composer to manage our project dependencies. Composer has  excellent documentation, both for package consumers and publishers.

March 19 / 2014
Author Raul Fraile
Category PHP, Tutorials
Comments 5 Comments
Tags

New TLDs

NewTLDs

Upcoming Conferences

CraftingTour

CraftingTour

PHP New Zealand









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

March 18 / 2014

New TLDs

NewTLDs

Upcoming Conferences

CraftingTour

CraftingTour

PHP New Zealand