Categoría: Cómo hacer para…

Estos artículos te explicarán cómo resolver problemas específicos usando PHP

  • Cómo debuggear un comando Symfony usando PhpStorm

    Cómo debuggear un comando Symfony usando PhpStorm

    En este post voy a mostrarte un caso algo particular de cómo debuggear un script que corre por línea de comandos.

    Se trata de debuggear un script hecho con el framework Symfony.

    La particularidad que tiene este escenario es que el código que escibiste (y que querés verificar), no es un archivo php común, si no que es una claseque extiende de ContainerAwareCommand, con lo cual, no es posible invocarla en forma directa.

    La ejecución de este comando requiere del paso por el script bin/console. Ahora, si te fijás su contenido notarás que no es más que un script php, con lo cual, podría ser debuggeado usando la configuración «estándar» de PhpStorm para estos casos…

    La idea en definitiva es simple, cuando ejecutás algo como:

    php bin/console miApp:miComando

    El script php que estás ejecutando es bin/console y «miApp:miComando» no es otra cosa que un argumento de CLI.

    Entonces precisamente, de esa forma es como tenés que configurar el IDE para que pueda ejecutar tu comando (Y puedas ponerle breakpoints, evaluar variables, etc…):

    En este ejemplo, el comando que yo uso (EnviarRecordatorios) recibe parámetros a su vez. Ningún problema, son más argumentos que se le pasan a bin/console.

    ¿De qué otra forma podrías debuggear comandos symfony?

  • Cómo debuggear un script de CLI con XDebug y PhpStorm

    Cómo debuggear un script de CLI con XDebug y PhpStorm

    Es muy común encontrarse con la necesidad de escribir scripts que deberán ser ejecutados por la línea de comandos dentro del contexto de una aplicación web, cronjobs por ejemplo.

    Hasta hace poco tenía muy claro cómo debuggear un script que ejecutaba a través del webserver, pero no había logrado (y francamente, no creía que fuera posible) debuggear un script que corría desde el CLI.

    Buscando un poco (y haciendo algo de prueba y error) finalmente lo logré.

    Si usás PhpStorm (y si no lo hacés aún, te lo recomiendo ampliamente) estos son los pasos que deberás seguir:

    Para empezar, asegurate de tener XDebug o ZendDebugger instalado en el entorno donde vas a correr tu script (En mi caso, usé una máquina virtual que ya tenía todo lo necesario).

    Después, la clave del tema está en el concepto de Run/Debug configuration:

    En este cuadro de diálogo:

    Debés indicar qué archivo vas a querer debuggear y, eventualmente, qué argumentos vas a querer pasarle al script al momento de ejecutar.

    Una vez que tengas la configuración armada… ¡listo! Ya podés ver la ejecución de tu script paso a paso como lo harías con una aplicación web.

    ¿Cómo debuggeas tus scripts?

  • Cómo autenticar usuarios en una aplicación web con PHP

    Cómo autenticar usuarios en una aplicación web con PHP

    Este es un tema que suele causar bastante confusión en la gente que se enfrenta con este problema por primera vez… hay tantos usuarios/passwords/niveles de autenticación que si no se presta mucha atención es fácil perderse.

    Lo principal es entender que la autenticación es un proceso de comunicación entre dos entidades:

    1. Un cliente
    2. Un servidor

    El cliente es el que se identifica (generalmente utilizando un par «nombre de usuario» + «contraseña») y el servidor es quien valida esas credenciales y devuelve algún tipo de identificador de acceso.

    Podríamos pensarlo como el guardián de una discoteca en una fiesta privada. Cualquiera puede pararse ante el grandote y decirle que es uno de los invitados, pero si tu nombre no figura en la lista… por más que patalees, te vas a quedar afuera (Y aún si tu nombre figura, si te olvidaste el DNI… más vale pegar la vuelta).

    En el caso de una aplicación web, existen muchos clientes y servidores interviniendo (De hecho, como vas a ver en breve, varios de estos servidores son a la vez clientes… ¡como para no confundirse!).

    En el clásico esquema de un request:

    El primer cliente que aparece es el browser (FireFox, Chrome, IE, etc…) y el servidor (En este post me voy a referir a «cliente» y «servidor» en su sentido como procesos, no como computadoras) es el webserver (Apache, NginX, etc…).

    Entonces, el primer nivel de autenticación que debe superarse es este: el cliente que se conectó al webserver ¿presentó credenciales válidas?

    Hoy en día es extremadamente raro encontrarse con este tipo de autenticación (por lo general, los servidores web están configurados de modo de aceptar conexiones de cualquier cliente sin pedir demasiado, pero se puede configurar este tipo de autenticación).

    Una vez que el webserver dió la bandera verde, dependiendo de cuál haya sido el pedido del cliente, se le enviará una respuesta (generalmente un texto HTML).

    El siguiente nivel de autenticación es el del webserver contra el servidor de base de datos:

    Desde el punto de vista del servidor de base de datos, quien hace la conexión es el cliente (Es indiferente si se trata de una acción iniciada por un humano o por una computadora… en última instancia, la comunicación siempre es computadora-a-computadora…).

    Entonces acá tenemos:

    1. Un cliente del webserver (el navegador)
    2. Un cliente del servidor de bases de datos (el webserver)

    Al igual que en el ejemplo anterior, el cliente (en este caso el webserver), debe proveer de credenciales para que el servidor de bases de datos le permita acceder a sus servicios.

    Es importante notar que las credenciales que utiliza el navegador para autenticarse ante el servidor web no tienen por qué ser las mismas que usa el webserver para conectarse a la base de datos (De hecho, lo más probable es que sean completamente diferentes).

    Hasta ahora te mostré los niveles más bajos de autenticación (Los que podríamos llamar de infraestructura), pero existe un tercer nivel de autenticación: el de la aplicación.

    En este nivel es responsabilidad del propio sistema (es decir tuya :)) decidir quién puede ingresar y qué puede hacer una vez adentro.

    Pueden usarse diversos mecanismos para lograr este tipo de autenticación: el más simple (y común) es el almacenamiento de nombres de usuario y contraseñas en una base de datos a la que la aplicación tiene acceso. Otro método más elaborado es el uso de servicios de terceros (Google, Facebook, Twitter, etc…).

    Entonces, recapitulando…

    Cuando te enfrentás a algo como esto:

    Lo que estás viendo es el resultado de la interpretación de HTML por parte de tu navegador.

    Los datos que ingreses ahí van a ser recibidos primero por algún webserver y luego, a través de algún script (PHP por ejemplo), contrastados contra alguna base de datos (A la cual el script accederá utilizando sus propias credenciales).

    Es por eso que, por lo general, una aplicación php tenga su propio username definido en su servidor de base de datos, aunque tenga un gran número de usuarios propios (visitantes registrados).

    ¿Te quedó alguna duda? ¡Espero tus comentarios!

  • Cómo autenticar usuarios con redes sociales en PHP

    Cómo autenticar usuarios con redes sociales en PHP

    Algo que se ha puesto bastante de moda últimamente es la posibilidad de logearse en un sitio cualquiera usando un usuario creado para otro (Por ejemplo, logearte a StackOverflow usando tu Gmail o a Clarin usando tu Facebook).

    Este enfoque presenta varias ventajas:

    1. Para el usuario significa no tener que inventar (¡y recordar!) una contraseña más y, por si eso fuera poco, ingresar a tu sitio haciendo un solo click.
    2. Para el sitio significa no tener que almacenar información de usuarios ni hacer un doble-opt-in (Se asume que el proveedor de autenticación ya ha validado la identidad del usuario).

    Ahora bien, desde el punto de vista de tu aplicación… ¿cómo podés implementar esto?

    La mayoría de los grandes proveedores de autenticación (Gmail, Facebook, Twitter, etc…) utilizan un protocolo que se conoce como OAuth, muy seguro pero algo complejo de implementar a mano.

    Afortunadamente existen algunas librerías que hacen el trabajo sucio por nosotros. Veamos algunas:

    HybridAuth

    Esta librería (Puede consultarse la documentación acá) ofrece muchas posibilidades además del login (Compartir en redes sociales, acceso a perfiles, etc…).

    Como de costumbre, el uso recomendado es mediante Composercomposer init y después (si no incluiste la dependencia interactivamente) composer require hybridauth/hybridauth

    En definitiva, el archivo composer.json debería quedar similar a:

    {
     "name": "leeway/hybrid_auth",
     "description": "Test de hybrid_auth",
     "require": {
     "hybridauth/hybridauth": "^2.10"
     },
     "authors": [
     {
     "name": "Mauro Chojrin",
     "email": "mauro.chojrin@leewayweb.com"
     }
     ]
    }

    Luego, instalamos las dependencias usando composer install y estamos listos para comenzar nuestra aplicación.

    HybridAuth provee una clase principal: Hybrid_Auth.

    Lo primero que vamos a necesitar hacer es, obviamente, crear una instancia de esta clase. El parámetro que recibe el constructor es la configuración.

    Esta configuración tiene una cantidad de parámetros, algunos opcionales y otros obligatorios. Los básicos son:

    • base_url URL a donde el proveedor (Google, Facebook, Twitter, etc…) redireccionará una vez realizada la autenticación. A esta URL se la conoce como callback
    • providers Lista de proveedores que quieras usar (¡Claro que deben estar soportados por la librería!).
      • enabled true o false
      • keys Credenciales de acceso para tu aplicación:
        • id Identificación de tu aplicación
        • key Clave de acceso
        • secret Clave secreta

    Las credenciales de acceso las debes generar en cada proveedor (Puedes ver cómo hacerlo para Facebook siguiendo este enlace, para Google este y para Twitter este).

    Veamos un ejemplo:

    <?php
    
    return [
            'base_url' => 'http://localhost:8080/vendor/hybridauth/hybridauth/hybridauth/',
            'providers' => [
                            'Facebook' => [
                                    'enabled' => true,
                                    'keys' => [
                                            'id' => 'XXXX',
                                            'secret' => 'YYYY',
                                    ]
                            ]
                    ],
    ];

    Aquí lo importante es (más allá de la identificación de mi app en particular), la dirección a la que Facebook (o el proveedor de autenticación) deberá realizar su callback.

    En mi caso es una dirección que comienza con http://localhost:8080 porque estoy desarrollando una prueba de concepto usando el servidor embebido en PHP, si fuera un sitio productivo habría que escribir el dominio.

    Sin entrar en los detalles de OAuth (U OpenID que a los efectos de este artículo no distan mucho), el circuito de autenticación implica dejar por un momento nuestro sitio para pedirle al usuario que realice un login en otro y, una vez logrado esto, vuelva.

    Toda esta complejidad precisamente la maneja HybridAuth a través del archivo vendor/hybridauth/hybridauth/hybridauth/index.php cuyo contenido no es más que:

    <?php
    /**
    * HybridAuth
    * http://hybridauth.sourceforge.net | http://github.com/hybridauth/hybridauth
    * (c) 2009-2015, HybridAuth authors | http://hybridauth.sourceforge.net/licenses.html
    */
    
    // ------------------------------------------------------------------------
    //      HybridAuth End Point
    // ------------------------------------------------------------------------
    
    require_once( "Hybrid/Auth.php" );
    require_once( "Hybrid/Endpoint.php" );
    
    Hybrid_Endpoint::process();

    Obviamente toda la magia está en el método Hybrid_Endpoint::process() el cual, una vez validada la autenticación, volverá a redirigir al visitante hacia la página en la que ésta se solicitó.

    En mi ejemplo sería el archivo login.php:

    <?php
    
    require_once __DIR__.'/vendor/autoload.php';
    
    session_start();
    $config = require_once 'config.php';
    
    $ha = new Hybrid_Auth( $config );
    
    try {
            $facebook = $ha->authenticate( 'facebook' );
            $user_profile = $facebook->getUserProfile();
    
            echo 'UserProfile: '.print_r($user_profile, 1);
    } catch ( Exception $e ) {
            echo $e->getMessage();
    }
    

    Al cual se accede desde login.html:

    <html>
            <form>
                    <fieldset>
                            <legend>Identif&iacute;cate con tu Facebook</legend>
                                <a href="login.php">Autorizar</a>
                    </fieldset>
            </form>
    </html>
    

    ¿Quedó alguna duda? ¡Espero tus comentarios!

  • Cómo autenticar usuarios vía HTTP usando PHP

    Cómo autenticar usuarios vía HTTP usando PHP

    La autenticación a nivel de HTTP se activa mediante la configuración del webserver.

    Es el nivel más bajo de autenticación que puede tenerse en un entorno web, ya que lo que va a verificar es que el cliente que solicita un determinado recurso (URI) tenga acceso a él, antes de hacer ningún otro tipo de verificación.

    De lo que estoy hablando es de lo que sucede cuando querés ingresar a un sitio, por ejemplo http://localhost y, en lugar de ver el contenido del sitio, se abre una ventana como esta:

    Por lo general, no es mucho lo que puede validarse (No hay posibilidad de ofrecer diferentes niveles de acceso ni nada parecido), con lo cual, no es algo que suela utilizarse en entornos de acceso público.

    La forma de configurar este tipo de acceso depende del web server que uses.

    En el caso de Apache, la forma de lograrlo es:

    1. Crear un archivo de autenticaciones (Usando la utilidad htpasswd por ejemplo).
    2. Dentro de la configuración del servidor, definir un bloque para el DocumentRoot:
     <Directory "/var/www/html">
       AuthType Basic
       AuthName "Restricted Content"
       AuthUserFile /etc/apache2/.htpasswd
       Require valid-user
     </Directory>
    1. Reiniciar el servidor (O, menos drástico, recargar la configuración).

    A partir de este momento, cualquier usuario que desee ingresar a tu sitio tendrá que identificarse (Ante el webserver).

    Otra forma de usar este tipo de autenticación (que no depende de la configuración del webserver), es hacerlo a través de PHP:

    La clave aquí es el uso de la función header (y conocer algunos detallitos del protocolo HTTP :)).

    Básicamente se trata de un script que será invocado dos veces:

    La primera será la que solicite las credenciales y la segunda será la que valide (Lo mismo que podrías hacer si hicieras un login clásico, con la diferencia de que la segunda llamada se produce automáticamente al completar los datos).

    El código es este:

    <?php
    if (!isset($_SERVER['PHP_AUTH_USER'])) {
        header('WWW-Authenticate: Basic realm="My Realm"');
        header('HTTP/1.0 401 Unauthorized');
        echo 'Este texto se ve si el usuario cancela';    
    } else {
        echo "<p>Hola {$_SERVER['PHP_AUTH_USER']}.</p>";
        echo "<p>Tu password es {$_SERVER['PHP_AUTH_PW']}.</p>";
    }

    El if está para validar si estás en la primera llamada o en la segunda. Las claves de la variable de $_SERVER se completan automáticamente con lo ingresado por el usuario.

    Esta forma puede ser más conveniente ya que te permite independizarte del WebServer con el que estás trabajando (Y por lo tanto, cambiarlo será más sencillo).

    Por último, una forma de acceder al sitio sin pasar por el formulario es pasar las credenciales como parte de URL:

    http://user:password@localhost

    ¿Qué forma de autenticación usas en tus aplicaciones?

  • Cómo guardar una imagen en una base de datos

    Cómo guardar una imagen en una base de datos

    Un clásico problema de una aplicación web es el almacenamiento de archivos subidos por los usuarios (Sus fotos por ejemplo).

    Si bien teóricamente se puede realizar sin mayores inconvenientes (Al fin y al cabo, un archivo digital no es más que una colección de datos binarios), es sumamente ineficiente hacerlo.

    Veamos cómo sería esto:

    Primero que nada, habría que manejar de alguna forma el upload de archivos, pero, asumiendo que el archivo ya está disponible para php se podría pensar en almacenarlo dentro de la base de datos utilizando algún campo tipo BLOB.

    Un problema inmediato que surge de tomar esta opción es que la base de datos crecerá mucho si el sistema es muy utilizado, lo cual impactará negativamente en su rendimiento, hará más costozos los backups, etc…

    Una alternativa simple es guardar, en lugar del contenido del archivo, su localización (Puede ser la ruta al archivo, si está guardado en algún medio de almacenamiento, ya sea el disco local o alguno externo, como S3 por ejemplo).

    De esa forma se logra aislar a la base de datos del manejo de información que, por otro lado, tampoco será sencilla de consultar y, a la vez, permite aprovechar capacidades del medio de almacenamiento elegido (Incluso hace mucho más simple la introducción de alguna capa de caché como ser un CDN).

    Si lo que buscas es realizar búsquedas dentro del contenido de archivos de texto, lo mejor que puedes hacer es usar alguna otra herramienta como Solr, ElasticSearch o Sphinx (Por nombrar sólo algunos).

    Si, de cualquier forma decides guardar el contenido del archivo directo en la base de datos, te recomiendo guardarlo con alguna forma de codificación que prevenga problemas con los caracteres especiales (acentos, eñes, etc…), por ejemplo base64 o algún formato comprimido (Zip por ejemplo).

    En general diría que los datos que se almacenan en la base deberían ser (al menos en su gran mayoría) de naturaleza dinámica (Es decir, que pueden cambiar a lo largo del tiempo). Aquellos datos que permanecerán estáticos probablemente tengan un mejor lugar donde residir.

    Por último, ¿pensaste en cómo mostrarías la foto si lo que está guardado en la base es el contenido del archivo en lugar de su ruta?.

    En el caso de que tomes mi sugerencia es bien fácil, basta con algo como:

    <img src="<?php echo $usuario['foto']; ?>"/>

    Si en cambio lo que está guardado en la base es el contenido del archivo… no es que sea imposible, claro, pero seguro que es algo más complicado (Tal vez tengas que hacer un script que devuelva los datos binarios de la foto o algo así)… ¿para qué hacer las cosas más difíciles de lo que debe ser?

  • Cómo acceder a Google Drive usando PHP

    Cómo acceder a Google Drive usando PHP

    El escenario que voy a analizar es este: Una aplicación desarrollada en PHP requiere acceder a archivos que sus usuarios tienen almacenados en sus propios documentos en Google Drive.

    Configuración de la API De Google

    1. Crear un proyecto en Google (yendo a https://console.developers.google.com/apis/credentials):

    1. Habilitar el acceso a la API via OAuth (https://console.developers.google.com/apis/api/drive/overview?project=MI_PROYECTO):
    1. Crear las credenciales de acceso vía OAuth:

    Seleccionar OAuth:

    1. Descargar las credenciales a un lugar seguro:
    1. Autorizar la URI de redireccionamiento

    Uso de la API de Google desde PHP

    Con esto hecho ya tenés lo básico para empezar a programar.

    Si bien podés hacer todo en un solo archivo, no es muy recomendable, es mejor tener un archivo para cada paso del trabajo.

    Ante todo, vas a necesitar la librería google/apiclient (Te recomiendo incorporarla con composer), acá podés ver un ejemplo de composer.json:

    {
     "name": "leeway/google-drive",
     "require": {
     "google/apiclient": "^2.1"
     },
     "authors": [
     {
     "name": "Mauro Chojrin",
     "email": "mauro.chojrin@leewayweb.com"
     }
     ]
    }

    No olvides después correr el comando

    composer install

    Este codigo (config.php) va a ser común a todos los archivos .php que vendrán luego:

    <?php
    require_once 'vendor/autoload.php';
    session_start();
    $redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . '/paso2.php';
    $client = new Google_Client();
    $client->setAuthConfig('<PATH_AL_ARCHIVO_JSON_DE_CREDENCIALES>');
    $client->setRedirectUri($redirect_uri);
    $client->addScope("https://www.googleapis.com/auth/drive");

    El primer paso:

    Ofrecer al usuario un formulario para conectarse a su Drive (paso1.php):

    <?php require_once 'config.php' ?>
    <div class="request">
     <a class='login' href='<?php echo $client->createAuthUrl() ?>'>Conectame!</a>
    </div>

    Contar con una URL a la que Google informe que la autenticación fue exitosa (paso2.php):

    <?php 
    require_once 'config.php';
    
    if (isset($_GET['code'])) {
     $token = $client->fetchAccessTokenWithAuthCode($_GET['code']);
     $client->setAccessToken($token);
    
     $_SESSION['upload_token'] = $token;
    
     header('Location: paso3.php');
    }

    Muy importante: este archivo (paso2.php) debe ser accesible para el cliente (Para eso es importante que su URL completa esté ingresada en la configuración de URI de redireccionamiento, sin eso nada de esto funcionará.

    Paso 3 (Completar la autenticación):

    <?php
    
    require_once 'config.php';
    
    if (!empty($_SESSION['upload_token'])) {
     $client->setAccessToken($_SESSION['upload_token']);
     if ($client->isAccessTokenExpired()) {
      unset($_SESSION['upload_token']);
      echo 'La sesion expiro. <a href="paso1.php">Volver a comenzar</a>';
      die;
     }
    }
    echo 'Autenticacion completa <a href="paso4.php">Descargar archivo</a>';

    Paso 4:

    Hacer lo que uno quiera en el drive autorizado :).

    Ejemplo: descargar un archivo desde Google Drive usando PHP

    <?php
    require_once 'config.php';
    
    echo '<p>Descargando el archivo "Mi archivo"</p>';
    $service = new Google_Service_Drive($client);
    $files = $service->files->listFiles([
     'q' => "name='Mi archivo'",
     'fields' => 'files(id,size)'
     ]);
    
    if ( count($files) < 1 ) {
     echo 'No se encontro el archivo :(';
     die;
    }
    $fileId = $files[0]->id;
    $fileSize = intval($files[0]->size);
    
    $http = $client->authorize();
    
    $fp = fopen('Mi archivo', 'w');
    
    $chunkSizeBytes = 1 * 1024 * 1024;
    $chunkStart = 0;
    
    while ($chunkStart < $fileSize) {
     $chunkEnd = $chunkStart + $chunkSizeBytes;
    
     $response = $http->request(
      'GET',
      sprintf('/drive/v3/files/%s', $fileId),
      [
       'query' => ['alt' => 'media'],
       'headers' => [
           'Range' => sprintf('bytes=%s-%s', $chunkStart, $chunkEnd)
       ]
      ]
      );
      $chunkStart = $chunkEnd + 1;
      fwrite($fp, $response->getBody()->getContents());
     }
     fclose($fp);
    echo '<p>Listo! Archivo disponible en: "'.realpath('Mi archivo').'"</p>';

    Y listo, tenemos el archivo buscado descargado a nuestro disco.

    Para probar esta aplicación (suponiendo que fuiste guardando cada porción de código en su propio archivo), iniciá el servidor de php:

    php -S localhost:8080

    y abrí en tu navegador:

    http://localhost:8080/paso1.php

    Claramente, no se trata de magia, lo que hay detrás de esto es un WebService, al que se está accediendo usando la clase Google_Service_Drive. Podes ver más información entrando a https://github.com/google/google-api-php-client.

    Y ahora que sabés cómo manipular archivos que están en el drive… ¡cuidado con lo que hacés!

  • Cómo enviar emails desde PHP

    Cómo enviar emails desde PHP

    Es muy común la necesidad de enviar emails desde PHP, tanto si se trata de un script de CLI (como puede ser un cronjob) o de una aplicación web.

    Existen varias opciones a la hora de conseguirlo:

    La más simple es el uso de la función mail

    Es una función de bastante bajo nivel (es decir, algo tosca), pero está disponible en casi cualquier instalación de php.

    Esta opción está bien para escenarios simples, pero para usos más avanzados (como enviar adjuntos, HTML o similares), es conveniente utilizar alguna otra opción algo más robusta.

    Algunas alternativas interesantes son:

    Cualquiera de estas resultará más adecuada que el uso de la simple función mail en contextos en los que la performance sea una preocupación (Por ejemplo, cuando hay alto tráfico en el sitio) o cuando se prefiere realizar el envío a través de un servidor SMTP externo.

    Si decides usar una librería (Es decir, pasar de la función mail), algo muy interesante por hacer es apoyarte en algún servicio externo (Por ejemplo MailGun).

    Si bien es posible utilizar un servicio como este (O algún otro SMTP externo) a través de la función mail, es bastante tortuoso (Ya no depende directamente de código PHP si no que requiere de meterle mano a la configuración del sendmail y otras cuestiones así…).

    Lo mejor es usar una librería que tenga esta posibilidad, como por ejemplo SwiftMailer (Aquí un poco más de detalle).

    Otro tema importante: enviar correos con HTML (Por ejemplo para que incluyan imágenes) no es nada divertido si usas la función mail a secas.

    Por último, ten en cuenta que la función mail no realiza ninguna clase de optimización… si usas SMTP, cada envío realizará su propia conexión… imagínate la penalización de rendimiento que tendrán tus usuarios… Para evitar este problema deberías, al menos, intentar usar una librería que maneje un pool de conexiones SMTP (Casi cualquiera que no use mail :p).

    Espero haberte convencido de que la función mail es sólo para casos de emergencia :).

    ¿Lo logré o tienes alguna experiencia que indique algo diferente? ¡Deja un comentario!

  • Cómo se usan las funciones anónimas en PHP

    Cómo se usan las funciones anónimas en PHP

    Las funciones anónimas son, como te habrás imaginado, funciones que no tienen un nombre.

    Esto puede sonar bastante raro, siendo que seguramente cuando aprendiste qué es y cómo se define una función en PHP te dijeron que era algo con la pinta:

    function f( $param1, $param2 )
    {
        // Algo de código
    }

    Pues te mintieron.

    Bueno, no, en realidad no te mintieron pero te dieron una versión parcial de la verdad.

    Lo que quiero decir es que generalmente las cosas son así como las conoces, pero existen otras posibilidades.

    Te presento la función anónima:

    $f = function( $param1, $param2 ) {
        return $param1 + $param2;
    }

    En este ejemplo estoy asignando a la variable $f la función que estoy definiendo (¡No el resultado de su evaluación! No te confundas).

    Y ¿por qué querrías hacer una cosa semejante? (Además de molestar a tus compañeros de trabajo o tratar de sacar un poco de chapa).

    Sucede que muchas veces la función que estás definiendo tiene un uso extremadamente puntual (Es decir, no va a ser reutilizada) y es más conveniente entonces definirla en el mismo lugar donde se usa (Además de dejar el código más claro).

    Es muy común usar las funciones anónimas en combinación con los callbacks, por ejemplo, si usás la función array_map, es más directo usar una función anónima para expresar la transformación que necesitás realizar. Ejemplo:

    $simples = [ 1, 2, 3, 4, 5 ];
    $dobles = array_map( function( $a ) { return $a * 2; }, $simples );

    También podés usar funciones anónimas en llamadas a funciones que vos mismo hayas definido (Siempre y cuando estén preparadas para recibir este tipo de parámetros).

    ¿Te parece más claro escribir de esta forma o usando funciones con nombre?

  • Cómo se usan los callbacks en PHP

    Cómo se usan los callbacks en PHP

    Este es uno de mis temas preferidos cuando a PHP se refiere.

    Debo confesar que lo descubrí bastante tarde, pero fue prácticamente amor a primera vista.

    Veamos un ejemplo:

    Si tengo un array con números ( [ 1, 2, 3, 4, 5 ] ) y quiero obtener sólo los números pares, la forma normal de hacerlo sería algo como:

    $pares = [];
    foreach ( $numeros as $numero ) {
       if ( $numero % 2 == 0 ) {
           $pares[] = $numero;
       }
    }

    Levantemos un poco el nivel de abstracción:

    function esPar( $numero )
    {
        return $numero % 2 == 0;
    }
    
    $pares = [];
    foreach ( $numeros as $numero ) {
       if ( esPar( $numero ) ) {
           $pares[] = $numero;
       }
    }

    Hasta acá la versión clásica.

    Ahora, si mirás nuevamente, podés ver que se está llamando a la función esPar() sobre cada uno de los elementos del array $numeros y nos estamos quedando sólo con aquellos que lo son, o, dicho de otro modo, estamos dejando afuera aquellos elementos que no son pares. A esta operación se la conoce como filtro.

    Ahora bien, es claro que esPar no es el único criterio por el que podríamos querer filtrar un array.

    Si tomamos un ejemplo donde sólo queremos quedarnos con los elementos mayores que tres:

    function esMayor3( $numero )
    {
        return $numero > 3;
    }
    
    $mayores = [];
    
    foreach ( $numeros as $numero ) {
       if ( esMayor3( $numero ) ) {
           $mayores[] = $numero;
       }
    }

    La estructura de estos dos scripts es muy similar:

    • Se parte de un array
    • Se aplica una función booleana a cada elemento
    • Se retornan sólo los elementos para los cuales la función evalúa en true

    Notá que la única diferencia entre los dos scripts es cuál es la condición que determina si el elemento queda adentro del array de resultado o no

    Y acá es donde aparece este genial concepto tomado de la programación funcional: la posibilidad de que las funciones puedan ser argumentos de otras funciones.

    Si nunca viste este concepto antes, te sugiero que te tomes un par de segundos para digerir lo que acabás de leer.

    ¿Ya está? Ok, sigamos.

    Lo que estoy diciendo es que podrías hacer una función genérica que tome las partes en común de ambas situaciones y reciba como parámetro el criterio de filtro que se debe usar.

    Se vería algo como:

    function filtrar( array $array, $f )
    {
       $res = [];
    
       foreach ( $array as $elemento ) {
          if ( $f( $elemento ) ) {
             $res[] = $elemento;
          }
       }
    
       return $res;
    }

    Notá como $f es a la vez una variable y una función.

    A mi esto me recuerda a la escena de Matrix donde el chico monje dobla la cuchara (Si no viste la peli no tiene mucho sentido el comentario, pero si la viste seguro entenderás de lo que hablo)

    Antes de seguir te cuento (por si no lo adivinaste aún) que esta función ya existe en PHP: array_filter.

    La tomé como ejemplo (existen muchas funciones nativas que usan callbacks) porque me parece de lo más claro para explicar el concepto para quien nunca lo vio, pero las posibilidades son realmente muchas.

    La pregunta que puede surgirte es: ¿por qué no puedo seguir escribiendo esto como lo hacía hasta ahora? ¿Qué me aporta conocer los callbacks?

    La respuesta es que sí, no hay nada que puedas lograr (desde el punto de vista de funcionalidad) que no pudieras lograr sin usar callbacks.

    La segunda pregunta es más interesante. Lo que te aporta usar este mecanismo es lo mismo que cualquier otro mecanismo de abstracción (como las funciones con parámetros por ejemplo) es la posibilidad de lograr el mismo resultado con menor esfuerzo (En este caso, escribiendo menos código, ya que tal vez te exija pensar un poquito más que de la otra forma).

    Un buen programador siempre está ávido de nuevas formas de conseguir esto.

    Menos código implica menos probabilidades de dejar errores molestos e indetectables, es decir, menos fines de semana y noches sin dormir arreglando el sistema.

    ¿Qué usos interesantes le ves a este mecanismo?