/ WebDev

Einfaches und elegantes URL-Routing mit PHP

Wenn man kleinere PHP-Projekte umsetzen möchte und sich gegen große Frameworks entscheidet steht irgendwann die Frage nach URL-Routing im Raum. Wie führt also die Eingabe einer bestimmten URL dazu, dass bestimmte Inhalte ausgegeben werden? Ich will hier eine einfache und sehr elegante Möglichkeit beschreiben, die suchmaschinenfreundliche URLS mit Hilfe von RegExp verarbeitet.

Hinweis: Die Code-Beispiele auf dieser Seite sind momentan nicht ganz aktuell. Bitte orientiere dich am aktuellen Git-Repository für die neusten Änderungen: https://github.com/steampixel/simplePHPRouter/tree/master

Als ich vor vielen Jahren begann mit PHP zu arbeiten wurden Seitenaufrufe oft über die GET-Methode gesteuert. Oft sah man URLs, denen viele Parameter wie index.php?page=5&action=’delete’&return_to_page=3 angehangen wurden. Die Frontcontroller sahen meist so aus:

if(isset($_REQUEST['page_id'])){

if($_REQUEST['page_id']==3){
	//load page
}

if($_REQUEST['action']=='delete'){
	//do something
}

//do more complex things

}

Diese Methode wird schnell unübersichtlich und man verliert sich in Paramern und If-Schleifen. Das Resultat ist Spaghetti-Code. Zudem sind die URLs nur schwer verständlich und auch nicht gerade leicht zu merken. Mit Hilfe von anonymen Funktionen und Regexp lassen sich jedoch leicht zu konfigurierende, suchmaschinenfreundliche und elegante Routen erstellen. Eine beispielhafte index.php könnte dann so aussehen:

//include
include('Config.php');
include('Route.php');
 
//config
Config::set('basepath','MYBASEPATH');
 
//init routing
Route::init();
 
//base route
Route::add('',function(){
	//Do something
	echo 'Welcome :-)';
});
 
//simple route
Route::add('test.html',function(){
	//Do something
	echo 'Hello from test.html';
});
 
//complex route with parameter
Route::add('user/(.*)/edit',function($id){
	//Do something
	echo 'Edit user with id '.$id;
});

Route::add404(function($url){
	
	//Send 404 Header
	header("HTTP/1.0 404 Not Found");
	echo '404 :-(';
 
});
 
Route::run();

Hier können beliebige Routen angelegt werden. Die Parameter können gleich per RegExp aus der Route geparst und an den Handler übergeben werden. Die Handler werden nur ausgeführt, wenn die Routen auch mit der eingegebenen URL übereinstimmen.

Aufbau der Config.php-Klasse:

class Config{
	
	private static $registry = Array();
	
	public static function set($key,$value){
		self::$registry[$key] = $value;
	}

	public static function get($key){
		if(array_key_exists($key,self::$registry)){
			return self::$registry[$key];
		}
		return false;
	}
}

Die Config.php enthält eine einfache Registry für die Konfiguration unserer Route. Sie kann später die Konfigurationsdaten weiterer Klassen enthalten.

Aufbau der Route.php-Klasse:

class Route{

public static $routes = Array();
public static $routes404 = Array();
public static $path;

public static function init(){

	$parsed_url = parse_url($_SERVER['REQUEST_URI']);//URI zerlegen
	
	if(isset($parsed_url['path'])){
		self::$path = trim($parsed_url['path'],'/');
	}else{
		self::$path = '';
	}

}

public static function add($expression,$function){

	array_push(self::$routes,Array(
		'expression'=>$expression,
		'function'=>$function
	));

}

public static function add404($function){
	
	array_push(self::$routes404,$function);
	
}

public static function run(){
	
	$route_found = false;
	
	foreach(self::$routes as $route){
		
		if(Config::get('basepath')){
			
			$route['expression'] = '('.Config::get('basepath').')/'.$route['expression'];
			
		}
		
		//Add 'find string start' automatically
		$route['expression'] = '^'.$route['expression'];

		//Add 'find string end' automatically
		$route['expression'] = $route['expression'].'$';
			
		//check match	
		if(preg_match('#'.$route['expression'].'#',self::$path,$matches)){
		
			//echo $expression;
			
			array_shift($matches);//Always remove first element. This contains the whole string
			
			if(Config::get('basepath')){
				
				array_shift($matches);//Remove Basepath
				
			}
				
			call_user_func_array($route['function'], $matches);
			
			$route_found = true;
			
		}
		
	}
	
	if(!$route_found){
		
		foreach(self::$routes404 as $route404){
		
			call_user_func_array($route404, Array(self::$path));
			
		}

	}
	
}

}

Erst wenn die Methode run() ausgeführt wird, werden die einzelnen Routen, die mit add() registriert wurden überprüft und gegebenenfalls ausgeführt. Beim Ausführen wird immer erst der Basispfad, in dem das Projekt läuft vor das Pattern gestellt und so mit berücksichtigt. Wird keine der Routen ausgeführt werden automatisch die 404-Routen ausgeführt.

Damit alles funktioniert müssen natürlich alle Requests mit einem Rewrite an die index.php weitergeleitet werden. Das erreicht man bei Appache in der Datei .htaccess:

DirectoryIndex index.php

RewriteEngine on

RewriteBase /MYBASEPATH

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

RewriteRule ^(.*)$ index.php [QSA]

Dieses URL-Routing ist natürlich noch stark ausbaufähig, kann aber schon eine solide Basis für kleinere Projekte bilden.

Den Source findest du auch auf Github: https://github.com/steampixel/simplePHPRouter/tree/master

Du suchst einen PHP-Entwickler?
So findest du mich!