Para usar sesiones en Zend Framework 2 podríamos usar la funcionalidad nativa de PHP para manejar sesiones, pero rápidamente nos vamos a encontrar con que conforme la complejidad de la aplicación crece, se nos va a salir de las manos el manejo de todos los datos concernientes a las sesiones de nuestros usuarios. Es ahí donde entra en juego el componente Zend\Session, que hace parte de Zend Framework 2. En esta entrada explicaré cómo usar Zend\Session para iniciar, guardar y consultar información de una sesión. Debo notar que lo expuesto acá solo representa el uso más básico de Zend\Session, e información más completa acerca de todas las posibilidades ofrecidas se encuentra en la documentación oficial, así como en diferentes lugares en la red. Para empezar, sin embargo, considero que lo expuesto en esta entrata consiste en un buen punto de partida.

Para este ejemplo no usaré la aplicación esqueleto de Zend, sino sólamente los módulos necesarios para el manejo de sesiones. Usando composer, mi archivo composer.json es el siguiente:

{
	"require": {
		"zendframework/zend-session" : "2.3.*@dev",
		"zendframework/zend-eventmanager": "2.3.*@dev"
	}
}

Después de ejecutar php composer.phar install, procedemos a requerir vendor/autoloader.php. Usaré un solo archivo, index.php, para ejemplifcar el uso del componente. Primero, añadimos los siguientes usos:

<?php
require "vendor/autoload.php";

use Zend\Session\Config\SessionConfig;
use Zend\Session\Container;
use Zend\Session\SessionManager;

SessionConfig maneja los diferentes parámetros de configuración de la sesión, SessionManager maneja acciones de manejo de sesión como Inicio, Destruír, etc., y Container es la interfaz priparia para el manejo de los datos de sesión. Ahora iniciamos el SessionManager con las opciones deseadas:

<?php
$sessionConfig = new SessionConfig();
$sessionConfig->setOptions(array(
	'remember_me_seconds' => 180,
	'use_cookies' => true,
	'cookie_httponly' => true
	)
);
$sessionManager = new SessionManager($sessionConfig);
$sessionManager->start();
Container::setDefaultManager($sessionManager);

Container::setDefaultManager($sessionManager) lo usamos en caso de que estemos usando varios SessionManager, o prefiramos ser explícitos. A continuación vamos a mostrar al usuario la página en caso de que no se encuentre logeado. Hay que tener que en cuenta que una cosa es la sesión, y otra cosa es el login. La sesión siempre la iniciamos, y la información sobre si un usuario se encuentra autenticado en el sistema es información que se encuentra en la sesión. Se puede pensar en la sesión como una sesión de navegación. Es decir, usuarios del sitio que no estén autenticados también tendrán una sesión, sólo que ésta no estará asociada a un usuario en particular.

<?php
if (!isset($_REQUEST['action'])) {
	$session = new Container('userdata');
	if ($session && $session->username) {
		echo "Username: " . $session->username;
		?>
		<a href="index.php?action=logoff">Salir</a>
		<?php
	}
	else {
		?>
		<form action="index.php">
			<label for='username'>Username</label>
			<input type="text" name="username" id="username" />
			<input type="submit" value="login" name="action" />
		</form>
		<br/>
		<?php
	}
}

Para verificar que se trata de la página principal, verificamos que la acción no exista, y que el nombre de usuario (atributo username) no esté iniciado. Para acceder a los datos de la sesión es necesario iniciar el contenedor, $session = new Container('userdata'). Los contenedores son usados para dividir los datos, siendo en este caso username el nombre de estos datos. Más contenedores pueden ser creados, con otros nombres y para otros datos.

Si el usuario se encuentra autenticado, vamos a mostrar su nombre y proveer un vínculo para salir. Si no se encuentra autenticado, mostramos un formulario en el que pedimos el nombre; en nuestro caso “autenticarse” se refiere a dar el nombre. Ahora vamos a mirar cómo se realiza la autenticación. De acá lo que nos interesa es cómo se guardan datos en el contenedor:

