Routing Urls in PHP

How to Implement URL Routing in PHP

If you use Apache you can do the URL routing via mod_rewrite.

Small example:

RewriteEngine On
RewriteRule ^(dir1)/?(path2)? main.php?dir=$1&path=$2

That'll have any request like

http://yoursite.com/dir1/path1 

served by

http://yoursite.com/main.php?dir=dir1&path=path2

More examples here.

The other alternative have every request redirect to a single php file

RewriteEngine On
RewriteRule (.*) main.php?request=$1

and then to do it in code, where you can use a similar approach, by having a set of regular expressions that are matched by some code and then redirected via header() or just internally.

Routing URLs in PHP

Use mod_rewrite to route everything to a single index.php file. Then check the variable in $_SERVER['REQUEST_URI'] within this file to dispatch to the required handler.

This configuration will enable mod_rewrite, if it's installed:

DirectorySlash Off
Options FollowSymLinks Indexes
DirectoryIndex index.php

RewriteEngine on

RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [L]

RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^.*$ - [L]

RewriteRule ^.*$ index.php [L]

URL Routing in PHP : url routes ony to 404-Not-Found page

The problem was with .htaccess file. I didn't include an .htaccess file which will re-route according to the routings I had in index.php file(the one in the root directory).
I'm posting the .htaccess code below

RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /das/index.php [L]

URL routing containing parameters

In this section ( method direct)

explode('@', $this->routes[$requestType][$uri])

This should be

explode('@', $this->routes[$requestType][$regex])

Or simply (and preferred):

explode('@', $controller)

as the URI (for the 3rd one) is something like this:

users/10
users/20

And the actual key is: users/:id which is also the $regex value (obviously)

Code (For testing only):

$routes = [
'GET' => [
'users'=>'UsersController@index',
'users/about'=>'UsersController@test',
'users/:id'=>'UsersController@show'
],
'POST' => []
];

$requestType = 'GET';

$uri = 'users/10';

foreach ($routes[$requestType] as $regex => $controller) {
$pattern = "@^" . preg_replace('/\\\:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', preg_quote($regex)) . "$@D";

if ( preg_match($pattern, $uri, $matches ) ) {

print_r($matches[0]);
echo "\n";
print_r($routes[$requestType][$uri]);
echo "\n";
print_r($routes[$requestType][$regex]);
}
}

Output:

  #$matches[0]
users/10
#with $uri as the key - $routes[$requestType][$uri]
<b>Notice</b>: Undefined index: users/10 in <b>[...][...]</b> on line <b>27</b><br />
#with $regex as the key - $routes[$requestType][$regex]
UsersController@show

Sandbox

Also I imagine the first and second one should work, only the one with the actual regex as the key would be affected due to it's "dynamic" nature.

Other Stuffs

One thing you are missing is arguments from the url, take the 3rd example (users/10) how do you pass that ID (10) into your controller? Also, If it was me, I would break the dependency you have on this line $controller = "App\\Controllers\\{$controller}"; as it limits you to only using classes of the App\\Controllers\... namespace.

So to fix that change your data structure to remove that @ sign. So instead of this:

 $router->get('users', 'UsersController@index');

Do it this way:

 #Obj::class returns the fully qualified class name (includes namespace)
# PHP 5.6+ I think?
$router->get('users', [UsersController::class,'index']);

