Manejador de sesiones propio en PHP y MySQL

Introducción

Vamos a ver hoy cómo implementar un manejador de sesiones en PHP hecho por nosotros mismos para poder entender a fondo cómo funcionan.

Como todos sabemos, PHP provee el uso de sesiones cuya persistencia es lograda a través de archivos en el sistema.

Es decir, guarda y lee los datos de las sesiones en archivos (imaginemos que lo hace en ficheros txt para darnos una pequeña idea).

Esto está bien si no usaremos las sesiones en múltiples peticiones AJAX, en donde puede desatarse una lectura concurrente del archivo, lo que ocasionará bloqueos.

Debido a que PHP bloquea el fichero cuando se está leyendo, si se desea abrir el mismo, se generará un error. Por ello es que hoy veremos cómo implementar nuestro propio manejador de sesiones.

Será implementado en MySQL utilizando PDO. Vamos allá.

Antes de todo

Recomiendo que el lector de este post esté familiarizado con las sesiones y con la conexión entre MySQL y PHP. Aquí dejo algunos sitios útiles:

Pequeña introducción a las sesiones

Antes de crear nuestro propio manejador de sesiones seguro, debemos entender lo que es una sesión y su funcionamiento.

Una sesión es algo que persiste en el servidor incluso cuando el usuario abandona la página. Se identifica por medio de un ID, por ejemplo 123.

Este id, la mayoría de veces (aunque hay mejores opciones) es almacenado en una cookie de nuestro navegador.

Cuando visitamos un sitio, el navegador envía esa cookie y el servidor busca si la tiene registrada. En caso de que sí, entonces llena el arreglo superglobal $_SESSION y con eso el programador puede trabajar.

En caso de que no exista, o no hayan datos, no habrá nada en el arreglo superglobal.

Luego, como programadores, comprobamos con algo así:

Y todo va bien.

Eso fue una pequeña introducción, si quieres saber más sobre sesiones de PHP haz click aquí. Ahora sí vamos al código.

Código fuente

He aquí el código. Si quieres la explicación, continúa leyendo 🙂

Preparar base de datos y crear tabla

Vamos a loguearnos en nuestro servidor MySQL y crearemos la siguiente tabla:

En este caso mi base de datos se llama test, tú puedes usar la tuya, pero recuerda cambiar las credenciales en el código PHP.

Id

Este será el id de sesión, una cadena aleatoria como 21312hsk12wk21jw21321. Lo dejamos en 255 para no quedar cortos de espacio

Datos

Lo almacenamos como texto, pues serán los datos serializados. Es decir, los datos que guardemos serán “planchados”.

Para darnos una idea, veamos lo siguiente…

Un lindo arreglo como:

Se serializará  y se convertirá en algo como:

Esto es convertir cualquier tipo de dato complicado en una cadena, para almacenarla. Para recuperar los datos simplemente hacemos el proceso inverso y listo.

Nota: dije “algo como” porque el algoritmo que utiliza PHP cuando serializa los datos de sesión es diferente al que utilicé para el ejemplo, pero el resultado es similar. Si quieres puedes probar llamando a la función serialize y pasándole algún dato 😉

Último acceso

Esto sirve para 2 cosas hasta donde yo sé. Una de ellas es para que podamos hacer cálculos y eliminar las sesiones viejas cuando el recolector de basura nos lo pida.

También podemos, gracias a este dato, saber cuándo fue la última vez que determinado usuario se logueó o realizó algún movimiento en la sesión.

Implementando interfaz para escribir nuestro manejador de sesiones

Creamos una clase que va a implementar la interfaz SessionHandlerInterface. Así:

Recordemos que una interfaz está hecha para que sus métodos sean sobrescritos. Vamos a implementar, por lo tanto, los métodos:

  • close
  • destroy
  • gc
  • open
  • read
  • write

No daré una descripción detallada de cómo funcionan los métodos, aquí dejo la referencia.

Sobrescribiendo métodos de la interfaz

Método open

Este es el método que es llamado siempre, lo que hace es abrir el almacenamiento de la sesión. Aquí es en donde vamos a instanciar nuestra base de datos.

Debemos devolver un booleano indicando si el almacenamiento pudo ser abierto o no.

Esta función es llamada con la ruta de guardado y el nombre de la sesión. La ruta de guardado es la ruta del directorio en donde se guardan las sesiones por defecto (por ejemplo C:\users\tmp ).

El segundo parámetro es el nombre de la sesión. Por defecto es PHPSESSID, y este identificador también se usa en las cookies para identificarnos.

No vamos a utilizar ninguno de estos parámetros, ya que nosotros vamos a guardar todo en MySQL. El código se explica pero igual aquí dejo la lista de variables

  • $pass: la contraseña con la que accedemos a nuestro servidor. Déjala en blanco si no tienes contraseña, pero ponla en caso contrario.
  • $usuario: el usuario, por ejemplo root.
  • $nombre_base_de_datos: el nombre de nuestra base de datos. En este caso es test, pero si usas otra recuerda cambiarla aquí.
  • $host: el host en donde MySQL escucha, que normalmente es localhost. Pero en determinados casos podemos utilizar MySQL remoto, así que cambiaríamos el host por una ip como 152.32.63.55.

Intentamos instanciar el objeto y si no se genera ninguna excepción regresamos true. En caso de que no, atrapamos la excepción y regresamos false.

Notar por favor que esta excepción debe ser manejada por nosotros. Se me ocurre escribirla en un log.

Método close

Cuando algo se abre también se tiene que cerrar. Cuando PHP deja de leer o escribir en la sesión, llama a este método. Aquí simplemente regresamos true y eliminamos las referencias a nuestra base de datos.