<?php
if ($_REQUEST['action'] == 'login') {
	$session = new Container('userdata');
	$session->username = $_REQUEST["username"];
	echo "Sesion iniciada. Username: " . $session->username;	
	echo "<a href='index.php'>Volver</a>";
}

Acá igual, iniciamos el contenedor y guardamos la información del nombre de usuario directamente desde $_REQUEST; también proveemos un vínculo para ir a la página principal. Ahora veamos cómo funcionaría el cierre de sesión:

<?php
if ($_REQUEST['action'] == 'logoff') {
	$session = new Container('userdata');
	unset($session->username);
	echo "Sesion cerrada.";
	echo "<a href='index.php'>Volver</a>";
}

Simplemente borramos la información del contenedor con unset.

Como podemos ver, el sistema de contenedores que utiliza Zend Framework es muy útil porque nos permite tener la información que necesitemos de manera organizada. Con las opciones de configuración tenemos más control de nuestra sesión, además. En un entorno MVC como el de la aplicación esqueleto de Zend Framework 2, la inicialización del SessionManager podría ir en Module.php, mientras que el acceso y guardado de datos en los contenedores iría en acciones de los controladores.

Uno de los aspectos más interesantes y con menos frecuencia mirados de X, es su naturaleza cliente-servidor. Desde sus inicios, X fue diseñado de manera que pudiera sacar todo el provecho posible de la red. Esto nos permite realizar cosas muy interesantes, como trabajar en varios computadores al tiempo desde una sola máquina o enviar ventanas a otros computadores. Precisamente eso último es lo que voy a describir en esta entrada.

En mi escritorio cuento con dos máquinas en las que normalmente trabajo:

  • Un escritorio (viejo portátil convertido) con Ubuntu 12.04.
  • Un portátil con el Windows 7 con el que venía.

Me gusta más trabajar en el escritorio cuando se trata de temas de programación, puesto que me parece que Linux es un ambiente más amigable para estos menesteres. Sin embargo, por temas de compatibilidad de aplicaciones y hardware, también suelo estar un buen tiempo en el portátil. Dado que mi computador con Windows solo cuenta con 4 GB de memoria RAM y que Windows no es tan bueno manejando memoria cono Linux, muchas veces tengo que mover la silla y cambiar de máquina varias veces en unos minutos.

Acepto que este puede no ser el mejor motivador para configurar lo que describiré en esta entrada, pero el hecho de que se puede hacer y es interesante también es un motivo válido.

Para este tutorial vamos a mostrar una ventana de Linux en el escritorio de Windows, por medio el protocolo X. Para esto vamos a:

  1. Instalar X y SSH en Windows por medio de Cygwin
  2. Configurar SSH en Linux (asumo que SSHD ya está instalado)
  3. Conectar los dos computadores

Es mejor que los computadores involucrados estén conectados a la red por medio de cable, para mejorar el rendimiento.

Instalación de Cygwin

Cygwin es un proyecto que pretende dar a usuarios de Windows un entorno similar a Unix para aplicaciones nativas a Windows y para desarrollo. Dentro de las aplcaciones proveídas por Cygwin están el cliente SSH y el servidor X requeridos para lo que vamos a hacer. Para instalar Cygwin vamos a ir a la página del proyecto y seguir las instrucciones, según queramos la versión de 32 o 64 bits. Los paquetes que vamos a instalar son los siguientes:

  • xorg-server, es el servidor X para correr los programas gráficos.
  • xinit, es requerido para iniciar el servidor X.
  • openssh, para realizar conesiones seguras a los programas cliente (en mi caso la máquina con Ubuntu).

Instalación Cygwin

Podemos usar la barra de búsqueda para buscar los paquetes. Una vez instalado el entorno en la máquina Windows, vamos a proseguir con el siguiente paso.

Configurar SSH en Linux

Para este punto voy a asumir que el servidor de ssh ya se encuentra instalado en la máquina con Linux. Si no es así, en Ubuntu esto es fácil de hacer con apt-get install openssh. Vamos a configurar ssh para poder conectarnos seguramente de un equipo al otro.

