Comparaison des frameworks Symfony2 et Zend framework 2


Technique

Symfony2.0 a été releasé il y a maintenant 1 an, et après de longs mois de développement et de tests, la version 2.1 a enfin vu le jour. Cette nouvelle mouture démontre déjà une certaine maturité, contrairement à la version majeure de Zend Framework 2 tant attendue et qui a montré le bout de son nez il y a seulement quelque jours.

C’est pourquoi, dans le but de faire le bon choix, il est maintenant temps de faire le point.

Chacun des frameworks embarque une organisation MVC.

Qu’est-ce que le MVC ?

Le système MVC autorise la séparation des différentes couches métiers, ce qui donne davantage de clarté dans le code, mais permet aussi de faciliter l’avancement entre les différents acteurs. Voici les différentes couches :

  • Les Modèles : ils contiennent le code permettant d’interoger la base de données. Il est maintenant courant d’utiliser des ORM (Object Relational Mapping), tels que Doctrine ou Propel, dans le but de manipuler les données via des objets.
  • Les Vues : elles sont les templates, c’est-à-dire les fichiers contenant du code html et qui incluent les données issues des controllers. De plus en plus de moteurs de templates sont utilisés (ex. Twig, Smarty, PHP…).
  • Les Controllers : ils contiennent les algorithmes et font appel aux Vues et aux Modèles.

1 – Les vues

a. Zend Framework 2

ZF2 possède une couche Vue mais n’utilise pas particulièrement de moteur de template. Pour combler cette absence, vous pouvez ajouter manuellement un moteur de template tel que Smarty, ce qui évite d’encombrer les Vues avec du PHP.

b. Symfony2

A contrario du framework cité précedemment, Symfony2 embarque par défaut un moteur de template appelé “Twig”. Développé par le créateur du framework Fabien Potencier himself, il a été fortement inspiré du framework Django en reprenant les points forts de ce dernier. Utiliser un moteur de template tel que Twig a pour avantages de rendre les templates plus lisibles par les intégrateurs (mais également par les développeurs), et d’avoir des syntaxes qui simplifient la vie tout en restant plus élégantes comme le démontre l’exemple ci-dessous :

Balayer les résultats d’une requête issue de la base de données

PHP


if( count($data) > 0 )
{
foreach( $data as $user )
{
echo $user[‘username’] ;
}
}else{
echo ‘Il n’y a aucun utilisateur dans la base de données’ ;
}

TWIG


{% for user in users %}
{{ user.username }}
{% else %}
Il n’y a aucun utilisateur dans la base de données.
{% endfor %}

2 – ORM

Un ORM (Object-Relational Mapping) est une couche d’abstraction entre votre application et la base de données qui vous permet de mapper directement votre base de données en objet. Vous ne vous préoccupez quasiment plus de requête SQL, il s’en chargera à votre place.
Son objectif est simple : se charger de l’enregistrement de vos données en vous faisant oublier que vous avez une base de données.

Doctrine 2 (Symfony 2 et Zend FrameWork 2)

Doctrine est l’un des ORM les plus connus qui existent actuellement. Utilisé dans des Frameworks très connus, il est aussi simple à prendre en main que puissant.

Sur ce schéma ci-dessous, le fonctionnement de Doctrine :

242089 300x225 Comparaison des frameworks Symfony2 et Zend framework 2

  • La première repose sur PDO, à laquelle elle ajoute des fonctionnalités (DBAL : Database Abstraction Layer). Son but est de fournir une couche d’abstraction. On ne se soucie plus de comment fonctionne notre base de données.
  • La deuxième couche (ORM) permet de faire le « lien » entre nos objets PHP et la DBAL, c’est ce qui nous fournit une interface orientée “objet” pour manipuler la base de données. Elle interagit avec la DBAL et nous retourne des résultats sous forme d’objets.

Prenons un exemple concret d’utilisation avec le Framework Symfony 2 :

Ici une entité “Voiture” avec, comme attributs, une marque et un modèle . Doctrine 2 utilise des annotations pour pouvoir définir le type des arguments, ou d’autres restrictions.


/**
* @ORMTable(name="voiture")
* @ORMEntity(repositoryClass=”DiskoAdminBundleEntityVoitureRepository”)
*/
class Voiture
{
/**
* @ORMColumn(name="id", type="integer", nullable=false)
* @ORMId
* @ORMGeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
*
* @ORMColumn(name="marque", type="text", length=255, nullable=false)
* @AssertNotBlank(message="Veuillez renseigner une marque")
*/
private $marque;
/**
*
* @ORMColumn(name="modele", type="text",length=255, nullable=false)
* @AssertNotBlank(message="Veuillez renseigner un modele")
*/
private $modele;
// ...
}

