Différences entre Symfony1.4 et Symfony2

conseils Symfony2

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com
Un premier grand projet Symfony2 se rapproche de sa mise en ligne. Il est temps de tirer quelques conclusions et de dresser une liste des différences entre la version 2 et 1.4 de ce framework PHP.

1. Structure des répertoires
Commençons par l'organisation des dossiers. Un projet Symfony2 n'a besoin que de 5 répertoires :
- app
- bin
- src
- vendor
- web

Cela a changé par rapport à la version 1.4 où l'on disposait parfois de 7 dossiers à la racine (même si plusieurs n'étaient pas nécessaires pour faire tourner l'application) :
- apps
- cache
- config
- data
- lib
- log
- web

Voyant maintenant en quoi consiste la différence dans la nouvelle version du framework :
- app : ce répertoire contient désormais le cache, les fichiers de configuration et les logs
- bin : il contient les scripts d'exécution (pour construire une webapp, vider le cache etc.)
- src : il joue le rôle du répertoire apps/ de la version 1.4 . C'est dedans vous allez placer vos différents environnements de l'application (frontend, backend) avec tout ce qui les concerne : les vues (les templates), les formulaires, les ressources (comme les fichiers de configuration).

Contrairement à la version 1.4, dans ce répertoire vous n'allez pas forcément stocker les webapps (frontend/backend). C'est là où vous allez mettre les composants de ces environnements; les composants qui sont fièrement nommés "bundles". Ce concept sera repris et présenté plus en détail dans le 3e point.

- vendor : il a pris le rôle du répertoire lib de la version précédente. C'est dedans que se trouvent les classes du framework (swiftMail, Doctrine, Twig ainsi que les librairies développées par SensioLabs).

- web : le répertoire web/ dans la version 2 du framework joue exactement le même rôle que dans l'édition précédente. Il stocke les images, les scripts JavaScript, les fichiers SEO (robots.txt), les feuilles de styles CSS.

2. Gestion des namespaces
Grâce à la nouvelle gestion des namespaces (espaces de noms), introduite dans PHP 5.3, Symfony2 a gagné de la rapidité. Dans sa version précédente il cherchait les classes dans le projet entier. Actuellement il utilise un mécanisme plus restrictif et emploie pour cela la classe UniversalClassLoader. Vous la trouverez dans : vendor/symfony/src/Symfony/Component/ClassLoader/ .

Le nouveau système du chargement des classes fonctionne selon ce schéma:
1) Au début on spécifie les namespaces dans notre fichier app.php. Pour cela on utilise la méthode registerNamespaces() de la classe UniversalClassLoader. La fonction registerNamespaces prend comme argument un tableau. On peut donc définir tous les endroits où l'on est susceptible de stocker les fichiers. En plus, on a la possibilité de définir plusieurs emplacements pour un namespace :

$loader->registerNamespaces(array(
'Symfony\Component' => __DIR__.'/component',
'Symfony' => __DIR__.'/framework',
'Sensio' => array(__DIR__.'/src', __DIR__.'/vendor'),
'Mylib' => array(__DIR__.'/mylib', __DIR__.'/mysrc')
));


2) Dans chaque contrôleur on a besoin de spécifier le namespace concerné. Il faut le faire au début du fichier, avant la déclaration de la classe. Imaginons qu'on va utiliser la classe FirstController qui se trouve quelque part le répertoire mysrc.

Dans cette situation on doit mettre dans notre fichier ceci :

namespace Mylib\FirstBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class FirstController extends Controller
{
}


Cette déclaration, grâce à l'enregistrement des namespaces dans UniversalClassLoader, va permettre la recherche du FirstController.php dans les chemins suivants :
- __DIR__.'/mylib'
- __DIR__.'/mysrc'

Si dans cette situation le fichier demandé n'est pas trouvé, on reçoit une erreur. Sinon on exécute la demande.

Pour le fonctionnement plus exact de la classe UniversalClassLoader, vous pouvez analyser la méthode loadClass(). Cette fonction est utilisée dans Symfony comme autoloader par défaut.

3. Bundles
Si vous avez déjà eu un rapport avec Zend Framework et Magento, le concept de bundles ne devrait pas vous poser des problèmes. Il ressemble beaucoup à des modules qui sous Magento possèdent des contrôleurs, des vues, des modèles, des fichiers de configuration. Si l'on compare ce système e-commerce à Symfony2, on remarque la présence des éléments supplémentaires dans le framework du SensioLabs : des formulaires, des dépendances.