Abrimos el archivo /etc/ssh/sshd_config con algún editor de texto, y nos aseguramos que la siguiente opción está activa (no está comentada y existe):

X11Forwarding yes

Esto habilitará la conexión segura entre el computador que tiene el programa y el que tiene el servidor X. De otra manera tendríamos que abrir el servidor X con la opción -ac, lo que deshabilita la autenticación y puede abrir nuestro equipo a ataques de seguridad.

Conectar las máquinas para usar la funcionalidad

Ahora que todo está configurado, vamos a iniciar un programa cliente en el computador con Linux, y usarlo en el computador con Windows. Primero, iniciamos el servidor X en Windows. Abrimos la terminal de Cygwin y introducimos el siguiente comando:

X -multiwindow &

Esto inicia el servidor X con la opción multiwindow, que significa que por cada ventana que el cliente cree en el computador con Linux, se va a crear una ventana de Windows. El parámetro &amp; nos devolverá el control una vez termine de iniciar el servidor, para poder seguir introduciendo comandos. Ahora vamos a iniciar la variable DISPLAY para que el reenvío por SSH funcione:

export DISPLAY=:0

Con esto podemos conectarnos al computador donde residen nuestros programas:

ssh -Y usuario@servidor

Nos pedirá la contraseña y una vez adentro, ya podemos iniciar la aplicación que queramos, la cual aparecerá en nuestro escritorio de Windows como una ventana mas, por ejemplo:

gnome-system-monitor

Una ventana más


Existe otra herramienta para realizar este proceso más fácilmente llamada xming, que provee un servidor X para Windows listo para ser usado. Sin embargo en mis pruebas el funcionamiento en general de Cygwin X fue muy superior, de modo que considero que el esfuerzo vale la pena.

Por supuesto, también es posible realizar el mismo proceso al revés. En esta caso la limitante está en que sólo los programas gráficos proveídos por Cygwin serán visibles en el escritorio de Linux usando este método. Es decir, tener Visual Studio, por ejemplo, funcionando en el escritorio de Linux no sería posible. Para lograr esto considero que lo más sensato es usar una aplicación de escritorio remoto como TeamViewer, o virtualizar un entorno Windows. Hay soluciones más óptimas pero requieren mucho más trabajo.

En esta entrada comentaré tres métodos de manejar Ajax. Para efectos de la entrada usaré PHP y jQuery, ya que son tecnologías que conozco y que me permiten codificar los ejemplos de manera compacta y rápida, pero otras tecnologías del lado del servidor y del cliente puede ser usada. La idea de la entrada es ejemplificar los conceptos usados.

AJAX es una manera de enviar solicitudes al servidor y actualizar la página con la información obtenida sin realizar una recarga completa. La idea es minimizar el tráfico de red usado, al evitar cargar cosas que ya hemos cargado como imágenes o cabeceras, al tiempo que agilizando la experiencia del usuario con nuestra aplicación. En la entrada usaré jQuery, que es una librería de JavaScript muy popular para, entre otras, ayudarnos con este tipo de tareas.

Por ejemplo, digamos que tenemos un formulario de un campo en el que escribimos un número de identificación, y un espacio de respuesta en el que debe aparecer el nombre de la persona que estamos buscando, así:

<!DOCTYPE html>
<html>
<head>
    <title>Consulta de usuario</title>
</head>
</html>
<body>
<h1>Consultar usuarios</h1>
<form id="form_consulta">
        <label for="documento">Documento: </label>
        <input type="text" id="documento" name="documento" />
<div id="respuesta"></div>
</form>
</body>
</html>

Guardamos el código en un archivo PHP, consulta.php. Tenemos así un formulario con id form_consulta, que tiene un único campo, documento, y un div con id respuesta. Vamos a empezar realizando el proceso sin usar AJAX. Para esto, cambiamos el archivo para que contenga lo siguiente:

