Sistema de configuración en PHP.

Introducción.

Establecer un sistema de configuración para una aplicación es una tarea prácticamente obligatoria y en algunos casos difícil de mantener y propensa a errores si no se le dedica el tiempo necesario a su diseño.

Es una tarea obligatoria porque la gran mayoría de las aplicaciones requieren de valores o parámetros preestablecidos para funcionar o habilitar ciertas funcionalidades dependiendo el caso que se dé.

Difícil porque a veces la falta de diseño (y en algunos casos de lógica) a la hora de pensar en un sistema o subsistema de este tipo hace que tengamos que editar manualmente el código fuente de ciertos archivos para luego subirlos por SFTP (o FTP si no hay más remedio) a algún servidor para poner ciertos cambios "en producción".

Es una tarea incómoda además para los programadores y sus clientes: los clientes, por lo general, no tienen (ni deberían tener) los conocimientos necesarios para editar el código de estos archivos y por consiguiente terminarían llamándonos a nosotros para llevar a cabo esos cambios. Es por esta misma razón que un mal diseño en estos casos también hace que nuestra aplicación sea propensa a errores.

La idea.

A continuación voy a dar mi punto de vista sobre cómo realizar un sistema de configuraciones que evita que tengamos que lidiar con este tipo de problemas. El código utilizado está basado en PHP y MySQL pero la idea es fácilmente reutilizable otras aplicaciones que han sido escritas en otro lenguaje de programación o que utilicen un motor de base de datos diferente.

Aclaro que no invento la pólvora si no que lo expuesto a continuación es fruto de horas de investigación y lectura de código relacionado al tema.

La idea consiste básicamente en lo siguiente:

  1. Vamos a crear una tabla en nuestra base de datos que va a tener en su interior las configuraciones de nuestra aplicación.
  2. Al inicio de nuestro script, mediante una consulta a la base de datos, vamos a obtener rápidamente los pares nombre y valor de cada configuración.
  3. Por cada par resultante se van a definir constantes llamadas con el valor de nombre a las que se les asignará el valor valor.

La tabla.

Para empezar primero vamos a crear una tabla llamada settings en nuestra base de datos. En esta tabla se van a guardar las configuraciones de la aplicación y valores relacionados a cada una de éstas.

