Une application MVC utilisant un service REST.

Le but est de construire une petite application qui permet de gérer ses contacts mails. La gestion des données sera assuré par un service REST. Toute la partie applicative écrite en javascript, avec jQuery, "respectera" le pattern MVC (model-view-controller). La liaison avec les données (l'api rest) sera assurée au moyen d'ajax.

Un aperçu de l'interface :

  • Une table avec tous les contacts de la base,
  • On peut (dé)sélectionner un contact en cliquant dessus qui (s'efface) s'affiche dans la zone de droite,
  • On peut le modifier, l'effacer, ou en ajouter un nouveau.


Le tp est découpé en 2 parties : Le service REST, et l'application.

Récupérez depuis le dépôt GIT les sources à compléter
https://dwarves.iut-fbleau.fr/git/monnerat/FI_WIM4.git 

Pourque qu'ils fonctionnent, Il vous faudra modifier :

  • Le fichier .htaccess avec la bonne url de réécriture.
  • Le fichier model.php, avec vos paramètres de connexion à la BD.
  • Le fichier model.js, avec votre url pour le service rest.

Le service REST utilisera votre base mysql sur dwarves. ( http://dwarves.iut-fbleau.fr/phpmyadmin).

Les données, et leur gestion

La première chose à faire est de créér une table dans votre base de données sur le serveur mysql de dwarves.
CREATE TABLE `contacts` (
  `id` INT(11) NOT NULL,
  `nom` VARCHAR(50) NOT NULL,
  `prenom` VARCHAR(50) NOT NULL,
  `email` VARCHAR(50) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `contacts`
  ADD PRIMARY KEY (`id`);
ALTER TABLE `contacts`
  MODIFY `id` INT(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=0;

Vous pouvez utiliser le site generatedata pour obtenir un jeu de données dans votre table.

La partie serveur, l'api REST, sera écrite avec PHP. Le but est d'obtenir l'api suivante :

URL Method Action
/vers/votre/api/rest/contacts GET Récupére la liste de tous les contacts
/vers/votre/api/rest/contacts/id GET Récupére le contact id
/vers/votre/api/rest/contacts/id PUT Modifie le contact id
/vers/votre/api/rest/contacts/id DELETE Efface contact id
/vers/votre/api/rest/contacts POST Ajoute un contact

Les données seront échangées au format JSON. Le fonctionnement de l'api

http GET localhost/~denis/json/contacts

HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Length: 337
Content-Type: application/json
Date: Tue, 15 Dec 2015 19:22:17 GMT
Keep-Alive: timeout=5, max=100
Server: Apache/2.4.17 (Unix) PHP/5.6.15
X-Powered-By: PHP/5.6.15
 
{"contacts" :
[
{
	"email": "lacus.Quisque.imperdiet@Nulla.org",
		"id": "59",
		"nom": "Warner",
		"prenom": "Scarlet"
},
{
	"email": "gravida@sit.com",
	"id": "81",
	"nom": "Sullivan",
	"prenom": "Kieran"
},
{
	"email": "magna.Cras.convallis@pharetraQuisque.edu",
	"id": "88",
	"nom": "Welch",
	"prenom": "Tyrone"
},
{
	"email": "valarcher@u-pec.fr",
	"id": "134",
	"nom": "Valarcher",
	"prenom": "Pierre"
}
]
}

echo '{"nom":"Monnerat","prenom":"Denis","email":"monnerat@u-pec.fr"}'|http POST localhost/~denis/json/contacts

HTTP/1.1 201 Created
Connection: Keep-Alive
Content-Length: 74
Content-Type: application/json
Date: Tue, 15 Dec 2015 19:39:14 GMT
Keep-Alive: timeout=5, max=100
Location: /contacts/135
Server: Apache/2.4.17 (Unix) PHP/5.6.15
X-Powered-By: PHP/5.6.15
 
{
	"email": "monnerat@u-pec.fr",
		"id": "135",
		"nom": "Monnerat",
		"prenom": "Denis"
}

echo '{"nom":"Monnerat","prenom":"Denis","email":"denis.monnerat@u-pec.fr"}'|http PUT localhost/~denis/json/contacts/135

HTTP/1.1 204 No Content
Connection: Keep-Alive
Content-Length: 0
Content-Type: text/html; charset=UTF-8
Date: Tue, 15 Dec 2015 19:42:56 GMT
Keep-Alive: timeout=5, max=100
Server: Apache/2.4.17 (Unix) PHP/5.6.15
X-Powered-By: PHP/5.6.15

http GET localhost/~denis/json/contacts/135

HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Length: 80
Content-Type: application/json
Date: Tue, 15 Dec 2015 19:44:54 GMT
Keep-Alive: timeout=5, max=100
Server: Apache/2.4.17 (Unix) PHP/5.6.15
X-Powered-By: PHP/5.6.15
 
{
	"email": "denis.monnerat@u-pec.fr",
		"id": "135",
		"nom": "Monnerat",
		"prenom": "Denis"
}

http DELETE localhost/~denis/json/contacts/135

HTTP/1.1 204 No Content
Connection: Keep-Alive
Content-Length: 0
Content-Type: text/html; charset=UTF-8
Date: Tue, 15 Dec 2015 19:45:59 GMT
Keep-Alive: timeout=5, max=100
Server: Apache/2.4.17 (Unix) PHP/5.6.15
X-Powered-By: PHP/5.6.15
Le modèle

Pour simplifier le code de l'api REST, nous allons écrire une couche intermédiaire, le modèle : Le but ? abstraire notre table et la base qui la stocke sous forme d'une classe php qui permettra de gérer les contacts simplement (Create/Read/Update/Delete). Il existe une classe PHP PDO que nous pourrions utiliser. Nous allons utiliser un framework (ORM) qui permet, à partir des informations de la table, d'avoir un modèle correspondant (mapping) avec les méthodes de gestion classique déjà prêtes.

On va utiliser l'orm paris, très simple d'utilisation.

<?php 
require_once "/vers/idiorm/idiorm.php";
require_once "/vers/paris/paris.php";
 
ORM::configure('mysql:host=localhost;dbname=contacts');
ORM::configure('username', '');
ORM::configure('password', '');
 
 
class Contacts extends Model {
}
?>

Cette simple définition crée la classe Contacts, que l'on peut manipuler très simplement :

<?php
$contact = Contacts::create();
$contact->nom = "Monnerat";
$contact->prenom = "Denis"
$contact->email = "monnerat@u-pec.fr";
$contact->save();
 
$contact=Contacts::find_one(12);
$contact->delete();
?>

Comme vous le voyez, on ne manipule pas de sql. On a directement accès aux objets (une table <-> une classe). On peut facilement créer au niveau des objets des relations, inutiles pour notre exemple).

Le service REST

Là encore, pour nous simplifier la vie, nous allons utiliser un petit framework php, slim (branche 2.x), pour :

  • la facilité de gestion du routage,
  • la facilité des entrées-sorties "http".

Vous pouvez télécharger les sources ici, et consulter la documentation.

<?php
 
// inlusion de slim et du modele
 
require 'Slim-2.6.0/Slim/Slim.php';
require './models/model.php';
 
\Slim\Slim::registerAutoloader();
$app = new \Slim\Slim();
 
// les routes de l'api REST 
 
$app->get('/contacts(/:id)','getContacts');
$app->post('/contacts','addContact');
$app->delete('/contacts/:id','deleteContact');
$app->put('/contacts/:id','updateContact');
 
$app->run();
?>

Il suffit ensuite d'écrire les fonctions exécutées pour chaque route. Exemple avec la suppression d'un contact

<?php
function deleteContact($id) {
	$response=\Slim\Slim::getInstance()->response();
	$response->headers->set('Content-Type', 'application/json');
	$contact = Contacts::find_one($id);
	if ($contact) {
		$contact->delete();
		$response->setStatus(200);
	}else{
		$reponse->setStatus(404);
	}
?>
Compléter les fonctions manquantes, et tester l'api.

L'application javascript

Le css est géré par concisecss. La partie fonctionnelle utilise jQuery, et l'échange de données se fait avec ajax, au format json (brute).

Au chargement, le formulaire est vide, et seul le bouton d'ajout est actif. On peut selectionner un contact en cliquant sur la ligne correspondante. Les informations sont automatiquement chagées dans le formulaire; seuls les boutons de suppression et de modification sont alors actifs. On peut delectionner un contact en recliquant sur la ligne correspondante.

Nous allons découper l'application suivant le modèle MVC :

  • Le modèle : model.js
  • La vue : view.js
  • Le controller :controller.js

La partie html se résumera au strict nécessaire :

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<!-- code html pout la table et le formulaire -->
		<script src="jquery-1.10.1.js"></script>
		<script src="./model.js"></script>
		<script src="./view.js"></script>
		<script src="./controller.js"></script>
		<script>
			var model=$.Contact;  
			var view=new $.View(); 
			var controller=new $.Controller(view,model);
		</script>
	</body>
</html>
Le modèle

Il fournit une "classe" contact qui permet de représenter un contact, avec les méthodes de mise à jour classique : lecture, ajout, modification, suppression.
Une "méthode statique" permettra en outre de récupérer tous les contacts.

Une fragment du modèle :

var serviceUrl = "/~denis/rest/contacts";
// a configurer vers votre propre url !
 
$.extend({
	Contact:function(id,nom,prenom,email){
		var self = this;
		this.data={
			"id":id,
			"nom":nom,
			"prenom":prenom,
			"email":email
		};
 
		this.add=function(){
			return		$.ajax({
				url : serviceUrl,
				method:"post",
				processData:false,
				contentType:"application/json",
				data:JSON.stringify(this.data),
			})
				.then(function(data){
					return data;
				})
		};
	}
});
 
$.Contact.getAll=function(){
	return 	$.getJSON(serviceUrl,null)
		.then(function(data){
			var items=[];
			$.each(data.contacts,function(i,el){
				items
					.push(
						new $.Contact(el.id,el.nom,el.prenom,el.email)
					);	
			})
			return items;
		});
}

Il faut le compléter de manière à disposer des "méthodes" de mise à jour. Notez que j'utilise, pour gérer l'asynchronisme d'ajax, la notion de promesse en javascript, avec jQuery.

La vue

C'est le module qui :

  • met à dispostion du controlleur les méthodes de présentation des données.
  • avertit le controlleur des changements (événements utilisateur).
Important La vue ne décide rien. Elle propose des méthodes qui sont utilisées par le contrôleur.

Un fragment de la vue

$.extend({
	View:function(){
		var form=$('#form'); // le formulaire
		var table=$('#ct'); // la table 
		var self=this; // une réference vers moi
 
		this.updateTable=function(contacts){
			var matable=$("#ct").children("tbody").empty();
			$.each(contacts,function(i,contact){
				matable.append("<tr data-id='"+contact.data.id+"'>"
						+"<td>"+contact.data.nom+"</td>"
						+"<td>"+contact.data.prenom+"</td>"
						+"<td>"+contact.data.email+"</td>"
						+"</tr>"
						);
			});
		};
 
		this.listeners={
			Add:function(){},
			Update:function(){},
			Delete:function(){},
		}; // listeners vide par défaut, que le
		   // controleur complétera pour etre notifié
		   // des événements dans la vue.
	}
 
});
Le Contrôleur

C'est lui qui décide des actions des modifications sur la vue en fonction des événements.

Un fragment du contrôleur

$.extend({
	Controller:function(view,contacts){
		var self=this;
		this._reloadContacts = function(){
			view.working();
			contacts
				.getAll()
				.done(function(items){
					view.updateTable(items);
					view.noworking();
				})
		}
		this._reloadContacts();
	}
});
Ecrivez toute l'application, en respectant la structure décrite précédemment.

retour à la page d'accueil

retour au sommet