<?php
$usuarios = array(
    '123' => 'Pepe Pelotas',
    '535' => 'Juana Juliana',
    '903' => 'Pancho Pecas'
    );
?>
<!DOCTYPE html>
<html>
<head>
    <title>Consulta de usuario</title>
</head>
</html>
<body>
<h1>Consultar usuarios</h1>
<form id="form_consulta" action="handler.php" method="post">
        <label for="documento">Documento: </label>
        <input type="text" id="documento" name="documento" value="<?php echo isset($_POST['documento'])?$_POST['documento']:'';?>" />
        <input type="submit" value="Consultar"></input>
    </form>
<div id="respuesta">
        <?php
        if($_SERVER['REQUEST_METHOD'] == 'POST'){

            if(in_array($_POST['documento'], array_keys($usuarios))){
                echo "Usuario: " . $usuarios[$_POST['documento']];
            }
            else{
                echo "Usuario no encontrado!";
            }
        }
        ?></div>
</body>
</html>

El arreglo $usuarios en este caso contiene la información que quedemos obtener. Normalmente este arreglo sería reemplazado por una base de datos, por ejemplo. El siguiente cambio que vemos es en el elemento input con id documento. Ahora vamos a poner en ese campo lo que sea que el usuario escribió en la solicitud anterior, o nada.

Dentro del div de respuesta viene la lógica del servidor que se va a encargar de realizar la consulta y escribirla en el documento. Sólo ejecutamos el código cuando se trata de una solicitud POST, y básicamente pintamos directamente lo que encontremos según la información proveída en la solicitud.

Por supuesto, el problema acá es que estamos recargando totalmente la página cada vez que hacemos una consulta. Estamos descargando la página completa cada vez que realizamos una consulta, y en esa descarga estamos volviendo a transmitir información que ya teníamos, como el título de la página. Eso es lo que queremos evitar.

Primer enfoque: carga parcial de HTML

En este primer enfoque la idea es cargar parcialmente un trozo de código HTML desde el servidor y reemplazar algún trozo definido de nuestro documento con el HTML cargado. Vamos a devolver únicamente el div con la información requerida, y con eso reemplazaremos el div que pusimos. Nuestro archivo quedará así:

<?php
$usuarios = array(
    '123' => 'Pepe Pelotas',
    '535' => 'Juana Juliana',
    '903' => 'Pancho Pecas'
    );
if($_SERVER['REQUEST_METHOD'] == 'GET'){
    ?>
    <!DOCTYPE html>
    <html>
    <head>
        <title>Consulta de usuario</title>
        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    </head>
    </html>
    <body>
        <h1>Consultar usuarios</h1>
        <form id="form_consulta" action="handler.php" method="post">
            <label for="documento">Documento: </label>
            <input type="text" id="documento" name="documento" />
            <input type="button" id="enviar" value="Consultar"></input>
        </form>
        <div id="respuesta">
        </div>
        <script type="text/javascript">
            $(function(){
                $("#enviar").click(function(){
                    $.ajax({
                        type: "POST",
                        url: $("#form_consulta").attr("action"),
                        data: $("#form_consulta").serialize()
                    }).success(function(data){
                        $("#respuesta").replaceWith(data)
                    }).error(function(){
                        alert("error!")
                    })
                })
            })
        </script>
    </body>
    </html>
<?php
}
if($_SERVER['REQUEST_METHOD'] == 'POST'){
    if(in_array($_POST['documento'], array_keys($usuarios))){
        echo '<div id="respuesta">';
        echo "Usuario: " . $usuarios[$_POST['documento']];
        echo '</div>';
    }
    else{
        echo '<div id="respuesta">';
        echo "Usuario no encontrado!";
        echo '</div>';
    }
}

Tomemos un vistazo al código. Lo que más salta a la vista es que ahora estamos dividiendo claramente el cógido entre POST y GET. Cuando el método es GET, mostramos la misma página de consulta que veníamos mostrando, más un código en Javascript que miraremos en detalle más adelante. Si el método es POST, por otro lado, sólo mostramos un div, con el resultado del procesamiento en el servidor.