CREATE TABLE `settings` (
  `name` varchar(255) NOT NULL default '',
  `val` varchar(255) NOT NULL default '',
  `type` varchar(10) NOT NULL default '',
  `title` varchar(255) NOT NULL default '',
  `description` text NOT NULL,
  `edit` tinyint(1) unsigned NOT NULL default '0',
  UNIQUE KEY `name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

En la consulta anterior se establece un índice de tipo UNIQUE en el campo name. De esta manera evitamos tener valores repetidos en este campo lo que nos va a ser muy útil posteriormente a la hora de definir las constantes ya que éstas no pueden sobreescribirse una vez que han sido definidas.

A continuación explico lo que representa cada campo:

name
Es el nombre que vamos a utilizar para cada configuración que definamos para nuestra aplicación. Su valor debe ser único, lo que implica que no pueden existir dos configuraciones con el mismo nombre.
val
Es el valor que se va a asignar a cada configuración. Solo admite valores escalares (tipo boolean, integer, float o string).
type
Indica el tipo de dato que representa a cada configuración y sus valores pueden ser cualquiera de los siguientes: bool, int, float o string.
title
Utilizado para definir de manera amigable el nombre de cada configuración.
description
Utilizado para definir de manera amigable las características de cada configuración.
edit
Sus posibles valores son 1 o 0 e indican si es posible o no modificar el valor de una configuración específica.

Una buena manera de entender el "por qué" de la definición de cada uno de los tres primeros campos (y evitar que este documento se extienda demasiado) es revisando la sección de constantes en el manual de PHP. Los tres últimos campos creo que son auto-explicativos y necesarios para la implementación posterior de este sistema en un backend.

Cargando configuraciones.

Lo siguiente será entonces agregar algunos datos a la tabla que hemos creado anteriormente. Para hacerlo ejecutamos la siguiente consulta SQL:

INSERT INTO `settings`
  (`name`, `val`, `type`, `title`, `description`, `edit`)
VALUES
  ('APP_NAME', 'juangiordana.com.ar', 'string', 'Application Name', 'Application name.', 0),
  ('APP_PATH', '/home/juangiordana/public_html', 'string', 'Application Path', 'PATH where the application is installed.', 0),
  ('APP_TITLE', 'Juan Fco. Giordana', 'string', 'Application Title', 'Default </title> used on application.', 1),
  ('BULK_EMAIL', '100', 'int', 'Bulk Email', 'Number of email to send per request.', 1),
  ('PRODUCTION', '0', 'bool', 'Production', 'Wether the system is running in a Production Environment or not.', 0)

);

la cual nos va a dar el siguiente resultado:

name val type title description edit
APP_NAME 'juangiordana.com.ar' string Application Name ... 0
APP_PATH '/home/juangiordana/public_html/' string Application Path ... 0
APP_TITLE 'Juan Fco. Giordana' string Application Title ... 1
BULK_EMAIL 100 int Bulk Email ... 1
PRODUCTION 1 bool Production ... 0

Hemos cargado en nuestra tabla cuatro valores que van a ser los encargados de definir ciertos comportamientos de nuestra aplicación.

Podemos ver en la tabla anterior el nombre asignado a cada configuración, su valor, de qué tipo es cada valor, cuales de ellos son editables y cuales no lo son, etc.

El código.

El siguiente paso es escribir el código encargado de cargar las configuraciones establecidas anteriormente. Dicho código debe ejecutarse al inicio de nuestro programa ya que es el encargado de definir todas las constantes que utilizará nuestra aplicación.

<?php
/*
 * Define Constants / Set Settings
 */
$sql = 'SELECT name, val, type FROM settings';
$result = mysql_unbuffered_query($sql, $link);
while ($row = mysql_fetch_assoc($result)){
  settype($row['val'], $row['type']);
  define($row['name'], $row['val']);
}
?>

La consulta anterior nos va a devolver los campos name, val y type de todos los registros que contiene la tabla settings.

A medida que iteramos sobre cada registro lo que hacemos en el primer paso del bucle es establecer o forzar que el valor de cada dato sea del tipo que nosotros hemos especificado en el campo type. Esto se logra utilizando la función settype(), la cual actúa por referencia.

El siguiente paso es entonces el encargado de definir las constantes y sus respectivos valores. En ellas se establecerán las configuraciones de nuestro programa y sus valores serán cargados en memoria durante la ejecución de nuestros scripts.

Aplicación.

Como ejemplo voy a mostrar de qué manera se comportaría nuestra aplicación dependiendo del valor de la configuración PRODUCTION.

La constante PRODUCTION es de tipo boolean, lo que significa que sus valores pueden ser TRUE o FALSE y su existencia indica si nuestra aplicación está siendo ejecutada en un entorno de producción (TRUE) o un entorno de desarrollo (FALSE).

Teniendo en cuenta lo anterior, el siguiente código imprime en nuestras páginas dinámicas el código javascript necesario para correr Google Analytics sólo cuando nuestro sistema está corriendo en producción y no imprime nada bajo un entorno de desarrollo.

<?php
if (PRODUCTION){
?>
  <!-- Google Analytics -->
  <script src="http://www.google-analytics.com/urchin.js" type="text/javascript"></script>
  <script type="text/javascript">_uacct="##-#####-#";urchinTracker();</script>
<?php
}
?>

Ya que nosotros establecimos con anterioridad (al momento de cargar ésta configuración) los límites que tendrá el valor de la constante (TRUE o FALSE) no hace falta realizar comparaciones del tipo PRODUCTION == TRUE o PRODUCTION == FALSE para saber bajo qué entorno la aplicación se está ejecutando.

Conclusión.

Algunas de las ventajas de utilizar un modelo de configuraciones dentro de nuestra base de datos se pueden ver instantáneamente una vez que empezamos a utilizar un modelo similar a el expuesto en este artículo. Por listar algunas:

  • Las configuraciones a nivel de archivos serán ahora sólo las encargadas de conectar con nuestra base de datos y a lo sumo establecer algunos valores requeridos como ciertos PATHs, passwords y datos que no deberían estar expuestos a nadie en el caso de que nuestra base de datos sea vulnerable, por ejemplo, mediante ataques de SQL injection.
  • Podemos crear rápidamente una sección en el backend de nuestro sistema para modificar solamente los valores que nosotros establecemos como modificables (edit=1) y asignar estos valores a cada configuración basándonos en el valor del campo type, valor que establecimos con anterioridad por única vez cuando agregamos el registro en nuestra tabla de configuraciones. El sistema incluso puede extenderse para soportar diferentes niveles de acceso.
  • Este sistema agrega simplicidad y practicidad a nuestra aplicación además de hacerlo menos propenso a errores. Nuestros clientes serán capaces de modificar ciertos valores permitidos y ver los resultados on-the-fly.
  • Agregar nuevas variables de configuración a nuestro sistema se hace de manera más simple ya que no debemos realizar comparaciones a ojo entre dos archivos de texto que pueden llegar a tener valores obsoletos.
  • Si el motor de base de datos que estamos utilizando nos lo permite nuestra tabla de configuraciones podría ser de tipo MEMORY (HEAP) lo que haría que nuestra aplicación se ejecute más rápidamente.

Comentarios:

  1. Nice articulo Juana!

    Solo un par de comentarios...

    1. El campo `type` deberia ser del tipo ENUM; probablemente no afecte al rendiemiento de la aplicacion porque este tipo de tablas no poseen gran cantidad de registros.. pero ya que se trata de hacer bien las cosas no veo porque no hacer bien la DB :)

    2. Para evitarnos la consulta a la DB por cada pageview, al editar la configuracion deberias generar un archivo asi queda cacheada.

    Saludos!

    Enviado por Babblo el 31/10/2007 05:31:20 (#).

  2. @Babblo: Gracias!

    Sobre el punto 1: Honestamente todavía no jugué demasiado con los campos tipo ENUM. Hace bastante había leído un comentario en el manual de MySQL en el que desaconsejaban su uso y después de analizar ese comentario simplemente me olvidé de él :P.

    Sobre el punto 2: No sé si habrá mucha diferencia entre llamar a un archivo cacheado o correr la consulta. MySQL viene con sus propios métodos de cacheo integrado e incluso leí por ahí que hasta se pueden implementar las DBs en formato RAW sobre el disco sin necesidad de utilizar un sistema de archivos.

    Hay un montón de detalles que se pueden mejorar y otro montonazo más que se pueden agregar a un sistema de este tipo. La idea es dar un punto de partida para que las mentes después vuelen. Cubrir todas las opciones me llevaría muchas horas más que las 4 o 5 que le dediqué a este post ;)

    Enviado por Juan el 31/10/2007 05:31:00 (#).