Cuando trato con AJAX, que devuelvo como JSON, un truco que uso es aprovechar el almacenamiento en búfer de salida. No puede simplemente hacer eco o generar lo que quiera porque estropeará los datos JSON, por ejemplo,
ob_start(); //turn on buffering at beginning of script.
.... other code ...
print_r($somevar);
.... other code ...
$debug = ob_get_clean(); //put output in a var
$data['debug'] = $debug;
header('Content-Type: application/json');
echo json_encode($data); //echo JSON data.
Lo que esto hace es envolver cualquier resultado de su secuencia de comandos en sus datos JSON para que su formato no se arruine.
Luego, en el lado de javascript, puede usar console.log
$.post(url, input, function(data){
if(data.debug) console.log(data.debug);
});
Si no está acostumbrado a depurar con console.log()
, normalmente puedes pulsar F12
y abra el depurador en la mayoría de los navegadores. Luego, allí, la salida se enviará a la "consola". IE9 tuvo un pequeño problema con console.log()
si no recuerdo mal, pero no quiero desviarme demasiado.
//$data['debug'] = $debug;
Y luego su información de depuración no estará expuesta en producción. Hay otras formas de hacer esto automáticamente, pero depende de si realiza el desarrollo local y luego publica en el servidor. Por ejemplo, puede activarlo en $_SERVER['SERVER_ADDR'];
que será ::1
o 127.0.0.1
cuando es local. Esto tiene algunos inconvenientes, principalmente la dirección del servidor no está disponible desde la interfaz de línea de comandos (CLI). Por lo general, lo vincularé a una constante global que indica en qué "modo" se encuentra el sitio (incluido en el punto de entrada común, generalmente index.php).
if(!defined('ENV_DEVELOPMENT')) define('ENV_DEVELOPMENT','DEVELOPMENT');
if(!defined('ENV_PRODUCTION')) define('ENV_PRODUCTION','PRODUCTION');
if(!defined('ENVIRONMENT')) define('ENVIRONMENT',ENV_DEVELOPMENT);
//site is in Development mode, uncomment for production
//if(!defined('ENVIRONMENT')) define('ENVIRONMENT',ENV_DEVELOPMENT);
Entonces es una cuestión simple comprobarlo:
if(ENVIRONMENT == ENV_PRODUCTION ) $data['debug'] = $debug;
Si sabe cómo usar el informe de errores, incluso puede vincularlo usando
if(ini_get('display_errors') == 1) $data['debug'] = $debug;
Que solo mostrará la depuración cuando los errores de visualización estén activados.
Espero que ayude.
ACTUALIZAR
Como lo mencioné en los comentarios, aquí hay un ejemplo envuelto en una clase (esta es una versión simplificada, así que no la probé)
class LibAjax{
public static function respond($callback, $options=0, $depth=32){
$result = ['userdata' => [
'debug' => false,
'error' => false
]];
ob_start();
try{
if(!is_callable($callback)){
//I have better exception in mine, this is just more portable
throw new Exception('Callback is not callable');
}
$callback($result);
}catch(\Exception $e){
//example 'Exception[code:401]'
$result['userdata']['error'] = get_class($e).'[code:'.$e->getCode().']';
//if(ENVIRONMENT == ENV_DEVELOPMENT){
//prevents leaking data in production
$result['userdata']['error'] .= ' '.$e->getMessage();
$result['userdata']['error'] .= PHP_EOL.$e->getTraceAsString();
//}
}
$debug = '';
for($i=0; $i < ob_get_level(); $i++){
//clear any nested output buffers
$debug .= ob_get_clean();
}
//if(ENVIRONMENT == ENV_DEVELPMENT){
//prevents leaking data in production
$result['userdata']['debug'] = $debug;
//}
header('Content-Type: application/json');
echo self::jsonEncode($result, $options, $depth);
}
public static function jsonEncode($result, $options=0, $depth=32){
$json = json_encode($result, $options, $depth);
if(JSON_ERROR_NONE !== json_last_error()){
//debug is not passed in this case, because you cannot be sure that, that was not what caused the error. Such as non-valid UTF-8 in the debug string, depth limit, etc...
$json = json_encode(['userdata' => [
'debug' => false,
'error' => json_last_error_msg()
]],$options);
}
return $json;
}
}
Luego, cuando realice una respuesta AJAX, simplemente envuélvala así (tenga en cuenta que $resultado se pasa por referencia, de esta manera no tenemos que regresar, y en el caso de una excepción, actualizamos $resultado en "tiempo real" en su lugar de al finalizar)
LibAjax::respond( function(&$result){
$result['data'] = 'foo';
});
Si necesita pasar datos adicionales al cierre, no olvide que puede usar use
declaración, así.
$otherdata = 'bar';
LibAjax::respond( function(&$result) use($otherdata){
$result['data'][] = 'foo';
$result['data'][] = $otherdata;
});
Esto maneja la captura de cualquier salida y la pone en depuración, si el entorno es correcto (comentado). Por favor, asegúrese de implementar algún tipo de protección para que la salida no se envíe a los clientes en producción, no puedo enfatizar eso lo suficiente. También detecta cualquier excepción que lo pone en error. Y también maneja el encabezado y la codificación.
Un gran beneficio de esto es la estructura consistente de su JSON, sabrá (en el lado del cliente) que si if(data.userdata.error)
entonces tienes una excepción en el back-end. Le brinda un lugar para modificar sus encabezados, codificación JSON, etc.
Una nota en PHP7 tendrá que agregar o debería agregar la interfaz Throwable (en lugar de Exception). Si desea capturar clases de error y excepción o hacer dos bloques de captura.
Digamos que uso mucho AJAX y me cansé de reescribir esto todo el tiempo, mi clase real es más extensa que esta, pero esa es la esencia.
Saludos.
ACTUALIZAR1
Por lo general, esto se debe a que no está devolviendo el encabezado correcto al navegador. Si envía (justo antes de llamar a json_encode)
header('Content-Type: application/json');
Esto solo le permite al navegador saber qué tipo de datos está recuperando. Una cosa que la mayoría de la gente olvida es que en la web todas las respuestas se realizan en forma de texto. Incluso imágenes o descarga de archivos y páginas web. Todo es solo texto, lo que hace que ese texto sea algo especial es el Content-Type
que el navegador cree que es.
Una cosa a tener en cuenta sobre header
es que no puede generar nada antes de enviar los encabezados. Sin embargo, esto funciona bien con el código que publiqué porque ese código capturará todo el resultado y lo enviará después de enviar el encabezado.
Actualicé el código original para tener el encabezado, lo tenía en la clase más compleja que publiqué más tarde. Pero si agrega eso, debería deshacerse de la necesidad de analizar manualmente el JSON.
Una última cosa que debo mencionar que hago es verificar si obtuve JSON o texto, aún podría obtener texto en caso de que ocurra algún error antes de que se inicie el almacenamiento en búfer de salida.
Hay 2 formas de hacer esto.
Si los datos son una cadena que debe analizarse
$.post(url, {}, function(data){
if( typeof data == 'string'){
try{
data = $.parseJSON(data);
}catch(err){
data = {userdata : {error : data}};
}
}
if(data.userdata){
if( data.userdata.error){
//...etc.
}
}
//....
}
O si tiene el encabezado y siempre es JSON, entonces es un poco más simple
$.post(url, {}, function(data){
if( typeof data == 'string'){
data = {userdata : {error : data}};
}
if(data.userdata){
if( data.userdata.error){
//...etc.
}
}
//....
}
¡Espero que eso ayude!
ACTUALIZACIÓN2
Debido a que este tema surge mucho, puse una versión modificada del código anterior en mi GitHub, puedes encontrarlo aquí.
https://github.com/ArtisticPhoenix/MISC/blob/master /AjaxWrapper/AjaxWrapper.php