En el código Javascript hacemos uso de jQuery (que previamente cargamos desde Google) para hacer el llamado AJAX a nuestra página con el método POST y los datos del formulario. Una vez tengamos el div retornado, reemplazamos el div en el documento con el que obtuvimos con la función replaceWith.

Este enfoque nos permite reducir considerablemente el tráfico necesario para realizar ua consulta, obteniendo los mismos resultados de manera más rápida y fluida. Sin embargo, es posible reducir aún más el uso de la red, sólo extrayendo del servidor los datos necesarios.

Segundo enfoque: carga de JSON

En el anterior enfoque cargamos desde el servidor únicamente el HTML que necesitamos actualizar, no toda la página. Como ejemplo, la respuesta del servidor en el caso de una consulta con el documento 535, se vería así:

<div id="respuesta">Usuario: Juana Juliana</div>

Sin embargo, el tamaño de la respuesta se puede disminuir aún más. Podemos devolver únicamente los datos que necesitamos, en este caso el nombre del usuario. Elementos concernientes con el HTML no son concernientes al servidor, de modo que vamos a omitirlos en la respuesta. Para tal efecto, devolveremos los datos que nos interesan, junto a un código de error que vamos a utilizar para mostrar la respuesta según el caso. Todo esto irá en formato JSON. Para que la nueva solución funcione, tenemos que modificar el código Javascript y del lado del servidor (PHP).

Javascript:

$(function(){
    $("#enviar").click(function(){
        $.ajax({
            dataType: "json",
            type: "POST",
            url: $("#form_consulta").attr("action"),
            data: $("#form_consulta").serialize()
        }).success(function(data){
            data = $.parseJSON(data);
            $("#respuesta").html("");
            switch(data.code){
                case "NF":
                $("#respuesta").html("Usuario no encontrado!");
                break;
                case "OK":
                $("#respuesta").html("Usuario: " + data.response);
                break;
            }
        }).error(function(){
            alert("error!")
        })
    })
})

PHP:

if($_SERVER['REQUEST_METHOD'] == 'POST'){
    $respuesta = array();
    if(in_array($_POST['documento'], array_keys($usuarios))){
        $respuesta['code'] = 'OK';
        $respuesta['response'] = $usuarios[$_POST['documento']];
    }
    else{
        $respuesta['code'] = 'NF';
    }
    echo json_encode(json_encode($respuesta));
}

Si bien en este ejemplo no se ve claramente el ahorro en ancho de banda que se obtiene con este enfoque, con una mayor cantidad de datos el cambio es importante.

Tercer enfoque: HTML Templates

Cuando tenemos una gran cantidad de datos que obtenemos del servidor, podemos usar el enfoque de HTML Templates para aliviar un poco el trabajo de actualizar los datos en la página. La idea en este enfoque es crear una plantilla de la información a mostrar, poniendo marcadores en las posiciones en que los datos del servidor irán. Una manera de lograr esto es creando una etiqueta script con un descriptor de lenguaje que sepamos que el navegador no soporta, y escogiendo un patrón de caracteres fácilmente identificable, para reemplazar posteriormente con los datos provenientes del servidor:

<script id="responseTemplate" type="html/template">
    <div id="respuesta"></div>
</script>

Nuevo script:

switch(data.code){
    case "NF":
    $("#respuesta").html("Usuario no encontrado!");
    break;
    case "OK":
    var template = $("#responseTemplate").html()
    for(key in data){
        template = template.replace(" + key + ", data[key])
    }
    $("#respuesta").replaceWith(template);
    break;
}

Podemos ver que en el bucle for estamos iterando los datos provenientes del servidor, y reemplazándolos en los marcadores que previamente preparamos en la plantilla. Finalmente, ponemos la plantilla ya llena en la página para mostrar.

