Enrutamiento de URLs con PHP

Algunos de los Frameworks más comunes nos ofrecen como característica un enrutamiento de URLs característico, ya que no se muestra ninguna extensión de archivo y las URLs apuntan directamente a una interfaz POO. Veámos como podemos hacer un pequeño script para simular este efecto.

La teoría

Para lograr nuestro objetivo tan solo necesitaremos dos archivos (aunque después siempre se pueden utilizar más, como en todo): .htaccess y index.php. El primero, para quien no lo sepa, es, por así decirlo, un archivo de configuración para un directorio. Toda petición pasará por él. Si realizamos una petición al directorio raíz se ejecutará el .htaccess que se encuentre ahí, mientras que si la petición es a la carpeta /app, el archivo que se ejecutará será /app/.htaccess.

Llegados a este punto, si se realiza una petición a una ruta que no exista en el servidor, se ejecutará el .htaccess del directorio raíz. En nuestro script haremos que este redirija a index.php, con el que después interpretaremos la URL y mostraremos el contenido deseado.

Paso 1:.htaccess

El contenido del archivo será el siguiente:

RewriteEngine On

RewriteBase /

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]

Así todas las URL apuntarán a index.php.

Paso 2:index.php

Este archivo irá alrededor de la variable $_SERVER['REQUEST_URI'], la cual contendrá el segmento de la URL actual sin el nombre del servidor. Aquí está el código documentado:

// Dividimos la URL.
$requestURI = explode( '/', $_SERVER['REQUEST_URI'] );
// Eliminamos los espacios del principio y final
// y recalculamos los índices del vector.
$requestURI = array_values( array_filter( $requestURI ) );

Por ejemplo, si tuviesemos /app/page/param1, $requestURI nos quedaría de la siguiente manera:

Array
(
    [0] => app
    [1] => page
    [2] => param1
)

Ejemplos prácticos

Tenemos la teoría. Solo falta una cosa: aplicar los datos que hemos obtenido. Esto es algo que cada cual debe adaptar a su manera. De todos modos, yo presento a continuación un par de aplicaciones prácticas que podrían serviros en vuestras webs.

Cargar un controlador

Comenzaremos nuestro index.php definiendo algunas funciones y alguna constante de configuración.

Nota: la ruta que utilizaremos a partir de ahora será /controller/method/param1/param1/ para que nos sea más fácil comprender el funcionamiento.

/**
 * Función original de Laravel4.
 *
 * Convierte una cadena separada por guiones o barras bajas
 * a PascalCase. Ej: hola-mundo = HolaMundo
 */
function studlyCase( $value )
{
	$value = ucwords( str_replace( array( '-', '_' ), ' ', $value ) );
	return str_replace( ' ', '', $value );
}

// Configuración.
define( 'CONTROLLER_PATH', 'controllers/' );
define( 'EXT', '.php' );

Ahora ya podemos utilizar nuestro código de más arriba para conseguir $requestURI. Después de eso, comprobaremos si estamos en la página principal con un simple if.

if ( empty( $requestURI ) ) {
	// Página principal.
	echo 'Prueba a escribir <a href="/controller/method/param1/param2"><code>controller/method/param1/param2</code></a> en la URL.';
}
else
{
	// Código (sigue leyendo).
	try
	{

	}
	catch // ...
}

En el caso de que sea la página principal ejecutamos lo que queramos. Si no lo es, continuaremos con el código. Lo que haremos será hacer unas cuantas comprobaciones de seguridad ayudándonos de excepciones y cargar el controlador siguiendo un patrón.

Lo siguiente será el código dentro del bloque try.

// Guardamos el nombre del controlador y la
// ruta de su archivo para utilizarlas más tarde.
$controllerName = studlyCase( $requestURI[0] );
$controllerPath = CONTROLLER_PATH . $controllerName . EXT;
// Guardamos el nombre del método a llamar.
$method = $controllerName . '::' . studlyCase( $requestURI[1] );
// Eliminamos el controlador y el método de
// $requestURI para quedarnos sólo con los parámetros.
$arguments = array_slice( $requestURI, 2 );

