Articles taggés "tutorial"

Poste par Cedric Sadai, le 14/07/08 - Technologie

One of the recurring concern when developing an internationalized application, is to not let any element untranslated. This is natively complicated, even with the brand new symfony 1.1. You often end up with untranslated URLs or meta tags. But using the Framework in conjonction with the Yahoo!’s plugin ysfDimensionsPlugin will fill all the blanks you could have experienced once.

The role of the plugin is to let you define specific dimensions (can be cultures, can be themes, etc.), and to make it possible to override any part of your application (actions, templates, config) by simply adding a subdirectory, with the name of your dimension.

Let’s have a quick exemple.

First of all, you will have to link your existing project to the plugin SVN.

svn propedit svn:externals plugins/

Then append to the file:

ysfDimensionsPlugin http://svn.symfony-project.com/plugins/ysfDimensionsPlugin/branches/1.1/

Update everything:

svn up

Configuration

Let’s say we want to internationalize the application in english and french. That means we will add a “culture” dimension with two possibilities in it: en, and fr. To do that, create a /config/dimensions.yml file and write:

allowed:
  culture: [en, fr]

Now, we have to edit the main configuration script, so the plugin can add our brand new configuration levels.

require_once '/path/to/your/project/lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php';
sfCoreAutoload::register();

require_once '/path/to/your/project/plugins/ysfDimensionsPlugin/lib/config/ysfProjectConfiguration.class.php';

class ProjectConfiguration extends ysfProjectConfiguration
{
	public function setup()
	{
	}
}

Notice that the ProjectConfiguration class now extends ysfProjectConfiguration (against sfProjectConfiguration, the y stands for yahoo!).

The dimension will be set up on an application level. In my example, the user culture is determined by the domain he is browsing. French will be something like fr.mydomain.com, and his culture will be set to english otherwise. To industrialize this behaviour, we have to edit our application configuration file.

Edit /apps/YOURAPP/config/yourAppConfiguration.class.php. First, manually require the plugin library.

require_once(dirname(__FILE__).'/../../../plugins/ysfDimensionsPlugin/lib/config/ysfApplicationConfiguration.class.php');

Then, make your class extend the Yahoo! Application configuration class, instead of the core symfony one.

class frontConfiguration extends ysfApplicationConfiguration // instead of sfApplicationConfiguration

Ok. Now, let’s define the dimension, in the configure method.

public function configure()
{
    //-- hacky. Demonstration purpose only.
	$host_part = explode('.', $_SERVER['HTTP_HOST']);
	$culture = (
		strLen($host_part[0])==2 &&
		(in_array($host_part[0], array('fr', 'en')))
	)?$host_part[0]:'en';

	$this->setDimension(array('culture' => $culture));
	parent::configure();
}

Note: this should go with an appropriate filter, like the one used here in symfonians.

Cool, we’re almost done. If you have an APC extension up and running on your environment, you can pretty safely trigger a symfony cc command. If everything goes as usual, try to browse your app, and if you nothing weird happens, you’re good to go.

If you don’t have APC, you have good chance it won’t work. Indeed, the plugin natively caches the configuration directories in the memory, using the PHP6 core library APC (you can grab it with “pecl install apc”).

If you can’t make it work, this is your last hope: override the main construction call, to make use of local cache or, if in debug mode, no cache at all (bad for production of course).

There you go:

class frontConfiguration extends ysfApplicationConfiguration
{
	public function configure()
	{
		$this->debug = true;

		//-- Hacky. Demonstration purpose only.
		$host_part = explode('.', $_SERVER['HTTP_HOST']);
		$culture = (
			strLen($host_part[0])==2 &&
			(in_array($host_part[0], array('fr', 'en')))
		)?$host_part[0]:'en';

		// setup dimensions before calling parent::configure();
		$this->dimension = new ysfConfigDimension(
			$this->getEventDispatcher(),
			(!isset($this->debug) || (isset($this->debug) && $this->debug === true))
			? new sfNoCache() :
			new sfCache(
				array(
					'prefix' => 'symfony.dimensions.config.default:'.$this->application.':'.$this->environment,
					'automatic_cleaning_factor' => 0, 'lifetime' => 86400)
			)
		);

		$this->setDimension(array('culture' => $culture));
		parent::configure();
	}
}

I had to force the $this->debug = true; because I had mysterious bugs on my development machine (Mac OSX). Try to remove it, if it works without that, it’s even better.