En algunas organizaciones existe una red que requiere de autenticación por proxy para salir a Internet. Normalmente la configuración del proxy de la red es bastante sencilla y gráfica cuando estamos en un computador con Windows o Macintosh, incluso cuando no vamos a hacer otra cosa más que navegar en una máquina con Linux y alguno de los escritorios más populares. Muchas aplicaciones no toman en cuenta la configuración del proxy del sistema operativo y proveen una interfaz propia para estos menesteres. Es el caso de Dropbox o Synaptic, por ejemplo. Esto se da aún con más frecuencia cuando estamos intentando acceder a Internet desde aplicaciones en modo de texto en Linux. Dado que estas aplicaciones no dependen de un entorno de escritorio, no pueden esperar que instalemos Gnome o KDE (por ejemplo) para configurar el proxy y usar esa configuración. Están, por así decirlo, una capa por debajo.

Configuración básica

Aún más por debajo de las aplicaciones en modo de texto se encuentran las variables de entorno del sistema. Las variables de entorno http_proxy, https_proxy, ftp_proxy, rsync_proxy y no_proxy son usadas por ciertos programas (como wget y lynx) para extraer la configuración del proxy. Tienen la forma protocolo_proxy. Por ejemplo:

export http_proxy = http://10.203.0.1:5187/
export https_proxy = $http_proxy

Automatizando

Alan Pope, según este artículo de ArchLinux en el que me basé, dio con una manera de automatizar la levantada del proxy:

function proxy(){
  echo -n "Usuario:"
  read -e username
  echo -n "Clave:"
  read -es password
  export http_proxy="http://$username:$password@proxyserver:8080/"
  export https_proxy=$http_proxy
  export ftp_proxy=$http_proxy
  export rsync_proxy=$http_proxy
  export no_proxy="localhost,127.0.0.1,localaddress,.localdomain.com"
  echo -e "\Proxy configurado."
}
function proxyoff(){
  unset HTTP_PROXY
  unset http_proxy
  unset HTTPS_PROXY
  unset https_proxy
  unset FTP_PROXY
  unset ftp_proxy
  unset RSYNC_PROXY
  unset rsync_proxy
  echo -e "\nProxy removido."
} 

La idea es añadir esas funciones a nuestro archivo .bashrc. De ese modo, siempre que tengamos que usar el proxy desde la consola, primero llamamos la función proxy:

$ proxy
Usuario:sergio
Clave:
Proxy configurado.
$ wget www.google.com
...
$ proxyoff
Proxy removido.
$

¿Y sudo?

Como para ahora se supondrá, la configuración del proxy no perdura cuando estamos haciendo tareas que involucran sudo, como instalar paquetes por medio de apt. Para esto, accedemos a la configuración de sudo por medio del comanto visudo y añadimos la siguiente línea:

Defaults env_keep += "http_proxy https_proxy ftp_proxy"

Esto mantendrá las variables de entorno dadas del usuario que está llamando a sudo, en este caso, las correspondientes al proxy.

Disclaimer

Esta entrada es básicamente una traducción al español de la documentación de ArchLinux encontrada en el artículo anterioemente mencionado. La escribo para poner la información a disposición de aquellos que puedan leerla, así como a manera de bitácora. La red de la Universidad Nacional me obligó a usar esta información. Que la red no haya funcionado y al final me haya dado la misma es otro cuento.

En este post explicaré los pasos que seguí para configurar la depuración remota sobre Internet de una aplicación web en PHP, usando Netbeans. Primero, la configuración de mis máquinas:

  • Por un lado, tengo una o varias máquinas de desarrollo. Estas máquinas por lo general tienen Windows 7, aunque eso no viene al caso. El desarrollo lo hago usando Netbeans 7.
  • Por otro lado, mi servidor de pruebas es una máquina modesta con Linux, Ubuntu. PHP 5.3 y Apache 2.

El hecho es que mi servidor es fijo, está en mi residencia, y muchas veces me llevo el portátil para adelantar trabajo desde donde me encuentre, usando la conexión móvil de mi celular.

