Implémentation d’un service Web avec « JSON » sous Typo3
Cet article va vous donner la possibilité d’implémenter une solution de « plugin FE Typo3 » de type service Web (côté serveur).
Pour ce faire nous avons besoins des fichiers suivants:
ext\common_json_call\res\misc\class.tx_commonjsoncall_misc.php
ext\common_json_call\pi1\class.tx_commonjsoncall_db.php
ext\common_json_call\pi1\class.tx_commonjsoncall_idb.php
ext\common_json_call\pi1\class.tx_commonjsoncall_pi1.php
ext\common_json_call\pi1\locallang.xml
1) Explication des différents fichiers
ext\common_json_call\res\misc\class.tx_commonjsoncall_misc.php
C’est une classe de type « helper », donc des utilitaires de base.
ext\common_json_call\pi1\class.tx_commonjsoncall_db.php
Objet responsable de produire les « dataset », c’est la couche « query layer ».
ext\common_json_call\pi1\class.tx_commonjsoncall_idb.php
Interface qui oblige le développeur à déclarer les méthodes « query layer ».
ext\common_json_call\pi1\class.tx_commonjsoncall_pi1.php
Classe de type « main », point d’entrée pour Typo3.
ext\common_json_call\pi1\locallang.xml
Fichier de localisation standard pour Typo3.
2) Contenu des différents fichiers
Classe: tx_commonjsoncall_misc
<?php /** * Plugin '[Common] Common JSON Call' for the 'common_json_call' extension. * * @author Regnier David <regnier_david@yahoo.fr> * @package TYPO3 * @subpackage tx_commonjsoncall */ abstract class tx_commonjsoncall_misc { /** * HTTP status message * * @param int $status: HTTP status * @return string */ public static function getHTTPStatusCodeMessage($status) { $codes = Array( 100 => 'Continue', 101 => 'Switching Protocols', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => '(Unused)', 307 => 'Temporary Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Requested Range Not Satisfiable', 417 => 'Expectation Failed', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported' ); return (isset($codes[$status])) ? $codes[$status] : ''; } /** * Get SQL method name, assign here the available method to call * * @return array */ public static $AVAILABLE_METHOD = array( 'getPlaceDisponibleByRegion', // ... ); /** * Object testing, is_object and instanceof * * @return boolean */ public static function isObjectInstanceOf($myObject = '', $myInstance = '') { $result = false; if (is_object($myObject) && $myObject instanceof $myInstance) { $result = true; } return (bool)$result; } /** * Class/Interface testing * * @return boolean */ public static function isClassAndInterfaceDefined($myClass = '', $myInterface = '') { $result = true; if (!class_exists($myClass) || !interface_exists($myInterface)) { $result = false; } return (bool)$result; } } ?>
Classe: tx_commonjsoncall_db
<?php // ... require_once(t3lib_extMgm::extPath('common_json_call').'pi1/class.tx_commonjsoncall_idb.php'); require_once(t3lib_extMgm::extPath('common_json_call').'res/misc/class.tx_commonjsoncall_misc.php'); // ... /** * Plugin '[Common] Common JSON Call' for the 'common_json_call' extension. * * @author Regnier David <regnier_david@yahoo.fr> * @package TYPO3 * @subpackage tx_commonjsoncall */ class tx_commonjsoncall_db implements tx_commonjsoncall_idb { private $requestVars; private $requestMethod; public $select = ''; // SQL Select public $fromTable = ''; // SQL from public $where = ''; // SQL where public $groupBy = ''; // SQL group by public $orderBy = ''; // SQL order by public $limit = ''; // SQL limit /** * Create object constructor * * @param array $structure: array of args used by constructor * @return void */ public function __construct(array $structure) { $this -> requestVars = $structure['requestVars']; $this -> requestMethod = $structure['requestMethod']; } /** * SQL dispatcher, call request method from URL * Interface, must be implemented * * @return void */ public function sqlDispatcher() { // Does the requested method exist/available if ($this -> validateRequestMethod($this -> requestVars)) { $callThisMethod = key($this -> requestVars); // Grab the method name unset($this -> requestVars[$callThisMethod]); // Delete function name from URL parameters call_user_func(array(__CLASS__, $callThisMethod), $this -> requestVars); } } /** * Validate input method name * Interface, must be implemented * * @param array $inputArray: Request method * @return boolean */ public function validateRequestMethod($inputArray = array()) { $result = false; if (is_array($inputArray) && count($inputArray) != 0) { if (in_array(key($inputArray), tx_commonjsoncall_misc::$AVAILABLE_METHOD)) { $result = true; } } return (bool)$result; } public function getPlaceDisponibleByRegion($args = array()) { $this -> select = 'field1, field2'; $this -> fromTable = 'table_name'; $this -> where = ''; } } ?>
Classe: tx_commonjsoncall_idb
<?php /** * Plugin '[Common] Common JSON Call' for the 'common_json_call' extension. * * @author Regnier David <regnier_david@yahoo.fr> * @package TYPO3 * @subpackage tx_commonjsoncall */ interface tx_commonjsoncall_idb { public function sqlDispatcher(); public function validateRequestMethod($inputArray = array()); public function getPlaceDisponibleByRegion($args = array()); // ... } ?>
Classe: tx_commonjsoncall_pi1
<?php // ... require_once(PATH_tslib.'class.tslib_pibase.php'); require_once(t3lib_extMgm::extPath('common_json_call').'res/misc/class.tx_commonjsoncall_misc.php'); require_once(t3lib_extMgm::extPath('common_json_call').'pi1/class.tx_commonjsoncall_db.php'); // ... /** * Plugin '[Common] Common JSON Call' for the 'common_json_call' extension. * * @author Regnier David <regnier_david@yahoo.fr> * @package TYPO3 * @subpackage tx_commonjsoncall */ class tx_commonjsoncall_pi1 extends tslib_pibase { const TYPE_JSON = 'application/json'; // Header json type const HTTP_OK = 200; // Default HTTP OK return code const DEFAULT_CHARSET = 'utf-8'; // Default charset for JSON const DB_CLASS = 'tx_commonjsoncall_db'; // DB class const DB_INTERFACE = 'tx_commonjsoncall_idb'; // DB interface public $prefixId = 'tx_commonjsoncall_pi1'; // Same as class name public $scriptRelPath = 'pi1/class.tx_commonjsoncall_pi1.php';// Path to this script relative to the extension dir. public $extKey = 'common_json_call'; // The extension key. private $dataSet = array(); // Row returned by MySQL call private $select = ''; // SQL Select private $fromTable = ''; // SQL from private $where = ''; // SQL where private $groupBy = ''; // SQL group by private $orderBy = ''; // SQL order by private $limit = ''; // SQL limit private $currentPid; // Current pid private $requestVars; // HTTP request vars private $requestMethod; // HTTP request method name private $dbObj; // SQL query layer object /** * The main method of the PlugIn * * @param string $content: The PlugIn content * @param array $conf: The PlugIn configuration * @return The content that is displayed on the website */ public function main($content, $conf) { $this -> conf = $conf; $this -> pi_loadLL(); $this -> pi_USER_INT_obj = 1; $this -> setParameters(); $this -> setHTTPData(); $this -> setSQLQueryLayerObject(); $this -> setSQLQueryLayerObjectDispatcher(); $this -> setMySQLCriteriasFromLayerObject(); $this -> getDataSetFromMySQLCriterias(); $this -> sendJSONResponse(self::HTTP_OK, $this -> dataSet); } /** * Set needed parameters * * @return void */ private function setParameters() { $this -> currentPid = $GLOBALS['TSFE'] -> id; $this -> requestMethod = t3lib_div::strtolower($_SERVER['REQUEST_METHOD']); } /** * Set data from URL for later use * * @return void */ private function setHTTPData() { if (!empty($this -> requestMethod)) { switch ($this -> requestMethod) { case 'get': $this -> setRequestVars(t3lib_div::_GET()); break; case 'post': $this -> setRequestVars(t3lib_div::_POST()); break; } } else { $this -> setLogAndResponse( $this -> pi_getLL($this -> extKey.'.label_error_http_empty_request_method'), 500 ); } } /** * Set vars from HTTP request * * @param string $requestVars: Get vars from HTTP method type * @return void */ private function setRequestVars($requestVars = '') { if (!empty($requestVars)) { $this -> requestVars = $requestVars; } else { $this -> setLogAndResponse( $this -> pi_getLL($this -> extKey.'.label_error_http_empty_url_vars_returned').$_SERVER['REQUEST_URI'], 204 ); } } /** * Set SQL query layer object * * @return void */ private function setSQLQueryLayerObject() { if (!empty($this -> requestVars)) { if (!isset($this -> dbObj)) { $this -> dbObj = t3lib_div::makeInstance( self::DB_CLASS, array( 'requestVars' => $this -> requestVars, 'requestMethod' => $this -> requestMethod ) ); } } } /** * Call the SQL dispatcher * * @return void */ private function setSQLQueryLayerObjectDispatcher() { if (tx_commonjsoncall_misc::isObjectInstanceOf($this -> dbObj, self::DB_CLASS)) { if (tx_commonjsoncall_misc::isClassAndInterfaceDefined(self::DB_CLASS, self::DB_INTERFACE)) { $this -> dbObj -> sqlDispatcher(); } else { $this -> setLogAndResponse( $this -> pi_getLL($this -> extKey.'.label_error_object_created_interface_created'), 500 ); } } else { $this -> setLogAndResponse( $this -> pi_getLL($this -> extKey.'.label_error_object_created'), 500 ); } } /** * Set needed SQL query from DB object * * @return void */ private function setMySQLCriteriasFromLayerObject() { $this -> select = $this -> dbObj -> select; $this -> fromTable = $this -> dbObj -> fromTable; $this -> where = $this -> dbObj -> where; $this -> groupBy = $this -> dbObj -> groupBy; $this -> orderBy = $this -> dbObj -> orderBy; $this -> limit = $this -> dbObj -> limit; } /** * Set Typo3 query * * @return void */ private function getDataSetFromMySQLCriterias() { if (!empty($this -> select) && !empty($this -> fromTable)) { $req = $GLOBALS['TYPO3_DB'] -> exec_SELECTquery( $this -> select, $this -> fromTable, $this -> where, $this -> groupBy, $this -> orderBy, $this -> limit ); while ($row = $GLOBALS['TYPO3_DB'] -> sql_fetch_assoc($req)) { $this -> dataSet[] = array_map(utf8_encode, $row); } if ($GLOBALS['TYPO3_DB'] -> sql_error()) { $this -> setLogAndResponse( $this -> pi_getLL($this -> extKey.'.label_error_mysql_setmysqlquery').$GLOBALS['TYPO3_DB'] -> sql_error(), 500 ); } else { if (count($this -> dataSet) == 0) { $this -> setLogAndResponse( $this -> pi_getLL($this -> extKey.'.label_notice_http_empty_dataset_returned').tx_commonjsoncall_misc::getHTTPStatusCodeMessage(204), 204 ); } } } else { $this -> setLogAndResponse( $this -> pi_getLL($this -> extKey.'.label_error_mysql_emptycriterias_returned'), 204 ); } } /** * Set log through Typo3, and send JSON if any * * @param string $logMessage: Log message * @param mixed $httpCode: http code returned * @param int $severity: severity for log * @return void */ private function setLogAndResponse($logMessage = '', $httpCode = null, $severity = 1) { if (!empty($logMessage)) { $this -> writeLog(intval($severity), (string)$logMessage); } if (!is_null($httpCode)) { $httpCode = intval($httpCode); $this -> sendJSONResponse($httpCode, tx_commonjsoncall_misc::getHTTPStatusCodeMessage($httpCode)); } } /** * Send JSON encoded response * * @param int $status: HTTP status * @param string $result: String result */ private function sendJSONResponse($httpStatus = self::HTTP_OK, $result = '') { if (!empty($result)) { $httpCode = intval($httpStatus); header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1 header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past header('HTTP/1.1 '.$httpCode.' '.tx_commonjsoncall_misc::getHTTPStatusCodeMessage($httpCode)); // Set the status header('Content-Type: '.self::TYPE_JSON.'; charset='.self::DEFAULT_CHARSET); // Set the content type echo json_encode($result); exit; } } /** * Write log to Typo3 sys_log table * * @param int $error: severity Flag. * 0 = message, * 1 = error (user problem), * 2 = System Error (which should not happen), * 3 = security notice (admin) * @param string $details: message * @return void */ private function writeLog($error, $details) { $fieldsValues = Array( 'userid' => 0, 'type' => 4, 'action' => 0, 'error' => intval($error), 'details_nr'=> 0, 'details' => $details, 'log_data' => serialize(debug_backtrace()), 'tablename' => $this -> extKey, 'recuid' => 0, 'recpid' => 0, 'IP' => t3lib_div::getIndpEnv('REMOTE_ADDR'), 'tstamp' => $GLOBALS['EXEC_TIME'], 'event_pid' => -1, 'NEWid' => '', 'workspace' => 0 ); $GLOBALS['TYPO3_DB'] -> exec_INSERTquery('sys_log', $fieldsValues); } } if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/common_json_call/pi1/class.tx_commonjsoncall_pi1.php']) { include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/common_json_call/pi1/class.tx_commonjsoncall_pi1.php']); } ?>
Fichier xml: ../pi1/locallang.xml
<?xml version="1.0" encoding="utf-8" standalone="yes" ?> <T3locallang> <meta type="array"> <type>module</type> <description>Language labels for plugin "tx_commonjsoncall_pi1"</description> </meta> <data type="array"> <languageKey index="default" type="array"> <label index="common_json_call.label_error_object_created">Error => Object : is not created, or not an correct instance</label> <label index="common_json_call.label_error_object_created_interface_created">Error => Object or Interface : is not created, or no interface defined</label> <label index="common_json_call.label_error_mysql_setmysqlquery">Error => MySQL : </label> <label index="common_json_call.label_error_mysql_emptycriterias_returned">Error => MySQL : Select and from criterias are empty</label> <label index="common_json_call.label_error_http_empty_request_method">Error => HTTP : Empty REQUEST_METHOD returned</label> <label index="common_json_call.label_error_http_empty_url_vars_returned">Error => HTTP : Empty URL parameters request call returned : </label> <label index="common_json_call.label_notice_http_empty_dataset_returned">Notice => HTTP : Empty DataSet returned : </label> </languageKey> </data> </T3locallang>
3) Faire appel au plugin
Il suffit simplement d’installer le « plugin » comme un objet Plugin FE ordinaire dans Typo3, puis dans un navigateur d’appeler l’url avec comme paramètre la méthode demandée.
http://domain_name/path_to_plugin/?getPlaceDisponibleByRegion.
L’iPhone va donc récupérer un « string » JSON avec une entête HTTP.
Vous pouvez faire évoluer ce plugin en ajoutant vos méthodes, getPlaceDisponibleByRegion est arbitraire.
domain_name correspond à l’instance (DNS) de votre installation Typo3.
Vous pouvez également coupler ce « plugin » avec une architecture REST pour vos url mais cela suppose une maintenance de votre fichier de configuration realurl (si évidemment vous utilisez ce « plugin » pour la réécriture de vos url).