Función recoge()

En la lección Recogida de datos se comentan algunos de los problemas asociados al uso de los datos enviados por los usuarios a través de los formularios. Debemos ser conscientes de que esos datos suponen una vía de entrada a nuestro sistema que puede ser utilizada por atacantes con malas intenciones. Una manera de tener en cuenta los aspectos comentados en esa lección puede ser la siguiente:

A continuación se definen dos funciones recoge(). La función recoge() básica, que recoge tanto datos sueltos (números y cadenas) como matrices, se puede utilizar en casi todos los ejercicios propuestos en estos apuntes es la primera. La segunda función, que permite definir valores por defecto o especificar los valores permitidos, se utilizará en programas que trabajen con bases de datos.

Función recoge() básica

// Función de recogida de datos
function recoge($key, $type = "")
{
    if (!is_string($key) && !is_int($key) || $key == "") {
        trigger_error("Function recoge(): Argument #1 (\$key) must be a non-empty string or an integer", E_USER_ERROR);
    } elseif ($type !== "" && $type !== []) {
        trigger_error("Function recoge(): Argument #2 (\$type) is optional, but if provided, it must be an empty array or an empty string", E_USER_ERROR);
    }
    $tmp = $type;
    if (isset($_REQUEST[$key])) {
        if (!is_array($_REQUEST[$key]) && !is_array($type)) {
            $tmp = trim(htmlspecialchars($_REQUEST[$key]));
        } elseif (is_array($_REQUEST[$key]) && is_array($type)) {
            $tmp = $_REQUEST[$key];
            array_walk_recursive($tmp, function (&$value) {
                $value = trim(htmlspecialchars($value));
            });
        }
    }
    return $tmp;
}

Esta función se puede utilizar para recoger datos escalares, como en el ejemplo siguiente:

<!-- Formulario -->
<form action="recoge-escalar-2.php">
  <p>Nombre: <input type="text" name="nombre"></p>

  <p>Apellidos: <input type="text" name="apellidos"></p>

  <p><input type="submit" value="Enviar"></p>
</form>
// Variables que recogen los datos
$nombre    = recoge("nombre");
$apellidos = recoge("apellidos");
Enlace a ejemplo

El código completo del programa que recibe los datos (recoge-escalar-2.php) podría ser el siguiente:

<?php
// Función de recogida de datos
function recoge($key, $type = "")
{
    if (!is_string($key) && !is_int($key) || $key == "") {
        trigger_error("Function recoge(): Argument #1 (\$key) must be a non-empty string or an integer", E_USER_ERROR);
    } elseif ($type !== "" && $type !== []) {
        trigger_error("Function recoge(): Argument #2 (\$type) is optional, but if provided, it must be an empty array or an empty string", E_USER_ERROR);
    }
    $tmp = $type;
    if (isset($_REQUEST[$key])) {
        if (!is_array($_REQUEST[$key]) && !is_array($type)) {
            $tmp = trim(htmlspecialchars($_REQUEST[$key]));
        } elseif (is_array($_REQUEST[$key]) && is_array($type)) {
            $tmp = $_REQUEST[$key];
            array_walk_recursive($tmp, function (&$value) {
                $value = trim(htmlspecialchars($value));
            });
        }
    }
    return $tmp;
}

// Variables que recogen los datos
$nombre    = recoge("nombre");
$apellidos = recoge("apellidos");

// Programa que utiliza los datos recogidos
if ($nombre == "") {
    print "<p>No ha escrito ningún nombre</p>";
} else {
    print "<p>Su nombre es <strong>$nombre</strong>.</p>\n";
}

if ($apellidos == "") {
    print "<p>No ha escrito ningún apellido</p>";
} else {
    print "<p>Sus apellidos son <strong>$apellidos</strong>.</p>\n";
}
?>

Esta función también se puede utilizar para recoger datos en forma de matriz, como en el ejemplo siguiente:

<!-- Formulario -->
<form action="recoge-matriz-2.php">
  <p>Primer apellido: <input type="text" name="apellidos[1]"></p>

  <p>Segundo apellido: <input type="text" name="apellidos[2]"></p>

  <p><input type="submit" value="Enviar"></p>
</form>
// Variables que recogen los datos
$apellidos = recoge("apellidos", []);
Enlace a ejemplo

El código completo del programa que recibe los datos (recoge-matriz-2.php) podría ser el siguiente:

<?php
// Función de recogida de datos
function recoge($key, $type = "")
{
    if (!is_string($key) && !is_int($key) || $key == "") {
        trigger_error("Function recoge(): Argument #1 (\$key) must be a non-empty string or an integer", E_USER_ERROR);
    } elseif ($type !== "" && $type !== []) {
        trigger_error("Function recoge(): Argument #2 (\$type) is optional, but if provided, it must be an empty array or an empty string", E_USER_ERROR);
    }
    $tmp = $type;
    if (isset($_REQUEST[$key])) {
        if (!is_array($_REQUEST[$key]) && !is_array($type)) {
            $tmp = trim(htmlspecialchars($_REQUEST[$key]));
        } elseif (is_array($_REQUEST[$key]) && is_array($type)) {
            $tmp = $_REQUEST[$key];
            array_walk_recursive($tmp, function (&$value) {
                $value = trim(htmlspecialchars($value));
            });
        }
    }
    return $tmp;
}

// Variable que recoge los datos
$apellidos = recoge("apellidos", []);

if ($apellidos[1] == "") {
    print "<p>No ha escrito su primer apellido</p>";
} else {
    print "<p>Su primer apellido es <strong>$apellidos[1]</strong>.</p>\n";
}

if ($apellidos[2] == "") {
    print "<p>No ha escrito su segundo apellido</p>";
} else {
    print "<p>Su segundo apellido es <strong>$apellidos[2]</strong>.</p>\n";
}
?>

El segundo argumento de la función recoge() sólo puede tener el valor [] (matriz vacía) o el valor "" (cadena vacía). El valor cadena vacía es el valor predeterminado del argumento, por lo que en la práctica únicamente hace falta escribir el segundo argumento (con el valor [], matriz vacía) cuando se quiera recoger un control cuyo nombre tiene forma de matriz. Si se da cualquier otro valor al segundo argumento, como muestra el siguiente ejemplo, la función genera un error E_USER_ERROR que detiene la ejecución del programa, lo que permite al programar detectar fácilmente este tipo de error.

En el ejemplo, el error cometido en el programa es que se ha escrito como segundo argumento "[]", cuando se debería haber escrito []. "[]" es una cadena que contiene los caracteres [], no una matriz vacía.

Incorrecto
<!-- Formulario -->
<form action="recoge-error-2.php">
  <p>Primer apellido: <input type="text" name="apellidos[1]"></p>

  <p><input type="submit" value="Enviar"></p>
</form>
// Variable que recoge el dato
$apellidos = recoge("apellidos", "[]");

Fatal error: Function recoge(): Argument #2 (\$type) is optional, but if provided, it must be an empty array or an empty string in ejemplo.php on line 19


