Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Tutorial en el que mostramos y enseamos los mtodos ms efectivos a la hora de validar y sanear datos en PHP. Todo programador debera conocer este tipo de tcnicas que permitirn mantener la integridad y seguridad de sus aplicaciones no os lo perdis!
La extensin Filter
Esta extensin de PHP nos expone un conjunto de funciones que nos permitirn la validacin y saneamiento de datos de una forma sencilla. La funcion filter_var nos permite filtrar una variable segn el filtro especificado. El orden de los parmetros es el siguiente:
$var: Variable que se quiere filtrar $filter: Filtro que se desea aplicar. Ser una constante numrica $options: Conjunto de opciones que modificarn el funcionamiento del filtro. Ser una constante numrica o un array
A continuacin un listado de filtros de validacin posibles: FILTER_VALIDATE_BOOLEAN Valida la variable como un booleano. FILTER_VALIDATE_EMAIL Valida la variable como una direccin de correo electrnico correcta. FILTER_VALIDATE_FLOAT Valida que la variable sea del tipo float. FILTER_VALIDATE_INT Valida la variable como un nmero entero.
FILTER_VALIDATE_IP Valida la variable como una direccin IP. FILTER_VALIDATE_REGEXP Valida la variable contra una expresin regular enviada en la variable de opciones. FILTER_VALIDATE_URL Valida el valor coma una URL de acuerdo con la RFC 2396. A continuacin el listado de filtros para sanear: FILTER_SANITIZE_EMAIL Elimina todos los caracteres execpto letras, nmeros y !#$%&*+-/=?^_`{|}~@.[]. FILTER_SANITIZE_ENCODED Codifica la cadena como una URL vlida. FILTER_SANITIZE_MAGIC_QUOTES Aplica la funcin addslashes. FILTER_SANITIZE_NUMBER_FLOAT Elimina todos los caracteres excepto nmeros, +- y opcionalmente ,.eE. FILTER_SANITIZE_NUMBER_INT Elimina todos los caracteres excepto nmeros y los signos + -. FILTER_SANITIZE_SPECIAL_CHARS Escapa caracteres HTML y caracteres con ASCII menor a 32. FILTER_SANITIZE_STRING Elimina etiquetas, opcionalmente elimina o codifica caracteres especiales. FILTER_SANITIZE_STRIPPED Alias del filtro anterior. FILTER_SANITIZE_URL Elimina todos los caracteres excepto nmeros, letras y $-_.+!*(),{}|\\^~[]`<>#%;/?:@&=.
La funcin nos devolver el entero en caso de que la validacin sea correcta, en caso contrario, nos devolver FALSE.
1. 2. 3. 4. 5. 6. 7.
<?php $var = 'dos'; if(filter_var($var, FILTER_VALIDATE_INT) === false){ echo 'Valor incorrecto'; }else{ echo 'Valor correcto'; }
A la hora de validar un entero, podemos jugar con las opciones para hacer ajustar el comportamiento de la funcin a nuestras necesidades. En el siguiente ejemplo se muestra como validar un entero que est dentro de un rango especfico:
view plaincopy to clipboardprint?
1. 2. 3. 4. 5. 6. 7.
$options = array('min_range'=>10, 'max_range' => 20)); if(filter_var($var, FILTER_VALIDATE_INT, $options) === false){ echo 'Valor incorrecto'; }else{ echo 'Valor correcto'; }
Ahora vamos a ver como sanear un entero, para los que no entiendan, sanear significa limpiar, es decir, quitamos todo lo que no tiene cabida en nuestro campo.
view plaincopy to clipboardprint?
El cdigo anterior nos devuelve 23 debido a que elimina todos los caracteres no numricos de la cadena.
Sin embargo, con este filtro tenemos una opcion interesante que nos permite especificar cual es el caracter que separa los decimales.
view plaincopy to clipboardprint?
1. 2. 3. 4.
Saneamiento de textos
La limpieza de los textos es algo muy importante y por ello tenemos varios flags que modifican el comporamiento del filtro FILTER_SANITIZE_STRING: FILTER_FLAG_NO_ENCODE_QUOTES No codificar las comillas simples ni dobles. FILTER_FLAG_STRIP_LOW Elimina caracteres cuyo varlor ASCII sea menor a 32. FILTER_FLAG_STRIP_HIGH Elimina caracteres cuyo valor ASCII sea mayor a 127. FILTER_FLAG_ENCODE_LOW Codifica caracteres cuyo valor ASCII sea mennor a 32. FILTER_FLAG_ENCODE_HIGH Codifica caracteres cuyo valor ASCII sea mayor a 127. FILTER_FLAG_ENCODE_AMP Codifica ampersands (&). A continuacin algunos ejemplos:
view plaincopy to clipboardprint?
1. 2. 3. 4.
<php $text = '<p>"Hola mundo!"</p>'; echo filter_var($text, FILTER_SANITIZE_STRING); //Resultado: "Hola mundo!"
1. 2. 3. 4.
<php $text = '<p>"Hola mundo!"</p>'; echo filter_var($text, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES); //Resultado: "Hola mundo!"
1. <php 2. $text = '"Ontuts & Cokidoo"'; 3. 4. echo filter_var($text, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES | FILTER_FLAG_ ENCODE_AMP); 5. //Resultado: "Ontuts & Cokidoo"
Es importante que todas las variables externas, es decir, que nos llegan desde el lado del cliente ($_GET, $_POST, $_COOKIE) las obtengamos a travs de esta funcin para ahorrarnos disgustos. En el siguiente ejemplo vemos como obtener un parmetro GET de la peticin con esta funcin:
view plaincopy to clipboardprint?
1. 2. 3. 4. 5. 6. 7. 8.
<php $page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT); if($page !== null && $page !== false){ //El parametro ha sido enviado y es un entero }else{ //El parametro no se ha enviado o no es un entero }
Esta funcin nos devolver NULL si la variable no ha sido enviada, FALSE si la variable ha sido enviada pero no validada o el valor de la variable si ha sido enviada y validada. El primer parmetro indica de que mbito ser extrada la variable, y puede ser uno de los siguientes valores:
Otra funcin relacionada y muy interesante es filter_input_array que nos permite definir una serie de validaciones para mltiples campos. Es decir, podemos hacer la validacin todos los campos de un formulario en un solo paso. La definicin de los campos con sus validaciones se hace mediante un array como se muestra en el siguiente ejemplo:
view plaincopy to clipboardprint?
1. <?php 2. $args = array( 3. 'product_id' => FILTER_SANITIZE_ENCODED, 4. 'component' => array('filter' => FILTER_VALIDATE_INT, 5. 'flags' => FILTER_REQUIRE_ARRAY, 6. 'options' => array('min_range' => 1, 'max_range' => 10) 7. ), 8. 'versions' => FILTER_SANITIZE_ENCODED 9. ); 10. 11. $myinputs = filter_input_array(INPUT_POST, $args);
Esta funcin devuelve un array del tipo clave/valor que contiene los nombres de los campos junto con su filtrado.
Conclusin
Como puedes observar, PHP nos brinda una gran librera con la cual podemos llevar a cabo validaciones y saneamientos en cuestin de segundos, cosa que antes se haca bastante engorrosa.
Ahora no hay excusa que valga para asegurarnos de que nuestros datos estn bien limpios y seguros. Creando una capa de conexin abstracta a base de datos con PHP
Introduccin
Hasta hace muy poco, usaba una librera de PHP llamada ADOdb Lite para abstraerme un poco de la base de datos en mis proyectos. En realidad, no era por ser vago y no querer desarrolar mi propio cdigo, si no que la comenc a usar porque siempre uso procedimientos y funciones de MySQL en mis modelos y las funciones de la librera nativa de PHP no me permitan hacer llamadas a estos (no se ahora, antes no). Desde hace unos pocos aos la empec a utilizar y desde entonces la carpeta adodb_lite se iba copiando de proyecto en proyecto casi de manera inconsciente. Hasta que un da me encontr con la librera Mysqli (MySQL Improved Extension) que es una librera nativa de PHP que nos permite hacer muchas ms cosas, entre otras, ejecutar procedimientos de la base de datos . Mediante la librera nativa de PHP Mysqli podemos ejecutar procedimientos definidos previamente en nuestra base de datos. Fue en ese momento cuando decid borrar la librera AdoDB Lite para crear mi propio cdigo de comunicacin con la base de datos. La verdad, que en esto, se puede hacer desde la cosa ms sencilla (lo que vamos a ver en este tutorial) hasta cosas realmente complejas como doctrine. Supongo que es cuestin de gustos y del tipo proyectoa m personalmente me gustan las cosas sencillas y potentes, no me importa escribir un poco ms de cdigo mientras tenga control total con la aplicacin, vosotros que prefers?. Bueno pues vamos a ello!
Qu vamos a hacer?
Antes de nada os explicar un poco qu caractersticas va a tener nuestra capa de comunicacin:
Abstraccin del tipo de proveedor: esto es algo bsico, nunca sabes cundo puedes cambiar de tipo de servidor (base de datos). A m ya me ha pasado en un proyecto tener que cambiar de PostgresSQL a MySQL. Por desgracia fue en mi primer proyecto y todava no saba estas cosas, por lo tanto, a cambiar cdigo por todos lados. Instancia nica: nuestro cdigo ser construdo usando el patrn de diseo Singleton, lo que nos garantizar que slo se establece una conexin con la base de datos, para qu ms?. Autocargar en array: si ejecutamos consultas de extraccin de filas, nuestra capa nos devolver dicha informacin cargada en un array. Parmetros en consultas: se gestionarn de forma fcil los parmetros que se envan en las consultas.
abstract class DatabaseProvider{ /*Definicion de que mtodos y propiedades van a tener todos los proveedores*/ } class MySqlProvider extends DatabaseProvider { /*Implementacin de los mtodos definidos en la clase base*/ } class SQLServerProvider extends DatabaseProvider { /*Implementacin de los mtodos definidos en la clase base*/ }
No hace falta entender mucho de POO para entederlo, no?. Simplemente se define una clase abstracta para definir lo que todos sus descendientes deben de implementar, as nos aseguramos de que todos los proveedores que hagamos tengan la misma estructura. De modo que si hacemos algo como $ob->foo(), tanto nos d si $ob es de la clase MysqlProvider o SQLServerProvider, sabemos que dicho mtodo tiene que existir y que en teora debera hacer lo mismo en un motor u otro de base de datos. Al crear nuestra propia capa de comunicacin con la base de datos, no importa cul sea el motor de base de datos: MySQL, PostgreSQL, Oracle no tendremos que rescribir todo el cdigo creado anteriormente, ser abstracta a nuestro motor. Pues bien, una vez entendida la teora, vamos a definir realmente nuestra clase base para los proveedores, yo he includo el siguiente esquema, que es el bsico: ejecucin de consultas y control de errores, vosotros lgicamente lo podris ampliar cuanto queris.
1. abstract class DatabaseProvider 2. { 3. //Guarda internamente el objeto de conexin 4. protected $resource; 5. //Se conecta segn los datos especificados 6. public abstract function connect($host, $user, $pass, $dbname); 7. //Obtiene el nmero del error 8. public abstract function getErrorNo(); 9. //Obtiene el texto del error 10. public abstract function getError(); 11. //Enva una consulta 12. public abstract function query($q); 13. //Convierte en array la fila actual y mueve el cursor 14. public abstract function fetchArray($resource); 15. //Comprueba si est conectado 16. public abstract function isConnected(); 17. //Escapa los parmetros para prevenir inyeccin 18. public abstract function escape($var); 19. }
Como podis ver, los mtodos son todos abstractos para que se implementen en sus clases hijas.
1. class MySqlProvider extends DatabaseProvider 2. { 3. public function connect($host, $user, $pass, $dbname){ 4. $this->resource = new mysqli($host, $user, $pass, $dbname); 5. return $this->resource; 6. } 7. public function getErrorNo(){ 8. return mysqli_errno($this->resource); 9. } 10. public function getError(){ 11. return mysqli_error($this->resource); 12. } 13. public function query($q){ 14. return mysqli_query($this->resource,$q); 15. } 16. public function fetchArray($result){ 17. return mysqli_fetch_array($result); 18. } 19. public function isConnected(){ 20. return !is_null($this->resource); 21. } 22. public function escape($var){ 23. return mysqli_real_escape_string($this->resource,$var); 24. } 25. }
Me he enfocado en el proveedor de MySQL, que supongo que es el ms usado. Como podis ver, todas las funciones definidas en nuestra clase base, tienen su correspondiente funcin en la librera Mysqli, por lo que se puede decir que funciona a modo de wrapper.
Antes de continuar, es importante ver cul ser el formato de las consultas. Para que el cdigo permanezca limpio y poder escapar los parmetros (para prevenir inyecciones maliciosas), los parmetros sern especificados en un array, de forma que sern insertados en la consulta reemplazando al caracter ?. Veamos un ejemplo:
1. $bd->execute("SELECT * FROM foo WHERE id=?", array(5)) 2. $bd>executeScalar("SELECT id FROM foo WHERE id=? AND name like '%?%'"), array(5,"ivan"));
La implementacin de este patrn nos asegura que solo un objeto de dicha clase es creado. Para ello tenemos que poner el constructor como privado y llamarlo desde una funcin esttica, en este caso getConnection. En el constructor creamos una conexin a la base de datos y se comprueba si se ha establecido.
1. private function __construct($provider){ 2. if(!class_exists($provider)){ 3. throw new Exception("El proveedor especificado no ha sido implentado o aadido."); 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. } $this->provider = new $provider; $this->provider>connect("localhost","usuarioBaseDatos", "tuPassword", "tuBaseDatos"); if(!$this->provider->isConnected()){ /*Controlar error de conexion*/ } } public static function getConnection($provider){ if(self::$_con){ return self::$_con; } else{ $class = __CLASS__; self::$_con = new $class($provider); return self::$_con; } }
sendQuery
prepare
Este mtodo privado es importante, ya que es el que se encarga de limpiar los parmetros para prevenir errores tpicos e insertarlos en su sitio correspondiente. Para reemplazar se usa una expresin regular mediante la funcion preg_replace_callback(), que nos permite ejecutar una funcion para cada coincidencia encontrada. En este caso la callback es replaceParams (la cual definiremos a continuacin).
1. private function prepare($sql, $params){ 2. for($i=0;$i<sizeof($params); $i++){ 3. if(is_bool($params[$i])){ 4. $params[$i] = $params[$i]? 1:0; 5. } 6. elseif(is_double($params[$i])) 7. $params[$i] = str_replace(',', '.', $params[$i]); 8. elseif(is_numeric($params[$i])) 9. $params[$i] = $this->provider->escape($params[$i]); 10. elseif(is_null($params[$i])) 11. $params[$i] = "NULL"; 12. else 13. $params[$i] = "'".$this->provider->escape($params[$i])."'"; 14. } 15. 16. $this->params = $params; 17. $q = preg_replace_callback("/(\?)/i", array($this,"replaceParams"), $sql); 18. 19. return $q; 20. }
replaceParams
Esta es la funcin llamada por preg_replace_callback() y que se encarga de devolver el parmetro especificado para cada interrogante (?) encontrado en la consulta. Para hacer esto se usa la funcin next() que va moviendo el cursor del array para cada coincidencia.
1. private function replaceParams($coincidencias){ 2. $b=current($this->params); 3. next($this->params); 4. return $b; 5. }
execute y executeScalar
Estas son las funciones que vamos a usar pblicamente para realizar consultas y son muy parecidas. Tan solo se diferencian en que la primera recorre el resultado cargndolo en un array, mientras que la segunda simplemente devuelve la primera columna de la primera fila:
1. public function executeScalar($q, $params=null){ 2. $result = $this->sendQuery($q, $params); 3. if(!is_null($result)){ 4. if(!is_object($result)){ 5. return $result; 6. } 7. else{ 8. $row = $this->provider->fetchArray($result); 9. return $row[0]; 10. }
11. } 12. return null; 13. } 14. public function execute($q, $params=null){ 15. $result = $this->sendQuery($q, $params); 16. if(is_object($result)){ 17. $arr = array(); 18. while($row = $this->provider->fetchArray($result)){ 19. $arr[] = $row; 20. } 21. return $arr; 22. } 23. return null; 24. 25. }
Paso 4: Probarlo
Una vez hecho todo lo anterior la forma de uso es muy sencilla:
1. 2. 3. 4. //Como parmetro va el nombre del proveedor que queris cargar $db = DatabaseLayer::getConnection("MySqlProvider"); //Imprimira la estructura del array print_r($db>execute("SELECT id,email FROM users WHERE name like ? LIMIT 20",array("ontuts%"))); 5. //Imprime un valor nmerico 6. echo($db->executeScalar("SELECT count(*) FROM users WHERE active=?",array(true)));
Reflexin final
Antes de despedirme recordaros que os podis descargar el cdigo completo en la zona superior de la pgina. Como siempre, espero que la informacin compartida en este tutorial os resulte til, con el que pretendo demostraros que no hay que tener miedo a crear tus propias libreras, siempre y cuando sepis lo que ests haciendo. No olvidis dejar un comentario para cualquier duda, fallo o valoracin. Un saludo y hasta el prximo tutorial!
Luego creamos los botones para mostra u ocultar <table border=0> <tr> <td> <input type=button value=Mostrar onclick=mostrardiv() /> <input type=button value=Ocultar onclick=ocultardiv() /> </td> </tr> </table>