Dans cette exemple, nous utilisons différentes annotations pour définir l’entité Voiture.
L’annotation Table permet de définir le nom de la table. Si ce champ est vide, il prend par défaut le nom de l’entité.
L’annotation Entity permet de définir le chemin vers le repository de l’entité Voiture (le repository peut être généré automatiquement via ligne de commande Symfony ou ZF2).

Chaque propriété de notre classe est privée et dispose donc de getter et setter, qui peuvent être générés automatiquement via ligne de commande. Les propriétés disposent elles aussi d’annotations permettant de définir le nom de la colonne ainsi que son type et sa longueur.

Par exemple, l’annotation ci-dessous permet, lors de la validation du formulaire, de contraindre les champs saisis par l’internaute sur un certain format, et de renvoyer un message dans le cas où ce n’est pas respecté.

Pour appeler une entité Doctrine 2 dans un controlleur Symfony, rien de plus simple :


$em = $this->get(‘doctrine’)->getEntityManager() ; // On récupère le service EntityManager
// On déclare ensuite notre entité voiture
$voiture = new Voiture() ;
$voiture->setMarque('Renault') ;
$voiture->setModele('Clio') ;
$em->persist($voiture) ; // L'entité est maintenant gérée par Doctrine. Cela n'exécute pas encore de requête SQL ni rien.
$em->flush() ; // Notre voiture est enregistré en base de données grâce au flush() qui effectue simplement un COMMIT pour les base de donnés transactionnels.

3 – Les controllers

a. Symfony2

Voici la nouvelle structure des controllers :

namespace DiskoFrontBundleController;
use SymfonyBundleFrameworkBundleControllerController,
SensioBundleFrameworkExtraBundleConfigurationRoute,
SymfonyComponentHttpFoundationResponse;
/**
* @Route(‘/users’)
*/
class UsersController extends Controller
{
/**
* @Route("/index")
*/
public function indexAction($scope)
{
return new Response('Hello world!');
}
}

La grande nouveauté dans Symfony2 est l’utilisation des annotations, comme illustré ici. Cela nous évite de jongler entre le routing.yml et le controller. Nous indiquons donc la route directement au-dessus de notre fonction indexAction, mais également au dessus de la classe. Notre action “index” sera donc accessible à titre d’exemple à l’adresse suivante : www.disko.fr/users/index, pratique, n’est-ce pas ? Et vous l’aurez compris, toutes les actions qui se trouveront dans la classe UsersController seront préfixées par la route ‘/users’… Magique !

Evidemment, selon les goûts et couleurs des développeurs, il est toujours possible d’utiliser le fichier de routing.yml.

b. Zend Framework 2

ZF2 bénéficie lui aussi des namespaces de PHP 5.3, ce qui rend les controllers bien mieux structurés que ceux de la première version.

namespace ApplicationController;
use ZendMvcControllerAbstractActionController;
use ZendViewModelViewModel,
ZendViewRendererPhpRenderer,
ZendViewResolver;
class UserController extends AbstractActionController
{
public function createAction()
{
$view = new ViewModel();
$request = $this->getRequest();
if($request->isPost())
{
// [...] Si c’est une requête POST, alors on effectue un traitement
}
return $view;
}
}

À la différence de Symfony2, ZF2 ne fait pas appel aux annotations, il faut alors avoir recours aux fichiers configs pour modifier les routes, ainsi que les autres paramètres.

4 – Les formulaires

Les formulaires servent à récupérer les informations d’un utilisateur, à vérifier que les données saisies sont bien celles attendues et à ajouter ou modifier la base de données en conséquence… Autant d’étapes qui rendent leur développement compliqué et contraignant.

Heureusement aujourd’hui différents outils intégrés au framework nous permettent de gagner un temps non négligable dans les développements, tout en s’assurant que les données saisies par l’utilisateur correspondent à notre modèle et sont bien celles attendues.

Les exemples suivants reprendront notre modèle Voiture.

a. Symfony2

Symonfy 2 peut se baser sur les entités (Objet) Doctrine, ou sur les formulaires simples non rattachés à une entité Doctrine, pour gérer ces formulaires, ainsi que des validateurs (Annotation directement dans l’entité (vue plus haut), Xml, Yml, ou directement dans le FormType) pour la validation.

Il est possible de créer directement un FormType correspondant à une Entity Doctrine existante avec une commande Symfony2 :

php app/console doctrine:generate:form DiskoBundle:Voiture

Cette commande va nous générer un fichier VoitureType.php dans le dossier Form de notre bundle.


namespace DiskoCoreBundleForm;
use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilder;
class VoitureType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('marque', ‘text’ array(
‘label’ => ‘Marque’
))
->add('modele','text', array(
'label' => 'Modèle’
))
;
}
public function getName()
{
return 'voiture';
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'DiskoCoreBundleEntityVoiture',
);
}
}