Which will actually simplify your code and give you the possibility of doing things like this (simpler and more flexible):

   $router->get('users', function(){
//do something simple
});
#or
$router->get('users', 'somefunction');
#or (drop in plugins outside of your normal controller folder)
$router->get('users', 'Plugins/Users/Controllers/User);

So we have to make this slight modifications:

public function direct($uri, $requestType)
{
$matches = [];

foreach ($this->routes[$requestType] as $regex => $controller) {

$pattern = "@^" . preg_replace('/\\\:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', preg_quote($regex)) . "$@D";

if ( preg_match($pattern, $uri, $matches ) ) {
//Simplify the code here and also pass the uri as an array
return $this->callAction($controller, explode('/', $uri));
}
}

throw new Exception('No route defined for this URI.');
}

protected function callAction($controller, array $args=[])
{
//you can check types here but all callables work with call_user_func & call_user_func_array

//you may be able to just check !is_callable($controller) for them all if you don't need the granularity

if(is_array($controller)){
//[object, method]
//[class name, method]
if(!class_exists($controller[0]) || !method_exists($controller[0], $controller[1])){
//the router has a direct interface to the end user
//because of this it must handle requests to bad URLs and such
//direct to 404 page, for example something like this
//you can and should "log" the errors, but don't show them
// ---- return $this->error404();
}
}else if(is_object($controller) && !is_callable($controller)){
//closure or magic method __invoke
// ---- return $this->error404();
}else if( !function_exists($controller) ){
//standard functions
// ---- return $this->error404();
}

return call_user_func_array($action, $args);
}

With this simple setup all the args are passed including the name of the controller if it's part of the url. For exampl, using the third route with a value of this users/10 would call

  $UsersController->show('users', '10');

It may prove challenging to remove that without baking the "method" into route path: For example

  $router->get('users/about', 'UsersController@test');

There is no way to "know" if "users" is important to the "test" method. Now if they matched:

  $router->get('test/about', 'UsersController@test');

You could remove it. Typically I have seen this pattern in urls

   www.yoursite.com/controller/method/...args

Which gives us a sort of "grantee" as to what the parts are. But it's your code, you may just decide you can discard the first one no matter what...

I should mention I didn't test any of the above code, but based on my experiance these are features you will probably want at some point.

Cheers!

How can I create route URL for views in core PHP?

If I get it right, you want to use clean (SEO) links instead of calling directly php scripts. Solution depends on web server you are using.

For apache (one of the most used ones) you must make redirection from your "imaginary" paths (i.e. /app/login) to your real script, which will handle the request.
Again, most used solution for apache is to add your redirection rules in .htaccess file. From there apache server will read your rule for every request to your server and if conditions are met redirection will be made.

There are many tutorials on how to do that, i.e.
https://www.youtube.com/watch?v=1pbAV6AU99I

Or: https://moz.com/blog/using-mod-rewrite-to-convert-dynamic-urls-to-seo-friendly-urls

Basic URL Routing in PHP Not working using .htaccess

Try this one ( also helpful if you want to use images/js/css/.. later)

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)\.(gif|jpg|png|jpeg|css|js|swf)$ /$1.$2 [END,NC]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^(.*)$ index.php?$1 [L,QSA]

Hint: You can combine the cases '/' and '

