Skip to content
août 29 / David Regnier

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).

Par exemple un mobile peut tout à fait faire une requête HTTP à ce module et ainsi récupérer le résultat sous forme de « string » JSON avec une entête HTTP.

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

Le nom common_json_call est arbitraire pour le « plugin », de plus l’implémentation d’une interface n’est pas obligatoire

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.

Par exemple je souhaite obtenir depuis une application iPhone le nombre de places disponibles dans une région:
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).

Laisser un commentaire


8 + 6 =