Si a la función recoge() no se le indica el tipo correcto del dato que se quiere recoger, no se generará ningún error, pero no se recogerá ningún dato, como muestran los siguientes ejemplos.

  1. En el ejemplo siguiente, el error cometido en el programa es que no se ha escrito el segundo argumento ([]) para indicar que el dato recibido tiene estructura de matriz. El programa se ejecuta, pero la función recoge() no recoge ningún valor.
    Desaconsejado
    <!-- Formulario -->
    <form action="recoge-uso-incorrecto-1-2.php">
      <p>Primer apellido: <input type="text" name="apellidos[1]"></p>
    
      <p><input type="submit" value="Enviar"></p>
    </form>
    
    // Variables que recogen los datos
    $apellidos = recoge("apellidos");
    
    Enlace a ejemplo
  2. En el ejemplo siguiente, el error cometido en el programa es que se ha escrito el segundo argumento ([]), lo que indica que el dato recibido tiene estructura de matriz. El programa se ejecuta, pero como el dato recibido no tiene estructura de matriz, la función recoge() no recoge ningún valor.
    Desaconsejado
    <!-- Formulario -->
    <form action="recoge-uso-incorrecto-2-2.php">
      <p>Primer apellido: <input type="text" name="apellidos"></p>
    
      <p><input type="submit" value="Enviar"></p>
    </form>
    
    // Variables que recogen los datos
    $apellidos = recoge("apellidos", []);
    
    Enlace a ejemplo

Para tratar los datos recibidos se aplican las funciones htmlspecialchars() y trim():

trim(htmlspecialchars($_REQUEST[$var]))

De esta manera, si se reciben etiquetas (<>), las etiquetas no se borrarán, sino que se conservarán como texto.
Si se quisieran borrar las etiquetas, habría que aplicar también la función strip_tags():

trim(htmlspecialchars(strip_tags($_REQUEST[$var])))

Función recoge() ampliada

// Función de recogida de datos
function recoge($key, $type = "", $default = null, $allowed = null)
{
    if (!is_string($key) && !is_int($key) || $key == "") {
        trigger_error("Function recoge(): Argument #1 (\$key) must be a non-empty string or an integer", E_USER_ERROR);
    } elseif ($type !== "" && $type !== []) {
        trigger_error("Function recoge(): Argument #2 (\$type) is optional, but if provided, it must be an empty array or an empty string", E_USER_ERROR);
    } elseif (isset($default) && !is_string($default)) {
        trigger_error("Function recoge(): Argument #3 (\$default) is optional, but if provided, it must be a string", E_USER_ERROR);
    } elseif (isset($allowed) && !is_array($allowed)) {
        trigger_error("Function recoge(): Argument #4 (\$allowed) is optional, but if provided, it must be an array of strings", E_USER_ERROR);
    } elseif (is_array($allowed) && array_filter($allowed, function ($value) { return !is_string($value); })) {
        trigger_error("Function recoge(): Argument #4 (\$allowed) is optional, but if provided, it must be an array of strings", E_USER_ERROR);
    } elseif (!isset($default) && isset($allowed) && !in_array("", $allowed)) {
        trigger_error("Function recoge(): If argument #3 (\$default) is not set and argument #4 (\$allowed) is set, the empty string must be included in the \$allowed array", E_USER_ERROR);
    } elseif (isset($default, $allowed) && !in_array($default, $allowed)) {
        trigger_error("Function recoge(): If arguments #3 (\$default) and #4 (\$allowed) are set, the \$default string must be included in the \$allowed array", E_USER_ERROR);
    }

    if ($type == "") {
        if (!isset($_REQUEST[$key]) || (is_array($_REQUEST[$key]) != is_array($type))) {
            $tmp = "";
        } else {
            $tmp = trim(htmlspecialchars($_REQUEST[$key]));
        }
        if ($tmp == "" && !isset($allowed) || isset($allowed) && !in_array($tmp, $allowed)) {
            $tmp = $default ?? "";
        }
    } else {
        if (!isset($_REQUEST[$key]) || (is_array($_REQUEST[$key]) != is_array($type))) {
            $tmp = [];
        } else {
            $tmp = $_REQUEST[$key];
            array_walk_recursive($tmp, function (&$value) use ($default, $allowed) {
                $value = trim(htmlspecialchars($value));
                if ($value == "" && !isset($allowed) || isset($allowed) && !in_array($value, $allowed)) {
                    $value = $default ?? "";
                }
            });
        }
    }
    return $tmp;
}

En construcciónPor completar con ejemplos.