Cómo hacer un framework ligero en PHP (II) – Parseo de URL

Seguimos con los posts sobre cómo hacer un framework ligero en PHP.

En esta ocasión trataremos el tema del parseo de la URL, muy habitual en el patrón MVC y por tanto en todos los frameworks existentes.

Por otro lado, aunque había anunciado que también trataría el tema del __autoload, prefiero dejarlo para más adelante y intentar poner un ejemplo usando ya los namespaces, tema que siendo sinceros llevo algo verde.

Para parsear la URL, tenemos que fijarnos en dos claves importantes dentro del array superglobal $_SERVER:

$_SERVER[‘SERVER_NAME’] : Contiene el dominio que está ejecutando la página, sin la parte ‘http://’ por ejemplo www.ricardclau.com
$_SERVER[‘REQUEST_URI’] : Contiene el resto de la URL solicitada, con los parámetros $_GET si los hay. Por ejemplo la página wordpress de este artículo contendría “/2011/02/como-hacer-un-framework-ligero-en-php-ii-–-parseo-de-url/”

Y a partir de esto, podremos determinar el entorno en el cual estamos ejecutando, el idioma de la página si queremos hacer una aplicación multiidioma (pudiendo ser parte de una o de otra, esto ya va a gustos), el controlador a ejecutar y su acción y parámetros. ¡Vamos a ello!

Obtención de entorno de ejecución

Para esto podemos usar varias técnicas, una de las más aceptadas es obtener el TLD del servidor y según eso, definir una constante ENVIRONMENT que podremos usar en toda nuestra aplicación.

Es decir, para las terminaciones de dominio: COM -> PRODUCCION, TEST -> INTEGRACION, DEV -> DESARROLLO

Obtención de idioma (opcional)

Aquí hay dos corrientes, ambas bastante difundidas, yo me quedo con la segunda pero es algo muy personal.

– Utilizar el $_SERVER[‘SERVER_NAME’] con un primer elemento que indique el país o idioma que queramos usar. Es decir es.ricardclau.com sería la versión en castellano y ca.ricardclau.com la versión en catalán. Para ello, bastaría con coger lo que va delante de ricardclau.com y evaluar posibilidades.

– Utilizar la primera parte del REQUEST_URI para el idioma (e incluso para la parte de administración). En este caso www.ricardclau.com/ca sería la versión en catalán, www.ricardclau.com/es la versión en castellano y incluso nos permitiría jugar con www.ricardclau.com/admin para gestionar la zona de administración y tener un front-controller específico, con cosas como www.ricardclau.com/images para gestionar temas de redimensionamiento automático (como veremos en próximos posts), etc…

Yo suelo utilizar esta opción en las aplicaciones que desarrollo, principalmente por el juego que da pero ambas son perfectamente válidas.

Resto de la URL para tener MVC

Aquí de nuevo cada framework lo hace un poco diferente. En este sentido me gusta la versión Zend que se basa en
/controlador/accion/param1/valor1/param2/valor2 y además permite internacionalizar de manera que por ejemplo www.ricardclau.com/ca/contacte, www.ricardclau.com/es/contacto y www.ricardclau.com/en/contact vayan al mismo controlador.

Mi querido Kohana tiene un modelo de rutas bastante diferente y al final para conseguir algo como esto tuve que hacer un pequeño hack que en próximos días publicaré.

En cualquier caso, una vez obtenidos el controlador y la acción (pudiendo definir unos valores por defecto, como public para el controlador y index para la acción) otra acción habitual es agrupar esos params, $_POST y $_GET y pasarle a la acción del controlador un array $params con el array_merge de los 3.

El resultado final sería algo así como:

<?php
 