Nous pourrons ensuite l’utiliser directement dans un controller :


/**
* @Route(‘/voiture/ajouter’)
* @Template()
*/
public function ajouterVoitureAction(){
$em = $this->get(‘doctrine’)->getEntityManager();
$voiture = new EntityVoiture();
$form = $this->createForm(new FormVoitureType(), $voiture);
if( $this->getRequest()->getMethod() == ‘POST’ ){
$form->bindRequest($this->getRequest());
if( $form->isValid() ){
$em->persist($voiture);
$em->flush();
// Redirection et message de confirmation de création d’une voiture.
}
}
return array(
‘form’ => $form->createView()
)
}

et dans notre template twig:


<form action="{{ path('ajouter_voiture') }}" method="post">{{ form_widget(form) }}<input type="submit" value="Ajouter" /></form>

Pour aller plus loin, nous pouvons également :
“Personnaliser” le rendu HTML des widgets du formulaire.
Utiliser des évènements FormEvents “PRE_POST, PRE_BIND, PRE_SET_DATA, …”
Gérer des collections de widgets, des formulaires embarqués, des relations du modèle pour générer automatiquement des listes “select” …

Comme nous pouvons le voir, la création de formulaires et leur mise en place sont très grandement simplifiées et rapides avec Symfony 2.

b. Zend Framework 2

Les formulaires de ZF2 sont quasi similaires de ceux de Symfony 2 dans leur fonctionnement.

Nous créons déjà notre formulaire Voiture :


namespace VoitureForm;
use ZendFormForm;
class VoitureForm extends Form
{
public function __construct($name = null)
{
parent::__construct('voiture');
$this->setAttribute('method', 'post');
$this->add(array(
'name' => 'marque',
'attributes' => array(
'type' => 'text',
),
'options' => array(
'label' => 'Marque',
),
));
$this->add(array(
'name' => 'modele',
'attributes' => array(
'type' => 'text',
),
'options' => array(
'label' => 'Modèle',
),
));
$this->add(array(
'name' => 'submit',
'attributes' => array(
'type' => 'submit',
'value' => 'Ajouter',
'id' => 'submitbutton',
),
));
}
}

Notre controller :


public function addAction()
{
$form = new VoitureForm();
$form->get('submit')->setValue('Ajouter');
$request = $this->getRequest();
if ($request->isPost()) {
$voiture = new Voiture();
$form->setInputFilter($voiture->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$voiture->exchangeArray($form->getData());
$this->getVoitureTable()->saveVoiture($voiture);
// Redirect to list of cars
return $this->redirect()->toRoute('voiture');
}
}
return array('form' => $form);
}

Pour gérer les filtres sur les champs du formulaire, il faut ajouter une fonction getInputFilter() dans le modèle Voiture, en implémentant le modèle avec “InputFilterAwareInterface”:


class Voiture implements InputFilterAwareInterface
{
public $id;
public $marque;
public $modele;
protected $inputFilter;
/ /

/ /
public function setInputFilter(InputFilterInterface $inputFilter)
{
throw new Exception("Not used");
}
public function getInputFilter()
{
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$factory = new InputFactory();
$inputFilter->add($factory->createInput(array(
'name' => 'id',
'required' => true,
'filters' => array(
array('name' => 'Int'),
),
)));
$inputFilter->add($factory->createInput(array(
'name' => 'marque',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100,
),
),
),
)));
$inputFilter->add($factory->createInput(array(
'name' => 'modele',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100,
),
),
),
)));
$this->inputFilter = $inputFilter;
}
return $this->inputFilter;
}
}

et notre template :


$form = $this->form;
$form->setAttribute('action', $this->url('voiture', array('action' => 'add')));
$form->prepare();
echo $this->form()->openTag($form);
echo $this->formRow($form->get('title'));
echo $this->formRow($form->get('artist'));
echo $this->formSubmit($form->get('submit'));
echo $this->form()->closeTag();

Conclusion

Pour conclure, ces deux frameworks restent des valeurs sûres, non seulement parce qu’ils bénéficient d’une large communauté à travers le monde, mais également parce que des sites à très fort trafic ont été développés sous l’un ou l’autre de ces deux Frameworks.

Après avoir réalisé des tests de performance et étudié de plus près ces deux Frameworks, notamment au niveau de la structuration et de l’architecture, chez Disko nous avons choisi d’utiliser Symfony 2 que nous avons d’ailleurs eu l’occasion de déployer sur des plateformes clients stratégiques sur plus de 50 pays dont certains encaissant des problématiques de charge de plusieurs centaines de milliers d’utilisateurs par jour et cela sans aucun problème majeur !

Laisser un commentaire