L’architecture MVC
Pour mieux comprendre les avantages du modèle MVC, nous allons voir comment convertir une application PHP standard en une application basée sur l’architecture MVC. Pour cela, nous étudierons une liste de messages (posts) d’un weblog.
La programmation standard
En programmation PHP standard, l’affichage d’une liste d’éléments d’une base de données pourrait ressembler au listing 2-1
Listing 2-1 - Un script standard
[php]
<?php
// Connection , sélection de la base de données
$link = mysql_connect('localhost', 'myuser', 'mypassword');
mysql_select_db('blog_db', $link);
// Exécution de la requête
$result = mysql_query('SELECT date, title FROM post', $link);
?>
<html>
<head>
<title>List of Posts</title>
</head>
<body>
<h1>List of Posts</h1>
<table>
<tr><th>Date</th><th>Title</th></tr>
<?php
// Affichage des résultats en HTML
while ($row = mysql_fetch_array($result, MYSQL_ASSOC))
{
echo "\t<tr>\n";
printf("\t\t<td> %s </td>\n", $row['date']);
printf("\t\t<td> %s </td>\n", $row['title']);
echo "\t</tr>\n";
}
?>
</table>
</body>
</html>
<?php
// Fermeture de la connexion
mysql_close($link);
?>
C’est rapide à écrire et à exécuter mais difficile à maintenir. Les principaux problèmes de ce code sont :
- Pas de contrôle d’erreur (que se passe-t-il si la connexion échoue ?).
- La syntaxe s’appuie sur du PHP et du HTML entremêlés.
- Le code n’est adapté qu’à la base de données MySQL.
Isoler la présentation
Sur le listing 2-1, l’utilisation des ordres echo et printf complexifie la lisibilité du code et tout changement de présentation, via une modification du source HTML, devient périlleux. Par conséquent nous diviserons ce script en deux parties. La première contiendra le code PHP et la logique de métier qui seront déplacés dans le script du contrôleur, comme indiqué dans l’exemple 2-2
Exemple 2-2 La partie contrôleur dans index.php
[php]
<?php
// Connexion, sélection de la base de données
$link = mysql_connect('localhost', 'myuser', 'mypassword');
mysql_select_db('blog_db', $link);
// Excécution de la requête sql
$result = mysql_query('SELECT date, title FROM post', $link);
// Filling up the array for the view
$posts = array();
while ($row = mysql_fetch_array($result, MYSQL_ASSOC))
{
$posts[] = $row;
}
// Fermeture de la connection
mysql_close($link);
// Affichage du résultat
require('view.php');
?>
Le code HTML, contenant la syntaxe PHP dédiée à la présentation, sera stocké dans le script de vue comme indiqué dans l’exemple 2-3.
Exemple 2-3 – La partie vue, dans view.php
[php]
<html>
<head>
<title>List of Posts</title>
</head>
<body>
<h1>List of Posts</h1>
<table>
<tr><th>Date</th><th>Title</th></tr>
<?php foreach ($posts as $post): ?>
<tr>
<td><?php echo $post['date'] ?></td>
<td><?php echo $post['title'] ?></td>
</tr>
<?php endforeach; ?>
</table>
</body>
</html>
Pour qu’un script de vue soit de bonne qualité, il doit contenir le moins de code PHP possible et aucun ordres PHP encapsulant des balises HTML de manière à être compréhensible par un graphiste démuni de connaissances en PHP. Les ordres les plus communs dans un script vue sont echo, if/endif, foreach/endforeach.
Toute la logique de traitement des données est placée dans le script Contrôleur et ne contient que la syntaxe PHP, sans aucun code HTML. Dans l’idéal, il faut pouvoir réutiliser le même contrôleur pour une présentation complètement différente comme une impression PDF ou un fichier XML.
Isoler la manipulation des données
La majeure partie du code d’un script contrôleur est dédiée à la manipulation des données. Mais que se passerait-il si vous aviez besoin de la même liste de messages pour un autre contrôleur, comme pour la gestion d’un flux RSS par exemple ? Comment faire si vous souhaitez conserver toutes les requêtes de base de données dans un même fichier pour éviter de dupliquer le code ? Que faire si vous décidez de changer la structure de la base de données et que la table post devient weblog_post ? N’allez-vous pas un jour migrer de PostgreSQL à MySQL ? Pour permettre toutes ces opérations nous allons devoir placer la manipulation des données dans un autre script appelé modèle (cf exemple 2-4).
Exemple 2-4 La partie modèle, dans model.php
[php]
<?php
function getAllPosts()
{
// Connexion, sélection de la base de données
$link = mysql_connect('localhost', 'myuser', 'mypassword');
mysql_select_db('blog_db', $link);
// Excécution de la requête sql
$result = mysql_query('SELECT date, title FROM post', $link);
// Remplissage du tableau
$posts = array();
while ($row = mysql_fetch_array($result, MYSQL_ASSOC))
{
$posts[] = $row;
}
// Fermeture de la connection
mysql_close($link);
return $posts;
}
?>
L’exemple 2-5 montre le contrôleur modifié.
Exemple 2-5 la partie contrôleur, dans index.php
[php]
<?php
// Appel du modèle
require_once('model.php');
// Récupération de la liste des posts
$posts = getAllPosts();
// Appel de la vue
require('view.php');
?>
Le contrôleur devient plus facile à lire. Son principal rôle est de transférer les données du modèle à la vue. Au sein d’applications plus complexes, il s’occupe aussi des requêtes, des sessions utilisateurs, de l’authentification etc… L’emploi de noms de fonctions compréhensibles peut même rendre les commentaires du contrôleur obsolètes.
Le script model.php est dédié à l’accès aux bases et doit être pensé en ce sens. Toutes les informations qui ne sont pas propres aux bases (comme les paramètres des requêtes) doivent être reçues du contrôleur et non intégrées directement dans le modèle. De cette manière le modèle peut être réutilisé dans un autre contrôleur.
La séparation en couche au-delà de MVC
Pour résumer, le principe de l’architecture MVC est de répartir les codes sur trois niveaux : la logique de données est placée dans le modèle, la présentation dans la vue, et enfin la logique applicative dans le contrôleur.
Afin de simplifier encore le codage, il est possible d’aller plus loin. Ainsi le modèle, la vue et le contrôleur peuvent aussi être divisés.
L’abstraction de données
La couche modèle peut être elle-même divisée en deux sous-couches : le niveau accès aux données et le niveau abstraction de données. De la sorte la partie accès de données n’utilisera pas de requêtes dépendantes du SGBD, mais fera appel à des fonctions prévues à cet effet. Si la base de données vient à changer, seule la couche d’abstraction de données devra alors être adaptée.
Le listing 2-6 vous montre un exemple d’abstraction de données lié à MySQL. Le listing montre l’exemple d’accès de données associés.
Listing 2-6 L’abstraction de données du modèle
[php]
<?php
function open_connection($host, $user, $password)
{
return mysql_connect($host, $user, $password);
}
function close_connection($link)
{
mysql_close($link);
}
function query_database($query, $database, $link)
{
mysql_select_db($database, $link);
return mysql_query($query, $link);
}
function fetch_results($result)
{
return mysql_fetch_array($result, MYSQL_ASSOC);
}
Listing 2-7 - Accès aux données du modèle
[php]
function getAllPosts()
{
// Connection à la base de données
$link = open_connection('localhost', 'myuser', 'mypassword');
// Excécution de la requête sql
$result = query_database('SELECT date, title FROM post', 'blog_db', $link);
// Remplissage du tableau
$posts = array();
while ($row = fetch_results($result))
{
$posts[] = $row;
}
// Fermeture de la connection
close_connection($link);
return $posts;
}
?>
On constate qu’aucune des fonctions dépendantes de la base de données ne se trouvent dans la partie accès de données. De plus, les fonctions de la partie abstraction de données peuvent être réutilisées par d’autres fonctions du modèle ayant besoin d’un accès à la base de données.
NOTE Quelques petites améliorations devraient encore être apportées aux exemples 2-6 et 2-7 pour qu’ils soient pleinement satisfaisants (indépendance du code SQL, mise en classes de toutes les fonctions etc …). Mais tel n’est pas le but de ce livre et dans le chapitre 8 vous découvrirez que Symfony gère très bien tous les types d’abstraction.
La partie Vue
La vue aussi peut faire l’objet d’une séparation de code. Une page contient souvent des éléments communs à toute l’application comme l’entête, le pied de page, la charte graphique, les principes de navigations etc… Seule le contenu de la page est appelé à changer. Ainsi on peut distinguer deux parties dans la vue : la maquette (layout) et le gabarit (template). La maquette est généralement globale à l’application ou à un groupe de pages et le gabarit met en forme les données fournies par le contrôleur. Une troisième partie est la vue logique (ou simplement vue) qui permet aux deux premières de travailler de concert. En appliquant ces principes, le listing 2-3 peut être séparé en trois parties comme le montrent les listings 2-8, 2-9 et 2-10
Listing 2-8 La partie gabarit de la vue, dans mytemplate.php
[php]
<h1>List of Posts</h1>
<table>
<tr><th>Date</th><th>Title</th></tr>
<?php foreach ($posts as $post): ?>
<tr>
<td><?php echo $post['date'] ?></td>
<td><?php echo $post['title'] ?></td>
</tr>
<?php endforeach; ?>
</table>
Listing 2-9 La vue logique de la vue
[php]
<?php
$title = 'List of Posts';
$content = include('mytemplate.php');
?>
Listing 2-10 La partie maquette de la vue
[php]
<html>
<head>
<title><?php echo $title ?></title>
</head>
<body>
<?php echo $content ?>
</body>
</html>
Action et contrôleur principal (front controller)
Dans les exemples précédents, le contrôleur ne fait pas grand chose, mais dans une application web réelle, il est sûrement celui qui a le plus de travail. De plus, la plupart des contrôleurs partagent en grande partie les mêmes actions comme la gestion des requêtes, la sécurité, la configuration de l’application etc… C’est pourquoi les contrôleurs sont plus souvent représentés par un contrôleur principal unique à l’application et par les actions, qui ne contiennent que le code contrôleur spécifique à certaines pages.
L'un des grands avantages d'un contrôleur frontal est qu'il offre un point d'entrée unique pour toute l'application. Si vous décidez de fermer l'accès à l'application, vous aurez seulement besoin de modifier le script de contrôleur frontal. Dans une application sans un contrôleur frontal, chaque contrôleur doit être désactivé.
Orienté objet.
Tous les exemples précédents s'appuient sur les principes de la programmation procédurale. Mais les possibilités de la programmation orientée objet (POO ou OOP) des langages modernes permettent de rendre les développements encore plus simples grâce à l’encapsulation, l’héritage et permettent, en outre, d’avoir des normes de codifications simples.
Implémenter une architecture avec un langage non objet augmente les risques de duplications de codes, de collisions de nom et d’une manière générale rend les sources plus difficiles à lire.
L’orientation objet permet aux développeurs de travailler avec les objets vue, contrôleur et les classes du modèle, et permet de transformer toutes les fonctions des exemples précédents (php standard) en méthodes. C'est un avantage considérable.
TIP Si vous voulez approfondir vos connaissances sur les motifs de conception pour le web en orienté objet, procurez-vous le livre Patterns of Entreprise Application Architecture de Martin Fowler (Addison-Wesley, ISBN : 0-32112-742-0). Les exemples sont en Java ou C#, mais restent compréhensibles pour un développeur PHP.
L’implémentation MVC de Symfony
Revenons à notre liste de messages. Combien de couches faut-il donc implémenter pour obtenir cette simple page ? Nous avons identifié les éléments illustrés par le schéma 2-2:
- Niveau Modèle
- Abstraction de données
- Accès aux données
- Niveau Vue
- Vue (logique de vue)
- Gabarit
- La maquette
- Le niveau Contrôleur
- Le contrôleur principal
- Les actions
Sept scripts ! Beaucoup de fichiers à ouvrir et à modifier pour chaque nouvelle page! Cependant, Symfony rend les choses faciles car tout en prenant le meilleur de l’architecture MVC, il l’implémente de manière à rendre le développement rapide et aisé.
Tout d’abord, le contrôleur principal et la maquette sont communs à toute l’application. Vous pouvez en avoir plusieurs mais un seul de chaque est nécessaire. Le contrôleur principal est un composant pur MVC, ce qui veut dire que vous n’aurez jamais à le coder, Symfony le générera pour vous.
L’autre bonne nouvelle est que les classes du niveau modèle sont aussi générées automatiquement en fonction de votre base de données et ce grâce à Propel. Cette librairie gère les clés étrangères et les champs de date, génère les accesseurs, etc facilitant encore plus la manipulation de données. De plus, l’abstraction de données est complètement transparente pour vous puisqu’entièrement gérée par une autre composant appelé Creole. Ainsi, si vous décidez de changer de SGBD, vous n’aurez pas de code à réécrire, mais juste à modifier un paramètre de la configuration.
Enfin, la logique de vue peut s’appuyer sur un simple fichier de configuration, sans avoir besoin de programmation.
Schéma 2-2 Le processus Symfony