// Comprobamos que el archivo del controlador existe.
if ( ! file_exists( $controllerPath ) ) {
	throw new DomainException( 'El archivo <code>' . $controllerPath . '</code> no existe.', 404 );
}

// Cargamos el archivo.
require_once $controllerPath;	

// Comprobamos que el archivo contenga el controlador.
if ( ! class_exists( $controllerName ) ) {
	throw new RuntimeException( 'El archivo <code>' . $controllerPath . '</code> debe contener un objeto <code>' . $controllerName . '</code>.' );
}

// Comprobamos que el método definido en la URL esté disponible.
if ( ! is_callable( $method ) ) {
	throw new DomainException( 'El archivo <code>' . $controllerPath . '</code> no contiene un método <code>' . $requestURI[1] . '</code>.', 404 );
}

// Creamos un nuevo método reflejo de $method.
$reflection = new ReflectionMethod( $method );

// Comprobamos que la URL tiene todos los
// parámetros requeridos por el método.
if ( $reflection->getNumberOfRequiredParameters() > count( $arguments ) ) {
	throw new DomainException( 'No hay suficientes parámetros como para ejecutar el método <code>' . $method . '</code>', 404 );
}

// Llamamos a la función.
call_user_func_array( $method, $arguments );

Para finalizar con el archivo, definiremos los bloques catch.

catch ( RuntimeException $e ) {
	echo $e->getMessage();
}
catch ( DomainException $e ) {
	echo '<strong>Error ' . $e->getCode() . '</strong>: ' . $e->getMessage();
	// O bien escribimos un mensaje de página no encontrada.
}

Ahora solo tendremos que crear un controlador para la ruta que deseemos. Para poner un ejemplo, crearemos el archivo controllers/Controller.php.

<?php

class Controller
{

	public static function Method( $param1, $param2 = 'No especificado' )
	{
		echo "Parámetro 1: $param1";
		echo '<br>';
		echo "Parámetro 2: $param2";
	}

}

?>

De esta forma podrémos llamar a la URL controller/method/param1 o bien controller/method/param1/param2 ya que el segundo parámetro es opcional. Hay muchas más cosas que se le podrían añadir al código, por ejemplo que al llamar a la URL controller se busque un método index() en el controlador al igual que hacen muchos frameworks. Aún así esto es algo que os dejo a vosotros, esto es tan solo un código básico para enseñar el funcionamiento del sistema.

Cargar un controlador “RESTful”

REST es un tipo de arquitectura web por así decirlo, el cual se basa en que cada URL tenga distintos funcionamientos. Si sabéis algo de formularios recordareis que el método de envio puede ser POST, GET, DELETE, PUT… Esto para un formulario simple carece de mucho sentido, pero es de suma importancia a la hora de desarrollar aplicaciones RESTful ya que dependiendo del método de la petición se ejecutará una acción u otra.

Pongamos el ejemplo de una aplicación común para la gestión de noticias:

URLAcción
/show/$idMostrar una noticia.
/edit/$idEditar una noticia.
/delete/$idEliminar una noticia.
/addAñadir una noticia.

Ahora bien, si nuestra página estuviese basada en REST, las URLs se comportarían de la siguiente manera:

URLMétodo de peticiónAcción
/news/$idGETMostrar una noticia.
/news/$idPUTActualizar una noticia.
/news/$idDELETEEliminar una noticia.
/news/$idPOSTAñadir una noticia.

Para adaptar nuestro código anterior no tendremos más que cambiar un par de líneas, de modo que si se llama a method y la petición es GET, el método del controlador al que accederemos será getMethod().

Tan sólo necesitaremos cambiar una variable en index.php.

$method = $controllerName . '::' . strtolower( $_SERVER['REQUEST_METHOD'] ) . studlyCase( $requestURI[1] );

Y muchas opciones más…

Estos ejemplos que he dado son los más básicos actualmente en aplicaciones web, pero se pueden hacer tantas cosas con las URL como a nuestra imaginación se le ocurra. Como siempre es cosa de investigar.

Espero que este pequeño truquito os haya servido para aumentar vuestros conocimientos. Como siempre no dudéis en comentar, tanto si tenéis dudas como si no, es algo que me hace escribir más tutoriales y motivarme. Un abrazo.

comments powered by Disqus