Como manejar objetos y sesiones de PHP cuando se usa AJAX

Publicado en Programación | 21 Comentarios

He estado trabajando en un pequeño proyecto personal ultimamente y me topé con el siguiente problema. Lo encontré muy mal documentado (al menos al momento de agregar AJAX) y ya que me costó un buen dolor de cabeza así pensé que sería buena idea publicar la solución. Ojalá este artículo ayude a alguien más a ahorrarse un par de horas y un derrame innecesario de bilis.

El problema 1.0

Este es el escenario. Estoy usando PHP orientado a objetos con el fin de poder guardar instancias en variables de sesión y asi poder pasar información constantemente entre documentos sin muchas complicaciones.

Hasta aquí nada especial pero tiene un pequeño detalle a tomar en cuenta, y es que cada que intentas hacer una operación con un objeto, PHP necesita tener a la mano la definición de la clase en la que está basado. Un ejemplo con código para explicarme mejor:

Supongan que tenemos una clase ‘usuario’ definida como:

<?php
// Archivo: usuario.php
class usuario {
    public $nombre;
    public $apellido;
    public function sayMyName() {
       echo $this->nombre . ‘ ‘ . $this->apellido;
    }
}
?>

En un segundo archivo creamos una instancia, la llenamos de datos y la asignamos a una variable de sesión para usarla más tarde:

<?php
// Archivo pag1.php
include(‘usuario.php’);
session_start();
$usuario = new usuario();
$usuario->nombre = ‘Gregory’;
$usuario->apellido = ‘House’;
$_SESSION[‘usuario’] = $usuario;
?>

Para terminar, en la siguiente página queremos desplegar el nombre de nuestro usuario:

<?php
// Archivo pag2.php
session_start();
$usuario = $_SESSION[‘usuario’];
$usuario->sayMyName();
?>

Parece lógico no? pues si, lo es hasta que lo ejecutamos y nos encontramos con un error pidiendonos la definición de la clase en pag2.php lo cual hasta cierto punto tiene sentido si entendemos como funciona PHP. Como nota, la definición debe de ir incluida antes de que llamemos a session_start(). Lo siguiente funcionaría correctamente:

<?php
// Archivo pag2.php
include(‘usuario.php’);
session_start();
$usuario = $_SESSION[‘usuario’];
$usuario->sayMyName();
//salida: “Gregory House”
?>

Simple y se resolvió con una sola linea extra de código, pero ese no era el verdadero problema, ahora viene lo interesante, que pasa cuando agregamos AJAX a la mezcla?

El problema 2.0

Cada que hacemos una llamada asíncrona a una página php tenemos que asumir que todos los archivos que incluimos están perdidos y que hay que llamarlos de nuevo si necesitamos usarlos…a excepción de la sesiones que se quedan almacenadas por su propia naturaleza.

Supongan que estamos usando el mismo código de arriba, pero ahora queremos ejecutar pag2 desde pag1 usando AJAX, entonces nos encontramos con un obstáculo extra, y es que gracias a que la sesión ya fue definida en pag1, al momento de que se llama a pag2, session_start() va a regresar un error diciendo que ya hay una sesión iniciada y que la ordén será ignorada.

Entonces solo eliminamos session_start() no? Nope por que resulta entonces que nos vamos a encontrar con otro error:

Fatal error: Unknown: The script tried to execute a method or access a
property of an incomplete object. Please ensure that the class
definition CLASE of the object you are trying to operate on was
loaded _before_ the session was started in Command line code on line #

El problema es que aunque tenemos la sesión inicializada, la definición de la clase no existe en nuestar segunda página, e incluso si tenemos el include() dentro del archivo, no le sirve a PHP por que no está antes del inicio de sesión y no hay manera de ponerla en el lugar correcto por que la sesión viene desde el primer archivo.

Después de estar intentando con todas las ideas que me encontraba en la red como modificar el valor de session_auto_start en la configuración de php y otros trucos de vudú muy poco prácticos y que no sirvieron de nada, por fin encontre una forma de hacer que funcione.

El truco es hacer lo mismo que hace session_start() con las variables de sesión pero manualmente, serializar.

Regresando al ejemplo de arriba, vamos a modificar pag1.php para serializar la instancia

<?php
// Archivo pag1.php
include(‘usuario.php’);
session_start();
$usuario = new usuario();
$usuario->nombre = ‘Gregory’;
$usuario->apellido = ‘House’;
$_SESSION[‘usuario’] = serialize($usuario);
?>

Y en pag2.php regresamos la variable a su forma original con unserialize()

<?php
// Archivo pag2.php
$usuario = unserialize($_SESSION[‘usuario’]);
$usuario->sayMyName();
//salida: “Gregory House”
?>

Noten que no incluí el archivo de definición de la clase (usuario.php).
Por alguna razón que aún no termino de entender, el include sigue sin funcionar, afortunadamente unserialize() es una de las funciones de PHP que permite declarar una función callback a la que llama como último recurso si está manejando un objeto y no encuentra la definición de la clase, se usa de la siguiente forma:

<?php
// Archivo pag2.php
ini_set('unserialize_callback_func', 'incluirClase');
function incluirClase($clase) {
    include(‘usuario.php’);
}

$usuario = unserialize($_SESSION[‘usuario’]);
$usuario->sayMyName();
//salida: “Gregory House”
?>

El parametro $clase es regresado automáticamente por PHP e incluye el nombre de la clase para la que no encontró definición.

Ahora si, eso resuleve todo y como este artículo ya se extendió mucho aquí lo termino. Si tienen alguna duda dejenla en los comentarios.

Nota: El código en el artículo no está depurado asi que no intenten ejecutarlo directamente, solo tomenlo como ejemplo.


Hay 21 Respuestas a este post, falta la tuya

Responde a este post