Maxime Flin
Nous avons souhaité créer un forum de discussion destiné aux étudiants de l'université Paris Diderot. C'est la première idée qui nous est venue, puisqu'une telle application comporte quasi-systématiquement les fonctionnalités qui étaient imposées (création de compte, page de profil, recherche dans le contenu, etc.). Le site a été conçu pour être simple/efficace à utiliser.
Nous nous sommes fixé deux contraintes à respecter :
- obtenir du code qui serait partiellement réutilisable au sein de futurs projets (donc clair, propre et modulaire) ;
- rendre accessible ledit forum en le mettant en ligne afin qu’il soit bel et bien utilisable par les étudiants.
Depuis plusieurs semaines, le forum est ainsi accessible à l'adresse suivante : http://diderot.club/
Nous nous sommes aperçus que, même lorsqu'une application est apparemment finalisée, la mise en production peut, elle aussi, réclamer énormément de temps : les surprises sont nombreuses (erreurs soudaines ; comportements inattendus; etc.) et il faut souvent faire faces à divers imprévus inhérents au monde de l'informatique (pannes, sauvegardes, etc.). La documentation des autres grands projets open source (PHP, notamment) est heureusement largement fournie, ce qui nous a permis de résoudre tous les problèmes rencontrés.
Durant sa mise en place, le serveur nous a en effet donné un sacré fil à retordre : l'installation et la configuration des différentes composantes d'un serveur web — Nginx, PHP et MariaDB/MySQL, dans notre cas — doivent être faites en tenant compte de la compatibilité (ou incompatibilité !…) selon les versions. Nous employons par exemple certaines fonctionnalités de PHP qui ne sont présentes qu’à partir de la version 5 (et PHP doit être associé à des librairies précises faisant l'interface entre les différentes composantes, etc.).
Mais travailler en commun sur ce projet a évidemment constitué une très belle expérience. Chacun possédait déjà certaines connaissances en développement web avant cette première année de licence, aussi nous avons pu immédiatement travailler de manière efficace. En mettant en place le serveur, nous nous sommes retrouvés immergés dans un environnement UNIX qu'il a fallu configurer en ligne de commande, ce qui a constitué un rappel bienvenue. Tout ceci nous a donné un aperçu de ce que peut être un travail en informatique.
Puisqu'il n'était pas permis de s'appuyer sur des frameworks existants, nous avons d'abord créé une base robuste (modérément inspirée du framework Laravel) autour de laquelle est ensuite venue se construire notre application.
Notre code s'organise selon une architecture de type Model-View-Controller (ou MVC) :
- /app Ensemble des fichiers de l'application
- /controllers Contrôleurs
- /model Modèles
- /helpers Helpers (utlisés dans les vues)
- /exception Gestion des erreurs
- /views Vues
- /layout Layouts de base depuis lesquels sont construits tous les autres
- config.php Fichier de configuration principal
- routes.php Fichier faisant le lien entre les URL et les contrôleurs
- /lib Classes de l'application
- /public Ressources client (face utilisateur de l'application)
- /tmp Fichiers temporaires utilisés lors du débuggage (fichiers de log, etc.)
Pour déployer notre application, il faut au préalable former les bases de données. La commande à exécuter depuis le répertoire racine de l'application est la suivante :
php serve init
Ceci affichera le fichier SQL à exécuter. Il faut aussi changer les variables utilisées pour l'accès à la base de données en éditant le fichier app/config.php
.
Pour lancer un serveur local qui fera tourner l'application, il faut entrer la commande suivante (toujours depuis le dossier racine de l'application) :
php serve
Les URL des pages du forum intuitives : plutôt que d'accéder à une page grâce à l'URL /posts/singlepost.php?id=12
(un chemin vers un fichier PHP), il a été fait en sorte que l'URL soit simplement /posts/1
.
Pour ce faire, on redirige toutes les requêtes HTTP de la façon qui suit :
- Quelle que soit l'URL, on redirige dans le dossier
public/
(face utilisateur de l'application) ; - Dans ce dossier
public/
, si l'URL mène vers un fichier, alors on redirige vers ce ficher, sinon on redirige vers le ficherindex.php
(mauvaise URL).
Il existe plusieurs manières d'effectuer de telles redirections d'URL. Sur notre serveur, c'est Nginx qui s'en charge directement (il s'agit alors de bien dresser Nginx grâce aux fichiers de configuration). En local, nous avons tous les deux utilisé les fichiers .htaccess
propres au serveur Apache. Nous avons également créé un petit script PHP, serve
, qui démarre l'application sur un serveur PHP local (ce script est d'ailleurs inspiré par les fichiers de configuration de Nginx).
Les ressources sont ainsi chargées avec des URL très intuitive (pour l'utilisateur, mais aussi au sein de nos propres scripts). Les deux avantages principaux sont :
- On sépare clairement la face visible par l'utilisateur (le front-end) de tout ce qu'il peut se passer d'autre dans le fonctionnement de l'application (le back end). Ici, le front-end est constitué exclusivement des fichiers et répertoires présents dans
public/
. - On peut gérer les exceptions — comme par exemple les posts non trouvés — directement au sein du code, et ceci offre beaucoup plus de liberté et de modularité.
Toutes les URL emmènent maintenant sur le même ficher : index.php
. Il faut donc lire les URL demandées par l'utilisateur afin de charger le contrôleur qui correspond. Pour ce faire, le fichier index.php
inclut le fichier /lib/bootstrap.php
qui, lui-même, va progressivement inclure les différentes composantes de l'application, dont notamment trois classes essentielles pour notre architecture MVC: Request
, Router
et Response
. Les instances de ces classes décrivent, respectivement, ce que reçoit le serveur, la manière dont il traite la requête, et la réponse qu'il fournit au client.
Le diagramme suivant illustre la structure globale de notre application :
Tous les fichiers d'après lesquels sont dynamiquement construites les pages se situent dans le dossier app/
. Les autres dossiers constituent une base modulaire dont nous nous resservirons au sein projets futurs. L'ajout d'une nouvelle page au site requiert plusieurs actions :
- Ajouter la nouvelle URL au fichier
routes.php
; - Créer le contrôleur et la fonction correspondante ;
- Charger et « rendre » une vue dans ce contrôleur ;
- Créer enfin la vue correspondante. S'ajoute à cela un éventuelle création de modèles (par exemple pour interagir avec la base de donnée, ou pour gérer des erreurs en créant des exceptions).
Nous allons maintenant détailler ces étapes du développement.
Le fichier routes.php
, situé à la base du dossier app/
, contient toutes les URL auxquelles l'application associera un contrôleur et une action (c'est-à-dire une méthode de ce contrôleur). On décrit une URL de la façon suivante :
App::$route->get(url, action) // pour une requête de type GET
App::$route->post(url, action) // pour une requête de type POST
App::$route->put(url, action) // pour une requête de type PUT
Dans les lignes précédentes url
est l'URL demandée lors de la requête. On peux y ajouter des paramètres afin que cette URL décrive un ensemble d'URL. Par exemple, on peut vouloir que les pages /posts/1
et /posts/254
appellent toutes les deux la même méthode du conrôleur mais avec des variables différentes (qui correspondraient ici à l'identifiant du post). On écrit alors ce paramètre {nom_du_paramètre}
dans l'URL.
Il faut toutefois spécifier le type du paramètre au moyen d'une expression régulière (ou regex). Pour ce faire, on utilise la fonction App::$route->setPattern(nom, regex)
. La route cherchera alors le paramètre d'URL correspondant à l'expression régulière donnée. Dans le cas des posts décrits ci-dessus, il faudrait ajouter le code suivant au ficher app/routes.php
:
App::$route->setPattern('id', '[0-9]+');
App::$route->get('/posts/{id}', 'Controller@methode');
La route nous fournit ainsi la possibilité d'appliquer des filtres aux URL. Par exemple, puisque la page de création d'une conversation est réservée aux utilisateurs connectés, il nous suffit d'ajouter un filtre authenticate
à l'URL :
App::$route->filter(['authenticate'], function () {
App::$route->get('/posts/new', 'Controller@method');
});
Ainsi la fonction passée en paramètre ne s'exécute que si tous les filtres du tableau sont vérifiés (renvoient true
). On peut aussi bien sûr prendre la négation d'un filtre et ajouter un ensemble d'URL dans le cas où au moins un des filtres n'est pas validé.
App::$route->filter(['filtre1', '!négation', function () {
// si tous les filtres sont validés
}, function () {
// si au moins un filtre n'est pas validé
});
Nous avons vu, dans la section précédente, comment l'application redirige dynamiquement les URL sur des méthodes spécifiques des contrôleurs. Nous allons maintenant voir comment les contrôleurs nous permettent, très simplement, de fournir une réponse élaborée en fonction de la requête.
Un contrôleur est une classe qui hérite de BaseController
et qui se trouve dans le dossier app/controllers/BaseController.php
. Le fichier dans lequel se trouve le contrôleur doit être app/controller/Nom.php
(où Nom est le nom du contrôleur. Le code minimum au sein du fichier d'un contrôleur est donc :
class PostsController extends BaseController {
}
Le contrôleur a plusieurs attributs qui peuvent être modifiés suivant des besoins :
- Le layout :
$this->layout
est le nom de la vue globale dans laquelle est injectée le contenu des vues chargées dans les contrôleurs ; - Les modèles :
$this->model
est le tableau de tous les modèles à charger dans les contrôleurs ; - Les helpers :
$this->autoLoadHelpers
est le tableau de tous les helpers chargées dans les vues.
Ces attributs, modifiables à tout moment, sont spécifiques à chaque contrôleur et valent par défaut, respectivement, "basics", ["NomDuControllerAuSingulier"] et ["Html"].
Admettons, pour l'exemple, que la route appelle l'action PostsController@index
. La méthode index
de la classe PostsController
est alors appellée. La syntaxe pour cette méthode est la suivante :
class PostsController extends BaseController {
public function index ($param_url1, $param_url2) {
// Actions: requêtes SQL, traitement de données, etc.
}
}
Les paramètres d'URL définis dans la route apparaissent donc dans les paramètres de la méthode appellée.
Il convient alors de renvoyer une vue. Pour ce faire, on appelle l'objet Response
(dont l'instance se trouve dans la class App
). Pour renvoyer une vue, c'est la fonction requireView
qui est employée. Cette fonction va stocker au sein d'une variable le contenu de ce qui doit être affiché la vue, puis retourner cette variable. Elle prend plusieurs paramètres :
- le chemin depuis
app/view/
(le point est utilisé comme séparateur pour les dossiers) ; - un tableau associatif des variables à injecter dans la vue ;
- les helpers pouvant être requis par la vue.
Si cette vue est le rendu définitif de la page (celle déliverée à l'utilisateur), on peut utiliser la fonction view
de l'objet Response (qui va donc injecter cette vue dans le layout).
Le code est donc :
class PostsController extends BaseController {
public function index () {
// sélection, via un modèle, des post dans la variable $posts
App::$response->view('posts.index', ['posts'=>$posts]);
}
}