Différences entre versions de « Js AJAX »

De The Linux Craftsman
Aller à la navigation Aller à la recherche
 
(79 versions intermédiaires par le même utilisateur non affichées)
Ligne 16 : Ligne 16 :
 
Nous allons utiliser l'objet XmlHttpRequest (XHR) avec une structure des informations en JSON. La partie serveur sera en Php et la partie cliente en Javascript / HTML / CSS.
 
Nous allons utiliser l'objet XmlHttpRequest (XHR) avec une structure des informations en JSON. La partie serveur sera en Php et la partie cliente en Javascript / HTML / CSS.
 
=Pré-requis=
 
=Pré-requis=
Pour développer en PHP, il ne faut pas oublier d'installer les paquetages suivants :
+
Pour développer en PHP, il ne faut pas oublier d'installer [[HTTPD | Apache HTTPD]], [[Php_devel#Installation_des_librairies | PHP ]] et pour ce projet nous allons utiliser également [[Php_memcached#Memcache | Memcached]].
  
<source lang="bash" style="border:1px solid black;font-size:120%">
 
# yum -y install php httpd
 
</source>
 
 
Paramétrez votre serveur [[HTTPD | HTTPD]].
 
 
=Partie serveur=
 
=Partie serveur=
 
==Structure du projet==
 
==Structure du projet==
Ligne 32 : Ligne 27 :
 
* prenom;
 
* prenom;
 
* age;
 
* age;
Cet objet implémentera une fonction ''toJson'' qui permettra de récupérer la chaine de caractères représentant l'objet.
+
Cet objet implémentera une fonction ''toJson'' qui permettra de récupérer la chaine de caractères représentant l'objet ainsi que la méthode inverse ''fromJson''.
  
Cela nous donne le contenu suivant pour le fichier ''user.php'':
+
Cela nous donne le contenu suivant pour le fichier ''User.class.php'':
 
<source lang="php" style="border:1px solid black;font-size:120%">
 
<source lang="php" style="border:1px solid black;font-size:120%">
 
<?php
 
<?php
 
+
class User {
class User{
+
   public $id, $nom, $prenom, $age;
+
 
   public $nom, $prenom, $age;
+
   public function User($id, $nom, $prenom, $age) {
+
    $this->id = $id;
   public function User($nom, $prenom, $age){
 
 
     $this->nom = $nom;
 
     $this->nom = $nom;
 
     $this->prenom = $prenom;
 
     $this->prenom = $prenom;
 
     $this->age = $age;
 
     $this->age = $age;
 
   }
 
   }
+
  /**
   public function toJson(){
+
  * Retourne la chaîne Json représentant l'objet
     return json_encode($this);
+
  */
 +
   public function toJson() {
 +
     return json_encode ( $this );
 
   }
 
   }
 +
  /**
 +
  * Retourne un objet User à partir de la chaîne Json ou false
 +
  * @param User | boolean $json
 +
  */
 +
  public static function fromJson($json) {
 +
    $obj = json_decode ( $json );
 +
    if (isset ( $obj->id ) && isset ( $obj->nom ) && isset ( $obj->prenom ) && isset ( $obj->age )) {
 +
      return new User ( $obj->id, $obj->nom, $obj->prenom, $obj->age );
 +
    }
 +
    return false;
 +
  }
 +
}
 +
?>
 +
</source>
 +
 +
==Database Access Object==
 +
Pour stoker nos objets nous allons utiliser ''Memcache'' et pour respecter la programmation [https://fr.wikipedia.org/wiki/Mod%C3%A8le-vue-contr%C3%B4leur MVC] nous allons utiliser le [https://fr.wikipedia.org/wiki/Patron_de_conception patron de conception] [https://fr.wikipedia.org/wiki/Objet_d'acc%C3%A8s_aux_donn%C3%A9es DAO].
 +
 +
Dans ce DAO, nous allons implémenter les méthodes ''setUserTab'', pour sauvegarder notre tableau d'utilisateur, et toutes les méthodes pour faire le [https://fr.wikipedia.org/wiki/CRUD CRUD].
  
 +
Ajoutez les lignes suivantes, dans le fichier ''UserDao.class.php'' :
 +
<source lang="php" style="border:1px solid black;font-size:120%">
 +
<?php
 +
class UserDao {
 +
  const USER_TAB = "users";
 +
  const USER_ID = "user_id";
 +
  const MEMCACHE_ADDRESS = "127.0.0.1";
 +
  const MEMCACHE_PORT = 11211;
 +
 
 +
  /**
 +
  * Retourne l'objet utilisateur en fonction de son identifiant
 +
  *
 +
  * @param int $id         
 +
  */
 +
  public function getUserById($id) {
 +
    $users = UserDao::getUsers ();
 +
    foreach ( $users as $user ) {
 +
      if ($user->id == $id) {
 +
        return $user;
 +
      }
 +
    }
 +
    return false;
 +
  }
 +
  /**
 +
  * Récupére le tableau d'utilisateur depuis memchache
 +
  */
 +
  public function getUsers() {
 +
    $users = UserDao::getMemcacheServer ()->get ( UserDao::USER_TAB );
 +
    if (! isset ( $users ) || ! $users) {
 +
      $users = array ();
 +
    }
 +
    return array_values ( $users );
 +
  }
 +
  /**
 +
  * Retourne l'id de l'utilisateur créer
 +
  *
 +
  * @param User $user         
 +
  */
 +
  public function createUser(User $user) {
 +
    // On récupére l'identifiant
 +
    $id = UserDao::getCurrentId ();
 +
    // On le positionne
 +
    $user->id = $id;
 +
    // On récupére le tableau d'utilisateur
 +
    $users = UserDao::getUsers ();
 +
    // On ajoute l'utilisateur
 +
    $users [$id] = $user;
 +
    // On sauvegarde le tableau
 +
    UserDao::setUserTab ( $users );
 +
    // On retourne l'identifiant
 +
    return $id;
 +
  }
 +
  /**
 +
  * Met à jour l'objet utilisateur
 +
  *
 +
  * @param User $user         
 +
  */
 +
  public function updateUser(User $user) {
 +
    $users = UserDao::getUsers ();
 +
    // On vérifie que l'utilisateur existe
 +
    foreach ( $users as $index => $tmp ) {
 +
      if ($tmp->id == $user->id) {
 +
        // On le remplace
 +
        $users [$index] = $user;
 +
        // On sauvegarde le tableau
 +
        UserDao::setUserTab ( $users );
 +
        return true;
 +
      }
 +
    }
 +
    return false;
 +
  }
 +
  /**
 +
  * Efface l'objet utilisateur
 +
  *
 +
  * @param int $id         
 +
  */
 +
  public function deleteUser($id) {
 +
    $users = UserDao::getUsers ();
 +
    // On vérifie que l'utilisateur existe
 +
    foreach ( $users as $index => $user ) {
 +
      if ($user->id == $id) {
 +
        // On l'efface du tableau
 +
        unset ( $users [$index] );
 +
        // On sauvegarde le tableau
 +
        UserDao::setUserTab ( $users );
 +
        return true;
 +
      }
 +
    }
 +
    return false;
 +
  }
 +
  /**
 +
  * Stocke le tableau d'utilisateur dans memchache
 +
  */
 +
  private function setUserTab($users) {
 +
    UserDao::getMemcacheServer ()->set ( UserDao::USER_TAB, $users );
 +
  }
 +
  /**
 +
  * Retourne l'identifiant du prochain utilisateur
 +
  */
 +
  private function getCurrentId() {
 +
    $user_id = UserDao::getMemcacheServer ()->get ( UserDao::USER_ID );
 +
    if (! isset ( $user_id ) || ! $user_id) {
 +
      $user_id = 0;
 +
      UserDao::getMemcacheServer ()->set ( UserDao::USER_ID, $user_id );
 +
    }
 +
    UserDao::getMemcacheServer ()->increment ( UserDao::USER_ID );
 +
    return $user_id;
 +
  }
 +
  /**
 +
  * Retourne le serveur memchache
 +
  */
 +
  private static function getMemcacheServer() {
 +
    $memcache = new Memcache ();
 +
    $memcache->addserver ( UserDao::MEMCACHE_ADDRESS, UserDao::MEMCACHE_PORT );
 +
    return $memcache;
 +
  }
 
}
 
}
 +
?>
 
</source>
 
</source>
 +
 
==Web Service RESTful en PHP==
 
