Ir al contenido principal

Manejo de clases globales únicas en PHP

Imagen de Tumisu en Pixabay

¿Cómo acceder desde cualquier script en tu proyecto a Clases y/o funciones de uso común? Este puede ser una de las primeras directrices a establecer para cualquier proyecto porque siempre, siempre, sea en PHP u otro lenguaje, será necesario usar recursos comunes. En PHP existen diferentes alternativas para su manejo, ya sea por medio de variables globales o de clases/objetos estáticos. A continuación consideraremos una propuesta para este manejo.

Creación de recursos globales

Para ilustrar esta solución, partimos de la necesidad de implementar una librería para manejo de servicios relacionados con el servidor Web, que de forma amigable nos permita disponer de información como:

  • Valores almacenados de la variable superglobal $_SERVER de PHP.
  • Valores asociados a la consulta realizada por el usuario, por Ej. la dirección IP del usuario o la URL ingresada.
  • Valores asociados al servidor web usado, por Ej. la dirección IP del servidor o la ubicación del script que ejecuta la petición del usuario.

Por supuesto, esta librería debe poder usarse desde cualquier script del proyecto, por tanto primero debe ser incluida, de preferencia en algún punto del código que siempre se ejecute. Este paso es mandatorio, aunque como veremos, existe una alternativa para reducir la cantidad de inclusiones manuales de archivos cuando se trata de clases PHP. De momento, incluimos nuestro script de funciones comunes así:

require_once '/miframe/commons/helpers.php';

Dentro de este script definimos una función estándar para cada servicio necesario (recuperar IP del usuario, script ejecutado, etc.) y tendremos al final un archivo con muchas funciones. Si posteriormente se crean archivos similares especializados en diferentes tareas para nuestro proyecto, tendremos una cantidad bárbara de funciones y archivos que no requieren ser explícitamente organizados de una u otra forma y podemos tener problemas para recordar sus nombres o qué archivo incluir para que funcione tal o cual función.

Aunque tal aproximación funciona y aprovecha precisamente esa capacidad nativa de PHP de dar acceso global a cada función declarada, es preferible contar con un mecanismo natural de agrupación y especialización para estas funciones. Eso podemos conseguirlo agrupando dichas funciones en Clases, que por naturaleza y definición, contienen funciones (aquí llamadas métodos) y variables (propiedades) que tienen un objetivo, una fuente y/o un destino común entre ellas. 

Es así como procedemos a crear una clase ServerData que encapsulará y proporcionará los servicios que necesitamos:

class ServerData {
    // Define propiedades y métodos de la clase.
    // Por ejemplo:

    /**
     * Dirección IP del cliente remoto.
     */
    public function client() {
        // ...
        return $user_ip;
    }
}

Otra ventaja de usar clases, es que cualquier método o propiedad que sea usada solo como apoyo para otras funciones de la clase pero que no aportan nada por fuera de ella, pueden mantenerse ocultas (privada) dentro de la clase.

Puedes conocer un poco más sobre clases en PHP consultando lo básico de Clases y objetos del manual de PHP.

Continuando con nuestra implementación, la lógica manda que cada vez que se necesite acceder a esta clases se debe instanciar una nueva copia y crear por ende un nuevo objeto del mismo tipo ServerData, lo que nos lleva a crear objetos por doquier en nuestro código, generando copias y ocupando recursos del servidor Web (especialmente la preciada memoria) de una clase que perfectamente puede ser la misma en cada caso, por ejemplo, la dirección IP del usuario no va a cambiar porque la consultamos en un punto u otro del código ya que no depende de dónde o cuándo se invoque. Para evitar esta multiplicidad, se sugiere que ese tedioso y aburrido trabajo de instanciar la clase se realice una única vez y se reutilice el objeto creado donde quiera que se necesite.

Una forma fácil y rápida es hacerlo mediante una variable global, algo así como:

$GLOBALS['miFrameServer'] = new ServerData();

function prueba() {
    global $miFrameServer;
    ...
    $user_ip = $miFrameServer->client(); 
    ...
}

Si están familiarizados con PHP, sabrán que esta práctica esta muy, pero muy mal vista dentro de la comunidad por varias razones, las que pueden resumirse en que el código se hace más difícil de depurar, testear, mantener y reutilizar.