<?php
$request = $_SERVER['REQUEST_URI'];echo $request;switch ($request) { case '/' : case '' : // you can combine the cases '/' and '' require __DIR__ . '/views/home.php'; break; case '/about' : require __DIR__ . '/views/about.php'; break; default: http_response_code(404); require __DIR__ . '/views/404.php'; break;}

how to make nice rewrited urls from a router

I found myself an answer to the question, i post here maybe it's useful.

I've added a .htaccess file in the root:

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

This will return each request to the root/index.php file.

Index file collect routes from the HTTP request, check if the route exist in the "routes.json" file.

URL are written in this way:
site.com/controller/action. GET params are written as follows
site.com/controller/action/[params]/[value]...... This output for example site.com/blog/post/id/1
That should be also fine for REST.

Here the index.php

    <?php
require 'controller/Frontend.php';
require 'Class/Router.php';

//require 'libraries/Router.php';
/*
* ** ROUTING SETTINGS **
*/
$app_root = $_SERVER["DOCUMENT_ROOT"].dirname($_SERVER["PHP_SELF"])."/";
$app_url = $_SERVER['REQUEST_SCHEME'].'://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['PHP_SELF']).'/';
define("APP_URL",$app_url);
define("APP_ROOT",$app_root);

$basepath = implode('/', array_slice(explode('/', $_SERVER['SCRIPT_NAME']), 0, -1));
$uri = substr($_SERVER['REQUEST_URI'], strlen($basepath));
//echo $uri;
if ($uri == "/") {
$frontend = new Frontend();
$frontend->index();
} else {
$root = ltrim ($uri, '/');
//$paths = explode("/", $uri);
$paths = parse_url($root, PHP_URL_PATH);
$route = explode("/",$paths);
$request = new \PlayPhp\Classes\Request();
// controller
$c = $route[0];
// action
$a = $route[1];

$reverse = Router::inverseRoute($c,$a);

$rest = $_SERVER['REQUEST_METHOD'];
switch ($rest) {
case 'PUT':
//rest_put($request);
break;
case 'POST':
if (Router::checkRoutes($reverse, "POST")) {
foreach ($_POST as $name => $value) {
$request->setPost($name,$value);
}
break;
} else {
Router::notFound($reverse,"POST");
}
case 'GET':
if (Router::checkRoutes($reverse, "GET")) {
for ($i = 2; $i < count($route); $i++) {
$request->setGet($route[$i], $route[++$i]);
}
break;
} else {
Router::notFound($reverse,"GET");
}
break;
case 'HEAD':
//rest_head($request);
break;
case 'DELETE':
//rest_delete($request);
break;
case 'OPTIONS':
//rest_options($request);
break;
default:
//rest_error($request);
break;
}

include_once APP_ROOT.'controller/'.$c.'.php';
$controller = new $c();
$controller->$a($request);

}

The Router class:

    <?php

include 'config/app.php';
/*
* Copyright (C) 2015 yuri.blanc
*/
require 'Class/http/Request.php';
class Router {
protected static $routes;

private function __construct() {
Router::$routes = json_decode(file_get_contents(APP_ROOT.'config/routes.json'));
}

public static function getInstance(){
if (Router::$routes==null) {
new Router();
}
return Router::$routes;
}

public static function go($action,$params=null) {
$actions = explode("@", $action);
$c = strtolower($actions[0]);
$a = strtolower($actions[1]);
// set query sting to null
$queryString = null;
if(isset($params)) {

foreach ($params as $name => $value) {
$queryString .= '/'.$name.'//'.$value;
}

return APP_URL."$c/$a$queryString";
}
return APP_URL."$c/$a";
}

public static function checkRoutes($action,$method){
foreach (Router::getInstance()->routes as $valid) {
/* echo $valid->action . ' == ' . $action . '|||';
echo $valid->method . ' == ' . $method . '|||';*/
if ($valid->method == $method && $valid->action == $action) {
return true;
}
}
}

public static function inverseRoute($controller,$action) {
return ucfirst($controller)."@".$action;
}
public static function notFound($action,$method) {

die("Route not found:: $action with method $method");

}

}

I use the json_decode function to parse the json object in stdClass().

The json file looks like this:

    {"routes":[
{"action":"Frontend@index", "method":"GET"},
{"action":"Frontend@register", "method":"GET"},
{"action":"Frontend@blog", "method":"GET"}
]}

This way i can whitelist routes with their methods and return 404 errors while not found.

System is still quite basic but gives and idea and works, hope someone will find useful.

How can I make a router in PHP?

Here's something basic, currently routes can have a pattern, and if the application paths start with that pattern then it's a match. The rest of the path is turned into params.

<?php
class Route
{
public $name;
public $pattern;
public $class;
public $method;
public $params;
}

class Router
{
public $routes;

public function __construct(array $routes)
{
$this->routes = $routes;
}

public function resolve($app_path)
{
$matched = false;
foreach($this->routes as $route) {
if(strpos($app_path, $route->pattern) === 0) {
$matched = true;
break;
}
}

if(! $matched) throw new Exception('Could not match route.');

$param_str = str_replace($route->pattern, '', $app_path);
$params = explode('/', trim($param_str, '/'));
$params = array_filter($params);

$match = clone($route);
$match->params = $params;

return $match;
}
}

class Controller
{
public function action()
{
var_dump(func_get_args());
}
}

$route = new Route;
$route->name = 'blog-posts';
$route->pattern = '/blog/posts/';
$route->class = 'Controller';
$route->method = 'action';

$router = new Router(array($route));
$match = $router->resolve('/blog/posts/foo/bar');

// Dispatch
if($match) {
call_user_func_array(array(new $match->class, $match->method), $match->params);
}

Output:

array (size=2)
0 => string 'foo' (length=3)
1 => string 'bar' (length=3)

building a PHP router

You should check out the code of klein.php, a small php router.
I think you should figure it from that solution.

If not, check out also slim here



Related Topics



Leave a reply



Submit