==Web Service RESTful en PHP==
Nous allons faire une simple page ''index.php'' qui ''répond'' au GET ainsi qu'au POST. ''GET'' sert à récupérer des informations et ''POST'' sert à créer / insérer des informations. N'hésitez pas à relire le [[:Media:webservices.pdf| cours]] !
+
Nous allons faire une page ''UserWS.class.php'' qui ''répond'' au ''GET'', au ''POST'', au ''PUT'' ainsi qu'au ''DELETE''. ''GET'' sert à récupérer des informations, ''POST'' sert à créer / insérer des informations, ''PUT'' à les modifier et ''DELETE'' à les supprimer. N'hésitez pas à relire le [[:Media:webservices.pdf| cours]] !
  
 +
<source lang="php" style="border:1px solid black;font-size:120%">
 +
<?php
 +
class UserWS {
 +
  const VAR_ID = "id";
 +
  /**
 +
  * Execute le web service
 +
  *
 +
  * @param string $methode
 +
  *          La méthode demandée (GET | POST | PUT | DELETE)
 +
  */
 +
  public static function execute($method) {
 +
    // On test si on fait une requête en POST
 +
    if ($method == "POST") {
 +
      // On fait un POST sur index.php
 +
      $id = UserWS::doPost ();
 +
      if ($id === FALSE) {
 +
        // HTTP 400 : Bad Request
 +
        http_response_code ( 400 );
 +
      } else {
 +
        // HTTP 201 : Created
 +
        http_response_code ( 201 );
 +
        // On retourne l'identifiant du nouvel utilisateur
 +
        echo json_encode ( $id );
 +
      }
 +
    } else if ($method == "GET") {
 +
      // On fait un GET sur index.php
 +
      if (! UserWS::doGet ()) {
 +
        // HTTP 400 : Bad Request
 +
        http_response_code ( 404 );
 +
      }
 +
    } else if ($method == "PUT") {
 +
      // On fait un PUT sur index.php
 +
      $result = UserWS::doPut ();
 +
      if ($result === -1) {
 +
        // HTTP 400 : Bad Request
 +
        http_response_code ( 400 );
 +
      } else if ($result === FALSE) {
 +
        // HTTP 404 : Not found
 +
        http_response_code ( 404 );
 +
      }else if ($result === TRUE) {
 +
        // HTTP 202 : Accepted
 +
        http_response_code ( 202 );
 +
      }
 +
    } else if ($method == "DELETE") {
 +
      // On fait un DELETE sur index.php
 +
      $result = UserWS::doDelete ();
 +
      if ($result === -1) {
 +
        // HTTP 400 : Bad Request
 +
        http_response_code ( 400 );
 +
      } else if ($result === FALSE) {
 +
        // HTTP 404 : Not found
 +
        http_response_code ( 404 );
 +
      }else if ($result === TRUE) {
 +
        // HTTP 202 : Accepted
 +
        http_response_code ( 202 );
 +
      }
 +
    }else {
 +
      // HTTP 405 : Method Not Allowed
 +
      http_response_code ( 405 );
 +
    }
 +
  }
 +
  /**
 +
  * Fonction appelée lors d'un GET
 +
  */
 +
  private static function doGet() {
 +
    if (! isset ( $_GET [UserWS::VAR_ID] )) {
 +
      // Pas d'identifiant on retourne le tableau
 +
      echo json_encode ( UserDao::getUsers() );
 +
      return true;
 +
    } else {
 +
      $user = UserDao::getUserById ( $_GET [UserWS::VAR_ID] );
 +
      if ($user != false) {
 +
        // L'utilisateur demandé existe
 +
        echo json_encode ( $user );
 +
        return true;
 +
      }
 +
    }
 +
    // L'utilisateur demandé n'existe pas
 +
    return false;
 +
  }
 +
  /**
 +
  * Fonction appelée lors d'un POST
 +
  * Elle retourne l'identifiant de
 +
  */
 +
  private static function doPost() {
 +
    /*
 +
    * On récupére le contenu de la requête HTTP
 +
    * qui contient la chaîne Json représentant l'utilisateur
 +
    */
 +
    $json = file_get_contents ( 'php://input' );
 +
    // On transforme la chaîne Json en objet
 +
    $user = User::fromJson ( $json );
 +
    if ($user !== false) {
 +
      // On retourne l'identifiant du nouvel utilisateur
 +
      return UserDao::createUser ( $user );
 +
    }
 +
    return false;
 +
  }
 +
  /**
 +
  * Fonction appelée lors d'un PUT
 +
  */
 +
  private static function doPut() {
 +
    /*
 +
    * On récupére le contenu de la requête HTTP
 +
    * qui contient la chaîne Json représentant l'utilisateur
 +
    */
 +
    $json = file_get_contents ( 'php://input' );
 +
    // On transforme la chaîne Json en objet
 +
    $user = User::fromJson ( $json );
 +
    // Si la chaîne ne contient pas un objet User
 +
    if (! $user) {
 +
      return -1;
 +
    }
 +
    // On transforme la chaîne Json en objet
 +
    return UserDao::updateUser ( $user );
 +
  }
 +
  /**
 +
  * Fonction appelée lors d'un DELETE
 +
  */
 +
  private static function doDelete() {
 +
    // On récupére l'identifiant utilisateur
 +
    $id = json_decode ( file_get_contents ( 'php://input' ) );
 +
    // On test la présence de l'identifiant
 +
    if (! isset ( $id ) && !is_numeric($id)) {
 +
      return -1;
 +
    }
 +
    // On efface l'utilisateur
 +
    return UserDao::deleteUser ( $id );
 +
  }
 +
}
 +
?>
 +
</source>
 +