// SIEMPRE TRY / CATCH !!!!!
try {
 
    $s_server = $_SERVER['SERVER_NAME'];
    $q_string = $_SERVER['REQUEST_URI'];
 
    // Limpieza de Query String con el filtro de PHP
    // Además, eliminamos la primera /
    // Y de paso, quitamos la última / si existe...
    // así /public/index = /public/index/
    $q_string = trim(filter_var($q_string, FILTER_SANITIZE_URL), '/');
 
    // Obtención de entorno -> Obtención de TLD
    $entorno = substr($s_server, strrpos($s_server, '.') + 1);
 
    // Entornos válidos
    $entornos_validos = array(
                  'local'   => 'LOCAL',
                  'dev'     => 'DEVELOPMENT',
                  'test'    => 'TESTING',
                  'com'     => 'PRODUCTION',
                  );
    if(!array_key_exists($entorno, $entornos_validos))
         throw new Exception('Entorno inválido');
 
    // Definimos constante
    define('ENVIRONMENT', $entornos_validos[$entorno]);
 
    // Obtenemos los trozos del querystring
    // 1. Eliminamos la parte de $_GET si existe, partiendo por ?
    if(strpos($q_string, '?') !== FALSE)
        $q_string = substr($q_string, 0, strpos($q_string,'?'));
    // 2. Nos montamos un array con los trozos
    $q_pieces = explode('/', $q_string);
 
    // Obtención de idioma
    // Versión 1: es.ricardclau.com
    $idioma = substr($s_server, 0, strpos($s_server, '.'));
    // Versión 2: www.ricardclau.com/es
    $idioma = array_shift($q_pieces);
 
    // Idiomas válidos (y dejamos uno por defecto incluso!)
    $idiomas_validos = array('es', 'ca', 'en');
    $idioma_defecto = 'en';
    if(!in_array($idioma, $idiomas_validos))
         $idioma = $idioma_defecto;
 
    // Y ya tenemos la constante
    define('LANGUAGE', $idioma);
 
    // Construcción MVC (por defecto, controlador public, acción index)
    $controller = (empty($q_pieces[0])) ? 'public': $q_pieces[0];
    $action = (empty($q_pieces[1])) ? 'index': $q_pieces[1];
 
    // Resto de parámetros, los recogemos como /clave1/valor1/clave2/valor2
    $params = array();
    for($i=2;$i<count($q_pieces);$i++) {
            $params[$q_pieces[$i++]] = $q_pieces[$i];
    }
 
    // Unimos estos parámetros con lo que pudiera venir por $_POST y $_GET
    // (Faltaría hacer algo de saneamiento, que podemos hacer aquí o con validadores)
    $c_params = array_merge($params, $_POST, $_GET);
 
    // Instanciamos controlador y ejecutamos la acción más sus parámetros
    // Debido a la falta de namespaces, para no colisionar las clases de controladores
    // se suelen concatenar los nombres con la palabra Controller, a las acciones se les pone
    // la palabra Action, etc... con los namespaces esto está cambiando
    $cname = $controller . 'Controller';
    $actionname = $action . 'Action' ;
    $c = new $cname;
    $c->$actionname($c_params);
 
 
} catch (Exception $ex) {
   // Aquí depende mucho de lo que queramos hacer...
   echo $ex->getMessage();
   // O incluso mejor, si venimos de una clase de las profundidades, veremos
   // la secuencia de clases, aquí no veremos nada ya que todo pasa en el index.php
   debug_print_backtrace();
}
?>

Lógicamente esto tiene multitud de puntos a cambiar, mejorar, pero espero haber aportado algo de luz a cómo los frameworks hacen magia con nuestras URLs para pasarlas a nuestros controladores.

You may also like...

3 Responses

  1. Pere says:

    Pregunta tonta:

    En los entornos válidos te basas en que tengamos un virtualhost con “URL.local”, “URL.test” etc, no?

    Supongo que lo has hecho así para que cuadre todo en una sola función, pero ¿no sería mejor tener un archivo de configuración, o una propiedad con el entorno en el que estamos trabajando?
    Así no dependemos de virtualhosts y nos sirve para dominios [dot]LoQueSea.

    Saludos y esperando la parte III 🙂

  2. Ricard Clau says:

    Hola Pere

    Sí, exacto, parto de la base de tener virtualhosts como comentas. Yo siempre trabajo así con mis cosillas freelance y en Privalia también, pensaba que era “lo normal” 🙂

    Desde luego, otra opción válida es como dices, al principio del index un require ‘conf.php’ y en ese conf definir las constantes de entorno que en este ejemplo recupero de la URL.

    En ese caso, lo importante es ir con cuidado con los deploys para no subir jamás ese archivo de devel al entorno de producción 😉