Pour ceux qui n'ont pas eu de contact avec Magento et ZF, on peut se servir des explications fournies par Symfony. Sur la page http://symfony2bundles.org/ on peut lire que "sous Symfony 2 tout est un bundle et même le coeur du framework est composé de plusieurs bundles".

Un bundle est donc une brique. L'ensemble de ces briques permet de développer une nouvelle fonctionnalité (par exemple l'authentification des utilisateurs, la gestion des commandes) aussi bien qu'une partie de l'application (par exemple un bundle qui gère le backend).

Un point important à souligner est la portabilité des bundles.

4. Le cache qui accélère votre application
Symfony 2 gère en natif le cache de l'HTTP. On peut alors indiquer au navigateur si dans le cas de l'actualisation de la page il doit rechercher la page sur notre serveur (donc se connecter à la base, recharger les images) ou dans sa mémoire.

Les méthodes qui vous permettent de gérer le cache HTTP se trouvent dans la classe Response et on peut en distinguer entre autres :

a) setMaxAge() - en secondes, correspond au temps d'expiration du cache (référence HTTP : max-age)
b) setSharedMaxAge() : comme setMageAge, correspond au temps d'expiration du cache. Il s'applique aux buffeurs proxy (référence HTTP : s-maxage)
c) setExpires() : cet en-tête contient l'information sur le temps de validité du cache. HTTP permet de spécifier cette valeur sous format du temps absolu (ex : Thu, 01 Dec 1994 16:00:00 GMT). S'il l'en-tête Expires est présent à côté du max-age, il est ignoré. La directive max-age qui écrase alors la valeur d'Expires (référence HTTP : Expires).
d) setLastModified() - dans le format du temps absolu (ex : Thu, 01 Dec 1994 16:00:00 GMT), indique la dernière modification du fichier
(référence HTTP : Last-Modified).

Quelque chose qui paraît évident, et qui jusque là, n'était pas trop exploité...

5. Un moteur des templates en natif
Symfony 2 emploie un système des templates TWIG qui a quelques fonctionnalités très utiles pendant le développement d'une application web. Il met à la disposition du développeur le système d'extensions, la protection contre XSS. Il permet d'éviter de placer des balises >?php ?< dans la partie vue (pour éviter le mélange du code qui déplaît à tellement de monde).

TWIG est un élément utile mais pas indisponible car pourquoi utiliser une double couche des templates (PHP et Twig) ?

6. Nouveautés dans le fichier security.yml
Le fichier security.yml possède des éléments supplémentaires. Dans la deuxième version du framework on peut préciser entre autres :
- la fonction du cryptage du mot de passe (l'entrée encoders)
- l'entité qui fournit les données d'authentification des utilisateurs (l'entrée providers)
- le filtre qui intercepte chaque requête spécifiée et détermine si l'internaute peut accéder à la page (l'entrée firewalls)
- la spécification des rôles qu'un internaute doit avoir pour pouvoir accéder à une partie du site (l'entrée access_control).

7. Gestion de la couche modèle
Quel changement dans la gestion des entités ? Désormais vous pouvez utiliser les annotations afin de définir le type de la colonne :
@orm:Column(type="string", length="16"), @orm:Column(type="integer"). Le gros plus de cette solution est le regroupement des caractéristiques. Vous n'êtes plus obligés à sauter entre un schema.yml et une classe du modèle pendant le développement pour s'assurer de la cohérence entre les données.

8. Utilisation des annotations
Qu'est-ce qu'une annotation ? A part une nouveauté sur Symfony, bien sûr. En général ce sont des méta-données ajoutées aux classes qui sont par la suite interprétées.

On peut utiliser les annotations dans les bundles ainsi que dans les entités :
- @Column(type="string", length="255") : pour indiquer le type de la colonne dans une entité
- @extra:Route("/helloInXml", defaults={"_format"="xml"}, name="_mycontroller_helloInXml") : pour indiquer la route à utiliser
- @extra:Secure(roles="ROLE_ADMIN") : pour préciser le rôle attendu pour exécuter une méthode
- @Cached(expires="tomorrow") : pour déterminer le cache HTTP
- @Cache(smaxage="15") : pour déterminer le cache HTTP

9. Différence en Routing
Le routing est également un peu différent dans la version 2. Le paramètre qui définit le format de l'URL ne s'appele plus url. Il a pris comme nom pattern.

En plus, dans le fichier de configuration de notre application on peut préciser lequel fichier de routing on veut utiliser. Cela peut être, par exemple, un fichier YML, XML ou PHP.

