Blog

  • ¿Qué son las extensiones de PHP?

    ¿Qué son las extensiones de PHP?

    Quién no se cruzó con mensajes como:

    Debes tener instalada/habilitada la extension en PHP sobre GD

    Pero… ¿qué es exactamente una extensión de PHP?

    Para comprenderlo debes conocer un poco cómo funciona PHP internamente.

    A continuación te daré una visión algo simplificada, si quieres una explicación completa y detallada te recomiendo este excelente tutorial de Diego Lázaro.

    PHP es un lenguaje interpretado, lo que significa que para ejecutar una aplicación escrita en PHP se requiere de un proceso auxiliar llamado intérprete (Si usas Windows ese proceso será php.exe, si usas Linux o similares será el binario php).

    Este binario se ha generado a partir de la compilación de una base de código C, la cual, como es abierta, cualquiera con los conocimientos necesarios puede leer y alterar a su antojo.

    Si bien el lenguaje incluye muchas características (funciones, variables, constantes, etc…), si tuviese que englobar TODAS las posibles necesidades se volvería algo sumamente pesado.

    Es por esta razón que los desarrolladores del lenguaje definieron un conjunto de características como parte del núcleo (o core) de PHP y otras como adicionales.

    Esto significa que cualquier instalación de PHP contará con las funcionalidades básicas mientras que, para utilizar las demás será necesario instalar y configurar algunas librerías de enlace dinámico (Archivos .dll en el caso de Windows o .so en el caso de Linux).

    Este mecanismo permite a su vez crear nuevas funciones para extender PHP (El único problema es que estas extensiones deben ser escritas también en lenguaje C).

    Por qué crear extensiones si puedo crear funciones PHP

    La razón principal es la eficiencia: el código binario, al no requerir la intervención del intérprete, ejecuta más rápido de lo que lo hace su par interpretado.

    Por supuesto que escribir una función directamente en PHP es mucho más simple que hacerlo en C, pero si la performance es un factor crítico no debe pasarse por alto esta posibilidad.

    Cómo instalar una extensión PHP

    Instalar una nueva extensión de php supone contar con un nuevo binario en nuestro servidor, con lo cual, los comandos efectivamente necesarios dependerán del sistema operativo que utilices.

    Si estás en un entorno tipo Ubuntu instalar una extensión de PHP será tan simple como escribir:

    sudo apt install php-NOMBRE_EXTENSION

    En otros entornos algo menos amigables el proceso puede incluir descargar los fuentes de la extensión y re-compilar php con algún modificador.

    Otra forma de instalar extensiones es utilizar la utilidad PECL

    Cómo activar una extensión PHP

    La activación de una extensión supone su incorporación al archivo de configuración de PHP (php.ini).

    Es muy común que las extensiones definan su propio archivo .ini, dentro del cual se agregarán variables específicas para configurar dicha extensión.

    Este es un ejemplo del archivo xdebug.ini que tengo en mi computadora:

    zend_extension=xdebug.so
    xdebug.remote_autostart=1
    xdebug.remote_connect_back=1
    xdebug.remote_enable=1

    Algunas extensiones PHP comúnmente utilizadas

    Las extensiones de PHP pueden ser desde utilidades simples hasta frameworks FullStack completos (Como Phalcon).

    Aquí un breve listado de las más utilizadas:

    • MySQLi para el acceso a bases de datos MySQL
    • cURL para la realización de peticiones HTTP
    • Zip para trabajar con archivos comprimidos
    • SimpleXML para trabajar con XML
  • Cómo exportar una tabla de MySQL a Excel usando PDO

    Cómo exportar una tabla de MySQL a Excel usando PDO

    Seguro que te ha pasado algo como esto: creaste una aplicación con su base de datos, con unas funcionalidades espectaculares, fantásticos reportes y al momento de la demo… la pregunta tan temida:

    «¿Cómo puedo hacer para llevar toda esta información a Excel?»

    Y en tu cabeza suena:

    «¿A Excel? ¿En serio? Pero si se puede hacer todo mucho mejor con esta aplicación… ¿para qué querrías usar una planilla de cálculo?»

    Creeme, no estás solo, a todos nos ha pasado.

    La triste realidad es que difícilmente vayas a ganar la batalla… más vale amigarte con el Excel (y ganar algo de dinero mientras tanto, ¿no?).

    Una de las razones más comunes que se escuchan por ahí es que a través de Excel es fácil compartir la información con otros sistemas (Personalmente elegiría una integración basada en webservices, pero… el cliente manda).

    Otra razón que, aunque a veces duela, la veo más entendible es la facilidad para analizar datos que tiene Excel (La posibilidad de aplicar filtros, sumatorias y demás).

    Pero como este no es un blog de Excel si no de Programación, pasemos a lo nuestro.

    En este artículo te voy a mostrar los pasos que tendrás que dar para generar archivos que puedan ser abiertos con Excel a partir de tu base de datos MySQL.

    Encontrarás ejemplos de código que, con pocas modificaciones, podrás adaptar a tu escenario particular.

    ¡Acompañame!

    Conectarse a MySQL usando PDO

    Lo primero que necesitarás para realizar esta exportación es conectarte a tu base de datos.

    Siendo que se trata de MySQL puedes usar la librería específica (mysqli) o la genérica (PDO).

    Personalmente, prefiero ir por PDO porque si eventualmente necesito cambiar de motor de BD es muy fácil hacerlo.

    Todo comienza por la creación de un objeto:

    <?php
    
    $db = new PDO('mysql:host=localhost;dbname=mydb', 'root', '');

    Con este código estoy intentando hacer una conexión a una base de datos MySQL instalada en la misma computadora donde se está corriendo el script.

    Digo intentando porque, como siempre, algo podría salir mal… para este ejemplo voy a dejarlo así, pero lo correcto sería incluir este código dentro de un bloque try…catch.

    (Si no tenés muy claros los conceptos de objetos, excepciones y/o PDO este libro puede ayudarte 😉

    Una vez realizada la conexión debemos hacer alguna consulta que nos permita obtener aquellos registros que queremos exportar.

    Realizar una consulta usando PDO

    Existen varios modos de realizar la consulta mediante PDO, pero hay algo de lo que no podrás escapar: escribir el SQL correspondiente.

    Usualmente lo que yo hago es algo como:

    $sql = "SELECT * FROM table WHERE field > 10";
    
    $results = $pdo->query($sql, PDO::FETCH_NUM);

    Nuevamente, en un caso real habría que verificar si la consulta se ejecutó o hubo algún error antes de avanzar… para no hacer un post kilométrico lo dejo aquí.

    Para darle algo de formato al resultado que vamos a generar vamos a necesitar los nombres de los campos (que, si usamos un SELECT * no siempre serán los mismos!)

    Obtener los nombres de los campos usando PDO

    $columns = [];
    
    for ($i = 0; $i < $results->columnCount(); $i++) {
        $columns[] = $results->getColumnMeta($i)['name'];
    }

    Una vez obtenidos los registros y los nombres de las columnas a exportar podemos comenzar a generar la respuesta.

    Y aquí tenemos varias opciones.

    Independientemente de cuál sea la que elijas para generar el resultado, seguramente quieras forzar la descarga del mismo, veamos cómo lograrlo.

    Forzar la descarga de un archivo

    Para obligar al navegador a guardar el resultado devuelto por el servidor en un archivo en el disco local del visitante debe usarse la función header:

    header("Content-Type:$contentType"); 
    header("Content-Disposition:attachment;filename=output.$outputFileExtension"); 

    A continuación te mostraré tres opciones diferentes para generar la respuesta.

    Las variables $contentType y $outputFileExtension dependerán de cuál de las opciones disponibles quieras utilizar.

    Generar un archivo CSV y abrirlo en Excel

    Esta es probablemente la manera más sencilla de exportar los resultados, usando un archivo de valores separados por comas (CSV).

    header("Content-Type:application/csv"); 
    header("Content-Disposition:attachment;filename=output.csv"); 
    
    $outputFile = fopen('php://output', 'w+'));
    
    fputcsv($outputFile, $columns);
    foreach ($results as $result) {
            fputcsv($outputFile, $result);
    }

    Este código permitirá que el usuario reciba un archivo separado por comas que podrá ser abierto usando Excel y eventualmente guardado en formato nativo (xls, xlsx, etc…).

    Generar un archivo HTML y abrirlo en Excel

    Una característica interesante del Excel (Y algunas otras aplicaciones tipo planilla de cálculo modernas) es que pueden interpretar archivos HTML que tengan estructuras de tablas y mostrarlos como planillas.

    En este caso el código de la generación de la salida sería algo como:

    header("Content-Type:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); 
    header("Content-Disposition:attachment;filename=output.xls"); 
    
    $outputFile = fopen('php://output', 'w+'));
    
    ?>
    <table>
            <thead>
                    <tr>
                    <?php foreach($columns as $column): ?>
                            <th><?php echo $column; ?></th>
                    <?php endforeach; ?>
                    </tr>
            </thead>
            <tbody>
                    <?php foreach( $results as $result ): ?>
                    <tr>
                            <?php foreach ($result as $value): ?>
                            <td><?php echo $value; ?></td>
                            <?php endforeach; ?>
                    </tr>
                    <?php endforeach; ?>
            </tbody>
    </table>

    Generar un archivo Excel usando PHP

    Si bien estas dos opciones pueden resultar aceptables en una gran cantidad de casos, existen otros en los que realmente se necesita exportar a un formato específico, como XLSX.

    Estos archivos se basan en XML (es decir, en texto) con lo cual podrías generarlos usando simples strings (O a lo sumo valiéndote de SimpleXMLElement) pero… ¿para qué reinventar la rueda no?

    Hay una librería realmente buena que podés usar: PhpSpreadsheet.

    Esta librería te permite tanto escribir como leer archivos con formato específico para muchas planillas de cálculo (Excel, LibreOffice, etc…).

    Una característica que la hace sumamente atractiva es que presenta una API orientada a objetos realmente simple de usar:

    $spreadsheet = new Spreadsheet();
    
    $activeSheet = $spreadsheet->getActiveSheet();
    
    foreach($columns as $i => $column) {
            $activeSheet->setCellValueByColumnAndRow($i + 1, 1, $column);
    }
    
    foreach($results as $result) {
            $activeSheet->insertNewRowBefore($activeSheet->getHighestRow() + 1);
            foreach ($result as $k => $field) {
                    $activeSheet->setCellValueByColumnAndRow($k + 1, $activeSheet->getHighestRow(), $field);
            }
    }
    
    $writer = IOFactory::createWriter($spreadsheet, 'Xls');
    $writer->save('php://output');

    Un detalle importante: para usar esta librería hay que instalarla primero (Preferentemente usando composer).

    Qué se puede hacer con Excel usando PHP

    Así como es posible exportar información hacia un archivo Excel es perfectamente posible importar información contenida en un archivo Excel hacia una base de datos MySQL o manipular un archivo existente.

    La librería PhpSpreadsheet permite realizar prácticamente cualquier operación que Excel soporte, todo lo que se requiere es leer un poco de documentación 😉

  • Cómo saber cuáles son los procedimientos que tiene un WebService SOAP usando PHP

    Cómo saber cuáles son los procedimientos que tiene un WebService SOAP usando PHP

    Consumir Servicios Web basados en SOAP es una tarea muy común estos días, especialmente cuando se trata de integrar con entidades gubernamentales (Típico caso es la facturación electrónica).

    Una de las características interesantes que tiene este protocolo (SOAP) es que están definidas en forma explícita las operaciones disponibles a través de un archivo de descripción de WebService (WSDL).

    La desventaja es que, salvo que conozcas bien la especificación, leer un archivo como este puede ser algo complicado:

    <?xml version = "1.0" encoding = "utf-8"?>
    <definitions name="WS_EmissionFactura" targetNamespace="Gx" xmlns:wsdlns="Gx" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="Gx">
    	<types>
    		<schema targetNamespace="Gx" xmlns="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" elementFormDefault="qualified">
    			<element name="WS_EmissionFactura.Execute">
    				<complexType>
    					<sequence>
    						<element minOccurs="1" maxOccurs="1" name="Xmlrecepcao" type="xsd:string" />
    					</sequence>
    				</complexType>
    			</element>
    			<element name="WS_EmissionFactura.ExecuteResponse">
    				<complexType>
    					<sequence>
    						<element minOccurs="1" maxOccurs="1" name="Xmlretorno" type="xsd:string" />
    					</sequence>
    				</complexType>
    			</element>
    		</schema>
    	</types>
    	<message name="WS_EmissionFactura.ExecuteSoapIn">
    		<part name="parameters" element="tns:WS_EmissionFactura.Execute" />
    	</message>
    	<message name="WS_EmissionFactura.ExecuteSoapOut">
    		<part name="parameters" element="tns:WS_EmissionFactura.ExecuteResponse" />
    	</message>
    	<portType name="WS_EmissionFacturaSoapPort">
    		<operation name="Execute">
    			<input message="wsdlns:WS_EmissionFactura.ExecuteSoapIn" />
    			<output message="wsdlns:WS_EmissionFactura.ExecuteSoapOut" />
    		</operation>
    	</portType>
    	<binding name="WS_EmissionFacturaSoapBinding" type="wsdlns:WS_EmissionFacturaSoapPort">
    		<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
    		<operation name="Execute">
    			<soap:operation soapAction="Gxaction/AWS_EMISSIONFACTURA.Execute" />
    			<input>
    				<soap:body use="literal" />
    			</input>
    			<output>
    				<soap:body use="literal" />
    			</output>
    		</operation>
    	</binding>
    	<service name="WS_EmissionFactura">
    		<port name="WS_EmissionFacturaSoapPort" binding="wsdlns:WS_EmissionFacturaSoapBinding">
    			<soap:address location="https://appuypruebas.migrate.info/InvoiCy/aws_emissionfactura.aspx" />
    		</port>
    	</service>
    </definitions>

    Un modo mucho más simple y conveniente es utilizar un método propio de la clase SoapClient (La que PHP pone a tu disposición para interactuar con esta clase de servicios web): __getFunctions.

    Un ejemplo de obtención de listado de funciones provistas por un WebService SOAP

    Para este ejemplo se necesita tener instalado el soporte de SOAP para PHP.

    Los comandos que te voy a mostrar a continuación pueden ejecutarse sin problemas en Ubuntu (Si usás otro sistema operativo puede que tengas que adaptar un poco los ejemplos).

    Asumiré que tenés acceso a una terminal (Si es tu máquina local no debería haber problema, si es un servidor remoto necesitarás algún tipo de acceso como ssh y si tu proveedor no te lo da te sugiero evaluar usar un VPS).

    Lo primero será verificar que tengas instalado el soporte para SOAP en la computadora donde vas a correr el script.

    Para ello podés usar el comando:

    php -i | grep soap

    Y la salida debería ser similar a :

    /etc/php/7.4/cli/conf.d/20-soap.ini,
    soap
    soap.wsdl_cache => 1 => 1
    soap.wsdl_cache_dir => /tmp => /tmp
    soap.wsdl_cache_enabled => 1 => 1
    soap.wsdl_cache_limit => 5 => 5
    soap.wsdl_cache_ttl => 86400 => 86400

    En caso contrario, deberás instalarlo. Este comando te ayudará:

    sudo apt install php-soap

    Ahora entonces sí, pasemos a ver un poco de PHP 🙂

    <?php
    
    $ws = new SoapClient($argv[1]);
    print_r($ws->__getFunctions());

    Guardá este código en un archivo llamado get_ws_functions.php y luego tenés que ejecutarlo de esta forma:

    php get_ws_functions.php URL_DEL_WSDL

    Por ejemplo, si ejecutás

    php get_ws_functions.php "https://appuypruebas.migrate.info/InvoiCy/aws_emissionfactura.aspx?wsdl"

    Obtendrás:

    Array
    (
        [0] => WS_EmissionFactura.ExecuteResponse Execute(WS_EmissionFactura.Execute $parameters)
    )

    Ahora te toca cambiar la URL por el WebService al que te querés conectar y listo, tenés ahí el listado de métodos que podés invocar.

  • ¿Qué tan «globales» son las variables globales en PHP?

    ¿Qué tan «globales» son las variables globales en PHP?

    Parece una pregunta rara, ¿no?

    Seguro estás pensando «¿Dónde está la trampa?», ¿cierto?

    En general se entiende que una variable global es aquella que está presente (es decir, puede ser leída y modificada) en cualquier lugar de un programa.

    El caso de PHP es algo particular.

    Para comenzar si hicieras algo como esto:

    <?php
    
    $global = 'Soy una variable global';
    
    function f()
    {
       echo '$global dentro de f vale: "'.$global.'"'.PHP_EOL;
    }
    
    echo '$global fuera de f vale: "'.$global.'"'.PHP_EOL;
    f();

    Esperarías ver:

     $global fuera de f vale: "Soy una variable global"
     $global dentro de f vale: "Soy una variable global"

    Y sin embargo, si ejecutás este script verás:

    $global fuera de f vale: "Soy una variable global"
    $global dentro de f vale: ""

    Y, en el reporte de errores encontrarás:

    PHP Notice: Undefined variable: global on line 7

    Esto ocurre debido a que en PHP, si querés acceder a una variable definida fuera del ámbito de la función que se está ejecutando debés hacerlo en forma explícita.

    Cómo usar variables globales en PHP

    Hay dos formas de hacer esto.

    La primera es referirte a la variable como un miembro del arreglo $GLOBALS

    function f()
    {
       echo '$global dentro de f vale: "'.$GLOBALS['global'].'"'.PHP_EOL;
    }

    La segunda es declarar que vas a usar una variable global:

    function f()
    {
            GLOBAL $global;
    
            echo '$global dentro de f vale: "'.$global.'"'.PHP_EOL;
    }

    Hasta aquí puede resultar algo raro, pero no deja de ser un tema de sintaxis… nada del otro mundo.

    El problema se hace algo más complejo cuando pensamos en acceder a una variable definida en otro script, algo como:

    script1.php

    <?php
    
    $global = "Soy una variable global";
    ?>
    <a href="script2.php">Ir al script 2</a>

    script2.php

    <?php
    
    echo '$global en script2 vale "'.$global.'"'.PHP_EOL;

    Si accedés al script1.php a través de un servidor web y hacés click en el link «Ir al script 2» te vas a encontrar con un error de que la variable $global no está definida.

    Y aquí es donde empiezan las confusiones.

    ¿Acaso script1.php y script2.php no forman parte de la misma aplicación?

    Bueno… sí y no.

    Conceptualmente todos los scripts que armaste para tu sitio están vinculados de alguna forma, por algo están todos ahí.

    Sin embargo, a un nivel técnico no, cada script es independiente de su contexto.

    Esto es así por el modelo de ejecución de PHP.

    A diferencia de otros lenguajes o entornos donde la aplicación y el webserver son una y la misma cosa (NodeJs por ejemplo), en PHP la separación es muy clara.

    Cada petición que recibe el servidor web (y que corresponde a un archivo php) es pasado a través de un hilo nuevo del intérprete.

    Esto implica justamente que al finalizar un script todo el espacio de memoria utilizado por éste se destruye… incluso las variables globales 🙁

    Entonces… ¿se puede compartir variables entre diferentes scripts de una aplicación PHP?

    Sí, se puede.

    Cómo compartir variables entre scripts de una aplicación PHP

    Depende de cuál sea exactamente el resultado buscado existen diferentes formas de lograrlo.

    Si lo que se busca es guardar variables que acompañen a un visitante a lo largo de su camino de navegación lo mejor es utilizar el mecanismo de sesiones.

    Si lo que se busca es algo más abarcativo, como definir variables que perduren entre la ejecución de diferentes scripts aún cuando no se trate del mismo usuario, habrá que recurrir a algún medio de almacenamiento externo a PHP.

    Entre ellos están los archivos, las bases de datos, espacios de memoria compartidos, etc…

    Espero haber contribuido a aclarar un poco este punto que suele traer problemas cuando uno comienza su recorrido en el mundo del PHP.

  • Cómo compartir la conexión a MySQL entre scripts PHP

    Cómo compartir la conexión a MySQL entre scripts PHP

    Leo frecuentemente comentarios como:

    Al guardar la conexión a MySQL usando serialize y unserialize me da errores

    O

    Guardo la conexión a la BD en una variable de sesión pero después cuando consulto ese valor desde otra página no existe

    Nadie quiere tener que abrir una nueva conexión a la base de datos cada vez que se ejecuta una página PHP. Compartir en X

    En un contexto de alta concurrencia esto puede suponer un gran desperdicio de recursos e incluso convertirse en un cuello de botella.

    Sin embargo, la solución no es guardar la conexión dentro de la sesión… de hecho, eso no va a funcionar.

    Se trata de un caso muy particular, pero existe una alternativa.

    Qué son las conexiones permanentes a MySQL

    En el uso normal de las conexiones a MySQL (usando, por ejemplo mysqli_connect), al finalizar la ejecución del script se cierran automáticamente las conexiones que hayan sido abiertas.

    Si tenemos en cuenta el modelo de ejecución de php notaremos que habrá un gran overhead con toda esa apertura/cierre de conexiones.

    Para evitar este problema PHP dispone de conexiones permanentes.

    Se trata de conexiones que, a diferencia de las comunes, continúan existiendo luego de que el script ha finalizado su ejecución.

    Cómo se crean conexiones permanentes de PHP a MySQL

    En general, existen dos modos de vincular PHP y MySQL:

    1. MySQLi
    2. PDO

    La principal diferencia entre ellas es que MySQLi es una librería específica (es decir, sólo puede trabajar con MySQL) mientras que PDO es genérica (Puede trabajar con diferentes motores de bases de datos).

    Ya que estamos hablando de MySQL en este post voy a explorar el uso de MySQLi.

    La forma de crear una conexión persistente es simple, sólo se necesita agregar antes del nombre del host el string p: ejemplo:

    <?php
    
    $conn = mysqli_connect("p:localhost", "root", "rootpwd");

    ¡Y listo! Con este sencillo código puedes olvidarte del problema de abrir y cerrar conexiones a MySQL en cada script de tu sitio.

  • Implementación de roles basada en PHP y MySQL

    Implementación de roles basada en PHP y MySQL

    Una pregunta que veo a menudo:

    Estoy haciendo un inicio de sesión en php. el usuario solo debe ingresar su nombre y sera re direccionado dependiendo del rol que tenga.


    Estoy realizando un sistema en php y mysql quisiera saber cómo trabajar con múltiples sesiones como por ejemplo que tenga una cuenta de administrador y pueda trabajar con toda las páginas y tener cuenta de usuario que algunos vean cierta cantidad de páginas y realizar pocas funciones en el sistema


    ¿Cómo debo crear una sesión para el administrador? ¿En qué lugar de la aplicación se tiene que reflejar?
    El usuario normal no debe ver esa parte que le corresponde al Administrador.


    quiero ingresar roles de usuario en mi código pero no se como hacerlo.


    Voy a crear una aplicación web para unas personas que llevan el control de unas muestras geológicas, básicamente intervienen dos grupos de usuarios el gerente del área y sus especialistas, por lo tanto no pueden tener los mismos privilegios.

    La necesidad de tener diferentes secciones del sitio disponibles para diferentes roles o niveles de usuario es sumamente común.

    En este artículo mostraré cómo podría implementarse un esquema como este usando PHP y MySQL.

    Aclaración: en mi caso personal, para resolver este problema utilizaría Symfony, pero en este post utilizaré PHP puro para hacer la solución más generalmente aplicable

    Cómo guardar los roles en la base de datos

    Mi sugerencia siempre es comenzar por definir el modelo de datos.

    La base de datos constituye los cimientos de cualquier aplicación web y si éstos no son sólidos, por más magia que tenga nuestro PHP, CSS, JavaScript y demás va a ser difícil terminar con algo mucho mejor que esto:

    Así que… mejor pisar sobre seguro.

    La primera pregunta que debés responder es: ¿cada usuario tendrá un único rol?

    Si la respuesta es sí el esquema de la base de datos es bastante simple, sólo se trata de agregar un campo a la tabla users: role.

    Aquí se abre una segunda bifurcación: ¿de qué tipo debe ser el campo role?

    La idea más natural es crearlo como string (o tal vez enumerado).

    Mi sugerencia, sin embargo, es hacer algo un poco más complicado:

    • Crear una nueva tabla roles con dos campos:
      • id
      • nombre
    • Agregar un campo role_id (en lugar de role) a la tabla users como una clave foránea a la tabla roles

    En definitiva, este sería el código SQL de la creación de las tablas:

    CREATE TABLE `roles` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `name` (`name`)
    );
    
    CREATE TABLE `users` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `email` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
      `password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
      `role_id` int(10) unsigned DEFAULT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `email` (`email`),
      KEY `fk_roles` (`role_id`),
      CONSTRAINT `fk_roles` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`)
    );

    De esta forma el sistema está preparado para incorporar nuevos roles sin necesidad de alterar la estructura de la base (Y por lo tanto, posibilitar hacerlo desde el front-end sin intervención del desarrollador).

    En el caso de que los usuarios puedan tener más de un rol simultáneamente la estructura sería un poco más compleja: deberá incluir una tabla intermedia para implementar la relación N-a-M:

    -- Queda igual que en el caso anterior
    CREATE TABLE `roles` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `name` (`name`)
    );
    
    -- Se elimina el campo role_id
    CREATE TABLE `users` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `email` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
      `password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `email` (`email`)
    );
    
    CREATE TABLE `users_roles` (
      `user_id` int(10) unsigned NOT NULL,
      `role_id` int(10) unsigned NOT NULL,
      PRIMARY KEY (`user_id`,`role_id`),
      KEY `fk_roles_2` (`role_id`),
      CONSTRAINT `fk_roles_2` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`),
      CONSTRAINT `fk_users` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
    );

    Para no complicar mucho el ejemplo voy a tomar el caso de un rol por usuario aunque no cambiará mucho si se trata del otro (Tal vez lo escriba en un próximo artículo).

    Bien, la base de datos está, ahora es momento de ver cómo hacer el código PHP.

    Cómo redireccionar al usuario según su rol

    El siguiente desafío consiste en, una vez haya ingresado el usuario, redirigirlo a la sección del sitio que le corresponda según su rol.

    Comencemos por un simple formulario de login:

    <html>
    <body>
      <form action="processLogin.php" method="post">
         <input type="text" name="username"/>
         <input type="password" name="password"/>
         <input type="submit" value="Ingresar"/>
      </form>
    </body>
    </html>

    Y el archivo del backend

    <?php
    
    $username = $_POST['username'];
    $password = $_POST['password'];
    
    $sql = "SELECT u.id, r.name AS role, password FROM users u INNER JOIN roles r ON r.id = u.role_id WHERE username = '$username';";
    
    // Conectar a la base de datos
    // ejecutar la consulta
    // $result contiene el resultado de la consulta
    
    if (password_verify($result['password'], password_hash($password, PASSWORD_BCRYPT))) {
       $startingPage = [
          'admin' => 'admin_home.php',
          'user' => 'user_home.php',
       ];
    
       $nextPage = array_key_exists($result['role'], $startingPage) ? $startinPage['role'] : 'user_home.php';
       if (array_key_exists($result['role'], $startingPage)) {
          $nextPage = $startinPage[$result['role']];
       } else {
          $nextPage = $startinPage['user'];
          error_log('There is no starting page for role '.$result['role']);
       }
       session_start();
       $_SESSION['user_id'] = $result['id'];
       $_SESSION['role'] = $result['role'];
       header('Location: '.$nextPage);
    } else {
       header('Location: login.html');
    }

    Aquí estoy usando las funciones password_verify y password_hash para hacer un login seguro.

    Y con esto hemos logrado dirigir a cada persona a la sección que le corresponde…

    Lo que nos falta es dejar afuera a los que quieran ir a mirar donde no les corresponde 😉

    Cómo restringir el acceso a una sección del sitio según el rol del usuario

    Una forma simple de restringir el acceso para alguien que no se logeó la podés leer acá.

    La idea de esta sección es ampliar un poco sobre esa misma línea y permitir el acceso sólo para aquellos usuarios cuyo rol está habilitado:

     <?php
    
    // admin_home.php
    
    session_start();
    
    if (!array_key_exists('user_id', $_SESSION)) {
       header('Location: login.html');
       die;
    }
    
    $allowedRoles = ['admin'];
    
    if (!array_key_exists('role', $_SESSION) || !in_array($_SESSION['role'], $allowdRoles)) {
       header('Location: login.html');
       die;
    }
    ?>
    <h1>Bienvenido amind!</h1>

    Habrás observado que el protagonista de esta pequeña novela es el array $_SESSION.

    Si todavía no lo tienes super claro, te sugiero continuar leyendo por aquí.

  • ¿Qué puede guardarse en las sesiones PHP?

    ¿Qué puede guardarse en las sesiones PHP?

    Es un hecho: las sesiones de PHP son una fuente de confusión y frustración para muchos desarrolladores.

    Desde mi punto de vista, el problema es que hay muchos puntos de fallo posible.

    En este artículo quiero detenerme sobre un punto que, si bien parece simple, tiene sus grises.

    Todo el mundo pre-supone que las sesiones son una suerte de baúl mágico donde puede guardarse cualquier cosa y, cual juego de rol, acompaña al héroe a donde vaya.

    La realidad no es tan así.

    Hay algunos objetos que no pueden ser almacenados en las sesiones PHP.

    Un ejemplo que veo con frecuencia es código similar a este:

    include("Conexion.php");
    $Conexion = Conectar(); --> Donde conectar es una funcion que conecta a mysql
    $_SESSION['Conex'] = $Conexion;

    Y luego, al querer levantar el dato en otro archivo:

    $Conexion = $_SESSION['Conex'];
    $Conexion->query('SELECT * FROM user;'); 

    Te encontrás con que la conexión no está en la sesión… ¿qué sucedió?

    El problema es que los datos que se guardan en la sesión tienen que ser serializados primero y no todos los objetos son serializables.

    De hecho, los objetos de tipo Recurso (Tales como las conexiones a MySQL) no lo son.

    Si lo que buscas es evitar abrir y cerrar conexiones a la base de datos en cada script lo que debes hacer es utilizar conexiones persistentes

  • Por qué se pierden las variables de sesión PHP

    Por qué se pierden las variables de sesión PHP

    Pocas cosas hay más frustrantes que ir a buscar algo donde sabés que lo dejaste y no encontrar nada.

    ¿Cómo es posible?

    Pusiste el session_start() al comienzo como Dios manda.

    El código es claro:

    $_SESSION['user'] = $user;

    ¿Qué puede ser más simple?

    Cuando hacés un echo $_SESSION['user'] en la misma página todo sale perfecto pero apenas clickeás en un link… nada por aquí, nada por allá.

    Y lo peor de todo es que el código funciona perfectamente en tu XAMPP pero en tu hosting no.

    ¿Cómo puede ser?

    Funcionó perfectamente bien durante meses y ahora, nadie sabe por qué, dejó de funcionar.

    Obviamente, dejar el problema sin resolver no es opción… ¿qué clase de sitio no recuerda al usuario que está logeado?

    No te preocupes, el manejo de sesiones en PHP es uno de los temas que más confusión trae a los que están recién empezando.

    Te sugiero comenzar por entender bien cómo funcionan las sesiones de PHP.

    En este artículo voy a explorar las causas más frecuentes que hacen que las variables de sesión se pierdan y qué podés hacer para solucionarlas.

    Te dejo las preguntas que deberías hacerte:

    ¿Todas las páginas tienen el session_start?

    En realidad, no es necesario que todas las páginas lo tengan… de hecho, hasta puede ser contraproducente.

    Lo que es seguro es que todas las páginas donde necesites guardar o leer información de la sesión deben invocar esta función.

    ¿La llamada a session_start está antes de enviar contenido al cliente?

    Esta pregunta puede parecer poco sensata. Después de todo, si la llamada a session_start estuviese después de enviar código al cliente verías un error como:

    PHP Warning: session_start(): Cannot start session when headers already sent

    ¿no?

    Pues… no necesariamente.

    El error se producirá, eso es seguro, pero que lo veas o no en pantalla depende de lo que figure en tu archivo php.ini (Específicamente en la entrada display_errors).

    ¿El valor de session.use_cookies es 1?

    Esta configuración determina que el mecanismo de propagación del ID de sesión sea el uso de una cookie.

    Muchas veces ocurre que el valor que tenés en tu entorno local no se condice con el del hosting.

    ¿El navegador está generando la cookie de sesión?

    Esto es fácil de verificar: usualmente esta cookie se llama PHPSESSID (aunque este nombre puede modificarse, también a través del archivo php.ini o la función ini_set).

    Si esto no sucede habría que probar si otras cookies se están generando (Lo más probable es que no).

    Esto puede deberse a la configuración del navegador (Que no acepta cookies).

    La verdad es que en este caso no hay mucho que puedas hacer… más allá de pedir por favor a los visitantes del sitio que habiliten las cookies.

    La buena noticia es que la gran mayoría sí lo harán 🙂

    ¿Cuál es el valor de session.cookie_lifetime?

    La configuración session.cookie_lifetime determina cuánto tiempo debe el navegador mantener la cookie de sesión.

    Si este tiempo es pequeño la cookie no vivirá lo suficiente como para llegar a la siguiente página.

    ¿La interacción desde el frontend es muy larga?

    Este es un problema que se da mucho en las SPA (Aplicación de una única página) donde casi toda la interacción se da a través de JavaScript.

    Si no se realiza ningún pedido al servidor en mucho tiempo se corre el riesgo de que la sesión se considere inactiva y se cierre automáticamente.

    En este artículo podés ver un ejemplo de esto (y cómo solucionarlo).

    ¿Qué valor tiene session.save_path?

    Esta configuración determina dónde se almacenan los archivos de sesión (asumiendo, claro, que el mecanismo de almacenamiento elegido sea en archivos).

    Si este valor es incorrecto no habrá dónde guardar la información.

    Si el valor corresponde a un directorio existente en tu servidor la siguiente pregunta es ¿los archivos de sesión están ahí?

    Si no están es probable que haya un problema de permisos: debes validar que el usuario con el que ejecuta tu servidor web sea capaz de escribir allí.

    Conclusión

    Si estás teniendo problemas para recuperar las variables de sesión es muy probable que se deba a fallas en la configuración de tu entorno, no desesperes: todo tiene solución 🙂

    Recorriendo esta lista de preguntas podrás identificar la causa específica de tu problema y resolverlo.

    ¡Vamos que los usuarios esperan!

  • Cómo funcionan las sesiones en PHP

    Cómo funcionan las sesiones en PHP

    Una fuente de mucha frustración y confusión para quienes arrancan con PHP es el manejo de sesiones.

    Hay muchas partes móviles y, a veces, tener todo esto en la cabeza marea…

    En este artículo intentaré hacer un repaso por cuáles son esas partes y cómo interactúan entre sí de modo que no te queden dudas respecto de cómo usar esta poderosa herramienta.

    Para qué sirven las sesiones

    Por supuesto que no puedo avanzar sin antes hablar de lo más importante: ¿para qué sirven las sesiones? O, dicho de otro modo: ¿por qué querrías complicarte la vida entendiendo todo esto?

    En pocas palabras: las sesiones sirven para compartir información no entre una página y otra (Un formulario HTML y el php que lo procesa por ejemplo), si no entre todas las páginas por las que el visitante pase.

    Un ejemplo simple de esto es cuando visitas un sitio y ves tu nombre de usuario en todas las páginas:

    Aunque sólo te identificaste una vez.

    Dónde se almacena la información de las sesiones

    Una pregunta sumamente importante es dónde se almacena esta información.

    Para empezar es importante comprender que la información de la sesión está almacenada del lado del servidor.

    Usualmente se guarda físicamente en archivos en el disco de la computadora que actúa como WebServer, aunque esta no es la única opción.

    Si querés saber exactamente dónde está guardada la información de las sesiones tenés que consultar dos variables de configuración de php:

    En cualquiera de los casos, la información almacenada es el resultado de la serialización de la información.

    Cómo se guardan datos en la sesión

    Para guardar datos en la sesión de un usuario existe un arreglo llamado $_SESSION.

    Este arreglo está presente siempre (es lo que se conoce como una variable super global).

    Entonces, guardar un elemento en la sesión del usuario es tan simple como hacer:

    <?php
    
    $_SESSION['var'] = 'value';

    Cómo se recuperan datos de la sesión

    Del mismo modo, recuperar datos de la sesión se reduce a leer el contenido del arreglo:

    <?php
    
    $var = $_SESSION['var'];

    Cómo se eliminan datos de la sesión

    Si lo que necesitas es quitar algún dato de la sesión basta con utilizar la función unset():

    <?php
    
    unset($_SESSION['var']);

    Cómo se vincula una sesión en el servidor con un cliente

    Esperablemente tu aplicación web tendrá muchos usuarios conectados a la vez.

    Seguramente no estés muy interesado en que el usuario A vea (o peor… ¡modifique!) los datos de la sesión del usuario B.

    Para evitar que esto suceda se necesita que cada sesión pueda vincularse a un (y sólo un) cliente.

    Efectivamente, cada sesión tiene un identificador único: el ID de sesión.

    En el caso del almacenamiento en archivos, el nombre de cada uno de ellos corresponde al ID de la sesión cuyo contenido está guardado dentro.

    Es decir, si tenemos un ID de sesión toqn1cl46210ear4e5t7seg6g7 encontraremos un archivo con ese mismo nombre en el directorio donde se almacenan las sesiones en el servidor.

    Si querés saber cuál es tu ID de sesión podés usar la función session_id()

    Este ID se genera cuando se crea la sesión, mediante el uso de la función session_start()

    Hasta acá no creo que haya mucho misterio, ¿cierto?

    Bueno, pues aquí es donde tenemos que ir un poco más atrás para comprender realmente qué pasa.

    Las aplicaciones web están basadas en el protocolo HTTP.

    Este protocolo estuvo pensado y diseñado en función de las necesidades de la web estática (Es decir, una web en la cual a todos los visitantes se les mostraba el mismo contenido).

    En un contexto donde da lo mismo si el que pidió la página B antes había pedido la página A no tiene sentido gastar recursos en implementar sesiones…

    Claro que, hoy en día las cosas son MUY diferentes… desafortunadamente, las aplicaciones web siguen estando basadas en HTTP, con lo cual, para mantener la coherencia entre diferentes peticiones sucesivas es necesario recurrir a algunos trucos.

    Básicamente, se necesita que el cliente informe al servidor cuál es su ID de sesión en cada petición.

    Esto puede realizarse de dos formas:

    1. A través de la URL (agregando un parámetro ?SID=$sid)
    2. Mediante una cookie

    El primer método no es muy aconsejable por un tema de seguridad, el segundo, si bien no es infalible, es mucho mejor.

    En caso de optar por la segunda opción, necesitarás que esa cookie se genere en el cliente luego de iniciar la sesión.

    Podrías usar setcookie, pero sería un poco redundante: session_start() ya lo hace 😉

    Claro que, dado que todo este proceso se realiza mediante el intercambio de encabezados HTTP, debes tener cuidado de llamar a session_start() antes de enviar contenido al cliente.

    Qué hace exactamente la función session_start()

    session_start() es una función algo rara.

    Digo rara porque realiza varias tareas según el caso, algo que en general no es muy aconsejable.

    Si la petición NO viene con una cookie (o un parámetro SID) O el valor no corresponde con una sesión activa en el servidor:

    1. Se genera un nuevo ID de sesión
    2. Se inicializa el espacio de almacenamiento en el servidor
    3. Se envía el encabezado correspondiente a la creación de la cookie de sesión (Asumiendo por supuesto que este el método de propagación de ID seleccionado)

    Si la petición SI viene con una cookie (o un parámetro SID) Y el valor corresponde con una sesión activa en el servidor:

    1. Se lee el contenido del archivo de sesión correspondiente
    2. Se inicializa el arreglo $_SESSION con los valores contenidos en dicho archivo

    Cómo se destruye una sesión

    Por último, en el caso de que quieras destruir por completo el contenido de una sesión (Por ejemplo, el caso en que un visitante esté abandonando tu aplicación), la función session_destroy() se encargará de ello.

    Un detalle importante a tener en cuenta es que session_destroy() sólo actuará del lado del servidor, así que, para mayor seguridad es conveniente usar setcookie() para eliminar la cookie del lado del cliente

  • Detalles del protocolo HTTP que todo desarrollador PHP debe conocer

    Detalles del protocolo HTTP que todo desarrollador PHP debe conocer

    Algo que siempre me llamó la atención es cómo en los cursos de PHP (o de programación web en general para el caso), suele pasarse por alto hablar de HTTP.

    Es cierto que esto puede sonar demasiado teórico y, lo admito, aburrido, sin embargo, es un conocimiento que va a aclararte muchas cosas.

    Un claro ejemplo de esto es cuando te encontrás con un error como este:

    PHP Warning: session_start(): Cannot start session when headers already sent

    Cuando tu código dice algo como:

    Hola!
    <?php
    session_start();

    O también:

    PHP Warning: Cannot modify header information - headers already sent

    Al entrar a una página con un código como:

    Hola!
    <?php
    setcookie('MyCookie','MyValue');

    O

    Hola!
    <?php
    header('Location: pagina2.php');

    Seguramente sabrás (tal vez por experiencia o porque «así es como se hace») que las funciones session_start, setcookie y header deben invocarse antes de enviar contenido al cliente, es decir:

    <?php
    header('Location: pagina2.php');
    ?>
    Hola!

    Y así las cosas funcionan.

    Pero, por supuesto que existe una razón para esto (No es que haya sido un capricho de Rasmus :)).

    Para comprender qué tienen de especial estas funciones es necesario entender un poquito qué es lo que sucede cuando visitas una página web.

    Más específicamente, debes saber lo básico sobre la forma en que se comunica el cliente (Tu navegador) y el servidor.

    Esa comunicación se realiza siguiendo las reglas que establece el protocolo HTTP.

    Cómo funciona HTTP

    Lo primero que debes saber es que HTTP es un protocolo de transferencia de texto (De ahí su nombre HyperText Transfer Protocol).

    La noción de hypertexto es algo anticuada pero no olvides que HTTP se diseñó a principios de la década de 1990!

    Se trata de un texto que puede contener referencias a otros textos (Es decir, links).

    Cuando comenzó la web, realmente era algo bastante simple.

    Una vez establecida la conexión física no había mucho que pudiera hacer el cliente más que solicitar algún archivo de texto que, esperablemente, estaría presente en el disco del servidor.

    Si este era efectivamente el caso, la tarea del servidor era enviar el contenido de ese archivo de vuelta al cliente.

    Corría por cuenta del cliente interpretar ese texto y mostrarlo al usuario de una forma humanamente agradable (Esto sigue siendo así hasta hoy).

    El flujo es algo similar a:

    Pero, además del contenido del archivo (El HTML en este caso), el servidor envía algo de información adicional al cliente.

    Esta información (o meta-información si se quiere) viaja al cliente a través de lo que se conoce como encabezados HTTP.

    Cómo se ve un encabezdo HTTP

    Los encabezados, como todo lo demás en HTTP, son cadenas de texto.

    En particular, los encabezados son cadenas con un formato especial:

    NOMBRE: VALOR

    Si nunca viste estos encabezados te invito a que abras una nueva ventana de tu navegador (esta por ejemplo) y busques la consola del desarrollador (Si usás Chrome la vas a ver al apretar F12):

    Te vas a encontrar con algo como:

    Y en particular, si vas a la pestaña Network:

    Vas a encontrarte con un detalle de todas las peticiones que tu browser hizo para poder mostrarte el sitio que estás viendo.

    ¿Me acompañás un pasito más?

    Hacé click en una petición cualquiera:

    Y ahí estás viendo los encabezados (Headers).

    Si bajás un poquito por el panel derecho te encontrás con:

    Los encabezados que el servidor le envió a tu navegador.

    A partir de esta información el navegador toma ciertas decisiones, por ejemplo, acá vez que hay un encabezado que dice content-encoding: gzip.

    Ese encabezado le está diciendo al navegador que el texto que va a recibir (cuando finalicen los encabezados) corresponde al resultado de comprimir el contenido buscado, de modo que, para poder interpretarlo, primero tendrá que descomprimirlo.

    Y aquí llegamos al fondo de la cuestión, la clave del misterio es que los encabezados siempre se envían antes del contenido propiamente dicho.

    Qué tienen en común session_start(), setcookie() y header()

    Lo que estas tres funciones tienen en común es que todas envían encabezados al cliente.

    La función header lo hace de forma explícita… de eso se trata precisamente, de enviar un encabezado arbitrario al cliente.

    La función setcookie en realidad es una especie de atajo para no escribir una expresión más compleja como:

    header('Set-Cookie: MyCookie=Value');

    Y la función session_start()… bueno, es algo más complicado explicar qué hace pero internamente hace uso de setcookie (es decir, de header()).

    La lista de encabezados HTTP (y sus funciones) son muchas, si estás con ganas de investigar podés seguir por acá.

    Algo que te recomiendo conocer (al menos básicamente) son los códigos de error HTTP, algo que te va a venir muy bien si te toca desarrollar WebServices REST por ejemplo.

    Y ahora sí, espero haberte aclarado algunos conceptos y que no te vuelva a sorprender un error de encabezados ya enviados 😉