On remarque au passage que la méthode ''http_response_code()'' permet de paramétrer le code HTTP dans l'entête. Cela va permettre de donner des informations au client sur le déroulement de la requête. Plus d'informations sur les codes HTTP [https://fr.wikipedia.org/wiki/Liste_des_codes_HTTP ici]
  
 
+
==Appel du Web Service ==
Il va falloir conserver les objets ''User'' créer au fil des appels. Pour cela nous pouvons utiliser un serveur [[Php_memcached |Memcache]] si nous voulons partager les objets entre les différents utilisateurs de notre Web Service. Pour faire simple, nous utiliserons uniquement les session qui vont nous permettre de faire persister nos objets ''User'' sur une même session.
+
Pour appeler notre Web Service nous devons encore ajouter quelques lignes.
 
+
Tout d'abord, il faut autoriser l'appel depuis un autre serveur que celui ou sera exécuter le code PHP.
 
Ajouter au début de ''index.php'' les lignes suivantes:
 
Ajouter au début de ''index.php'' les lignes suivantes:
 
<source lang="php" style="border:1px solid black;font-size:120%">
 
<source lang="php" style="border:1px solid black;font-size:120%">
Ligne 66 : Ligne 332 :
 
// Utilisé pour autoriser les appel Web Service en AJAX depuis toutes les adresses
 
// Utilisé pour autoriser les appel Web Service en AJAX depuis toutes les adresses
 
header ( "Access-Control-Allow-Origin: *" );
 
header ( "Access-Control-Allow-Origin: *" );
 +
// Utilisé pour autoriser les méthodes GET, POST, PUT et DELETE
 +
header ( "Access-Control-Allow-Methods: GET, POST, PUT, DELETE" );
 +
// On autorise les données dans le header
 +
header ( "Access-Control-Allow-Headers: Content-Type" );
 +
// On positione l'encodage en UTF-8 sinon json_encode ne fonctionnera pas !
 +
header ( 'Content-Type: application/json; charset=utf-8' );
 +
// On inclut l'objet User ainsi que les classes nécessaires
 
include_once 'class/User.class.php';
 
include_once 'class/User.class.php';
/**
+
include_once 'class/UserDao.class.php';
* Fonction appelée lors d'un GET
+
include_once 'class/UserWS.class.php';
*/
 
function get($users) {
 
  // Si le tableau est vide ou on ne demande pas d'utilisateur en particulier
 
  if(empty($users) && !isset ( $_GET ["id"] )){
 
    echo json_encode ( $users );
 
    return true;
 
  }else if (isset ( $users [$_GET ["id"]] )) {
 
    // L'utilisateur demandé existe
 
    $user = $users [$_GET ["id"]];
 
    echo json_encode ( $user );
 
    return true;
 
  }
 
  // L'utilisateur demandé n'existe pas
 
  echo json_encode(false);
 
  return false;
 
}
 
/**
 
* Fonction appelée lors d'un POST
 
*/
 
function post($users) {
 
  /*
 
  * On récupére le contenu de la requête HTTP
 
  * qui contient la chaîne Json représentant l'utilisateur
 
  */
 
  $json_user = file_get_contents ( 'php://input' );
 
  // On transforme la chaîne Json en objet
 
  $user = json_decode ( $json_user );
 
  // On teste si l'objet est bien de la classe User
 
  if ($user instanceof User) {
 
    // On ajoute l'utilisateur dans le tableau de la session
 
    $_SESSION ["users"] [] = $user;
 
    // On retourne vrai si l'objet est un utilisateur
 
    echo json_encode ( true );
 
  } else {
 
    // On retourne faux si l'objet n'est pas un utilisateur
 
    echo json_encode ( false );
 
  }
 
  // Dans tous les cas on retourne le tableau d'utilisateurs
 
  return $users;
 
}
 
 
/**
 
/**
 
  * Fonction principale
 
  * Fonction principale
 
  */
 
  */
 
function main() {
 
function main() {
  session_start ();
+
   // On récupére la méthode
   // Renvoie un tableau vide si la variable n'existe pas
+
   $method = $_SERVER ['REQUEST_METHOD'];
   $users = isset ( $_SESSION ["users"] ) ? $_SESSION ["users"] : array ();
+
   // Si la méthode est de type option on ne continue pas l'exécution du script
   if (isset ( $_GET )) {
+
  // Cela arrive avec certains navigateurs
    // On fait un GET sur index.php
+
   if ($method == "OPTIONS") {
    get();
+
     header ( 'Access-Control-Allow-Origin: *' );
   } else if (isset ( $_POST )) {
+
     exit ();
     // On fait un POST sur index.php
 
     post();
 
 
   }
 
   }
 +
  // On appel notre Web Service
 +
  UserWS::execute ( $method );
 
}
 
}
 +
main ();
 
?>
 
?>
main();
 
 
</source>
 
</source>
 +
On est fin prêt à passer à la partie cliente !
  
 
=Partie cliente=
 
=Partie cliente=
Ligne 134 : Ligne 368 :
  
 
==Objet ''user''==
 
==Objet ''user''==
Nous allons créer un objet User qui contiendra les attributs suivants:
+
Nous allons créer un objet ''User'' qui contiendra les attributs suivants:
 +
*id;
 
*nom;
 
*nom;
 
*prenom;
 
*prenom;
*age;
+
*age.
  
 
Cet objet implémentera une fonction toJson qui permettra de récupérer la chaine de caractères représentant l'objet.
 
Cet objet implémentera une fonction toJson qui permettra de récupérer la chaine de caractères représentant l'objet.
Cela nous donne le contenu suivant pour le fichier user.class.js:
+
Cela nous donne le contenu suivant pour le fichier ''user.class.js'' :
<source lang="javascript">
+
<source lang="javascript" style="border:1px solid black;font-size:120%">
 +
function User(id, nom, prenom, age) {
 +
 
 +
  this.id = id;
 +
  this.nom = nom;
 +
  this.prenom = prenom;
 +
  this.age = age;
 +
 
 +
  this.toJson = function(){
 +
    return JSON.stringify(this);
 +
  }
 +
   
 +
}
 +
// Simulation d'une méthode statique en la définissant après l'objet
 +
User.fromJson = function (json){
 +
  try{
 +
    var obj = JSON.parse(json);
 +
    if(!obj){
 +
      return false;
 +
    }
 +
    return new User(obj.id, obj.nom, obj.prenom, obj.age);
 +
  }catch (e) {
 +
    return false;
 +
  }
 +
}
 +
User.fromObject = function (obj){
 +
  try{
 +
    if(!obj){
 +
      return false;
 +
    }
 +
    return new User(obj.id, obj.nom, obj.prenom, obj.age);
 +
  }catch (e) {
 +
    return false;
 +
  }
 +
}
 +
</source>
 +
 
 +
==XmlHttpRequest==
 +
Dans cette partie, il s'agit de créer la fonction qui retournera l'objet ''XmlHttpRequest'' en fonction du navigateur.
 +
Ajoutez les lignes suivantes dans le fichier ''ajax.js'' :
 +
<source lang="javascript" style="border:1px solid black;font-size:120%">
 +
function getXhr() {
 +
  var xhr;
 +
  if (window.XMLHttpRequest) {
 +
    // code for IE7+, Firefox, Chrome, Opera, Safari
 +
    xhr = new XMLHttpRequest();
 +
  } else {
 +
    // code for IE6, IE5
 +
    xhr = new ActiveXObject("Microsoft.XMLHTTP");
 +
  }
 +
  return xhr;
 +
}
 +
</source>
  
 +
== Appel des Web Services ==
 +
Il faut maintenant appeler les Web Service grâce à l'objet ''XmlHttpRequest'', ce que nous allons faire dans le fichier ''user-ws.js'' :
 +
<source lang="javascript" style="border:1px solid black;font-size:120%">
 +
function getUsers() {
 +
  // Création de l'objet XHR
 +
  var xhr = getXhr();
 +
  // Ouverture de l'URL en GET
 +
  xhr.open("GET", "http://127.0.0.1/index.php", false);
 +
  // Envoie de la requête
 +
  xhr.send();
 +
  // Récupération de la réponse
 +
  var json = xhr.responseText;
 +
  // Parse du tableau d'utilisateur
 +
  var users = JSON.parse(json);
 +
  // Parse de chaque utilisateur
 +
  for(var i=0; i < users.length; i++){
 +
    users[i] = User.fromObject(users[i]);
 +
  }
 +
  return users;
 +
}
 +
function getUserById(id) {
 +
  // Création de l'objet XHR
 +
  var xhr = getXhr();
 +
  // Ouverture de l'URL en GET avec l'identifiant
 +
  xhr.open("GET", "http://127.0.0.1/index.php?id="+id, false);
 +
  // Envoie de la requête
 +
  xhr.send();
 +
  // Récupération de la réponse
 +
  var json = xhr.responseText;
 +
  // Parse de l'utilisateur
 +
  var user = User.fromJson(json);
 +
  return user;
 +
}
 +
function addUser(user){
 +
  // Création de l'objet XHR
 +
  var xhr = getXhr();
 +
  // Ouverture de l'URL en POST
 +
  xhr.open("POST", "http://127.0.0.1/index.php", false);
 +
  // Envoie de la requête avec l'objet User dans le body
 +
  xhr.send(user.toJson());
 +
  // Récupération de la réponse
 +
  var result = xhr.responseText;
 +
  return result;
 +
}
 +
function updateUser(user){
 +
  // Création de l'objet XHR
 +
  var xhr = getXhr();
 +
  // Ouverture de l'URL en POST
 +
  xhr.open("PUT", "http://127.0.0.1/index.php", false);
 +
  xhr.setRequestHeader('Content-Type', 'application/json');
 +
  // Envoie de la requête avec l'objet User dans le body
 +
  xhr.send(user.toJson());
 +
  // Récupération de la réponse
 +
  return xhr.status == 202;
 +
}
 +
function deleteUser(id){
 +
  // Création de l'objet XHR
 +
  var xhr = getXhr();
 +
  // Ouverture de l'URL en DELETE
 +
  xhr.open("DELETE", "http://127.0.0.1/index.php", false);
 +
  // Envoie de la requête avec l'objet User dans le body
 +
  xhr.send(JSON.stringify(id));
 +
  // Récupération de la réponse
 +
  return xhr.status == 202;
 +
}
 
</source>
 
</source>
  
 
==index.html==
 
==index.html==
==XmlHttpRequest==
+
Il nous faut maintenant un fichier d'entrée pour appeler ce code ''Javascript'', ce que nous allons faire avec ''index.html'' :
==
+
<source lang="html5" style="border:1px solid black;font-size:120%">
 +
<!DOCTYPE html>
 +
<html>
 +
<head>
 +
<meta charset="UTF-8">
 +
<title>GUI Utilisateurs</title>
 +
<script type="text/javascript" src="js/ajax.js" ></script>
 +
<script type="text/javascript" src="js/user.class.js" ></script>
 +
<script type="text/javascript" src="js/user-ws.js" ></script>
 +
<script type="text/javascript" src="js/buttons.js" ></script>
 +
</head>
 +
<body>
 +
  <div>
 +
    Nom:<br/>
 +
    <input type="text" id="nom" ><br/>
 +
    Prénom:
 +
    <br/>
 +
    <input type="text" id="prenom" ><br/>
 +
    Age:
 +
    <br/>
 +
    <input type="number" id="age" min="1" >
 +
    <br/><br/>
 +
    <button onClick="doCreateUser();">Créer un utilisateur</button>
 +
    <button onClick="doUpdateUser();">Modifier un utilisateur</button>
 +
    <hr>
 +
    <button onClick="doGetUser();">Afficher un utilisateur</button>
 +
    <input type="text" id="id" placeholder="user id" >
 +
    <button onClick="doDeleteUser();">Effacer un utilisateur</button>
 +
    <br/><br/>
 +
    <button onClick="doGetUsers();">Afficher tous les utilisateurs</button>
 +
  </div>
 +
  <div id="affiche">
 +
  </div>
 +
</body>
 +
</html>
 +
</source>
 +
 
 +
==Ajout des événements==
 +
Comme vous avez pu le remarquer, notre fichier ''index.html'' inclue le fichier ''buttons.js''. C'est dans ce fichier que l'on va mettre les fonctions qui vont appeler les Web Serivces en fonction de l'appuie sur les boutons :
 +
<source lang="javascript" style="border:1px solid black;font-size:120%">
 +
function doCreateUser(){
 +
  var nom = document.getElementById("nom").value;
 +
  var prenom = document.getElementById("prenom").value;
 +
  var age = document.getElementById("age").value;
 +
  if(nom == "" || prenom == "" || age == ""){
 +
    alert("Missing field !");
 +
    return false;
 +
  }
 +
  var user = new User(0, nom, prenom, age);
 +
  if(!addUser(user)){
 +
    alert("Something went wrong !");
 +
    return false;
 +
  }
 +
  alert("Creation successful !");
 +
  eraseFieldValues();
 +
  return true;
 +
}
 +
function doGetUser(){
 +
  var id = document.getElementById("id").value;
 +
  if(id == ""){
 +
    alert("Missing field !");
 +
    return false;
 +
  }
 +
  var user = getUserById(id);
 +
  if(!user){
 +
    alert("User does not exists !");
 +
    return false;
 +
  }
 +
  document.getElementById("nom").value = user.nom;
 +
  document.getElementById("prenom").value = user.prenom;
 +
  document.getElementById("age").value = user.age;
 +
  var div = document.getElementById("affiche");
 +
  div.innerHTML = user.toJson();
 +
  return true;
 +
}
 +
function doGetUsers(){
 +
  var users = getUsers();
 +
  if(!users){
 +
    alert("Something went wrong !");
 +
    return false;
 +
  }
 +
  var div = document.getElementById("affiche");
 +
  if(users.length == 0){
 +
    div.innerHTML = "Pas d'utilisateur !";
 +
  }else{
 +
    div.innerHTML = "";
 +
    for(var i=0; i < users.length; i++){
 +
      var user = users[i];
 +
      div.innerHTML += user.toJson()+"<br/>";
 +
    }
 +
  }
 +
  return true;
 +
}
 +
function doUpdateUser(){
 +
  var nom = document.getElementById("nom").value;
 +
  var prenom = document.getElementById("prenom").value;
 +
  var age = document.getElementById("age").value;
 +
  var id = document.getElementById("id").value;
 +
  if(nom == "" || prenom == "" || age == "" || id == ""){
 +
    alert("Missing field !");
 +
    return false;
 +
  }
 +
  var user = new User(parseInt(id), nom, prenom, age);
 +
  if(!updateUser(user)){
 +
    alert("User not found !");
 +
    return false;
 +
  }
 +
  alert("User modified !");
 +
  return true;
 +
}
 +
function doDeleteUser(){
 +
  var id = document.getElementById("id").value;
 +
  if(id == ""){
 +
    alert("Missing field !");
 +
    return false;
 +
  }
 +
  if(!deleteUser(id)){
 +
    alert("User does not exists !");
 +
    return false;
 +
  }
 +
  alert("User deleted !");
 +
  return true;
 +
}
 +
function eraseFieldValues(){
 +
  document.getElementById("nom").value = "";
 +
  document.getElementById("prenom").value = "";
 +
  document.getElementById("age").value = "";
 +
  return true;
 +
}
 +
</source>
 +
 
 +
= Gestion des URLs=
 +
== Introduction ==
 +
Pour faire un Web Service complètement ''Restful'', il faudrait que les URLs soient ''explicitent''. Par exemple, notre Web Service utilisateur devrait être accessible depuis l'URL ''http://127.0.0.1/user'' et non directement depuis ''http://127.0.0.1'' comme c'est actuellement le cas.
 +
 
 +
Pour réaliser cette fonctionnalité nous allons utiliser un module d'''Apache'' qui s'appelle ''mod_rewrite''. Comme sont nom l'indique, ce module permet la réécriture des URL pour qu'une partie soit passée en paramètre de la requête ''HTTP''.
 +
 
 +
==''.htaccess''==
 +
A la racine de projet serveur vous allez ajouter un fichier ''.htaccess'' qui contiendra les lignes suivantes :
 +
<source lang="bash">
 +
# On active le module de réécriture
 +
RewriteEngine on
 +
# On test si un fichier existe et on l'affiche
 +
RewriteCond %{REQUEST_FILENAME} !-f
 +
# On test si un répertoire existe et on l'affiche
 +
RewriteCond %{REQUEST_FILENAME} !-d
 +
# Si aucun fichier ou répertoire n'existe on applique la régle de réécriture
 +
# Dans ce cas on prend toute l'URL est on la met dans $_GET['ws']
 +
RewriteRule ^(.*)$ index.php?ws=$1 [QSA,L]
 +
</source>
 +
 
 +
N'oubliez pas [[HTTPD#Le_fichier_.htaccess | d'activer la lecture des fichiers ''.htaccess'']] !
 +
 
 +
==''Digestion'' de l'URL et appel du Web Service==
 +
Il va falloir prendre en compte la nouvelle URL que nous allons utiliser:
 +
* pour inclure les fichiers correspondant au Web Service;
 +
* pour passer le paramètre id.
 +
 
 +
Ajoutez un fichier ''WebService.class.php'' dans le répertoire ''class'' qui contiendra les lignes suivantes:
 +
<source lang="php" style="border:1px solid black;font-size:120%">
 +
<?php
 +
class WebService {
 +
  const URL_VAR = "ws";
 +
  const PHP_CLASS_DIR = "class";
 +
  const PHP_WS_DIR = self::PHP_CLASS_DIR . "/ws";
 +
  const PHP_CLASS_DAO = "Dao";
 +
  const PHP_CLASS_WS = "WS";
 +
  const PHP_CLASS_EXT = "class.php";
 +
  const WS_EXEC_FCT = "execute";
 +
 
 +
  /**
 +
  * Execute le Web Service
 +
  * @param string $method
 +
  * La méthode HTTP
 +
  */
 +
  public static function execute($method){
 +
    // On récupére le nom du Web Service
 +
    $webService = WebService::getWebService();
 +
    // Si le Web Service existe
 +
    if(WebService::includeWebService($webService)){
 +
      // On prépare le nom de la fonction à appeler
 +
      $toCall = ucfirst ( $webService ).self::PHP_CLASS_WS."::".self::WS_EXEC_FCT;
 +
      // On l'appelle
 +
      call_user_func($toCall, $method);
 +
      return true;
 +
    }
 +
    return false;
 +
  }
 +
  /**
 +
  *
 +
  * Retourne l'identifiant contenu dans l'URL ou false
 +
  *
 +
  * @return string|boolean
 +
  */
 +
  public static function getId() {
 +
    // On récupére l'URL dans la variable ws
 +
    $url = $_GET [WebService::URL_VAR];
 +
    // On vérifie si l'URL contient un '/' avec l'id
 +
    $pos = strpos ( $url, '/' );
 +
    if ($pos > 0 && $pos != strlen ( $url ) - 1) {
 +
      // S'il y a un ID (eg. user/1) on le récupére
 +
      $id = explode ( '/', $url ) [1];
 +
      // On retourne ce qu'il y a après le '/' si c'est de type numérique
 +
      if (is_numeric ( $id )) {
 +
        return $id;
 +
      }
 +
    }
 +
    return false;
 +
  }
 +
  /**
 +
  * Inclue les fichiers correspondant au Web Service demandé
 +
  *
 +
  * @param string $webService         
 +
  * @return boolean
 +
  */
 +
  private static function includeWebService($webService) {
 +
    // On construit le chemin
 +
    $dir = self::PHP_WS_DIR . '/' . $webService;
 +
    // Si le chemin existe
 +
    if (is_dir ( $dir )) {
 +
      // On construit un tableau avec les trois fichiers
 +
      $files = array ();
 +
      // le fichier class/user/User.class.php
 +
      $files [] = $dir . '/' . ucfirst ( $webService ) . '.' . self::PHP_CLASS_EXT;
 +
      // le fichier class/user/UserDao.class.php
 +
      $files [] = $dir . '/' . ucfirst ( $webService ) . self::PHP_CLASS_DAO . '.' . self::PHP_CLASS_EXT;
 +
      // le fichier class/user/UserWS.class.php
 +
      $files [] = $dir . '/' . ucfirst ( $webService ) . self::PHP_CLASS_WS . '.' . self::PHP_CLASS_EXT;
 +
      foreach ( $files as $file ) {
 +
        // Si le fichier existe, on l'inclue
 +
        if (is_file ( $file )) {
 +
          include_once $file;
 +
        } else {
 +
          // Si le fichier n'existe pas, on retourne false;
 +
          return false;
 +
        }
 +
      }
 +
    } else {
 +
      return false;
 +
    }
 +
    return true;
 +
  }
 +
 
 +
  /**
 +
  * Retourne le nom du Web Service demandé ou false
 +
  *
 +
  * @return string|boolean
 +
  */
 +
  private static function getWebService() {
 +
    // On récupére l'URL dans la variable ws
 +
    $url = $_GET [self::URL_VAR];
 +
    // On vérifie si l'URL contient un '/' avec l'id
 +
    if (strpos ( $url, '/' ) > 0) {
 +
      // S'il y a un ID (eg. user/1)
 +
      // On retourne ce qu'il y a avant le '/'
 +
      return explode ( '/', $url ) [0];
 +
    }
 +
    return strlen ( $url ) ? $url : false;
 +
  }
 +
}
 +
?>
 +
</source>
 +
 
 +
== Modification de ''User.class.php''==
 +
Pour que le Web Service continue à fonctionner, il faut modifier la méthode de récupération de la variable ''id''.
 +
 
 +
* Dans la classe :
 +
<source lang="php" style="border:1px solid black;font-size:120%">
 +
// La ligne suivante n'est plus utile !
 +
const VAR_ID = "id";
 +
</source>
 +
 
 +
* Dans ''doGet'' :
 +
 
 +
<source lang="php" style="border:1px solid black;font-size:120%">
 +
if (! isset ( $_GET [UserWS::VAR_ID] )) {
 +
// devient
 +
if (WebService::getId() === false) {
 +
// Et
 +
$_GET [UserWS::VAR_ID]
 +
// devient
 +
WebService::getId()
 +
</source>
 +
 
 +
* Dans ''doDelete'' :
 +
 
 +
<source lang="php" style="border:1px solid black;font-size:120%">
 +
$id = json_decode ( file_get_contents ( 'php://input' ) );
 +
// devient
 +
$id = WebService::getId();
 +
</source>
 +
 
 +
== Modification de ''index.php''==
 +
Nous n'avons plus qu'a appeler notre classe dans l'index :
 +
<source lang="php" style="border:1px solid black;font-size:120%">
 +
<?php
 +
// Utilisé pour autoriser les appel Web Service en AJAX depuis toutes les adresses
 +
header ( "Access-Control-Allow-Origin: *" );
 +
// Utilisé pour autoriser les méthodes GET, POST, PUT et DELETE
 +
header ( "Access-Control-Allow-Methods: GET, POST, PUT, DELETE" );
 +
// On autorise les données dans le header
 +
header ( "Access-Control-Allow-Headers: Content-Type" );
 +
// On positione l'encodage en UTF-8 sinon json_encode ne fonctionnera pas !
 +
header ( 'Content-Type: application/json; charset=utf-8' );
 +
// On inclut l'objet WebService
 +
include_once 'class/WebService.class.php';
 +
/**
 +
* Fonction principale
 +
*/
 +
function main() {
 +
  // On récupére la méthode
 +
  $method = $_SERVER ['REQUEST_METHOD'];
 +
  // Si la méthode est de type option on ne continue pas l'exécution du script
 +
  // Cela arrive avec certains navigateurs
 +
  if ($method == "OPTIONS") {
 +
    header ( 'Access-Control-Allow-Origin: *' );
 +
    exit ();
 +
  }
 +
  // On appel notre Web Service
 +
  if(!WebService::execute($method)){
 +
    // HTTP 405 : Method Not Allowed
 +
    http_response_code(405);
 +
    return false;
 +
  }
 +
}
 +
main ();
 +
?>
 +
</source>
 +
Comme vous pouvez le voir, cette technique est beaucoup plus propre et permet l'ajout de Web Service sans modification de code.
 +
 
 +
Il faut en revanche modifier l’arborescence pour placer tous les Web Services sous le répertoire ''ws'' :
 +
[[Fichier:Project user js ws.png|centré]]
 +
 
 +
==Partie Cliente==
 +
Nous allons maintenant prendre en compte ces modification côté client.
 +
 
 +
Tout d'abord, il faut modifier toutes les URL dans le fichier ''user-ws.js'' :
 +
 
 +
* Dans ''getUsers''  :
 +
 
 +
<source lang="javascript" style="border:1px solid black;font-size:120%">
 +
xhr.open("GET", "http://127.0.0.1/index.php", false);
 +
//devient
 +
xhr.open("GET", "http://127.0.0.1/user", false);
 +
</source>
 +
* Dans ''addUser'' :
 +
 
 +
<source lang="javascript" style="border:1px solid black;font-size:120%">
 +
xhr.open("POST", "http://127.0.0.1/index.php", false);
 +
//devient
 +
xhr.open("POST", "http://127.0.0.1/user", false);
 +
</source>
 +
* Dans ''updateUser'',  :
 +
 
 +
<source lang="javascript" style="border:1px solid black;font-size:120%">
 +
xhr.open("PUT", "http://127.0.0.1/index.php", false);
 +
//devient
 +
xhr.open("PUT", "http://127.0.0.1/user", false);
 +
</source>
 +
 
 +
* Dans ''getUserById :
 +
 
 +
<source lang="javascript" style="border:1px solid black;font-size:120%">
 +
xhr.open("GET", "http://127.0.0.1/index.php?id="+id, false);
 +
//devient
 +
xhr.open("GET", "http://127.0.0.1/user/"+id, false);
 +
</source>
 +
* Dans ''delUser'' :
 +
 
 +
<source lang="javascript" style="border:1px solid black;font-size:120%">
 +
xhr.open("DELETE", "http://127.0.0.1/index.php", false);
 +
//devient
 +
xhr.open("DELETE", "http://127.0.0.1/user/"+id, false);
 +
 
 +
// On supprime la ligne suivante :
 +
 
 +
// Envoie de la requête avec l'objet User dans le body
 +
xhr.send(JSON.stringify(id));
 +
</source>

Version actuelle datée du 20 juillet 2018 à 10:34

Introduction

L'architecture informatique Ajax (acronyme d'Asynchronous JavaScript and XML) permet de construire des applications Web et des sites web dynamiques interactifs sur le poste client en se servant de différentes technologies ajoutées aux navigateurs web entre 1995 et 2005.

Ajax combine JavaScript, les CSS, JSON, XML, le DOM et le XMLHttpRequest afin d'améliorer maniabilité et confort d'utilisation des applications internet riches (RIA) 1,2 :

  • DOM et JavaScript permettent de modifier l'information présentée dans le navigateur en respectant sa structure ;
  • l'objet XMLHttpRequest sert au dialogue asynchrone avec le serveur Web ;
  • XML structure les informations transmises entre serveur Web et navigateur.

Outre le XML, les échanges de données entre client et serveur peuvent utiliser d'autres formats, tels que JSON.

Les applications Ajax fonctionnent sur tous les navigateurs Web courants : Google Chrome, Safari, Mozilla Firefox, Internet Explorer, Konqueror, Opera, etc.

Wikipedia


Nous allons utiliser l'objet XmlHttpRequest (XHR) avec une structure des informations en JSON. La partie serveur sera en Php et la partie cliente en Javascript / HTML / CSS.

Pré-requis

Pour développer en PHP, il ne faut pas oublier d'installer Apache HTTPD, PHP et pour ce projet nous allons utiliser également Memcached.

Partie serveur

Structure du projet

Créez l'arborescence suivante dans votre projet :

Projet user js php.png

Objet user

Nous allons créer un objet User qui contiendra les attributs suivants:

  • nom;
  • prenom;
  • age;

Cet objet implémentera une fonction toJson qui permettra de récupérer la chaine de caractères représentant l'objet ainsi que la méthode inverse fromJson.

Cela nous donne le contenu suivant pour le fichier User.class.php:

<?php
class User {
  public $id, $nom, $prenom, $age;
  
  public function User($id, $nom, $prenom, $age) {
    $this->id = $id;
    $this->nom = $nom;
    $this->prenom = $prenom;
    $this->age = $age;
  }
  /**
   * Retourne la chaîne Json représentant l'objet
   */
  public function toJson() {
    return json_encode ( $this );
  }
  /**
   * Retourne un objet User à partir de la chaîne Json ou false
   * @param User | boolean $json
   */
  public static function fromJson($json) {
    $obj = json_decode ( $json );
    if (isset ( $obj->id ) && isset ( $obj->nom ) && isset ( $obj->prenom ) && isset ( $obj->age )) {
      return new User ( $obj->id, $obj->nom, $obj->prenom, $obj->age );
    }
    return false;
  }
}
?>

Database Access Object

Pour stoker nos objets nous allons utiliser Memcache et pour respecter la programmation MVC nous allons utiliser le patron de conception DAO.

Dans ce DAO, nous allons implémenter les méthodes setUserTab, pour sauvegarder notre tableau d'utilisateur, et toutes les méthodes pour faire le CRUD.

Ajoutez les lignes suivantes, dans le fichier UserDao.class.php :

<?php
class UserDao {
  const USER_TAB = "users";
  const USER_ID = "user_id";
  const MEMCACHE_ADDRESS = "127.0.0.1";
  const MEMCACHE_PORT = 11211;
  
  /**
   * Retourne l'objet utilisateur en fonction de son identifiant
   *
   * @param int $id          
   */
  public function getUserById($id) {
    $users = UserDao::getUsers ();
    foreach ( $users as $user ) {
      if ($user->id == $id) {
        return $user;
      }
    }
    return false;
  }
  /**
   * Récupére le tableau d'utilisateur depuis memchache
   */
  public function getUsers() {
    $users = UserDao::getMemcacheServer ()->get ( UserDao::USER_TAB );
    if (! isset ( $users ) || ! $users) {
      $users = array ();
    }
    return array_values ( $users );
  }
  /**
   * Retourne l'id de l'utilisateur créer
   *
   * @param User $user          
   */
  public function createUser(User $user) {
    // On récupére l'identifiant
    $id = UserDao::getCurrentId ();
    // On le positionne
    $user->id = $id;
    // On récupére le tableau d'utilisateur
    $users = UserDao::getUsers ();
    // On ajoute l'utilisateur
    $users [$id] = $user;
    // On sauvegarde le tableau
    UserDao::setUserTab ( $users );
    // On retourne l'identifiant
    return $id;
  }
  /**
   * Met à jour l'objet utilisateur
   *
   * @param User $user          
   */
  public function updateUser(User $user) {
    $users = UserDao::getUsers ();
    // On vérifie que l'utilisateur existe
    foreach ( $users as $index => $tmp ) {
      if ($tmp->id == $user->id) {
        // On le remplace
        $users [$index] = $user;
        // On sauvegarde le tableau
        UserDao::setUserTab ( $users );
        return true;
      }
    }
    return false;
  }
  /**
   * Efface l'objet utilisateur
   *
   * @param int $id          
   */
  public function deleteUser($id) {
    $users = UserDao::getUsers ();
    // On vérifie que l'utilisateur existe
    foreach ( $users as $index => $user ) {
      if ($user->id == $id) {
        // On l'efface du tableau
        unset ( $users [$index] );
        // On sauvegarde le tableau
        UserDao::setUserTab ( $users );
        return true;
      }
    }
    return false;
  }
  /**
   * Stocke le tableau d'utilisateur dans memchache
   */
  private function setUserTab($users) {
    UserDao::getMemcacheServer ()->set ( UserDao::USER_TAB, $users );
  }
  /**
   * Retourne l'identifiant du prochain utilisateur
   */
  private function getCurrentId() {
    $user_id = UserDao::getMemcacheServer ()->get ( UserDao::USER_ID );
    if (! isset ( $user_id ) || ! $user_id) {
      $user_id = 0;
      UserDao::getMemcacheServer ()->set ( UserDao::USER_ID, $user_id );
    }
    UserDao::getMemcacheServer ()->increment ( UserDao::USER_ID );
    return $user_id;
  }
  /**
   * Retourne le serveur memchache
   */
  private static function getMemcacheServer() {
    $memcache = new Memcache ();
    $memcache->addserver ( UserDao::MEMCACHE_ADDRESS, UserDao::MEMCACHE_PORT );
    return $memcache;
  }
}
?>

Web Service RESTful en PHP

Nous allons faire une page UserWS.class.php qui répond au GET, au POST, au PUT ainsi qu'au DELETE. GET sert à récupérer des informations, POST sert à créer / insérer des informations, PUT à les modifier et DELETE à les supprimer. N'hésitez pas à relire le cours !

<?php
class UserWS {
  const VAR_ID = "id";
  /**
   * Execute le web service
   *
   * @param string $methode
   *          La méthode demandée (GET | POST | PUT | DELETE)
   */
  public static function execute($method) {
    // On test si on fait une requête en POST
    if ($method == "POST") {
      // On fait un POST sur index.php
      $id = UserWS::doPost ();
      if ($id === FALSE) {
        // HTTP 400 : Bad Request
        http_response_code ( 400 );
      } else {
        // HTTP 201 : Created
        http_response_code ( 201 );
        // On retourne l'identifiant du nouvel utilisateur
        echo json_encode ( $id );
      }
    } else if ($method == "GET") {
      // On fait un GET sur index.php
      if (! UserWS::doGet ()) {
        // HTTP 400 : Bad Request
        http_response_code ( 404 );
      }
    } else if ($method == "PUT") {
      // On fait un PUT sur index.php
      $result = UserWS::doPut ();
      if ($result === -1) {
        // HTTP 400 : Bad Request
        http_response_code ( 400 );
      } else if ($result === FALSE) {
        // HTTP 404 : Not found
        http_response_code ( 404 );
      }else if ($result === TRUE) {
        // HTTP 202 : Accepted
        http_response_code ( 202 );
      }
    } else if ($method == "DELETE") {
      // On fait un DELETE sur index.php
      $result = UserWS::doDelete ();
      if ($result === -1) {
        // HTTP 400 : Bad Request
        http_response_code ( 400 );
      } else if ($result === FALSE) {
        // HTTP 404 : Not found
        http_response_code ( 404 );
      }else if ($result === TRUE) {
        // HTTP 202 : Accepted
        http_response_code ( 202 );
      }
    }else {
      // HTTP 405 : Method Not Allowed
      http_response_code ( 405 );
    }
  }
  /**
   * Fonction appelée lors d'un GET
   */
  private static function doGet() {
    if (! isset ( $_GET [UserWS::VAR_ID] )) {
      // Pas d'identifiant on retourne le tableau
      echo json_encode ( UserDao::getUsers() );
      return true;
    } else {
      $user = UserDao::getUserById ( $_GET [UserWS::VAR_ID] );
      if ($user != false) {
        // L'utilisateur demandé existe
        echo json_encode ( $user );
        return true;
      }
    }
    // L'utilisateur demandé n'existe pas
    return false;
  }
  /**
   * Fonction appelée lors d'un POST
   * Elle retourne l'identifiant de
   */
  private static function doPost() {
    /*
     * On récupére le contenu de la requête HTTP
     * qui contient la chaîne Json représentant l'utilisateur
     */
    $json = file_get_contents ( 'php://input' );
    // On transforme la chaîne Json en objet
    $user = User::fromJson ( $json );
    if ($user !== false) {
      // On retourne l'identifiant du nouvel utilisateur
      return UserDao::createUser ( $user );
    }
    return false;
  }
  /**
   * Fonction appelée lors d'un PUT
   */
  private static function doPut() {
    /*
     * On récupére le contenu de la requête HTTP
     * qui contient la chaîne Json représentant l'utilisateur
     */
    $json = file_get_contents ( 'php://input' );
    // On transforme la chaîne Json en objet
    $user = User::fromJson ( $json );
    // Si la chaîne ne contient pas un objet User
    if (! $user) {
      return -1;
    }
    // On transforme la chaîne Json en objet
    return UserDao::updateUser ( $user );
  }
  /**
   * Fonction appelée lors d'un DELETE
   */
  private static function doDelete() {
    // On récupére l'identifiant utilisateur
    $id = json_decode ( file_get_contents ( 'php://input' ) );
    // On test la présence de l'identifiant
    if (! isset ( $id ) && !is_numeric($id)) {
      return -1;
    }
    // On efface l'utilisateur
    return UserDao::deleteUser ( $id );
  }
}
?>

On remarque au passage que la méthode http_response_code() permet de paramétrer le code HTTP dans l'entête. Cela va permettre de donner des informations au client sur le déroulement de la requête. Plus d'informations sur les codes HTTP ici

Appel du Web Service

Pour appeler notre Web Service nous devons encore ajouter quelques lignes. Tout d'abord, il faut autoriser l'appel depuis un autre serveur que celui ou sera exécuter le code PHP. Ajouter au début de index.php les lignes suivantes:

<?php
// Utilisé pour autoriser les appel Web Service en AJAX depuis toutes les adresses
header ( "Access-Control-Allow-Origin: *" );
// Utilisé pour autoriser les méthodes GET, POST, PUT et DELETE
header ( "Access-Control-Allow-Methods: GET, POST, PUT, DELETE" );
// On autorise les données dans le header
header ( "Access-Control-Allow-Headers: Content-Type" );
// On positione l'encodage en UTF-8 sinon json_encode ne fonctionnera pas !
header ( 'Content-Type: application/json; charset=utf-8' );
// On inclut l'objet User ainsi que les classes nécessaires
include_once 'class/User.class.php';
include_once 'class/UserDao.class.php';
include_once 'class/UserWS.class.php';
/**
 * Fonction principale
 */
function main() {
  // On récupére la méthode
  $method = $_SERVER ['REQUEST_METHOD'];
  // Si la méthode est de type option on ne continue pas l'exécution du script
  // Cela arrive avec certains navigateurs
  if ($method == "OPTIONS") {
    header ( 'Access-Control-Allow-Origin: *' );
    exit ();
  }
  // On appel notre Web Service
  UserWS::execute ( $method );
}
main ();
?>

On est fin prêt à passer à la partie cliente !

Partie cliente

Structure du projet

Créez l'arborescence suivante dans votre projet :

Structure user ajax client.png

Objet user

Nous allons créer un objet User qui contiendra les attributs suivants:

  • id;
  • nom;
  • prenom;
  • age.

Cet objet implémentera une fonction toJson qui permettra de récupérer la chaine de caractères représentant l'objet. Cela nous donne le contenu suivant pour le fichier user.class.js :

function User(id, nom, prenom, age) {
  
  this.id = id;
  this.nom = nom;
  this.prenom = prenom;
  this.age = age;
  
  this.toJson = function(){
    return JSON.stringify(this);
  }
    
}
// Simulation d'une méthode statique en la définissant après l'objet
User.fromJson = function (json){
  try{
    var obj = JSON.parse(json);
    if(!obj){
      return false;
    }
    return new User(obj.id, obj.nom, obj.prenom, obj.age);
  }catch (e) {
    return false;
  }
}
User.fromObject = function (obj){
  try{
    if(!obj){
      return false;
    }
    return new User(obj.id, obj.nom, obj.prenom, obj.age);
  }catch (e) {
    return false;
  }
}

XmlHttpRequest

Dans cette partie, il s'agit de créer la fonction qui retournera l'objet XmlHttpRequest en fonction du navigateur. Ajoutez les lignes suivantes dans le fichier ajax.js :

function getXhr() {
  var xhr;
  if (window.XMLHttpRequest) {
    // code for IE7+, Firefox, Chrome, Opera, Safari
    xhr = new XMLHttpRequest();
  } else {
    // code for IE6, IE5
    xhr = new ActiveXObject("Microsoft.XMLHTTP");
  }
  return xhr;
}

Appel des Web Services

Il faut maintenant appeler les Web Service grâce à l'objet XmlHttpRequest, ce que nous allons faire dans le fichier user-ws.js :

function getUsers() {
  // Création de l'objet XHR
  var xhr = getXhr();
  // Ouverture de l'URL en GET
  xhr.open("GET", "http://127.0.0.1/index.php", false);
  // Envoie de la requête
  xhr.send();
  // Récupération de la réponse
  var json = xhr.responseText;
  // Parse du tableau d'utilisateur
  var users = JSON.parse(json);
  // Parse de chaque utilisateur
  for(var i=0; i < users.length; i++){
    users[i] = User.fromObject(users[i]);
  }
  return users;
}
function getUserById(id) {
  // Création de l'objet XHR
  var xhr = getXhr();
  // Ouverture de l'URL en GET avec l'identifiant
  xhr.open("GET", "http://127.0.0.1/index.php?id="+id, false);
  // Envoie de la requête
  xhr.send();
  // Récupération de la réponse
  var json = xhr.responseText;
  // Parse de l'utilisateur
  var user = User.fromJson(json);
  return user;
}
function addUser(user){
  // Création de l'objet XHR
  var xhr = getXhr();
  // Ouverture de l'URL en POST
  xhr.open("POST", "http://127.0.0.1/index.php", false);
  // Envoie de la requête avec l'objet User dans le body
  xhr.send(user.toJson());
  // Récupération de la réponse
  var result = xhr.responseText;
  return result;
}
function updateUser(user){
  // Création de l'objet XHR
  var xhr = getXhr();
  // Ouverture de l'URL en POST
  xhr.open("PUT", "http://127.0.0.1/index.php", false);
  xhr.setRequestHeader('Content-Type', 'application/json');
  // Envoie de la requête avec l'objet User dans le body
  xhr.send(user.toJson());
  // Récupération de la réponse
  return xhr.status == 202;
}
function deleteUser(id){
  // Création de l'objet XHR
  var xhr = getXhr();
  // Ouverture de l'URL en DELETE
  xhr.open("DELETE", "http://127.0.0.1/index.php", false);
  // Envoie de la requête avec l'objet User dans le body
  xhr.send(JSON.stringify(id));
  // Récupération de la réponse
  return xhr.status == 202;
}

index.html

Il nous faut maintenant un fichier d'entrée pour appeler ce code Javascript, ce que nous allons faire avec index.html :

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>GUI Utilisateurs</title>
<script type="text/javascript" src="js/ajax.js" ></script>
<script type="text/javascript" src="js/user.class.js" ></script>
<script type="text/javascript" src="js/user-ws.js" ></script>
<script type="text/javascript" src="js/buttons.js" ></script>
</head>
<body>
  <div>
    Nom:<br/>
    <input type="text" id="nom" ><br/>
    Prénom:
    <br/>
    <input type="text" id="prenom" ><br/>
    Age:
    <br/>
    <input type="number" id="age" min="1" >
    <br/><br/>
    <button onClick="doCreateUser();">Créer un utilisateur</button>
    <button onClick="doUpdateUser();">Modifier un utilisateur</button>
    <hr>
    <button onClick="doGetUser();">Afficher un utilisateur</button>
    <input type="text" id="id" placeholder="user id" >
    <button onClick="doDeleteUser();">Effacer un utilisateur</button>
    <br/><br/>
    <button onClick="doGetUsers();">Afficher tous les utilisateurs</button>
  </div>
  <div id="affiche">
  </div>
</body>
</html>

Ajout des événements

Comme vous avez pu le remarquer, notre fichier index.html inclue le fichier buttons.js. C'est dans ce fichier que l'on va mettre les fonctions qui vont appeler les Web Serivces en fonction de l'appuie sur les boutons :

function doCreateUser(){
  var nom = document.getElementById("nom").value;
  var prenom = document.getElementById("prenom").value;
  var age = document.getElementById("age").value;
  if(nom == "" || prenom == "" || age == ""){
    alert("Missing field !");
    return false;
  }
  var user = new User(0, nom, prenom, age);
  if(!addUser(user)){
    alert("Something went wrong !");
    return false;
  }
  alert("Creation successful !");
  eraseFieldValues();
  return true;
}
function doGetUser(){
  var id = document.getElementById("id").value;
  if(id == ""){
    alert("Missing field !");
    return false;
  }
  var user = getUserById(id);
  if(!user){
    alert("User does not exists !");
    return false;
  }
  document.getElementById("nom").value = user.nom;
  document.getElementById("prenom").value = user.prenom;
  document.getElementById("age").value = user.age;
  var div = document.getElementById("affiche");
  div.innerHTML = user.toJson();
  return true;
}
function doGetUsers(){
  var users = getUsers();
  if(!users){
    alert("Something went wrong !");
    return false;
  }
  var div = document.getElementById("affiche");
  if(users.length == 0){
    div.innerHTML = "Pas d'utilisateur !";
  }else{
    div.innerHTML = "";
    for(var i=0; i < users.length; i++){
      var user = users[i];
      div.innerHTML += user.toJson()+"<br/>";
    }
  }
  return true;
}
function doUpdateUser(){
  var nom = document.getElementById("nom").value;
  var prenom = document.getElementById("prenom").value;
  var age = document.getElementById("age").value;
  var id = document.getElementById("id").value;
  if(nom == "" || prenom == "" || age == "" || id == ""){
    alert("Missing field !");
    return false;
  }
  var user = new User(parseInt(id), nom, prenom, age);
  if(!updateUser(user)){
    alert("User not found !");
    return false;
  }
  alert("User modified !");
  return true;
}
function doDeleteUser(){
  var id = document.getElementById("id").value;
  if(id == ""){
    alert("Missing field !");
    return false;
  }
  if(!deleteUser(id)){
    alert("User does not exists !");
    return false;
  }
  alert("User deleted !");
  return true;
}
function eraseFieldValues(){
  document.getElementById("nom").value = "";
  document.getElementById("prenom").value = "";
  document.getElementById("age").value = "";
  return true;
}

Gestion des URLs

Introduction

Pour faire un Web Service complètement Restful, il faudrait que les URLs soient explicitent. Par exemple, notre Web Service utilisateur devrait être accessible depuis l'URL http://127.0.0.1/user et non directement depuis http://127.0.0.1 comme c'est actuellement le cas.

Pour réaliser cette fonctionnalité nous allons utiliser un module d'Apache qui s'appelle mod_rewrite. Comme sont nom l'indique, ce module permet la réécriture des URL pour qu'une partie soit passée en paramètre de la requête HTTP.

.htaccess

A la racine de projet serveur vous allez ajouter un fichier .htaccess qui contiendra les lignes suivantes :

# On active le module de réécriture
RewriteEngine on
# On test si un fichier existe et on l'affiche
RewriteCond %{REQUEST_FILENAME} !-f
# On test si un répertoire existe et on l'affiche
RewriteCond %{REQUEST_FILENAME} !-d
# Si aucun fichier ou répertoire n'existe on applique la régle de réécriture
# Dans ce cas on prend toute l'URL est on la met dans $_GET['ws']
RewriteRule ^(.*)$ index.php?ws=$1 [QSA,L]

N'oubliez pas d'activer la lecture des fichiers .htaccess !

Digestion de l'URL et appel du Web Service

Il va falloir prendre en compte la nouvelle URL que nous allons utiliser:

  • pour inclure les fichiers correspondant au Web Service;
  • pour passer le paramètre id.

Ajoutez un fichier WebService.class.php dans le répertoire class qui contiendra les lignes suivantes:

<?php
class WebService {
  const URL_VAR = "ws";
  const PHP_CLASS_DIR = "class";
  const PHP_WS_DIR = self::PHP_CLASS_DIR . "/ws";
  const PHP_CLASS_DAO = "Dao";
  const PHP_CLASS_WS = "WS";
  const PHP_CLASS_EXT = "class.php";
  const WS_EXEC_FCT = "execute";
  
  /**
   * Execute le Web Service
   * @param string $method
   * La méthode HTTP
   */
  public static function execute($method){
    // On récupére le nom du Web Service
    $webService = WebService::getWebService();
    // Si le Web Service existe
    if(WebService::includeWebService($webService)){
      // On prépare le nom de la fonction à appeler
      $toCall = ucfirst ( $webService ).self::PHP_CLASS_WS."::".self::WS_EXEC_FCT;
      // On l'appelle
      call_user_func($toCall, $method);
      return true;
    }
    return false;
  }
  /**
   *
   * Retourne l'identifiant contenu dans l'URL ou false
   *
   * @return string|boolean
   */
  public static function getId() {
    // On récupére l'URL dans la variable ws
    $url = $_GET [WebService::URL_VAR];
    // On vérifie si l'URL contient un '/' avec l'id
    $pos = strpos ( $url, '/' );
    if ($pos > 0 && $pos != strlen ( $url ) - 1) {
      // S'il y a un ID (eg. user/1) on le récupére
      $id = explode ( '/', $url ) [1];
      // On retourne ce qu'il y a après le '/' si c'est de type numérique
      if (is_numeric ( $id )) {
        return $id;
      }
    }
    return false;
  }
  /**
   * Inclue les fichiers correspondant au Web Service demandé
   * 
   * @param string $webService          
   * @return boolean
   */
  private static function includeWebService($webService) {
    // On construit le chemin
    $dir = self::PHP_WS_DIR . '/' . $webService;
    // Si le chemin existe
    if (is_dir ( $dir )) {
      // On construit un tableau avec les trois fichiers
      $files = array ();
      // le fichier class/user/User.class.php
      $files [] = $dir . '/' . ucfirst ( $webService ) . '.' . self::PHP_CLASS_EXT;
      // le fichier class/user/UserDao.class.php
      $files [] = $dir . '/' . ucfirst ( $webService ) . self::PHP_CLASS_DAO . '.' . self::PHP_CLASS_EXT;
      // le fichier class/user/UserWS.class.php
      $files [] = $dir . '/' . ucfirst ( $webService ) . self::PHP_CLASS_WS . '.' . self::PHP_CLASS_EXT;
      foreach ( $files as $file ) {
        // Si le fichier existe, on l'inclue
        if (is_file ( $file )) {
          include_once $file;
        } else {
          // Si le fichier n'existe pas, on retourne false;
          return false;
        }
      }
    } else {
      return false;
    }
    return true;
  }
  
  /**
   * Retourne le nom du Web Service demandé ou false
   * 
   * @return string|boolean
   */
  private static function getWebService() {
    // On récupére l'URL dans la variable ws
    $url = $_GET [self::URL_VAR];
    // On vérifie si l'URL contient un '/' avec l'id
    if (strpos ( $url, '/' ) > 0) {
      // S'il y a un ID (eg. user/1)
      // On retourne ce qu'il y a avant le '/'
      return explode ( '/', $url ) [0];
    }
    return strlen ( $url ) ? $url : false;
  }
}
?>

Modification de User.class.php

Pour que le Web Service continue à fonctionner, il faut modifier la méthode de récupération de la variable id.

  • Dans la classe :
// La ligne suivante n'est plus utile !
const VAR_ID = "id";
  • Dans doGet :
if (! isset ( $_GET [UserWS::VAR_ID] )) {
// devient
if (WebService::getId() === false) {
// Et
$_GET [UserWS::VAR_ID]
// devient 
WebService::getId()
  • Dans doDelete :
$id = json_decode ( file_get_contents ( 'php://input' ) );
// devient
$id = WebService::getId();

Modification de index.php

Nous n'avons plus qu'a appeler notre classe dans l'index :

<?php
// Utilisé pour autoriser les appel Web Service en AJAX depuis toutes les adresses
header ( "Access-Control-Allow-Origin: *" );
// Utilisé pour autoriser les méthodes GET, POST, PUT et DELETE
header ( "Access-Control-Allow-Methods: GET, POST, PUT, DELETE" );
// On autorise les données dans le header
header ( "Access-Control-Allow-Headers: Content-Type" );
// On positione l'encodage en UTF-8 sinon json_encode ne fonctionnera pas !
header ( 'Content-Type: application/json; charset=utf-8' );
// On inclut l'objet WebService
include_once 'class/WebService.class.php';
/**
 * Fonction principale
 */
function main() {
  // On récupére la méthode
  $method = $_SERVER ['REQUEST_METHOD'];
  // Si la méthode est de type option on ne continue pas l'exécution du script
  // Cela arrive avec certains navigateurs
  if ($method == "OPTIONS") {
    header ( 'Access-Control-Allow-Origin: *' );
    exit ();
  }
  // On appel notre Web Service
  if(!WebService::execute($method)){
    // HTTP 405 : Method Not Allowed
    http_response_code(405);
    return false;
  }
}
main ();
?>

Comme vous pouvez le voir, cette technique est beaucoup plus propre et permet l'ajout de Web Service sans modification de code.

Il faut en revanche modifier l’arborescence pour placer tous les Web Services sous le répertoire ws :

Project user js ws.png

Partie Cliente

Nous allons maintenant prendre en compte ces modification côté client.

Tout d'abord, il faut modifier toutes les URL dans le fichier user-ws.js :

  • Dans getUsers :
xhr.open("GET", "http://127.0.0.1/index.php", false); 
//devient
xhr.open("GET", "http://127.0.0.1/user", false);
  • Dans addUser :
xhr.open("POST", "http://127.0.0.1/index.php", false); 
//devient
xhr.open("POST", "http://127.0.0.1/user", false);
  • Dans updateUser, :
xhr.open("PUT", "http://127.0.0.1/index.php", false); 
//devient
xhr.open("PUT", "http://127.0.0.1/user", false);
  • Dans getUserById :
xhr.open("GET", "http://127.0.0.1/index.php?id="+id, false); 
//devient
xhr.open("GET", "http://127.0.0.1/user/"+id, false);
  • Dans delUser :
xhr.open("DELETE", "http://127.0.0.1/index.php", false); 
//devient
xhr.open("DELETE", "http://127.0.0.1/user/"+id, false);

// On supprime la ligne suivante : 

// Envoie de la requête avec l'objet User dans le body
xhr.send(JSON.stringify(id));