Tratamiento del AJAX en un solo fichero

Últimamente estoy desarrollando sobre todo con PHP+JavaScript (jQuery). En un proyecto interno de Arcadina, que hace un uso intensivo del AJAX, decidí probar un planteamiento diferente del utilizado hasta ahora.

Normalmente las llamadas a la parte AJAX del servidor se hacen a diferentes scripts PHP en función de lo que deseamos obtener (query) o guardar (submit). Por ejemplo, si estamos tratando una lista de clientes podemos llamar a get_cliente.php para solicitar los datos de un cliente pasando su identificador, podemos llamar a delete_cliente.php para eliminarlo o a save_cliente.php para guardar cambios en la ficha, etc. (tengo la manía de utilizar spanglish en el nombre de funciones, procedimientos, etc.). Ejemplo de llamada según esté método:

?Descargar form1.php
1
2
3
4
5
<form name="ejemplo1" action="/api/save_cliente.php" method="post">
<input type="hidden" name="id" value="<?= $cliente->id ?>">
Nombre: <input type="text" name="nombre" value="<?= $cliente->nombre ?>"><br/>
EMail: <input type="text" name="email" value="<?= $cliente->email ?>"><br/>
</form>

Según el proyecto va avanzando nos podemos encontrar con decenas de scripts: get_<em>loquesea</em>, delete_<em>loquesea</em>.... En cada uno de ellos añadir el código de validación, los includes, etc. Se me ocurrió centralizarlo en dos ficheros ajaxsubmit.php y ajaxquery.php, y utilizar un parametro para indicar la acción a realizar. Ejemplo de un formulario utilizando este sistema:

?Descargar form2.php
1
2
3
4
5
6
<form name="ejemplo2" action="/api/ajaxsubmit.php" method="post">
<input type="hidden" name="ajaxsubmit" value="save_cliente">
<input type="hidden" name="id" value="<?= $cliente->id ?>">
Nombre: <input type="text" name="nombre" value="<?= $cliente->nombre ?>"><br/>
EMail: <input type="text" name="email" value="<?= $cliente->email ?>"><br/>
</form>

Como se ve tampoco es que varíe mucho en la parte que se envía al navegador, se añade un nuevo campo oculto “ajaxsubmit” que indica la acción que queremos realizar. Veamos el ajaxsubmit.php:

?Descargar ajaxsubmit.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<?php
 
// Incluimos código mínimo de autenticación y librerías
include('includes.inc.php');
 
// Si no está logeado el usuario, devolvemos error
if (!isset($usuario_login) || ($usuario_login == '')) {
 
	$respuesta['resultado'] = 'nok';
	$respuesta['mensaje'] = 'Error: primero debe identificarse';
 
} else {
 
	$respuesta['resultado'] = 'nok';
	$respuesta['mensaje'] = 'Error no identificado';
 
	$ajaxsubmit = $_POST['ajaxsubmit'];
 
	// "AS_" viene de AjaxSubmit ... ;)	
	$funcion = 'AS_' . $ajaxsubmit;
	// Comprobamos que tenemos una función con ese nombre
	if (function_exists($funcion)) {
		$funcion(); // Llamamos a la función AS_ que indique el ajaxsubmit
	} else {
		$respuesta['resultado'] = 'nok';
		if ($ajaxsubmit == '') {
			$respuesta['mensaje'] = 'No se ha indicado "ajaxsubmit"';
		} else {
			$respuesta['mensaje'] = 'AjaxSubmit: orden "' . $ajaxsubmit . '" no reconocida.';
		}		
	}
 
}
 
// Si el resultado es NOK registramos en el log de errores
if ($respuesta['resultado'] == 'nok') {
	error_log('*** AJAXSUBMIT - error - ' . $respuesta['mensaje']);
}
 
// ---------------------------------------------------------------------------------------
// Devolvemos respuesta en formato JSON
$json = new Services_JSON;
echo $json->encode($respuesta);
 
exit;
 
// =======================================================================================
// Procesos de guardar, eliminar, actualizar, etc...
// =======================================================================================
 
function AS_save_cfg() {
 
	global $respuesta;
	$cfg = array();
 
	foreach($_POST as $clave => $valor) {
		// Comprobar si $clave empieza por 'cfg_'
		if (ereg('^cfg_', $clave)) {
			$k = ereg_replace("^cfg_", "", $clave);
			$cfg[$k] = $valor;
		}
	}
	$errormsg = SaveConfig($cfg);
	if ($errormsg == 'ok') {
		$respuesta['resultado'] = 'ok';
		$respuesta['mensaje'] = 'Configuración guardada';
	} else {
		$respuesta['resultado'] = 'nok';
		$respuesta['mensaje'] = $errormsg;
	}
 
}
 
function AS_save_cliente() {
 
	global $respuesta;
 
	// Código simplificado para el ejemplo: se debería
	// añadir validación y protección contra SQL injection
	$cliente = new Cliente($_POST['id']);
	$cliente->nombre = $_POST['nombre'];
	$cliente->email = $_POST['email'];
 
	$errormsg = $cliente->Save();
	if ($errormsg == 'ok') {
		$respuesta['resultado'] = 'ok';
		$respuesta['mensaje'] = 'Cliente guardado';
	} else {
		$respuesta['resultado'] = 'nok';
		$respuesta['mensaje'] = $errormsg;
	}
}
 
?>

La “gracia” del código está en ésta parte:

19
20
21
22
23
24
25
	$ajaxsubmit = $_POST['ajaxsubmit'];
 
	// "AS_" viene de AjaxSubmit ... ;)	
	$funcion = 'AS_' . $ajaxsubmit;
	// Comprobamos que tenemos una función con ese nombre
	if (function_exists($funcion)) {
		$funcion(); // Llamamos a la función AS_ que indique el ajaxsubmit

Lo que se envia en la variable “ajaxsubmit” es el nombre de la función a llamar, pero la precedemos de “AS_” para evitar conflictos con otros nombres de funciones. Entonces gracias a la magia de PHP, porque es un lenguaje de script, aprovechamos la variable $funcion para realizar una llamada a una funcion: $funcion()

¿Qué ventajas tiene este sistema? Tenerlo centralizado, de esa forma, añadir una capa de autenticación o variar el formato de salida de JSON a XML, por ejemplo, es mucho más sencillo. Evidentemente si alguna función es compleja se puede separar en un fichero aparte y hacer el correspondiente include, que además lo recomiendo.

Para facilitar la labor de serializar los inputs de un formulario y enviarlo por ajax estoy utilizando el plugin ajaxForm para jQuery.