De modo que necesitaba una manera de usar la funcionalidad de depurado de Netbeans, para que se justifique en algo los cerca de 400MB en memoria que consume (bromeo, sí se justifica, además estoy acostumbrado al IDE). Configurar Xdebug para un entorno local es relativamente sencillo, y ya lo había hecho en varias ocasiones en máquinas con Windows. De modo que me lancé de lleno a la tarea de configurarlo para depuración remota. Primero sobre red local, luego sobre Internet.

Instalar y activar Xdebug en Linux

La página de soporte de Netbeans provee cierta información sobre cómo configurar Xdebug en Linux para que funcione con el IDE instalado en la misma máquina del servidor, pero hasta ahí llega; es decir, asume que Xdebug ya se encuentra instalado. Tuve que basarme en otro recurso para poder instalarlo. Resumo el procedimiento para Ubuntu:

Primero, hay que asegurarse que el comando pecl está disponible. Simplemente con escribirlo en consola, el sistema tratará de ejecutarlo y, de no encontrarlo, dirá qué paquete hay que instalar para que esté disponible. En el tutorial dice que se trata de php-dev, pero en mi caso el comando estaba en el paquete php-pear. Sudo apt-get bla bla bla.

Ahora viene lo bueno. Una vez está instalada la aplicación que nos provee pecl, tenemos que ejecutar dicho comando para instalar Xdebug:

# pecl install xdebug

Esto descargará, compilará e instalará la extensión de PHP que tanto anhelamos. Al final de mensajes crípticos y un poco abrumadores por parte de la terminal, se nos informará la nueva ruta en la que quedó el archivo xdebug.so recién compilado. Debemos anotar esa ruta, pues la usaremos después.

Ahora tenemos que añadir las correspondientes lineas de configuración al archivo php.ini, para activar la extensión. Para ubicar el archivo php.ini que vamos a modificar tenemos dos opciones:

  • Escribir un script PHP y correrlo en el servidor con el siguiente código: <?php phpinfo() ?>, o
  • en la terminal, php -i | grep php.ini

Una vez ubicado el archivo, lo editamos y añadimos (o descomentamos) la línea que reza más o menos:

zend_extension = /ruta/que/habiamos/anotado/xdebug.so

Eso instalará y activará Xdebug en Linux, previo reinicio del servidor, claro. Para probar podemos correr el script que dan en el tutorial en el cual me basé.

Configurar Xdebug para depuración remota

Como lo que yo quería hacer no era desarrollar en mi servidor y depurar a golpe de comando, sino en otras máquinas y depurar por medio de Netbeans, no vamos a parar acá.

En el archivo php.ini que hemos editado, el tutorial oficial de Netbeans nos dice que pongamos además las siguientes líneas:

xdebug.remote_enable=1
xdebug.remote_handler=dbgp
xdebug.remote_mode=req
xdebug.remote_host=127.0.0.1
xdebug.remote_port=9000

Estas líneas están bien si se quiere usar Netbeans en la misma máquina que tiene el servidor. Eso lo vemos en xdebug.remote_host = 127.0.0.1. Ahí nos está diciendo que el cliente de Xdebug está en 127.0.0.1, es decir en localhost. Lo que vamos a hacer es comentar esa línea, y en vez añadir la siguiente, para que quede todo igual, menos:

; xdebug.remote_host=127.0.0.1
xdebug.remote_connect_back=1

Esta directiva le está diciendo a Xdebug que trate de conectar a todos los clientes que inicien una sesión de depuración. Esto permitirá hacer depuraciones desde equipos remotos cuyas direcciones IP pueden cambiar. Ahora, sólo es cuestión de configurar los puertos.

Puertos

Tenemos que abrir o redirigir externamente el puerto 9000 a nuestro servidor desde Internet, y a nuestro cliente. Si el cliente (Netbeans) está bajo un router o un firewall, debemos redirigir las conexiones entrantes por dicho puerto en TCP hacia nuestra máquina.

Bono: una buena aplicación que me sirvió para redirigir el puerto de mi teléfono Android hacia mi portátil es Port Forwarder.