Now, let’s trigger a clear cache (symfony cc) and hit a fresh browser pointing to your application. Everything works as expected? Congrats, now we can go to the fun part.

Internalization with sfDimensionPlugin

In a big scale project, you generally have four types of I18N:

  • Stored data (work of ORMs to make this easy)
  • Localized short strings (handled natively by the symfony I18n support, enhanced in 1.1 with symfony i18n:extract –auto-save)
  • Configuration data (routing, meta tags, etc.)
  • And the last, often neglicted but rarely optional: long static (graphical) pages that are not made to be integrated in the DB or in the XLIFF file (about us, credit, contacts). They have to stay HTML fragments.

 

symfony natively supports nb 1 (thanks to Doctrine or Propel), nb 2, and that’s pretty much it. Let’s go back to our little project. You’re working hard on it, and the whole project is almost done. You have written pretty short SEO-friendly URL in /apps/YOUR_APP/config/routing.yml, and nice metas in your /apps/YOUR_APP/modules/YOUR_MOD/config/view.yml. Last, but not least, you decided to put all the static content in a “static” module, where you have empty actions and 5 to 10 HTML fragments.

Let’s internationalize everything:

routing.yml
homepage:
  url: /
  param: { module: home, action: index }

team:
  url: /team
  param: { module: team, action: index }

about_us:
  url: /about-us
  param: { module: static, action: about }

Obviously, we don’t need to translate the first routing rule (@homepage). So let’s create a routing.yml file in /apps/YOUR_APP/config/fr/routing.yml. (create the fr directory if it doesn’t exist yet).

team:
  url: /equipe

about_us:
  url: /a-propos

And that’s it! The plugin is smart enough to automatically inherit your main routing file, and as we gave them the same names, you can restrict your work to defining only the elements that change.

Now, try to go to http://fr.yoursite.dev, and hover a link to @team. It should show the french version now. Now point to the @about_us page. The URL is in french, but the content is in english, right? Remember, it’s a static page we created in a dedicated module. Let’s correct that:

Create a fr directory in

/apps/YOUR_APP/modules/static/templates

Now, copy the aboutSuccess.php template in that directory, and translate all its content. When it’s done, refresh your page. And boom, you should now have the french version.

 

This process can be done for almost anything in your website. It’s especially useful for metas. Just create a fr directory in /apps/YOUR_APP/modules/YOUR_MOD/config/fr/, and a view.yml file in it, and enjoy:

indexSuccess:
  metas:
    title: My French Title
[etc..]

When you’ve done that work once, you can reproduce a fully I18n environnement very quickly for your new project. Don’t forget that this ysfDimensionsPlugin is very powerful, and can be the perfect solution when you need to build many websites using the same engine (like a white label).

If you have any tip with I18N or ysfDimensionsPlugin, feel free to leave a comment.

Poste par Cedric Sadai, le 03/03/08 - Technologie

Premier tutorial sur ce blog, avec la création d’un nouveau projet Symfony. Rien de bien compliqué en théorie puisque la documentation est déjà bien fournie, mais nous souhaitons ici avoir une installation permettant le travail collaboratif. Nous allons donc utiliser le système de versionnage Subversion. Rentrons dans le vif du sujet.

1/ Création du repository

Un repository SVN est l’endroit où va être stocké l’ensemble des modifications apportées au projet.

mkdir /home/projects/sfProject
svnadmin create /home/projects/sfProject

2/ Création de la structure de notre projet.

Un projet SVN se doit de comporter trois répertoires principaux: tags, branches et trunk. Le tronc étant la version actuelle, et les branches/tags des versions de développement ou des versions passées qui continuent néanmoins à être maintenues.

Je créé donc cette structure dans un répertoire temporaire, pour l’intégrer au repository

mkdir /tmp/project /tmp/project/trunk /tmp/project/tags /tmp/project/branches
svn import /tmp/project/ file:///home/projects/sfProject -m "Import de la structure"

Voila. Si tout se passe bien on devrait désormais avoir une structure classique dans notre repository. On peut vérifier cela avec la fonction svn list.

root@localhost# svn list file:///home/projects/sfProject
branches/
tags/
trunk/

La structure est bien installée. On va pouvoir créer la copie de travail.

3/ Création de la copie de travail

La copie de travail, c’est une copie de votre projet que vous allez pouvoir modifier, enrichir, recréer, tout en étant certain que chacune des modification sera bien enregistrée dans le repository. Il doit donc exister autant de copies de travail que de personnes qui interviennent sur votre projet.