Il en résulte que notre liste de messages ne requiert plus que trois fichiers pour fonctionner comme le montrent les listings 2-11, 2-12, 2-13.
Listing 2-11 Action de list de myproject/apps/myapp/modules/weblog/actions/actions.class.php
[php]
<?php
class weblogActions extends sfActions
{
public function executeList()
{
$this->posts = PostPeer::doSelect(new Criteria());
}
}
?>
Listing 2-12 Template de liste, de myproject/apps/myapp/modules/weblog/templates/listSuccess.php
[php]
<h1>List of Posts</h1>
<table>
<tr><th>Date</th><th>Title</th></tr>
<?php foreach ($posts as $post): ?>
<tr>
<td><?php echo $post->getDate() ?></td>
<td><?php echo $post->getTitle() ?></td>
</tr>
<?php endforeach; ?>
</table>
Listing 2-13 Vue de liste, de myproject/apps/myapp/modules/weblog/config/view.yml
listSuccess:
metas: { title: List of Posts }
De plus, vous allez aussi devoir définir une maquette, comme le montre le listing 2-14. Elle sera régulièrement réutilisée.
Listing 2-14 – Maquette de liste, dans myproject/apps/myapp/templates/layout.php
[php]
<html>
<head>
<?php echo include_title() ?>
</head>
<body>
<?php echo $sf_data->getRaw('sf_content') ?>
</body>
</html>
Voilà donc tout ce qui vous est nécessaire. Ce code a exactement le même effet que celui écrit en PHP standard vu plus haut sur le listing 2-1. Faire fonctionner tous les composants ensemble n'est pas de votre ressort et est géré pas Symfony. Si vous inspectez le code vous verrez que le développement de la liste de messages en architecture MVC ne requiert pas plus de temps, ni de code que l'écriture en PHP standard. Cependant, cela vous apporte de grands avantages comme une organisation plus claire du code, la possibilité de le réutiliser facilement, de la flexibilité, des possibilités de deboggage, une configuration aisée, l'abstraction de données, la réécriture d'URL, le multi-environnement et encore beaucoup d'outils de développement.