Puedes encontrar más información al respecto de porqué no usar variables globales en stackoverflow.com

La alternativa tampoco es que sea muy recomendada, porque igual parece que viola algunos lineamientos SOLID, pero es más aceptada, e implica el uso de un patrón Singleton, del que hablaremos a continuación.

Aplicando el Patrón Singleton

Los patrones son configuraciones recurrentes en el manejo de clases, puede que incluso sin saberlo, hayas realizado implementaciones que pueden identificarse con algún patrón.

Si no estas familiarizado con este tema, como me pasó a mi, este es un recurso que puede resultar muy útil: Patrones de diseño en refactoring.guru

Es precisamente de esta página que extraje el siguiente ejemplo conceptual de un patrón Singleton:

class Singleton
{
    /**
     * Referencias a las instancias singleton.
     */
    private static $instances = [];

    protected function __construct() { }

    protected function __clone() { }

    public function __wakeup()
    {
        throw new \Exception("Cannot unserialize a singleton.");
    }

    /**
     * Retorna la instancia actual, creada solamente una vez por tipo de Clase hija.
     *
     * @return self
     */
    public static function getInstance(): Singleton
    {
        $cls = static::class;
        if (!isset(self::$instances[$cls])) {
            self::$instances[$cls] = new static();
        }

        return self::$instances[$cls];
    }
}

Merece especial atención al manejo de instancias como un arreglo de valores. ¿Por qué se hace así? Esta misma clase será usada como “padre” por diferentes clases “hijas” con diferentes métodos y propiedades. Si todas se almacenan en una misma variable local sin distinguir unas de otras, todas las invocaciones harán uso de la primera clase “hija” creada. Es por esto que esas instancias se discriminan por tipo de clase (indicado por static::class) como llave del arreglo de valores $instances.

Aclarado este punto, redefinimos la clase ServerData como hija de esta clase Singleton:

class ServerData extends Singleton {
    // Propiedades y métodos propias de la clase ServerData
    // ...
}

Ahora ya podemos invocarla en cualquier lugar de nuestro código con la certeza que invocará la clase que necesitamos y que será una única copia para todo el proyecto. Esta invocación la realizamos así:

use miFrame\Commons\Classes\ServerData;

$user_ip = ServerData::getInstance()->client();

Para simplificar y facilitar su uso, proporcionamos una función miframe_server (que como mencionamos, PHP manejará de forma global por defecto) para usarla con mucho menos repetición de código. Esta función se define en el script helpers.php que vimos antes:

use miFrame\Commons\Classes\ServerData;

function miframe_server() : ServerData {
    return ServerData::getInstance();
}

Y así se invoca desde cualquier otro punto en el código:

$user_ip = miframe_server()->client();

Autocarga de scripts

De nuevo, en un proyecto podemos tener muchas y variadas clases. No es muy cómodo para nosotros como programadores, tener que escribir manualmente la inclusión de cada uno de los archivos donde se definen (porque si, por recomendación se tiene que cada clase debe ir declarada en un archivo independiente para cada una). Hacerlo también puede llevarnos a crear puntos de error si olvidamos hacerlo o si la línea donde ocurre dicha inclusión se queda por fuera del flujo de ejecución en un momento dado.

PHP viene al rescate con un utilitario que increíblemente poco había usado o por el que poco me había interesado en el pasado. Se trata de spl_autoload_register(), que permite registrar una función que evalúe el nombre de la clase requerida y realice la carga de los scripts que necesite. Esta función de autocarga o autoload nos evita la tarea de tener que registrar manualmente dichos scripts y garantiza que solamente se carguen cuando sean requeridos, optimizando uso de recursos y memoria.

Consulta información detallada sobre spl_autoload_register() en el manual de PHP.

En nuestro caso y siguiendo el ejemplo de miframe_server, declaramos una función miframe_autoload que hace uso de una clase Singleton AutoLoader, que permita registrar las rutas donde buscar los scripts de clases.

Este proceso es mejor realizarlo en una librería independiente a la anterior (helpers.php) para limitar cualquier posible error que pueda presentarse y para permitir reusarla sin depender del otro. La creamos en un archivo autoload.php que deberá incluirse en las primeras líneas de nuestro código:

require_once '/miframe/commons/autoload.php';

Dentro de esta librería, definimos nuestra función global miframe_autoload:

require_once '/patterns/Singleton.php';
require_once '/classes/AutoLoader.php';

function miframe_autoload() : AutoLoader {
    return AutoLoader::getInstance();
}

spl_autoload_register(function ($className) {
    miframe_autoload()->load($className);
    });

Nótese que:

  • Como recién vamos a definir una función para indicar a PHP donde encontrar los archivos de clases, tenemos que incluir manualmente todos los archivos necesarios para que esta librería funcione correctamente, esto es, los archivos donde se declaran las clases Singleton y AutoLoader.
  • En este mismo script ejecutamos de forma automática spl_autoload_register.

Finalmente, para su uso, basta invocar el método “register”, por ejemplo:

miframe_autoload()->register('miFrame\Commons\Classes\*', $path. '/classes/*.php');

¿Cómo funciona?

Cuando se invoque una clase del tipo “miFrame\Commons\Classes\ServerData” se buscará el archivo “commons\Classes\ServerData.php”. El carácter asterisco “*” se usa siempre al final del patrón de la clase y todo valor abarcado por este carácter (“Classes\ServerData” en el ejemplo) se traslada al path en disco, que puede (debe) también incluir el “*” y usarlo para reflejar su contenido como parte de la ruta de archivo a buscar.

Y creo que con eso terminamos por ahora.

Espera, ¿y el código completo?

Nada de nervios, que no cunda el pánico. El código completo para estas librería puedes encontrarlo en:

miframe-server-y-miframe-autoload en github.com

Ese repositorio se actualizará conforme nuevas utilidades se vayan adicionando en nuestra implementación del framework propuesto. Si no tienes la menor idea de qué va esto, te invito a leer el artículo donde hablamos del tema en:

¿Vale la pena construir tu propio framework en PHP?

Quedo atento a tus comentarios y sugerencias en la sección de Comentarios.

Comentarios

Entradas populares de este blog

Sesión de usuarios en aplicaciones web

Uno de los módulos más importantes y a la vez menospreciados cuando se aborda la tarea de crear un sitio web de servicios, ya sea para una intranet corporativa o un sistema de gestión de información ( SGI ) es la gestión y administración  requerida para una correcta implementación de sesiones de usuario. Y es que llevamos tanto tiempo usando usuarios y contraseñas en Internet, en cualquiera de sus muchas variaciones, que se asume muchas veces que esto ya forma parte del ADN de toda solución web y como tal, se destina muy poco tiempo y estudio a este apartado cuando se planifican las actividades de desarrollo. Lo cierto es que cada aplicación acostumbra desarrollar su propio esquema de manejo de sesiones y asumir que es algo superfluo puede equivaler a “pegarse un tiro en el pie”, especialmente cuando un módulo de este tipo se diseña desde ceros. Al referirse al manejo de sesiones de usuario suele pensarse únicamente en el proceso de capturar el nombre de usuario ( username ) y su cont

¿Qué tan bueno es realmente el “foreach” en PHP?

Imagen de garionhk en Pixabay Como toda buena historia, esta comienza hace algún tiempo. El que fuera mi jefe por allá en la primera década del 2000, realmente odiaba (y mucho) el uso del foreach en el código PHP . Prefería que usáramos alguna alternativa diferente, alguna combinación del  for o del while . ¿Por qué? Ve tú a saber, nunca fue abierto respecto a las razones de su aprensión hacia ese constructor propio del lenguaje. Pero antes de continuar, veamos qué es y para qué nos puede servir. Arreglos, tenían que ser arreglos ¿Qué es foreach ? De acuerdo al manual de PHP , su definición es la siguiente: El constructor foreach proporciona un modo sencillo de iterar sobre arrays . foreach funciona sólo sobre arrays y objetos , y emitirá un error al intentar usarlo con una variable de un tipo diferente de datos o una variable no inicializada. Para su uso correcto existen dos sintaxis validas, a saber: foreach (expresión_array as $value) { ... } foreach (expresión_array as