Un autre point supplémentaire est la détermination de la route à la base du type de la requête. On peut avoir un URL "add_item/" qui pour la requête type GET va exécuter le code de la méthode getFormItem(). Le même URL va, pour la méthode POST, appeler la méthode sendFormItem(). Cette distinction peut s'avérer très utile lorsqu'on développe un web service en REST.

10. Création des helpers
Les helpers du Symfony 2 ressemblent à des helpers de la version 1.4. La différence majeure se trouve dans la structure. Désormais les helpers héritent d'une classe-mère Symfony\Component\Templating\Helper\Helper. Avant on pouvait se limiter à utiliser les fonctions séparées.

De nouveaux helpers fonctionnent un peu comme ceux du Zend Framework qui utilisent Zend_View_Helper_Abstract ou Zend_View_Helper_Interface.

11. Validation des formulaires
Du côté des formulaires, pas beaucoup de choses ont changé. Le principe a resté le même. Les concepteurs ont gardé l'idée des "widgets" du Symfony 1.4. Ils l'ont juste renommée en builders. Or désormais, la classe mère des formulaires est AbstractType, localisée dans Symfony/Component/Form. Egalement la méthode qui construit un formulaire a modifié son nom. A partir de cette 2e version, on parle de buildForm(FormBuilder $builder, array $options). Pour un petit rappel, précédemment Symfony utilisait la fonction configure() dans ce but.

Une différence fondamentale réside dans le fait que la validation des formulaires a été déplacée dans les entités. Dans la version 1.4 du framework, on déclarait les règles pour les champs dans la méthode configure(). Ceci était peu pratique dans la situation, où par exemple, on voulait répéter un champ avec les mêmes validateurs dans plusieurs formulaire. Actuellement, grâce au liaison entre une entité et un ou plusieurs formulaires, on peut dupliquer un champ en gardant le même validateur à chaque fois.

12. DependencyInjection (DI) mis en avant
C'est un concept très mis en avant dans Symfony 2.0. Il doit permettre de développer des applications web encore plus flexibles.

Il s'agit d'un design pattern qui se base sur l'injection des dépendances entre les composants d'un système écrit en un langage orienté objet.

Pour illustrer le concept, on peut imaginer la situation où l'on veut gérer une écurie automobile. On aura donc une classe Car qui va se connecter à une base de données pour récupérer les caractéristiques d'une voiture BMW et Renault.

On commencera par la création des classes DatabaseStorage et Car.

class DatabaseStorage
{
public function __construct($host, $login, $password, $database)
{
$this->connectToDatabase($host, $login, $password, $database);
// ... do some others actions
}

public function getCharacteristics()
{
return $this->createQuery("SELECT * FROM car_characteristics")
->getRows();
}
}

class Car
{
public function setStorage($storage)
{
$this->storage = $storage;
}

public function getCaracteristics()
{
return $this->storage->getCharacteristics();
}
}


Maintenant, grâce à l'injection des dépendances, la classe DatabaseStorage permet la manipulation des deux bases de données différentes.

// Imagine that we want to compare BMW and Renault characteristics
$carClass = new Car();

// First, we load BMW characteristics
$baseBmw = new DatabaseStorage('localhost', 'root', 'root', 'bmw_database');
$carClass->setStorage($baseBmw);
$bmwChars = $carClass->getCaracteristics();

// After that, we load Renault informations
$baseRenault = new DatabaseStorage('localhost', 'root', 'root', 'renault_database');
$carClass->setStorage($baseRenault);
$renaultChars = $carClass->getCaracteristics();

Pour moi, le développement d'une première application web sous Symfony2 s'achève. Le démarrage était un peu poussif. J'ai dû réapprendre les concepts modifiés (les webapps passent par bundles, l'introduction des annotations dans le projet à la place de plusieurs fichiers YML). Après cette étape d'adaptation, le développement s'est véritablement accéléré. Les bouts de code facilement adaptables m'ont permis de me concentrer sur l'essentiel tout en gardant une bonne dynamique de travail.

L'article écrit en rythme de:
Rahil Kayden - Soleil
Bartosz KONIECZNY 09-10-2011 19:19 Symfony2
Moi

Développeur d'applications Internet et journaliste passionné par l'adjectif français. Un aigle polonais orienté vers la progression, volant très haut et écoutant du zouk après les matches du foot français.

Vous appréciez mon travail ?

Pour contribuer au développement de ce site, ou pour remercier pour des articles rédigés, vous pouvez faire un don.

Un conseil Zend Framework

Comment faciliter le travail entre notre application et l'Ajax ?

L'un des moyens est l'utilisation du ContextSwitch. Il permet de définir le type de réponse renvoyé.