Si vous travaillez sur Linux, que vous avez configuré un sous domaine sur /home/domains/sfProject, voici comment extraire une copie de travail:

# cd /home/domains/sfProject
# svn co file:///home/projects/sfProject ./

On se retrouve avec un répertoire contenant nos trois fameux dossiers: branches, tags et trunk.
Nous allons rajouter un nouveau répertoire: vendor.

# mkdir vendor

Ce répertoire n’existe pas encore dans le repository, puisque nous l’avons créé dans une copie de travail. Pour l’intégrer au repository, nous effectuons la commande suivante (dans le répertoire /home/domaines/sfProject):

# svn add vendor

Puis nous allons connecter ce répertoire à une copie distante de Symfony. Cette opération permettra de récupérer automatiquement les nouvelles mises à jour du packages.

# svn propedit svn:externals ./vendor

Dans la fenetre qui s’ouvre on rentre simplement le nom du package, puis l’adresse SVN correspondant à la version que l’on souhaite récupérer. Ici, la version 1.0

symfony http://svn.symfony-project.com/branches/1.0/

Nous allons soumettre nos changements aux repository (une fenetre s’ouvre, fermer là puis validez):

# svn commit

Une fois les changements propagés, effectuez un simple update de votre copie de travail.

# svn up

Et là, miracle! Le système est en train de récupérer tout seul l’ensemble de l’arbre de Symfony. L’avantage, c’est qu’à chaque correction de bug sur la branche que vous avez choisie, votre version locale sera immédiatement patchée, à chaque fois que vous ferez un update.

4/ Création du projet Symfony

Dans Symfony, beaucoup de choses se font en ligne de commande. Pour l’instant, l’executable symfony se trouve dans le répetoire vendor/data/bin/. Pour créer un projet, on va donc simplement se rendre dans le répertoire cible (trunk/ dans notre cas), puis ajouter un nouveau projet à partir de l’executable.

# cd /home/domaines/sfProject/trunk // toujours notre copie de travail
# php5 /home/domaines/sfProject/vendor/symfony/data/bin/symfony init-project sfProject

Les fichiers se copient, voila, vous avez votre premier projet en bon état de marche.

5/ Réglages

Si l’on effectue un test coté web, il y a de fortes chances que cela ne marche pas. Ceci parce qu’il faut faire pointer Apache vers le bon répertoire, à savoir /home/domaines/sfProject/trunk/web. Par ailleurs, les feuilles de style par défaut du package se trouvent dans notre copie synchronisée. Nous allons donc créer un Alias. Ce qui vous donne:

# nano /usr/local/apache/conf/httpd.conf // dépend de votre configuration


        DocumentRoot /home/domaines/sfProject/trunk/web
        Alias /sf /home/domaines/sfProject/vendor/symfony/data/web/sf
	ServerName sfProject.mondomaine.com
        AddHandler x-httpd-php5 .php

Un petit test sur sfProject.mondomaine.com devrait également vous fournir une erreur. Il faut pour obtenir une réponse créer une application. C’est ce que nous faisons avec la ligne de commande suivante:

# cd /home/domaines/sfProject/trunk/
# php5 ./symfony init-app frontend
# chown -fR vous:votregroupe ./

La dernière commande sert à donner les bons droits à votre projet, attribué a l’utilisateur root par défaut.
Cette fois, un test sur sfProject.mondomaine.com fonctionne bien, avec la bonne feuille de style grâce à nos directives Apache.

6/ Finalisation

Il nous reste à finaliser le projet. Nous avons effectué pas mal de changements, mais aucun n’est encore dans le repository. Une commande simple permet d’ajouter tous les nouveaux fichiers d’un coup (sur la racine de votre copie de travail):

# svn add * --force

Puis on commit ces changements pour les intégrer au repository.

# svn commit

Enfin, certains répertoires de Symfony n’ont pas vocation à être inclus dans les updates. C’est le cas pour les fichiers de cache et de logs. Nous corrigeons ce problème comme suit (dans le répertoire trunk de votre copie de travail):

# cd /home/domaines/sfProject/trunk
# svn propedit svn:ignore ./cache

Un éditeur de texte s’ouvre, tapez simplement une étoile * puis sauvegardez. Puis refaite la meme chose avec la commande

# svn propedt svn:ignore ./log

Envoyez les changements au repository:

# svn commit

Et voilà, vous avez un environnement de travail puissant et propre, vous pouvez commencer à développer sur le framework puissant qu’est Symfony. D’autres articles sur ce framework vous seront bientôt proposés ici-même.