Search code examples
javascriptphphtmlxmlhttprequest

Why is xhr.response set to null if a warning arises in my PHP code?


For clarification: the .php files are my professor's and are intended NOT to be changed. The problem arises because $VALORES in line 186 is uninitialized, and I already issued it to my professor.

This is the code:

  • nueva.html:
<form onsubmit="crearPublicacion(event);">
    <div>
        <label>Título:</label>
        <input type="text" name="titulo">
    </div>
    <div>
        <label>Texto:</label>
        <textarea name="texto"></textarea>
    </div>
    <div>
        <label>Zona:</label>
        <input type="text" name="zona">
    </div>
    <div id="fotos">
        <div>
            <input type="file" name="fotos[]" accept="image/*">
            <textarea name="descripciones[]"></textarea>
        </div>
        <div>
            <input type="file" name="fotos[]" accept="image/*">
            <textarea name="descripciones[]"></textarea>
        </div>
        <div>
            <input type="file" name="fotos[]" accept="image/*">
            <textarea name="descripciones[]"></textarea>
        </div>
        <div>
            <input type="submit">
        </div>
    </div>
</form>
  • nueva.js:
function crearPublicacion(evt) {
    evt.preventDefault();   // Cancela el comportamiento por defecto

    let xhr = new XMLHttpRequest(),
        url = 'api/publicaciones',
        fd  = new FormData(evt.currentTarget),
        usu = JSON.parse(sessionStorage['_datos_']),
        auth = `${usu.LOGIN}:${usu.TOKEN}`;
    
    xhr.open('POST', url, true);
    xhr.responseType = 'json';
    xhr.onload = (evt) => {
        let r = xhr.response;

        console.log(r);
    };
    xhr.setRequestHeader('Authorization', auth);
    xhr.send(fd);
}
  • publicaciones.php:
<?php
require_once('../inc/config.php'); // Constantes, etc ...
require_once('../inc/database.php');

$db    = new Database();
$dbCon = $db->getConnection();
$dbCon->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);

header("Access-Control-Allow-Orgin: *");
header("Access-Control-Allow-Methods: POST");
header("Content-Type: application/json; charset=UTF-8");

$RECURSO = explode("/", substr($_GET['prm'],1));
$headers = apache_request_headers();
if(isset($headers['Authorization']))
    $AUTORIZACION = $headers['Authorization'];
elseif (isset($headers['authorization']))
    $AUTORIZACION = $headers['authorization'];

if(!isset($AUTORIZACION))
{ // Acceso no autorizado
  $RESPONSE_CODE    = 403;
  $R['RESULTADO']   = 'ERROR';
  $R['CODIGO']      = $RESPONSE_CODE;
  $R['DESCRIPCION'] = 'Falta autorización';
}
else
{
  $R             = [];  // Almacenará el resultado.
  $RESPONSE_CODE = 200; // código de respuesta por defecto: 200 - OK
  $PARAMS = $_POST;
  list($login,$token) = explode(':', $AUTORIZACION);

  if( !$db->comprobarSesion($login,$token) )
  {
    $RESPONSE_CODE    = 401;
    $R['RESULTADO']   = 'ERROR';
    $R['CODIGO']      = $RESPONSE_CODE;
    $R['DESCRIPCION'] = 'Error de autenticación.';
  }
  else
  {
    $ID = array_shift($RECURSO);
    try{
      $dbCon->beginTransaction();
      if(!is_numeric($ID)) // NUEVO REGISTRO
      { // Si no es numérico $ID es porque se está creando un nuevo registro
        $titulo        = $PARAMS['titulo'];
        $texto         = nl2br($PARAMS['texto'],false);
        $zona          = $PARAMS['zona'];
        $descripciones = $PARAMS['descripciones'];

        $mysql = 'select * from zona where nombre=:ZONA';
        $RESPUESTA = $db->select($mysql, [':ZONA'=>$zona]);
        if( $RESPUESTA['CORRECTO'] ) // execute query OK
        {
          if(count($RESPUESTA['RESULT']) > 0)
          { // encontrado
            $idZona = $RESPUESTA['RESULT'][0]['id'];
          }
          else
          { // No existe la zona. Hay que crearla.
            $mysql = 'insert into zona(nombre) values(:NOMBRE)';
            if( $db->executeStatement($mysql, [':NOMBRE'=>$zona]) )
            {
              $mysql = 'select max(id) as idZona from zona';
              $RESPUESTA = $db->select($mysql, $VALORES);
              if( $RESPUESTA['CORRECTO'] ) // execute query OK
              {
                if(count($RESPUESTA['RESULT']) > 0)
                { // encontrado
                  $idZona = $RESPUESTA['RESULT'][0]['idZona'];
                }
              }
            }
          }
        }

        $mysql  = 'insert into publicacion(titulo,texto,idZona,autor) ';
        $mysql .= 'values(:TITULO,:TEXTO,:ID_ZONA,:AUTOR)';
        $VALORES             = [];
        $VALORES[':TITULO']  = $titulo;
        $VALORES[':TEXTO']   = $texto;
        $VALORES[':ID_ZONA'] = $idZona;
        $VALORES[':AUTOR']   = $login;

        if( $db->executeStatement($mysql, $VALORES) )
        {
          $mysql = "select MAX(id) as id_pub from publicacion";
          $RESPUESTA = $db->select($mysql);
          if($RESPUESTA['CORRECTO'])
          {
            $ID = $RESPUESTA['RESULT'][0]['id_pub'];

            $RESPONSE_CODE    = 201;
            $R['RESULTADO']   = 'OK';
            $R['CODIGO']      = $RESPONSE_CODE;
            $R['DESCRIPCION'] = 'Registro creado correctamente';
            $R['ID']          = $ID;
            $R['TITULO']      = $titulo;
            $R['FOTOS']       = $fotos;
          }
          else
            $ID = -1;
        }
        else
        {
          $RESPONSE_CODE    = 500; // INTERNAL SERVER ERROR
          $R['RESULTADO']   = 'ERROR';
          $R['CODIGO']      = $RESPONSE_CODE;
          $R['DESCRIPCION'] = 'Error indefinido al crear el nuevo registro';
        }
      }
      $dbCon->commit();
    }catch(Exception $e){
      echo $e;
      $dbCon->rollBack();
    }
  }
}
$dbCon = null;
http_response_code($RESPONSE_CODE);
echo json_encode($R);
?>
  • database.php:
<?php
class Database{
    private $HOST              = "127.0.0.1";
    private $DB_DATABASE_NAME  = "websocial";
    private $DB_USERNAME       = "pcw";
    private $DB_PASSWORD       = "pcw";
    public  $conn;

    public function __construct() {...}

    public function select($query = "" , $params = [])
    {
        $result = false;
        $RESPUESTA = [];

        try {
            $stmt = $this->conn->prepare( $query );
            if($stmt === false) {
                throw New Exception("Unable to do prepared statement: " . $query);
            }

            $stmt->execute($params);

            $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
            $stmt->closeCursor();
            $RESPUESTA['CORRECTO'] = true;
        } catch(Exception $e) {
            // throw New Exception( $e->getMessage() );
            $RESPUESTA['CORRECTO'] = false;
            $RESPUESTA['ERROR'] = $e->getMessage();
        }

        $RESPUESTA['RESULT'] = $result;

        return $RESPUESTA;
    }

    public function executeStatement($query = "", $params = [])
    {
        $retVal = false;
        try {
            $stmt = $this->conn->prepare( $query );
 
            if($stmt === false) {
                throw New Exception("Unable to do prepared statement: " . $query);
            }
 
            if( $params )
                $retVal = $stmt->execute($params);
            else
                $retVal = $stmt->execute();

        } catch(Exception $e) {
            throw New Exception( $e->getMessage() );
        }   

        return $retVal;
    }

}
?>

As you can see, $VALORES (publicacion.php) has not been initialized when calling select in line 186 (that is the warning it gives me whenever I try to do a POST in Postman). The solution is as easy as to remove $VALORES altogether.

To give you an idea, this is the expected result:

{
    "RESULTADO": "OK",
    "CODIGO": 201,
    "DESCRIPCION": "Registro creado correctamente",
    "ID": 109,
    "TITULO": "",
    "FOTOS": []
}

But this is what happens only when the value for "zona" does not exist yet in the database (if it already exists, then the response is always the above):

  • Postman:

Warning: Undefined variable $VALORES in C:\xampp\htdocs\pcw\practica2\api\post\publicaciones.php on line 186

{
    "RESULTADO": "OK",
    "CODIGO": 201,
    "DESCRIPCION": "Registro creado correctamente",
    "ID": 109,
    "TITULO": "",
    "FOTOS": []
}
  • JS console in VSCode debugger / Web DevTools:
null

As I stated before, I already solved the problem, but my question is about why does it happen and where exactly.

What moment my xhr.response sets to null? The only explanation I could find is that rising a warning somehow nullifies the response even if the code is running alright (i.e., without falling into catch and throw statements and without exiting at all).


Solution

  • https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType#value:

    When setting responseType to a particular value, the author should make sure that the server is actually sending a response compatible with that format. If the server returns data that is not compatible with the responseType that was set, the value of response will be null.

    With xhr.responseType = 'json'; you told the browser, that the response would be JSON, and that it should automatically parse it as such for you.

    But "random PHP warning message followed by some JSON" is not valid JSON. And so it set the value of your response to null.