Categoría: Conceptos

  • Ejemplo de inyección de depencias en PHP

    Ejemplo de inyección de depencias en PHP

    Un concepto muy simple y, a la vez, muy potente de la Programación Orientada a Objetos es la inyección de dependencias.

    Diría que se trata de la piedra angular de cualquier sistema desacoplado y, por lo tanto, fácil de evolucionar y testear.

    Y lo mejor de todo es que su implementación es realmente sencilla. En otras palabras: puras ventajas.

    Acompañame a darle una mirada más de cerca.

    Qué es una dependencia en POO

    Como en muchas ocasiones en este mundo de la informática, un mismo término puede referirse a diferentes cosas y, para saber realmente de qué estamos hablando es necesario poner en claro el contexto.

    Por ejemplo, si hablamos de las dependencias de una aplicación, nos estaremos refiriendo a aquellas librerías necesarias para que dicha aplicación funcione. Tema que está mejor tratado en este artículo.

    En este contexto, cuando hablo de dependencia me refiero a aquellos objetos de los que otros dependen para realizar sus tareas.

    Te pongo un ejemplo que me compartió un lector de mi newsletter y me viene muy bien para ilustrar este concepto:

    <?php
    namespace Controllers;
    
    use ApplicationService\BillFinderService;
    use ApplicationService\TotalDeductiblesByYearService;
    use Dao\DeductibleDao;
    use Infraestructure\Connection\ConnectionMySql;
    
    class FrontController extends Controller{
    
        private $connection;
    
        public function __construct()
        {
            parent::__construct();
            $this->connection = new ConnectionMySql();
        }
        
        public function home($year = null){
            if (!$year) {
                $year = new \DateTime();
                $year = $year->format('Y');
            }
            $billFinderService = new BillFinderService($this->connection);
            $totalDeductibleByYear = new TotalDeductiblesByYearService($this->connection);
            $lastBillsRegistered = $billFinderService->findByYear($year);
            $totalByDeductible = $totalDeductibleByYear->getTotalByYear($year);
            echo $this->templates->render('index', [
                'title' => 'Main menu',
                'lastBillsRegistered' => $lastBillsRegistered,
                'totalByDeductible' => $totalByDeductible,
                'year' => $year,
                'years' => [2023, 2024, 2025]
            ]);
    
        }
        
        public function error404(){
            
            echo $this->templates->render('404');
        
        }
    
        public function getBillsByDeductibleIdAndYear($deductibleId, $year){
    
            $totalDeductibleByYear = new TotalDeductiblesByYearService($this->connection);
            $bills = $totalDeductibleByYear->getBillsByDeductibleIdAndYear($deductibleId, $year);
            $deductibleDao = new DeductibleDao($this->connection);
            $deductible = $deductibleDao->findById($deductibleId);
            echo $this->templates->render('bills-by-deductible-and-year', [
                'title' => "Bills by deductible by year",
                'deductibleName' => $deductible->getName(),
                'bills' => $bills,
                'year' => $year
            ]);
        }
    }

    Aquí tenemos una clase llamada FrontController que depende, entre otras, de:

    • ConnectionMySql
    • BillFinderService
    • TotalDeductiblesByYearService
    • DeductibleDao

    ¿Por qué digo que FrontController depende de estas clases? Porque, para lograr sus objetivos se sirve de métodos de ellas, ejemplo:

    public function home($year = null){
            ...
            $lastBillsRegistered = $billFinderService->findByYear($year);
            $totalByDeductible = $totalDeductibleByYear->getTotalByYear($year);
            ...
        }

    Para que este código pueda ejecutarse, será necesario que las variables que contienen referencias a dichos objetos ($billFinderService y $totalDeductibleByYear) hayan sido previamente asignadas a instancias de sus respectivas clases.

    Algo que se está haciendo precisamente al comienzo del método:

    public function home($year = null){
    ...
            $billFinderService = new BillFinderService($this->connection);
            $totalDeductibleByYear = new TotalDeductiblesByYearService($this->connection);
    ...
    }

    Entonces… ¿cuál es el problema?

    Bueno, más que un problema es una oportunidad perdida.

    Implementando inyección de dependencias

    Empiezo por una pregunta que seguramente se pasó por alto al escribir este código:

    ¿Es necesario crear una nueva instancia de cada colaborador cada vez que se llama al método?

    Ya dije que crear la instancia es inevitable pero la pregunta apunta a determinar si el mejor momento (O lugar, como quieras verlo) para hacerlo es aquí.

    ¿Qué pasaría si modificáramos ligeramente el código para que crear las dependencias fuera responsabilidad de alguien más?

    Algo como:

    class FrontController extends Controller{
    
        private $connection;
        private $billFinderService;
        private $totalDeductiblesByYearService;
    
        public function __construct(ConnectionMySql $connection, BillFinderService $billFinderService, TotalDeductiblesByYearService $totalDeductiblesByYearService)
        {
            parent::__construct();
            $this->connection = $connection;
            $this->billFinderService = $billFinderService;
            $this->totalDeductiblesByYearService = $totalDeductiblesByYearService;
        }
        
        public function home($year = null){
            if (!$year) {
                $year = new \DateTime();
                $year = $year->format('Y');
            }
            echo $this->templates->render('index', [
                'title' => 'Main menu',
                'lastBillsRegistered' => $this->billFinderService->findByYear($year),
                'totalByDeductible' => $this->totalDeductiblesByYearService->getTotalByYear($year),
                'year' => $year,
                'years' => [2023, 2024, 2025]
            ]);
        }
    ...
    }

    Con este pequeño cambio pateamos la responsabilidad hacia arriba. Ahora es problema del cliente de la clase FrontController conseguir instancias válidas para pasarle (Ya sea creándolas él mismo o pidiéndoselas a algún otro componente).

    Esta forma de vincular una clase con sus colaboradores es lo que se conoce como inyección de dependencias.

    Ventajas de la Inyección de Dependencias

    Ahora que el tecnicismo está claro, veamos por qué vale la pena diseñar de esta manera.

    Para empezar, porque el costo de hacerlo es muy bajo. Si mirás el código inicial y lo comparás con el actual diríamos que mucha diferencia no hay, ¿cierto?

    Pero vamos a ver los aspectos más jugosos del tema:

    Performance

    Este punto depende mucho del contexto particular de tu aplicación. Si el método que crea los objetos no se ejecuta con mucha frecuencia la diferencia no va a ser mucha, pero si es al revés, te vas a estar ahorrando la creación de objetos idénticos, lo que implica menos gasto de memoria y de tiempo de procesamiento.

    Es cierto que en PHP esta ventaja se diluye por el hecho de que cada request se procesa en su propio hilo pero aún así, algo se puede ganar.

    Versatilidad

    Este punto me parece el más interesante ya que es el que da lugar a los dos siguientes.

    Pensalo así: al dejar de lado la decisión de qué instancia específica del colaborador va a usar tu clase dejás abierta la puerta a que un mismo método pueda usarse en diferentes contextos.

    Imaginá un escenario en el que existe no una si no dos instancias de la clase BillFinderService y que, dependiendo de alguna configuración se necesita usar una o la otra.

    Si el código está escrito usando Inyección de Dependencias, implementar ese cambio no supone ningún inconveniente, basta con cambiar el parámetro que se usa para construir FrontController y listo.

    Es más, dado que estás trabajando con Programación Orientada a Objetos, bien podrías pasarle una instancia de una clase derivada de BillFinderService y todo seguiría funcionando como si tal cosa.

    De hecho, este mecanismo es el que posibilita mucho de los principios SOLID.

    Testabilidad

    Un caso muy claro en el que viene bien poder cambiar la instancia de un colaborador por otra es a la hora de hacer tests unitarios.

    Si no podés aislar de sus la clase que querés testear, las cosas se te van a complicar.

    Una vez que lo viste…

    … no podés des-verlo.

    No te digo que vayas corriendo a desarmar todos tus proyectos que no usan Inyección de Dependencias pero sí te propongo que le des una segunda mirada teniendo en cuenta lo que podrías ganar si lo implementás y, de a poco, lo vayas incorporando a tu día a día.

  • ¿Cuál es la diferencia entre PDO y MySQLi?

    ¿Cuál es la diferencia entre PDO y MySQLi?

    Si me decís que estás desarrollando una aplicación web usando PHP y no me das más datos, cerrando los ojos diría que estás usando una base de datos MySQL y, seguro que en el 80% de los casos acertaría.

    Es un hecho: PHP y MySQL son un dúo muy común en este mundillo.

    Lo que no está tan definido es cuál es el mejor modo de conectarse a MySQL utilizando PHP.

    A priori destacan dos:

    Ambos pueden usarse para armar CRUDs, reportes y cualquier otra necesidad que implique comunicar una aplicación PHP con una base de datos MySQL.

    Entonces… ¿cómo elegir?

    Para hacerte (algo) más fácil la tarea te voy a comentar brevemente de qué se trata cada uno.

    Qué es MySQLi

    MySQLi es una extensión de PHP creada con el propósito de facilitar la comunicación entre un script PHP y un servidor MySQL.

    Es la heredera de la vieja y querida extensión MySQL que ya ha quedado obsoleta.

    MySQLi hizo su aparición junto con la versión 4.1 de MySQL.

    Lo más probable es que nunca te cruces con un servidor que utiliza una versión anterior (Al día en que estoy escribiendo este artículo la versión de MySQL es la 8.0) pero… si llegara a pasar, vas a tener que arreglártelas con la extensión mysql (sin la «i»).

    Su nombre precisamente viene de MySQL improved (Mejorada).

    Una característica interesante de esta extensión es que muestra una API Orientada a Objetos y también una estructurada.

    Por ejemplo, para establecer una conexión a un servidor podés usar tanto:

    <?php
    $link = mysqli_connect("localhost", "root", "", "mydb");

    Como

    <?php
    $mysqli = new mysqli('localhost', 'root', '', 'mydb');

    Si estás migrando un sistema que usaba la vieja extensión y no tenés mucho tiempo para hacer grandes cambios es trivial actualizar al uso de la nueva librería: basta agregar una «i» al nombre de las funciones que venías usando.

    Claro que, si querés dar un salto, te recomiendo hacer el esfuerzo de escribir el código usando objetos.

    Qué es PDO

    PDO también es una extensión pero, a diferencia de MySQLi, PDO está diseñada como una capa de abstracción sobre una amplia cantidad de motores específicos.

    Para que se entienda mejor, esta es la relación entre PDO y los motores específicos:

    Cuál es la ventaja de usar PDO en lugar de MySQLi

    La ventaja principal de usar PDO en lugar de MySQLi es que, llegado el caso de decidir migrar a un motor de base de datos diferente del elegido originalmente, el cambio es trivial.

    Por ejemplo, si tuvieses que pasar de MySQL a SqlServer deberías cambiar:

    $conn = new PDO('mysql:dbname=mydb;host=127.0.0.1');

    Por

    $conn = new PDO('mssql:dbname=mydb;host=127.0.0.1');

    Y el resto de tu código seguiría funcionando sin alteraciones.

    Cuál es la ventaja de usar MySQLi en lugar de PDO

    Pero si PDO es tan potente y versátil… ¿no hay lugar para MySQLi en la actualidad?

    La verdad es que sí lo hay (a diferencia de lo que muchos creen).

    La versatilidad de PDO es un arma de doble filo.

    Como sucede con cualquier solución genérica, quien mucho abarca poco aprieta 🙂

    En términos técnicos, lo que ocurre es que PDO no puede aprovechar las pequeñas diferencias existentes entre los diferentes motores, lo cual lo hace menos eficiente que MySQLi en ciertas situaciones.

    Por otro lado, una capa más de abstracción agrega más overhead.

    Cómo decidir si usar PDO o MySQLi

    No quiero que te vayas de aquí con una confusión mayor de la que traías al comienzo, así que preparé este sencillo diagrama de flujo para ayudarte a tomar la decisión, espero te sirva:

  • ¿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
  • ¿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.

  • ¿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 😉

  • Cuál es la diferencia entre isset() y empty()

    Cuál es la diferencia entre isset() y empty()

    Empiezo por las formalidades:

    Ambas son funciones del lenguaje.

    isset informa si un identificador (el nombre de una variable) está definido o no dentro del hilo de ejecución actual (Muy importante entender esto, no se trata de que haya sido definido dentro del archivo actual).

    empty indica si el valor asociado a un identificador es o no vacío (La definición de vacío de PHP tiene sus peculiaridades como te voy a mostrar más adelante).

    Veamos algunos ejemplos para aclarar el punto:

    $a = 0;
    
    if (empty($a)) {
      echo 'empty'.PHP_EOL;
    }
    
    if (isset($a)) {
      echo 'isset'.PHP_EOL;
    }

    Este código dará como salida:

    empty
    
    isset

    Si se lo ejecuta desde la línea de comandos.

    Este otro

    if (isset($a)) {
      echo 'isset'.PHP_EOL;
    }
    
    $a = 0;
    
    if (empty($a)) {
      echo 'empty'.PHP_EOL;
    }

    Mostrará en cambio:

    empty

    Ya que la variable $a recién se define después de la primera verificación.

    Es interesante ver el siguiente ejemplo:

    if (isset($a)) {
      echo 'isset'.PHP_EOL;
    }
    
    if (empty($a)) {
      echo 'empty'.PHP_EOL;
    }

    Que también dará como resultado

    empty

    En definitiva, podrías pensar que !isset() es un caso especial de empty().

    Respecto de los valores que PHP considera como vacío, son estos:

    • 0
      null
      false
      ""
      "0"
      []
      array()
      0.0

    A pesar de que la diferencia parezca sutil, existen casos de uso muy diferentes para cada una.

    Una técnica que he visto usada alguna vez para verificar si un archivo ha sido accedido en forma directa o no es verificar si un identificador ha sido definido.

    Algo como:

    primero.php

    <?php
    
    $a = "1";
    
    require_once "segundo.php";
    

    segundo.php

    <?php
    
    if ( isset($a) ) {
       ... // Hacer algo util
    } else {
       die ('Acceso no autorizado');
    }

    No sé si es la forma que más me convence, pero funciona. Si alguien quiere acceder ilegalmente a segundo.php no podrá hacerlo.

    Un caso de uso deempty sería cuando una variable puede tomar cualquiera de los valores considerados vacíos, pero a los efectos de nuestro script, cualquiera de ellos vale… usando empty la expresión if será mucho más clara que una que tenga todos los || posibles (o bien algo como !in_array()). La desventaja clara de este enfoque es que depende de saber muy bien cuáles son los valores que PHP considera como vacíos… Usualmente yo prefiero dejar la lógica lo más explícita que pueda para evitar sorpresas.

    ¿Me olvidé de algo importante?