Blame | Last modification | View Log | RSS feed
<?php/*** Classe principale gérant les services.* Paramètres liés dans config.ini :* - serveur.baseURL** Encodage en entrée : utf8* Encodage en sortie : utf8** @category Php 5.2* @package JRest* @author Jean-Pascal MILCENT <jpm@tela-botanica.org>* @license GPL v3 <http://www.gnu.org/licenses/gpl.txt>* @license CECILL v2 <http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt>* @copyright 2010 Tela-Botanica* @version $Id$*/// TODO : gerer les retours : dans ce controleur : code retour et envoi ...class Serveur {/** Nom de la méthode appelée dans un service pour éxécuter une requête de type GET. */const METHODE_GET = 'getElement';/** Nom de la méthode appelée dans un service pour éxécuter une requête de type POST. */const METHODE_POST = 'getElement';/** Nom de la méthode appelée dans un service pour éxécuter une requête de type DELETE. */const METHODE_DELETE = 'getElement';/** Nom de la méthode appelée dans un service pour éxécuter une requête de type PUT. */const METHODE_PUT = 'getElement';/** Les paramètres de configuration dynamiques internes au serveur. */private static $config;/** La méthode de la requête HTTP utilisée. */private $methode = 'GET';/** Le contenu de la requête HTTP (s'il y en a). */private $requeteDonnees = null;//requestData/** Version de l'API demandée.* Généralement deux nombres séparés par un point. Ex. : 1.0* Ex. http://www.mondomaine.org/services/[apiVersion]/monService/*/private $apiVersion = null;/** Nom du service demandé.* Ex. http://www.mondomaine.org/services/apiVersion/[monService]/*/private $service = null;// $ressource/** Morceaux de l'url servant à préciser la ressource concerné pour service demandé.* Ex. http://www.mondomaine.org/services/apiVersion/monService/[maRessource/maSousResource...]*/private $ressources = array();// $uid/** Partie de l'url servant à paramétrer le service demandé.* Ex. http://www.mondomaine.org/services/apiVersion/monService?monParametre1=maValeur1&monParametre2=maValeur2*/private $parametres = array();/** Codes HTTP. */private static $http10 = array('201' => 'Created','204' => 'No Content','400' => 'Bad Request','401' => 'Unauthorized','404' => 'Not Found','405' => 'Method Not Allowed','406' => 'Not Acceptable','411' => 'Length Required','500' => 'Internal Server Error');/*** Analyse les données envoyées au serveur, enregistre la méthode HTTP utilisée pour appeler le serveur et parse* l'url appelée pour trouver le service demandé.*/public function __construct() {if (isset($_SERVER['REQUEST_URI']) && isset($_SERVER['REQUEST_METHOD']) && isset($_SERVER['QUERY_STRING'])) {if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['CONTENT_LENGTH'] > 0) {$this->requeteDonnees = '';$httpContent = fopen('php://input', 'r');while ($data = fread($httpContent, 1024)) {$this->requeteDonnees .= $data;}fclose($httpContent);}if (strlen($_SERVER['QUERY_STRING']) == 0) {$tailleURL = strlen($_SERVER['REQUEST_URI']);} else {$tailleURL = -(strlen($_SERVER['QUERY_STRING']) + 1);}$urlString = substr($_SERVER['REQUEST_URI'], strlen(Config::get('serveur.baseURL')), $tailleURL);$urlParts = explode('/', $urlString);if (isset($urlParts[0]) && !empty($urlParts[0])) {$this->apiVersion = $urlParts[0];self::$config['chemins']['api'] = Config::get('chemin_modules').$this->apiVersion.DS;} else {$e = "Aucune version d'API n'a été spécifié dans l'url qui doit avoir la forme suivante http://www.mondomaine.org/services/apiVersion/monService/";trigger_error($e, E_USER_ERROR);}if (isset($urlParts[1]) && !empty($urlParts[1])) {$this->service = $this->traiterNomService($urlParts[1]);} else {$e = "Aucun service n'a été spécifié dans l'url qui doit avoir la forme suivante http://www.mondomaine.org/services/apiVersion/monService/";trigger_error($e, E_USER_ERROR);}if (count($urlParts) > 2 && $urlParts[2] != '') {array_shift($urlParts);array_shift($urlParts);foreach ($urlParts as $ressource) {if ($ressource != '') {$this->ressources[] = urldecode($ressource);}}}$this->nettoyerGet();$this->parametres = $_GET;$this->methode = $_SERVER['REQUEST_METHOD'];// Enregistrement en première position des autoload de la méthode gérant les classes des servicesspl_autoload_register(array(get_class(), 'chargerClasse'));} else {$e = "La classe Serveur du TBFRamework nécessite, pour fonctionner, l'accès aux variables serveurs REQUEST_URI, REQUEST_METHOD et QUERY_STRING.";trigger_error($e, E_USER_ERROR);}}private function traiterNomService($serviceNom) {return str_replace(' ', '', ucwords(str_replace('-', ' ', strtolower($serviceNom))));}private function nettoyerGet() {if (isset($_GET) && count($_GET) > 0) {foreach ($_GET as $cle => $valeur) {$verifier = array('NULL', "\n", "\r", "\\", "'", '"', "\x00", "\x1a", ';');$_GET[$cle] = strip_tags(str_replace($verifier, '', $valeur));}}}/*** La méthode __autoload() charge dynamiquement les classes trouvées dans le code.* Cette fonction est appelée par php5 quand il trouve une instanciation de classe dans le code.**@param string le nom de la classe appelée.*@return void le fichier contenant la classe doit être inclu par la fonction.*/public static function chargerClasse($classe) {if (class_exists($classe)) {return null;}$chemins = array('', self::$config['chemins']['api']);foreach ($chemins as $chemin) {$chemin = $chemin.$classe.'.php';if (file_exists($chemin)) {require_once $chemin;}}}/*** Execute la requête.*/function executer() {switch ($this->methode) {case 'GET':$this->get();break;case 'POST':$this->post();break;case 'DELETE':$this->delete();break;case 'PUT':$this->add();break;}// Affichage des exceptions et erreurs générées par les servicesecho GestionnaireException::getExceptions();}/*** Execute a GET request. A GET request fetches a list of resource when no resource name is given, a list of element* when a resource name is given, or a resource element when a resource and resource unique identifier are given. It does not change the* database contents.*/private function get() {Debug::printr($this);$Service = new $this->service(self::$config);if (method_exists($Service, self::METHODE_GET)) {$methodeGet = self::METHODE_GET;$Service->$methodeGet($this->ressources, $this->parametres);} else {$e = "La classe '{$this->service}' ne contient pas de méthode '".self::METHODE_GET."'.";trigger_error($e, E_USER_ERROR);}}private function post() {$pairs = array();// Récupération des paramètres passés dans le contenu de la requête HTTP (= POST)if ($this->requestData) {$pairs = $this->parserDonneesRequete();}// Ajout des informations concernant l'upload de fichier passées dans la variable $_FILEif(isset($_FILES)) {foreach ($_FILES as $v) {$pairs[$v['name']] = $v;}// Ne pas effacer cette ligne ! Elle est indispensable pour les services du Carnet en ligne// qui n'utilisent que le tableau pairs dans les posts$pairs = array_merge($pairs, $_POST);}// Gestion du contenu du postif(isset($_POST)){// Safari ne sait pas envoyer des DELETE avec gwt...// Nous utilisons le parametre "action" passé dans le POST qui doit contenir DELETE pour lancer la supressionif ($pairs['action'] == 'DELETE') {$this->delete();return;}if (count($pairs) != 0) {if ($this->uid) { // get a resource element$resource_file = self::$config['settings']['servicesDir'].$this->ressource.'.php';$resource_class = $this->ressource;if (file_exists($resource_file)) {include_once $resource_file;if (class_exists($resource_class)) {$service = new $resource_class(self::$config);if (method_exists($service,'updateElement')) { // Update element// TODO : a voir le retour ...if ($service->updateElement($this->uid, $pairs)) {$this->envoyerEnteteHttp(201);// Created}}}}} else { // get all elements of a ressource$this->add($pairs);}} else {$this->envoyerEnteteHttp(411);// Length required}}}private function delete() {$resource_file = self::$config['settings']['servicesDir'].$this->ressource.'.php';$resource_class = $this->ressource;if (file_exists($resource_file)) {include_once $resource_file;if (class_exists($resource_class)) {$service = new $resource_class(self::$config);if ($this->uid) { // get a resource elementif (method_exists($service, 'deleteElement')) { // Delete elementif ($service->deleteElement($this->uid)) {$this->envoyerEnteteHttp(204);// No Content}}}}}}private function add($pairs = null) {if (is_null($pairs)) {$pairs = array();// Récupération des paramètres passés dans le contenu de la requête HTTP (= POST)// FIXME : vérifier que l'on récupère bien les données passées par PUTif ($this->requestData) {$pairs = $this->parserDonneesRequete();}}if (count($pairs) != 0) {$resource_file = self::$config['settings']['servicesDir'].$this->ressource.'.php';$resource_class = $this->ressource;if (file_exists($resource_file)) {include_once $resource_file;if (class_exists($resource_class)) {$service = new $resource_class(self::$config);if (method_exists($service,'createElement')) { // Create a new elementif ($service->createElement($pairs)) {$this->envoyerEnteteHttp(201);// Created}}}}} else {$this->envoyerEnteteHttp(411);// Length required}}/*** Parse les données de la requête HTTP.* @return str[] Array of name value pairs*/private function parserDonneesRequete() {$values = array();$pairs = explode('&', $this->requeteDonnees);foreach ($pairs as $pair) {$parts = explode('=', $pair);if (isset($parts[0]) && isset($parts[1])) {$parts[1] = rtrim(urldecode($parts[1]));$values[$parts[0]] = $parts[1];}}return $values;}/*** Envoyer un entête HTTP.* @param int $code entier indiquant le code de l'entête http à envoyer*/public static function envoyerEnteteHttp($code) {if (isset(self::$http10[$code])) {$txt = self::$http[$code];header("HTTP/1.0 $code $txt");}}}?>