¿por qué eliminar la referencia a la base de datos, no se elimina por sí sola? claro que sí, pero al final de nuestro script. Hay casos en los que leemos o escribimos en la sesión y realizamos otras actividades.

Si dejamos abierta la conexión y hacemos algunas otras tareas, la conexión quedará abierta, gastando recursos. Mejor prevenir que lamentar 🙂

Método write

Este método es llamado cuando se hace un update o un insert. Es decir, cuando iniciamos sesión por primera vez, algo así:

Y también cuando actualizamos un dato, por ejemplo, si ya habíamos iniciado la sesión antes pero vamos a actualizar el nombre hacemos esto:

Puede que no notemos la diferencia, pero primero insertamos y en el segundo caso sólo actualizamos. PHP llama a este método en ambos casos. He aquí el código:

Hacemos un REPLACE INTO, que es algo como decirle al servidor: si ya existe el id que te estoy pasando, sólo actualiza los datos. Pero si no, inserta uno nuevo.

Por cierto, veamos que PHP nos pasa los parámetros id de sesión y datos de sesión. Los datos son una cadena serializada de los verdaderos datos (qué redundancia).

Ah, y también ponemos el último acceso llamando a time. Eso devuelve el número de segundos transcurridos desde la fecha Unix, o el 1 de enero de 1970.

Método read

PHP siempre esperará una cadena (aunque sea vacía) como resultado de llamar a este método. Nos pasa como parámetro el id de la sesión.

Lo buscamos en nuestra base de datos. Si existe, devolvemos los datos serializados (PHP se encarga de des-serializarlos y llenar el arreglo superglobal $_SESSION por nosotros).

Pero si no existe, entonces regresamos "".

Método destroy

Cuando se cierra una sesión, ya sea porque el gc (recolector de basura) lo pida o porque el usuario cierre su sesión (con session_destroy) se llamará a este método:

Lo que hacemos es eliminar el dato de nuestra base de datos. Por favor hay que observar que se llama a esta función con el id de sesión.

Método gc o garbage collector

PHP llama periódicamente a este método. Le pasa el tiempo de vida máximo que puede tener una sesión. Por ejemplo, si tiene 24 horas, un día (60 segundos * 60 minutos * 24 horas) nos llamará con el número 86400.

Con ese número y el tiempo actual hacemos una resta. Si hay sesiones cuyo último acceso haya sido antes de el resultado (por ejemplo, ayer) entonces las eliminamos. Esto es por seguridad, ya que no podemos dejar abierta una sesión por siempre.

Y listo, con este método terminamos.

Ejemplo de uso

En el ejemplo más básico hacemos un include_once de la clase, la instanciamos, indicamos a PHP que utilice ese manejador y listo, todo correcto.

Veamos este código que inicia la sesión:

Como vemos, sólo las 3 primeras líneas son diferentes a lo que usualmente hacemos. Aparte de eso, llamamos a session_start como siempre. Y asignamos valores a $_SESSION.

Quiero que veamos una cosa importante, cuando iniciamos sesión (en el ejemplo de arriba) y luego vamos a nuestra base de datos, esto pasa:

Datos de sesión guardados gracias a nuestro manejador de sesiones
Datos de sesión guardados gracias a nuestro manejador de sesiones

Como vemos, los datos han sido guardados. También vemos que han sido serializados de alguna forma, y en la columna de último acceso vemos un entero que representa el tiempo.

Para leer, hacemos esto:

Y para cerrar esto:

Las ventajas de todo esto es que tenemos el control casi total de las sesiones. Podemos cerrarlas haciendo una simple consulta, o cosas de esas.

El manejador ya está programado, es cuestión del desarrollador implementar bien los métodos y tal vez agregar algunas cosas 🙂

Por cierto, debemos registrar a nuestro manejador siempre, con estas líneas:

Recomiendo tener otra clase que maneje las sesiones, así cada vez que lo llamemos se encargará de registrar el manejador, pero eso es otra historia que será contada en otro momento.

Encantado de ayudarte


Estoy disponible para trabajar en tu proyecto, modificar el programa del post o realizar tu tarea pendiente, no dudes en ponerte en contacto conmigo.

No te pierdas ninguno de mis posts

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.

4 comentarios en “Manejador de sesiones propio en PHP y MySQL”

  1. No quisiera ser grosero, asi que intentare ir al grano.
    ¿que ganas haciendo esto contra el hacer tu propio sistema de sesiones SIN sesiones?
    ya que vas a tener que llamar a la base de datos a cada paso que das, si simplemente almaceno la IP como id de la ‘sesion’ y con el control de tiempos e incluso navegador usado me sobra para mejorar todo lo que las sesiones te pueden ofrecer y gano en seguridad.
    Pero si quieres tienes razones para seguir con tu idea, por favor! convenceme

    1. Hola. Por defecto las sesiones usan archivos, el problema aparece cuando se quiere modificar la sesión de manera concurrente. Los motores de bases de datos están pensados para manejar la concurrencia, por ello es que a veces se necesita usar una base de datos en lugar de los archivos.
      No veo la necesidad de convencerlo, me imagino que usted llegó a mi blog buscando en Google o algo así, ni siquiera lo conozco y no lo obligué ni invité a usar lo que aparece aquí. Usted puede usar lo que prefiera.
      Saludos!

  2. Pingback: Sistema web de pagos y cooperaciones open source con PHP - Parzibyte's blog

  3. Pingback: Cannot change save handler when session is active - Parzibyte's blog

Dejar un comentario